1use std::cell::Cell;
2use std::cmp::Ordering;
3use std::collections::{HashMap, HashSet, VecDeque};
4use std::fs::File;
5use std::io::{self, BufRead, BufReader, Cursor, Read, Write as IoWrite};
6#[cfg(unix)]
7use std::os::unix::process::ExitStatusExt;
8use std::path::{Path, PathBuf};
9use std::process::{Child, Command, Stdio};
10use std::sync::atomic::AtomicUsize;
11use std::sync::Arc;
12use std::sync::{Barrier, OnceLock};
13use std::time::{Duration, Instant};
14
15use indexmap::IndexMap;
16use parking_lot::{Mutex, RwLock};
17use rand::rngs::StdRng;
18use rand::{Rng, SeedableRng};
19use rayon::prelude::*;
20
21use caseless::default_case_fold_str;
22
23use crate::ast::*;
24use crate::builtins::PerlSocket;
25use crate::crypt_util::perl_crypt;
26use crate::error::{ErrorKind, PerlError, PerlResult};
27use crate::mro::linearize_c3;
28use crate::perl_decode::decode_utf8_or_latin1;
29use crate::perl_fs::read_file_text_perl_compat;
30use crate::perl_regex::{perl_quotemeta, PerlCaptures, PerlCompiledRegex};
31use crate::pmap_progress::{FanProgress, PmapProgress};
32use crate::profiler::Profiler;
33use crate::scope::Scope;
34use crate::sort_fast::{detect_sort_block_fast, sort_magic_cmp};
35use crate::value::{
36 perl_list_range_expand, CaptureResult, PerlAsyncTask, PerlBarrier, PerlDataFrame,
37 PerlGenerator, PerlHeap, PerlPpool, PerlSub, PerlValue, PipelineInner, PipelineOp,
38 RemoteCluster,
39};
40
41pub(crate) fn preduce_init_merge_maps(
44 mut acc: IndexMap<String, PerlValue>,
45 b: IndexMap<String, PerlValue>,
46) -> PerlValue {
47 for (k, v2) in b {
48 acc.entry(k)
49 .and_modify(|v1| *v1 = PerlValue::float(v1.to_number() + v2.to_number()))
50 .or_insert(v2);
51 }
52 PerlValue::hash_ref(Arc::new(RwLock::new(acc)))
53}
54
55#[inline]
57fn splice_compute_range(
58 arr_len: usize,
59 offset_val: &PerlValue,
60 length_val: &PerlValue,
61) -> (usize, usize) {
62 let off_i = offset_val.to_int();
63 let off = if off_i < 0 {
64 arr_len.saturating_sub((-off_i) as usize)
65 } else {
66 (off_i as usize).min(arr_len)
67 };
68 let rest = arr_len.saturating_sub(off);
69 let take = if length_val.is_undef() {
70 rest
71 } else {
72 let l = length_val.to_int();
73 if l < 0 {
74 rest.saturating_sub((-l) as usize)
75 } else {
76 (l as usize).min(rest)
77 }
78 };
79 let end = (off + take).min(arr_len);
80 (off, end)
81}
82
83pub(crate) fn merge_preduce_init_partials(
86 a: PerlValue,
87 b: PerlValue,
88 block: &Block,
89 subs: &HashMap<String, Arc<PerlSub>>,
90 scope_capture: &[(String, PerlValue)],
91) -> PerlValue {
92 if let (Some(m1), Some(m2)) = (a.as_hash_map(), b.as_hash_map()) {
93 return preduce_init_merge_maps(m1, m2);
94 }
95 if let (Some(r1), Some(r2)) = (a.as_hash_ref(), b.as_hash_ref()) {
96 let m1 = r1.read().clone();
97 let m2 = r2.read().clone();
98 return preduce_init_merge_maps(m1, m2);
99 }
100 if let Some(m1) = a.as_hash_map() {
101 if let Some(r2) = b.as_hash_ref() {
102 let m2 = r2.read().clone();
103 return preduce_init_merge_maps(m1, m2);
104 }
105 }
106 if let Some(r1) = a.as_hash_ref() {
107 if let Some(m2) = b.as_hash_map() {
108 let m1 = r1.read().clone();
109 return preduce_init_merge_maps(m1, m2);
110 }
111 }
112 let mut local_interp = Interpreter::new();
113 local_interp.subs = subs.clone();
114 local_interp.scope.restore_capture(scope_capture);
115 local_interp.enable_parallel_guard();
116 local_interp
117 .scope
118 .declare_array("_", vec![a.clone(), b.clone()]);
119 let _ = local_interp.scope.set_scalar("a", a.clone());
120 let _ = local_interp.scope.set_scalar("b", b.clone());
121 let _ = local_interp.scope.set_scalar("_0", a);
122 let _ = local_interp.scope.set_scalar("_1", b);
123 match local_interp.exec_block(block) {
124 Ok(val) => val,
125 Err(_) => PerlValue::UNDEF,
126 }
127}
128
129pub(crate) fn preduce_init_fold_identity(init: &PerlValue) -> PerlValue {
132 if let Some(m) = init.as_hash_map() {
133 return PerlValue::hash(m.clone());
134 }
135 if let Some(r) = init.as_hash_ref() {
136 return PerlValue::hash_ref(Arc::new(RwLock::new(r.read().clone())));
137 }
138 init.clone()
139}
140
141pub(crate) fn fold_preduce_init_step(
142 subs: &HashMap<String, Arc<PerlSub>>,
143 scope_capture: &[(String, PerlValue)],
144 block: &Block,
145 acc: PerlValue,
146 item: PerlValue,
147) -> PerlValue {
148 let mut local_interp = Interpreter::new();
149 local_interp.subs = subs.clone();
150 local_interp.scope.restore_capture(scope_capture);
151 local_interp.enable_parallel_guard();
152 local_interp
153 .scope
154 .declare_array("_", vec![acc.clone(), item.clone()]);
155 let _ = local_interp.scope.set_scalar("a", acc.clone());
156 let _ = local_interp.scope.set_scalar("b", item.clone());
157 let _ = local_interp.scope.set_scalar("_0", acc);
158 let _ = local_interp.scope.set_scalar("_1", item);
159 match local_interp.exec_block(block) {
160 Ok(val) => val,
161 Err(_) => PerlValue::UNDEF,
162 }
163}
164
165pub const FEAT_SAY: u64 = 1 << 0;
167pub const FEAT_STATE: u64 = 1 << 1;
169pub const FEAT_SWITCH: u64 = 1 << 2;
171pub const FEAT_UNICODE_STRINGS: u64 = 1 << 3;
173
174#[derive(Debug)]
176pub(crate) enum Flow {
177 Return(PerlValue),
178 Last(Option<String>),
179 Next(Option<String>),
180 Redo(Option<String>),
181 Yield(PerlValue),
182 GotoSub(String),
184}
185
186pub(crate) type ExecResult = Result<PerlValue, FlowOrError>;
187
188#[derive(Debug)]
189pub(crate) enum FlowOrError {
190 Flow(Flow),
191 Error(PerlError),
192}
193
194impl From<PerlError> for FlowOrError {
195 fn from(e: PerlError) -> Self {
196 FlowOrError::Error(e)
197 }
198}
199
200impl From<Flow> for FlowOrError {
201 fn from(f: Flow) -> Self {
202 FlowOrError::Flow(f)
203 }
204}
205
206enum PatternBinding {
208 Scalar(String, PerlValue),
209 Array(String, Vec<PerlValue>),
210}
211
212pub fn perl_bracket_version() -> f64 {
215 const PERL_EMUL_MINOR: u32 = 38;
216 const PERL_EMUL_PATCH: u32 = 0;
217 5.0 + (PERL_EMUL_MINOR as f64) / 1000.0 + (PERL_EMUL_PATCH as f64) / 1_000_000.0
218}
219
220#[inline]
222fn fast_rng_seed() -> u64 {
223 let local: u8 = 0;
224 let addr = &local as *const u8 as u64;
225 (std::process::id() as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ addr
226}
227
228fn cached_executable_path() -> String {
230 static CACHED: OnceLock<String> = OnceLock::new();
231 CACHED
232 .get_or_init(|| {
233 std::env::current_exe()
234 .map(|p| p.to_string_lossy().into_owned())
235 .unwrap_or_else(|_| "stryke".to_string())
236 })
237 .clone()
238}
239
240fn build_term_hash() -> IndexMap<String, PerlValue> {
241 let mut m = IndexMap::new();
242 m.insert(
243 "TERM".into(),
244 PerlValue::string(std::env::var("TERM").unwrap_or_default()),
245 );
246 m.insert(
247 "COLORTERM".into(),
248 PerlValue::string(std::env::var("COLORTERM").unwrap_or_default()),
249 );
250
251 let (rows, cols) = term_size();
252 m.insert("rows".into(), PerlValue::integer(rows));
253 m.insert("cols".into(), PerlValue::integer(cols));
254
255 #[cfg(unix)]
256 let is_tty = unsafe { libc::isatty(1) != 0 };
257 #[cfg(not(unix))]
258 let is_tty = false;
259 m.insert(
260 "is_tty".into(),
261 PerlValue::integer(if is_tty { 1 } else { 0 }),
262 );
263
264 m
265}
266
267fn term_size() -> (i64, i64) {
268 #[cfg(unix)]
269 {
270 unsafe {
271 let mut ws: libc::winsize = std::mem::zeroed();
272 if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 {
273 return (ws.ws_row as i64, ws.ws_col as i64);
274 }
275 }
276 }
277 let rows = std::env::var("LINES")
278 .ok()
279 .and_then(|s| s.parse().ok())
280 .unwrap_or(24);
281 let cols = std::env::var("COLUMNS")
282 .ok()
283 .and_then(|s| s.parse().ok())
284 .unwrap_or(80);
285 (rows, cols)
286}
287
288#[cfg(unix)]
289fn build_uname_hash() -> IndexMap<String, PerlValue> {
290 fn uts_field(slice: &[libc::c_char]) -> String {
291 let n = slice.iter().take_while(|&&c| c != 0).count();
292 let bytes: Vec<u8> = slice[..n].iter().map(|&c| c as u8).collect();
293 String::from_utf8_lossy(&bytes).into_owned()
294 }
295 let mut m = IndexMap::new();
296 let mut uts: libc::utsname = unsafe { std::mem::zeroed() };
297 if unsafe { libc::uname(&mut uts) } == 0 {
298 m.insert(
299 "sysname".into(),
300 PerlValue::string(uts_field(uts.sysname.as_slice())),
301 );
302 m.insert(
303 "nodename".into(),
304 PerlValue::string(uts_field(uts.nodename.as_slice())),
305 );
306 m.insert(
307 "release".into(),
308 PerlValue::string(uts_field(uts.release.as_slice())),
309 );
310 m.insert(
311 "version".into(),
312 PerlValue::string(uts_field(uts.version.as_slice())),
313 );
314 m.insert(
315 "machine".into(),
316 PerlValue::string(uts_field(uts.machine.as_slice())),
317 );
318 }
319 m
320}
321
322#[cfg(unix)]
323fn build_limits_hash() -> IndexMap<String, PerlValue> {
324 use libc::{getrlimit, rlimit, RLIM_INFINITY};
325 fn get_limit(resource: libc::c_int) -> (i64, i64) {
326 let mut rlim = rlimit {
327 rlim_cur: 0,
328 rlim_max: 0,
329 };
330 if unsafe { getrlimit(resource, &mut rlim) } == 0 {
331 let cur = if rlim.rlim_cur == RLIM_INFINITY {
332 -1
333 } else {
334 rlim.rlim_cur as i64
335 };
336 let max = if rlim.rlim_max == RLIM_INFINITY {
337 -1
338 } else {
339 rlim.rlim_max as i64
340 };
341 (cur, max)
342 } else {
343 (-1, -1)
344 }
345 }
346 let mut m = IndexMap::new();
347 let (cur, max) = get_limit(libc::RLIMIT_NOFILE);
348 m.insert("nofile".into(), PerlValue::integer(cur));
349 m.insert("nofile_max".into(), PerlValue::integer(max));
350 let (cur, max) = get_limit(libc::RLIMIT_STACK);
351 m.insert("stack".into(), PerlValue::integer(cur));
352 m.insert("stack_max".into(), PerlValue::integer(max));
353 let (cur, max) = get_limit(libc::RLIMIT_AS);
354 m.insert("as".into(), PerlValue::integer(cur));
355 m.insert("as_max".into(), PerlValue::integer(max));
356 let (cur, max) = get_limit(libc::RLIMIT_DATA);
357 m.insert("data".into(), PerlValue::integer(cur));
358 m.insert("data_max".into(), PerlValue::integer(max));
359 let (cur, max) = get_limit(libc::RLIMIT_FSIZE);
360 m.insert("fsize".into(), PerlValue::integer(cur));
361 m.insert("fsize_max".into(), PerlValue::integer(max));
362 let (cur, max) = get_limit(libc::RLIMIT_CORE);
363 m.insert("core".into(), PerlValue::integer(cur));
364 m.insert("core_max".into(), PerlValue::integer(max));
365 let (cur, max) = get_limit(libc::RLIMIT_CPU);
366 m.insert("cpu".into(), PerlValue::integer(cur));
367 m.insert("cpu_max".into(), PerlValue::integer(max));
368 let (cur, max) = get_limit(libc::RLIMIT_NPROC);
369 m.insert("nproc".into(), PerlValue::integer(cur));
370 m.insert("nproc_max".into(), PerlValue::integer(max));
371 #[cfg(target_os = "linux")]
372 {
373 let (cur, max) = get_limit(libc::RLIMIT_MEMLOCK);
374 m.insert("memlock".into(), PerlValue::integer(cur));
375 m.insert("memlock_max".into(), PerlValue::integer(max));
376 }
377 m
378}
379
380#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
382pub(crate) enum WantarrayCtx {
383 #[default]
384 Scalar,
385 List,
386 Void,
387}
388
389impl WantarrayCtx {
390 #[inline]
391 pub(crate) fn from_byte(b: u8) -> Self {
392 match b {
393 1 => Self::List,
394 2 => Self::Void,
395 _ => Self::Scalar,
396 }
397 }
398
399 #[inline]
400 pub(crate) fn as_byte(self) -> u8 {
401 match self {
402 Self::Scalar => 0,
403 Self::List => 1,
404 Self::Void => 2,
405 }
406 }
407}
408
409#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
411pub(crate) enum LogLevelFilter {
412 Trace,
413 Debug,
414 Info,
415 Warn,
416 Error,
417}
418
419impl LogLevelFilter {
420 pub(crate) fn parse(s: &str) -> Option<Self> {
421 match s.trim().to_ascii_lowercase().as_str() {
422 "trace" => Some(Self::Trace),
423 "debug" => Some(Self::Debug),
424 "info" => Some(Self::Info),
425 "warn" | "warning" => Some(Self::Warn),
426 "error" => Some(Self::Error),
427 _ => None,
428 }
429 }
430
431 pub(crate) fn as_str(self) -> &'static str {
432 match self {
433 Self::Trace => "trace",
434 Self::Debug => "debug",
435 Self::Info => "info",
436 Self::Warn => "warn",
437 Self::Error => "error",
438 }
439 }
440}
441
442fn arrow_deref_array_assign_rhs_list_ctx(index: &Expr) -> bool {
444 match &index.kind {
445 ExprKind::Range { .. } => true,
446 ExprKind::QW(ws) => ws.len() > 1,
447 ExprKind::List(el) => {
448 if el.len() > 1 {
449 true
450 } else if el.len() == 1 {
451 arrow_deref_array_assign_rhs_list_ctx(&el[0])
452 } else {
453 false
454 }
455 }
456 _ => false,
457 }
458}
459
460pub(crate) fn assign_rhs_wantarray(target: &Expr) -> WantarrayCtx {
463 match &target.kind {
464 ExprKind::ArrayVar(_) | ExprKind::HashVar(_) => WantarrayCtx::List,
465 ExprKind::ScalarVar(_) | ExprKind::ArrayElement { .. } | ExprKind::HashElement { .. } => {
466 WantarrayCtx::Scalar
467 }
468 ExprKind::Deref { kind, .. } => match kind {
469 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
470 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
471 },
472 ExprKind::ArrowDeref {
473 index,
474 kind: DerefKind::Array,
475 ..
476 } => {
477 if arrow_deref_array_assign_rhs_list_ctx(index) {
478 WantarrayCtx::List
479 } else {
480 WantarrayCtx::Scalar
481 }
482 }
483 ExprKind::ArrowDeref {
484 kind: DerefKind::Hash,
485 ..
486 }
487 | ExprKind::ArrowDeref {
488 kind: DerefKind::Call,
489 ..
490 } => WantarrayCtx::Scalar,
491 ExprKind::HashSliceDeref { .. } | ExprKind::HashSlice { .. } => WantarrayCtx::List,
492 ExprKind::ArraySlice { indices, .. } => {
493 if indices.len() > 1 {
494 WantarrayCtx::List
495 } else if indices.len() == 1 {
496 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
497 WantarrayCtx::List
498 } else {
499 WantarrayCtx::Scalar
500 }
501 } else {
502 WantarrayCtx::Scalar
503 }
504 }
505 ExprKind::AnonymousListSlice { indices, .. } => {
506 if indices.len() > 1 {
507 WantarrayCtx::List
508 } else if indices.len() == 1 {
509 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
510 WantarrayCtx::List
511 } else {
512 WantarrayCtx::Scalar
513 }
514 } else {
515 WantarrayCtx::Scalar
516 }
517 }
518 ExprKind::Typeglob(_) | ExprKind::TypeglobExpr(_) => WantarrayCtx::Scalar,
519 ExprKind::List(_) => WantarrayCtx::List,
520 _ => WantarrayCtx::Scalar,
521 }
522}
523
524#[derive(Clone)]
529pub(crate) struct RegexMatchMemo {
530 pub pattern: String,
531 pub flags: String,
532 pub multiline: bool,
533 pub haystack: String,
534 pub result: PerlValue,
535}
536
537#[derive(Clone, Copy, Default)]
539struct FlipFlopTreeState {
540 active: bool,
541 exclusive_left_line: Option<i64>,
545}
546
547#[derive(Clone)]
549pub(crate) struct IoSharedFile(pub Arc<Mutex<File>>);
550
551impl Read for IoSharedFile {
552 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
553 self.0.lock().read(buf)
554 }
555}
556
557pub(crate) struct IoSharedFileWrite(pub Arc<Mutex<File>>);
558
559impl IoWrite for IoSharedFileWrite {
560 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
561 self.0.lock().write(buf)
562 }
563
564 fn flush(&mut self) -> io::Result<()> {
565 self.0.lock().flush()
566 }
567}
568
569pub struct Interpreter {
570 pub scope: Scope,
571 pub(crate) subs: HashMap<String, Arc<PerlSub>>,
572 pub(crate) file: String,
573 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
575 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
576 pub ofs: String,
578 pub ors: String,
580 pub irs: Option<String>,
583 pub errno: String,
585 pub errno_code: i32,
587 pub eval_error: String,
589 pub eval_error_code: i32,
591 pub eval_error_value: Option<PerlValue>,
593 pub argv: Vec<String>,
595 pub env: IndexMap<String, PerlValue>,
597 pub env_materialized: bool,
599 pub program_name: String,
601 pub line_number: i64,
603 pub last_readline_handle: String,
605 pub(crate) last_stdin_die_bracket: String,
607 pub handle_line_numbers: HashMap<String, i64>,
609 pub(crate) flip_flop_active: Vec<bool>,
613 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
616 pub(crate) flip_flop_sequence: Vec<i64>,
620 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
624 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
626 pub sigint_pending_caret: Cell<bool>,
628 pub auto_split: bool,
630 pub field_separator: Option<String>,
632 begin_blocks: Vec<Block>,
634 unit_check_blocks: Vec<Block>,
636 check_blocks: Vec<Block>,
638 init_blocks: Vec<Block>,
640 end_blocks: Vec<Block>,
642 pub warnings: bool,
644 pub output_autoflush: bool,
646 pub default_print_handle: String,
648 pub suppress_stdout: bool,
650 pub child_exit_status: i64,
652 pub last_match: String,
654 pub prematch: String,
656 pub postmatch: String,
658 pub last_paren_match: String,
660 pub list_separator: String,
662 pub script_start_time: i64,
664 pub compile_hints: i64,
666 pub warning_bits: i64,
668 pub global_phase: String,
670 pub subscript_sep: String,
672 pub inplace_edit: String,
675 pub debug_flags: i64,
677 pub perl_debug_flags: i64,
679 pub eval_nesting: u32,
681 pub argv_current_file: String,
683 pub(crate) diamond_next_idx: usize,
685 pub(crate) diamond_reader: Option<BufReader<File>>,
687 pub strict_refs: bool,
689 pub strict_subs: bool,
690 pub strict_vars: bool,
691 pub utf8_pragma: bool,
693 pub open_pragma_utf8: bool,
695 pub feature_bits: u64,
697 pub num_threads: usize,
699 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
701 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
704 regex_match_memo: Option<RegexMatchMemo>,
713 regex_capture_scope_fresh: bool,
717 pub(crate) regex_pos: HashMap<String, Option<usize>>,
719 pub(crate) state_vars: HashMap<String, PerlValue>,
721 state_bindings_stack: Vec<Vec<(String, String)>>,
723 pub(crate) rand_rng: StdRng,
725 pub(crate) dir_handles: HashMap<String, DirHandleState>,
727 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
729 pub(crate) pipe_children: HashMap<String, Child>,
731 pub(crate) socket_handles: HashMap<String, PerlSocket>,
733 pub(crate) wantarray_kind: WantarrayCtx,
735 pub struct_defs: HashMap<String, Arc<StructDef>>,
737 pub enum_defs: HashMap<String, Arc<EnumDef>>,
739 pub class_defs: HashMap<String, Arc<ClassDef>>,
741 pub trait_defs: HashMap<String, Arc<TraitDef>>,
743 pub profiler: Option<Profiler>,
746 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
748 pub(crate) virtual_modules: HashMap<String, String>,
750 pub(crate) tied_hashes: HashMap<String, PerlValue>,
752 pub(crate) tied_scalars: HashMap<String, PerlValue>,
754 pub(crate) tied_arrays: HashMap<String, PerlValue>,
756 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
758 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
760 pub(crate) special_caret_scalars: HashMap<String, PerlValue>,
762 pub format_page_number: i64,
764 pub format_lines_per_page: i64,
766 pub format_lines_left: i64,
768 pub format_line_break_chars: String,
770 pub format_top_name: String,
772 pub accumulator_format: String,
774 pub max_system_fd: i64,
776 pub emergency_memory: String,
778 pub last_subpattern_name: String,
780 pub inc_hook_index: i64,
782 pub multiline_match: bool,
784 pub executable_path: String,
786 pub formfeed_string: String,
788 pub(crate) glob_handle_alias: HashMap<String, String>,
790 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
792 pub(crate) special_var_restore_frames: Vec<Vec<(String, PerlValue)>>,
797 pub(crate) reflection_hashes_ready: bool,
801 pub(crate) english_enabled: bool,
802 pub(crate) english_no_match_vars: bool,
804 pub(crate) english_match_vars_ever_enabled: bool,
808 english_lexical_scalars: Vec<HashSet<String>>,
810 our_lexical_scalars: Vec<HashSet<String>>,
812 pub vm_jit_enabled: bool,
815 pub disasm_bytecode: bool,
817 pub pec_precompiled_chunk: Option<crate::bytecode::Chunk>,
821 pub pec_cache_fingerprint: Option<[u8; 32]>,
825 pub(crate) in_generator: bool,
827 pub line_mode_skip_main: bool,
829 pub(crate) line_mode_eof_pending: bool,
833 pub line_mode_stdin_pending: VecDeque<String>,
836 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
838 pub(crate) log_level_override: Option<LogLevelFilter>,
840 pub(crate) current_sub_stack: Vec<Arc<PerlSub>>,
843 pub debugger: Option<crate::debugger::Debugger>,
845 pub(crate) debug_call_stack: Vec<(String, usize)>,
847}
848
849#[derive(Debug, Clone, Default)]
851pub struct ReplCompletionSnapshot {
852 pub subs: Vec<String>,
853 pub blessed_scalars: HashMap<String, String>,
854 pub isa_for_class: HashMap<String, Vec<String>>,
855}
856
857impl ReplCompletionSnapshot {
858 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
860 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
861 let mro = linearize_c3(class, &parents, 0);
862 let mut names = HashSet::new();
863 for pkg in &mro {
864 if pkg == "UNIVERSAL" {
865 continue;
866 }
867 let prefix = format!("{}::", pkg);
868 for k in &self.subs {
869 if k.starts_with(&prefix) {
870 let rest = &k[prefix.len()..];
871 if !rest.contains("::") {
872 names.insert(rest.to_string());
873 }
874 }
875 }
876 }
877 for k in &self.subs {
878 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
879 if !rest.contains("::") {
880 names.insert(rest.to_string());
881 }
882 }
883 }
884 let mut v: Vec<String> = names.into_iter().collect();
885 v.sort();
886 v
887 }
888}
889
890fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
891 let left = left.trim_end();
892 if left.is_empty() {
893 return None;
894 }
895 if let Some(i) = left.rfind('$') {
896 let name = left[i + 1..].trim();
897 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
898 return state.blessed_scalars.get(name).cloned();
899 }
900 }
901 let tok = left.split_whitespace().last()?;
902 if tok.contains("::") {
903 return Some(tok.to_string());
904 }
905 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
906 return Some(tok.to_string());
907 }
908 None
909}
910
911pub fn repl_arrow_method_completions(
913 state: &ReplCompletionSnapshot,
914 line: &str,
915 pos: usize,
916) -> Option<(usize, Vec<String>)> {
917 let pos = pos.min(line.len());
918 let before = &line[..pos];
919 let arrow_idx = before.rfind("->")?;
920 let after_arrow = &before[arrow_idx + 2..];
921 let rest = after_arrow.trim_start();
922 let ws_len = after_arrow.len() - rest.len();
923 let method_start = arrow_idx + 2 + ws_len;
924 let method_prefix = &line[method_start..pos];
925 if !method_prefix
926 .chars()
927 .all(|c| c.is_alphanumeric() || c == '_')
928 {
929 return None;
930 }
931 let left = line[..arrow_idx].trim_end();
932 let class = repl_resolve_class_for_arrow(state, left)?;
933 let mut methods = state.methods_for_class(&class);
934 methods.retain(|m| m.starts_with(method_prefix));
935 Some((method_start, methods))
936}
937
938#[derive(Debug, Clone, Default)]
940pub(crate) struct ModuleExportLists {
941 pub export: Vec<String>,
943 pub export_ok: Vec<String>,
945}
946
947fn piped_shell_command(cmd: &str) -> Command {
949 if cfg!(windows) {
950 let mut c = Command::new("cmd");
951 c.arg("/C").arg(cmd);
952 c
953 } else {
954 let mut c = Command::new("sh");
955 c.arg("-c").arg(cmd);
956 c
957 }
958}
959
960fn expand_perl_regex_octal_escapes(pat: &str) -> String {
967 let mut out = String::with_capacity(pat.len());
968 let mut it = pat.chars().peekable();
969 while let Some(c) = it.next() {
970 if c == '\\' {
971 if let Some(&'0') = it.peek() {
972 let mut oct = String::new();
974 while oct.len() < 3 {
975 if let Some(&d) = it.peek() {
976 if ('0'..='7').contains(&d) {
977 oct.push(d);
978 it.next();
979 } else {
980 break;
981 }
982 } else {
983 break;
984 }
985 }
986 if let Ok(val) = u8::from_str_radix(&oct, 8) {
987 out.push_str(&format!("\\x{:02x}", val));
988 } else {
989 out.push('\\');
990 out.push_str(&oct);
991 }
992 continue;
993 }
994 }
995 out.push(c);
996 }
997 out
998}
999
1000fn expand_perl_regex_quotemeta(pat: &str) -> String {
1001 let mut out = String::with_capacity(pat.len().saturating_mul(2));
1002 let mut it = pat.chars().peekable();
1003 let mut in_q = false;
1004 while let Some(c) = it.next() {
1005 if in_q {
1006 if c == '\\' && it.peek() == Some(&'E') {
1007 it.next();
1008 in_q = false;
1009 continue;
1010 }
1011 out.push_str(&perl_quotemeta(&c.to_string()));
1012 continue;
1013 }
1014 if c == '\\' && it.peek() == Some(&'Q') {
1015 it.next();
1016 in_q = true;
1017 continue;
1018 }
1019 out.push(c);
1020 }
1021 out
1022}
1023
1024pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
1030 let mut out = String::with_capacity(replacement.len() + 8);
1031 let mut it = replacement.chars().peekable();
1032 while let Some(c) = it.next() {
1033 if c == '\\' {
1034 match it.peek() {
1035 Some(&d) if d.is_ascii_digit() => {
1036 it.next();
1037 out.push_str("${");
1038 out.push(d);
1039 while let Some(&d2) = it.peek() {
1040 if !d2.is_ascii_digit() {
1041 break;
1042 }
1043 it.next();
1044 out.push(d2);
1045 }
1046 out.push('}');
1047 }
1048 Some(&'\\') => {
1049 it.next();
1050 out.push('\\');
1051 }
1052 _ => out.push('\\'),
1053 }
1054 } else if c == '$' {
1055 match it.peek() {
1056 Some(&d) if d.is_ascii_digit() => {
1057 it.next();
1058 out.push_str("${");
1059 out.push(d);
1060 while let Some(&d2) = it.peek() {
1061 if !d2.is_ascii_digit() {
1062 break;
1063 }
1064 it.next();
1065 out.push(d2);
1066 }
1067 out.push('}');
1068 }
1069 Some(&'{') => {
1070 out.push('$');
1072 }
1073 _ => out.push('$'),
1074 }
1075 } else {
1076 out.push(c);
1077 }
1078 }
1079 out
1080}
1081
1082fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
1085 debug_assert_eq!(chars.get(i), Some(&'['));
1086 out.push('[');
1087 i += 1;
1088 if i < chars.len() && chars[i] == '^' {
1089 out.push('^');
1090 i += 1;
1091 }
1092 if i >= chars.len() {
1093 return i;
1094 }
1095 if chars[i] == ']' {
1099 if i + 1 < chars.len() && chars[i + 1] == ']' {
1100 out.push(']');
1102 i += 1;
1103 } else {
1104 let mut scan = i + 1;
1105 let mut found_closing = false;
1106 while scan < chars.len() {
1107 if chars[scan] == '\\' && scan + 1 < chars.len() {
1108 scan += 2;
1109 continue;
1110 }
1111 if chars[scan] == ']' {
1112 found_closing = true;
1113 break;
1114 }
1115 scan += 1;
1116 }
1117 if found_closing {
1118 out.push(']');
1119 i += 1;
1120 } else {
1121 out.push(']');
1122 return i + 1;
1123 }
1124 }
1125 }
1126 while i < chars.len() && chars[i] != ']' {
1127 if chars[i] == '\\' && i + 1 < chars.len() {
1128 out.push(chars[i]);
1129 out.push(chars[i + 1]);
1130 i += 2;
1131 continue;
1132 }
1133 out.push(chars[i]);
1134 i += 1;
1135 }
1136 if i < chars.len() {
1137 out.push(']');
1138 i += 1;
1139 }
1140 i
1141}
1142
1143fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1148 if multiline_flag {
1149 return pat.to_string();
1150 }
1151 let chars: Vec<char> = pat.chars().collect();
1152 let mut out = String::with_capacity(pat.len().saturating_add(16));
1153 let mut i = 0usize;
1154 while i < chars.len() {
1155 let c = chars[i];
1156 if c == '\\' && i + 1 < chars.len() {
1157 out.push(c);
1158 out.push(chars[i + 1]);
1159 i += 2;
1160 continue;
1161 }
1162 if c == '[' {
1163 i = copy_regex_char_class(&chars, i, &mut out);
1164 continue;
1165 }
1166 if c == '$' {
1167 if let Some(&next) = chars.get(i + 1) {
1168 if next.is_ascii_digit() {
1169 out.push(c);
1170 i += 1;
1171 continue;
1172 }
1173 if next == '{' {
1174 out.push(c);
1175 i += 1;
1176 continue;
1177 }
1178 if next.is_ascii_alphanumeric() || next == '_' {
1179 out.push(c);
1180 i += 1;
1181 continue;
1182 }
1183 }
1184 out.push_str("(?=\\n?\\z)");
1185 i += 1;
1186 continue;
1187 }
1188 out.push(c);
1189 i += 1;
1190 }
1191 out
1192}
1193
1194#[derive(Debug, Clone)]
1196pub(crate) struct DirHandleState {
1197 pub entries: Vec<String>,
1198 pub pos: usize,
1199}
1200
1201pub(crate) fn perl_osname() -> String {
1203 match std::env::consts::OS {
1204 "linux" => "linux".to_string(),
1205 "macos" => "darwin".to_string(),
1206 "windows" => "MSWin32".to_string(),
1207 other => other.to_string(),
1208 }
1209}
1210
1211fn perl_version_v_string() -> String {
1212 format!("v{}", env!("CARGO_PKG_VERSION"))
1213}
1214
1215fn extended_os_error_string() -> String {
1216 std::io::Error::last_os_error().to_string()
1217}
1218
1219#[cfg(unix)]
1220fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1221 unsafe {
1222 (
1223 libc::getuid() as i64,
1224 libc::geteuid() as i64,
1225 libc::getgid() as i64,
1226 libc::getegid() as i64,
1227 )
1228 }
1229}
1230
1231#[cfg(not(unix))]
1232fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1233 (0, 0, 0, 0)
1234}
1235
1236fn unix_id_for_special(name: &str) -> i64 {
1237 let (r, e, _, _) = unix_real_effective_ids();
1238 match name {
1239 "<" => r,
1240 ">" => e,
1241 _ => 0,
1242 }
1243}
1244
1245#[cfg(unix)]
1246fn unix_group_list_string(primary: libc::gid_t) -> String {
1247 let mut buf = vec![0 as libc::gid_t; 256];
1248 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1249 if n <= 0 {
1250 return format!("{}", primary);
1251 }
1252 let mut parts = vec![format!("{}", primary)];
1253 for g in buf.iter().take(n as usize) {
1254 parts.push(format!("{}", g));
1255 }
1256 parts.join(" ")
1257}
1258
1259#[cfg(unix)]
1261fn unix_group_list_for_special(name: &str) -> String {
1262 let (_, _, gid, egid) = unix_real_effective_ids();
1263 match name {
1264 "(" => unix_group_list_string(gid as libc::gid_t),
1265 ")" => unix_group_list_string(egid as libc::gid_t),
1266 _ => String::new(),
1267 }
1268}
1269
1270#[cfg(not(unix))]
1271fn unix_group_list_for_special(_name: &str) -> String {
1272 String::new()
1273}
1274
1275#[cfg(unix)]
1278fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1279 use libc::{getpwuid_r, getuid};
1280 use std::ffi::CStr;
1281 use std::os::unix::ffi::OsStringExt;
1282 let uid = unsafe { getuid() };
1283 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1284 let mut result: *mut libc::passwd = std::ptr::null_mut();
1285 let mut buf = vec![0u8; 16_384];
1286 let rc = unsafe {
1287 getpwuid_r(
1288 uid,
1289 &mut pw,
1290 buf.as_mut_ptr().cast::<libc::c_char>(),
1291 buf.len(),
1292 &mut result,
1293 )
1294 };
1295 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1296 return None;
1297 }
1298 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1299 if bytes.is_empty() {
1300 return None;
1301 }
1302 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1303}
1304
1305#[cfg(unix)]
1307fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1308 use libc::getpwnam_r;
1309 use std::ffi::{CStr, CString};
1310 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1311 let bytes = login.as_bytes();
1312 if bytes.is_empty() || bytes.contains(&0) {
1313 return None;
1314 }
1315 let cname = CString::new(bytes).ok()?;
1316 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1317 let mut result: *mut libc::passwd = std::ptr::null_mut();
1318 let mut buf = vec![0u8; 16_384];
1319 let rc = unsafe {
1320 getpwnam_r(
1321 cname.as_ptr(),
1322 &mut pw,
1323 buf.as_mut_ptr().cast::<libc::c_char>(),
1324 buf.len(),
1325 &mut result,
1326 )
1327 };
1328 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1329 return None;
1330 }
1331 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1332 if dir_bytes.is_empty() {
1333 return None;
1334 }
1335 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1336}
1337
1338impl Default for Interpreter {
1339 fn default() -> Self {
1340 Self::new()
1341 }
1342}
1343
1344#[derive(Clone, Copy)]
1346pub(crate) enum CaptureAllMode {
1347 Empty,
1349 Append,
1351 Skip,
1353}
1354
1355impl Interpreter {
1356 pub fn new() -> Self {
1357 let mut scope = Scope::new();
1358 scope.declare_array("INC", vec![PerlValue::string(".".to_string())]);
1359 scope.declare_hash("INC", IndexMap::new());
1360 scope.declare_array("ARGV", vec![]);
1361 scope.declare_array("_", vec![]);
1362
1363 let path_vec: Vec<PerlValue> = std::env::var("PATH")
1365 .unwrap_or_default()
1366 .split(if cfg!(windows) { ';' } else { ':' })
1367 .filter(|s| !s.is_empty())
1368 .map(|p| PerlValue::string(p.to_string()))
1369 .collect();
1370 scope.declare_array_frozen("path", path_vec.clone(), true);
1371 scope.declare_array_frozen("p", path_vec, true);
1372
1373 let fpath_vec: Vec<PerlValue> = std::env::var("FPATH")
1375 .unwrap_or_default()
1376 .split(':')
1377 .filter(|s| !s.is_empty())
1378 .map(|p| PerlValue::string(p.to_string()))
1379 .collect();
1380 scope.declare_array_frozen("fpath", fpath_vec.clone(), true);
1381 scope.declare_array_frozen("f", fpath_vec, true);
1382 scope.declare_hash("ENV", IndexMap::new());
1383 scope.declare_hash("SIG", IndexMap::new());
1384
1385 let term_map = build_term_hash();
1387 scope.declare_hash_global_frozen("term", term_map);
1388
1389 #[cfg(unix)]
1391 {
1392 let uname_map = build_uname_hash();
1393 scope.declare_hash_global_frozen("uname", uname_map);
1394 }
1395 #[cfg(not(unix))]
1396 {
1397 scope.declare_hash_global_frozen("uname", IndexMap::new());
1398 }
1399
1400 #[cfg(unix)]
1402 {
1403 let limits_map = build_limits_hash();
1404 scope.declare_hash_global_frozen("limits", limits_map);
1405 }
1406 #[cfg(not(unix))]
1407 {
1408 scope.declare_hash_global_frozen("limits", IndexMap::new());
1409 }
1410
1411 scope.declare_scalar(
1430 "stryke::VERSION",
1431 PerlValue::string(env!("CARGO_PKG_VERSION").to_string()),
1432 );
1433 scope.declare_array("-", vec![]);
1434 scope.declare_array("+", vec![]);
1435 scope.declare_array("^CAPTURE", vec![]);
1436 scope.declare_array("^CAPTURE_ALL", vec![]);
1437 scope.declare_hash("^HOOK", IndexMap::new());
1438 scope.declare_scalar("~", PerlValue::string("STDOUT".to_string()));
1439
1440 let script_start_time = std::time::SystemTime::now()
1441 .duration_since(std::time::UNIX_EPOCH)
1442 .map(|d| d.as_secs() as i64)
1443 .unwrap_or(0);
1444
1445 let executable_path = cached_executable_path();
1446
1447 let mut special_caret_scalars: HashMap<String, PerlValue> = HashMap::new();
1448 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1449 special_caret_scalars.insert(format!("^{}", name), PerlValue::UNDEF);
1450 }
1451
1452 let mut s = Self {
1453 scope,
1454 subs: HashMap::new(),
1455 struct_defs: HashMap::new(),
1456 enum_defs: HashMap::new(),
1457 class_defs: HashMap::new(),
1458 trait_defs: HashMap::new(),
1459 file: "-e".to_string(),
1460 output_handles: HashMap::new(),
1461 input_handles: HashMap::new(),
1462 ofs: String::new(),
1463 ors: String::new(),
1464 irs: Some("\n".to_string()),
1465 errno: String::new(),
1466 errno_code: 0,
1467 eval_error: String::new(),
1468 eval_error_code: 0,
1469 eval_error_value: None,
1470 argv: Vec::new(),
1471 env: IndexMap::new(),
1472 env_materialized: false,
1473 program_name: "stryke".to_string(),
1474 line_number: 0,
1475 last_readline_handle: String::new(),
1476 last_stdin_die_bracket: "<STDIN>".to_string(),
1477 handle_line_numbers: HashMap::new(),
1478 flip_flop_active: Vec::new(),
1479 flip_flop_exclusive_left_line: Vec::new(),
1480 flip_flop_sequence: Vec::new(),
1481 flip_flop_last_dot: Vec::new(),
1482 flip_flop_tree: HashMap::new(),
1483 sigint_pending_caret: Cell::new(false),
1484 auto_split: false,
1485 field_separator: None,
1486 begin_blocks: Vec::new(),
1487 unit_check_blocks: Vec::new(),
1488 check_blocks: Vec::new(),
1489 init_blocks: Vec::new(),
1490 end_blocks: Vec::new(),
1491 warnings: false,
1492 output_autoflush: false,
1493 default_print_handle: "STDOUT".to_string(),
1494 suppress_stdout: false,
1495 child_exit_status: 0,
1496 last_match: String::new(),
1497 prematch: String::new(),
1498 postmatch: String::new(),
1499 last_paren_match: String::new(),
1500 list_separator: " ".to_string(),
1501 script_start_time,
1502 compile_hints: 0,
1503 warning_bits: 0,
1504 global_phase: "RUN".to_string(),
1505 subscript_sep: "\x1c".to_string(),
1506 inplace_edit: String::new(),
1507 debug_flags: 0,
1508 perl_debug_flags: 0,
1509 eval_nesting: 0,
1510 argv_current_file: String::new(),
1511 diamond_next_idx: 0,
1512 diamond_reader: None,
1513 strict_refs: false,
1514 strict_subs: false,
1515 strict_vars: false,
1516 utf8_pragma: false,
1517 open_pragma_utf8: false,
1518 feature_bits: FEAT_SAY,
1520 num_threads: 0, regex_cache: HashMap::new(),
1522 regex_last: None,
1523 regex_match_memo: None,
1524 regex_capture_scope_fresh: false,
1525 regex_pos: HashMap::new(),
1526 state_vars: HashMap::new(),
1527 state_bindings_stack: Vec::new(),
1528 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1529 dir_handles: HashMap::new(),
1530 io_file_slots: HashMap::new(),
1531 pipe_children: HashMap::new(),
1532 socket_handles: HashMap::new(),
1533 wantarray_kind: WantarrayCtx::Scalar,
1534 profiler: None,
1535 module_export_lists: HashMap::new(),
1536 virtual_modules: HashMap::new(),
1537 tied_hashes: HashMap::new(),
1538 tied_scalars: HashMap::new(),
1539 tied_arrays: HashMap::new(),
1540 overload_table: HashMap::new(),
1541 format_templates: HashMap::new(),
1542 special_caret_scalars,
1543 format_page_number: 0,
1544 format_lines_per_page: 60,
1545 format_lines_left: 0,
1546 format_line_break_chars: "\n".to_string(),
1547 format_top_name: String::new(),
1548 accumulator_format: String::new(),
1549 max_system_fd: 2,
1550 emergency_memory: String::new(),
1551 last_subpattern_name: String::new(),
1552 inc_hook_index: 0,
1553 multiline_match: false,
1554 executable_path,
1555 formfeed_string: "\x0c".to_string(),
1556 glob_handle_alias: HashMap::new(),
1557 glob_restore_frames: vec![Vec::new()],
1558 special_var_restore_frames: vec![Vec::new()],
1559 reflection_hashes_ready: false,
1560 english_enabled: false,
1561 english_no_match_vars: false,
1562 english_match_vars_ever_enabled: false,
1563 english_lexical_scalars: vec![HashSet::new()],
1564 our_lexical_scalars: vec![HashSet::new()],
1565 vm_jit_enabled: !matches!(
1566 std::env::var("STRYKE_NO_JIT"),
1567 Ok(v)
1568 if v == "1"
1569 || v.eq_ignore_ascii_case("true")
1570 || v.eq_ignore_ascii_case("yes")
1571 ),
1572 disasm_bytecode: false,
1573 pec_precompiled_chunk: None,
1574 pec_cache_fingerprint: None,
1575 in_generator: false,
1576 line_mode_skip_main: false,
1577 line_mode_eof_pending: false,
1578 line_mode_stdin_pending: VecDeque::new(),
1579 rate_limit_slots: Vec::new(),
1580 log_level_override: None,
1581 current_sub_stack: Vec::new(),
1582 debugger: None,
1583 debug_call_stack: Vec::new(),
1584 };
1585 s.install_overload_pragma_stubs();
1586 crate::list_util::install_scalar_util(&mut s);
1587 crate::list_util::install_sub_util(&mut s);
1588 s.install_utf8_unicode_to_native_stub();
1589 s
1590 }
1591
1592 fn install_utf8_unicode_to_native_stub(&mut self) {
1594 let empty: Block = vec![];
1595 let key = "utf8::unicode_to_native".to_string();
1596 self.subs.insert(
1597 key.clone(),
1598 Arc::new(PerlSub {
1599 name: key,
1600 params: vec![],
1601 body: empty,
1602 prototype: None,
1603 closure_env: None,
1604 fib_like: None,
1605 }),
1606 );
1607 }
1608
1609 pub(crate) fn ensure_reflection_hashes(&mut self) {
1613 if self.reflection_hashes_ready {
1614 return;
1615 }
1616 self.reflection_hashes_ready = true;
1617 let builtins_map = crate::builtins::builtins_hash_map();
1618 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1619 let extensions_map = crate::builtins::extensions_hash_map();
1620 let aliases_map = crate::builtins::aliases_hash_map();
1621 let descriptions_map = crate::builtins::descriptions_hash_map();
1622 let categories_map = crate::builtins::categories_hash_map();
1623 let primaries_map = crate::builtins::primaries_hash_map();
1624 let all_map = crate::builtins::all_hash_map();
1625 self.scope
1626 .declare_hash_global_frozen("stryke::builtins", builtins_map.clone());
1627 self.scope
1628 .declare_hash_global_frozen("stryke::perl_compats", perl_compats_map.clone());
1629 self.scope
1630 .declare_hash_global_frozen("stryke::extensions", extensions_map.clone());
1631 self.scope
1632 .declare_hash_global_frozen("stryke::aliases", aliases_map.clone());
1633 self.scope
1634 .declare_hash_global_frozen("stryke::descriptions", descriptions_map.clone());
1635 self.scope
1636 .declare_hash_global_frozen("stryke::categories", categories_map.clone());
1637 self.scope
1638 .declare_hash_global_frozen("stryke::primaries", primaries_map.clone());
1639 self.scope
1640 .declare_hash_global_frozen("stryke::all", all_map.clone());
1641 for (name, val) in [
1644 ("b", builtins_map),
1645 ("pc", perl_compats_map),
1646 ("e", extensions_map),
1647 ("a", aliases_map),
1648 ("d", descriptions_map),
1649 ("c", categories_map),
1650 ("p", primaries_map),
1651 ("all", all_map),
1652 ] {
1653 if !self.scope.any_frame_has_hash(name) {
1654 self.scope.declare_hash_global_frozen(name, val);
1655 }
1656 }
1657 }
1658
1659 fn install_overload_pragma_stubs(&mut self) {
1663 let empty: Block = vec![];
1664 for key in ["overload::import", "overload::unimport"] {
1665 let name = key.to_string();
1666 self.subs.insert(
1667 name.clone(),
1668 Arc::new(PerlSub {
1669 name,
1670 params: vec![],
1671 body: empty.clone(),
1672 prototype: None,
1673 closure_env: None,
1674 fib_like: None,
1675 }),
1676 );
1677 }
1678 }
1679
1680 pub fn line_mode_worker_clone(&self) -> Interpreter {
1683 Interpreter {
1684 scope: self.scope.clone(),
1685 subs: self.subs.clone(),
1686 struct_defs: self.struct_defs.clone(),
1687 enum_defs: self.enum_defs.clone(),
1688 class_defs: self.class_defs.clone(),
1689 trait_defs: self.trait_defs.clone(),
1690 file: self.file.clone(),
1691 output_handles: HashMap::new(),
1692 input_handles: HashMap::new(),
1693 ofs: self.ofs.clone(),
1694 ors: self.ors.clone(),
1695 irs: self.irs.clone(),
1696 errno: self.errno.clone(),
1697 errno_code: self.errno_code,
1698 eval_error: self.eval_error.clone(),
1699 eval_error_code: self.eval_error_code,
1700 eval_error_value: self.eval_error_value.clone(),
1701 argv: self.argv.clone(),
1702 env: self.env.clone(),
1703 env_materialized: self.env_materialized,
1704 program_name: self.program_name.clone(),
1705 line_number: 0,
1706 last_readline_handle: String::new(),
1707 last_stdin_die_bracket: "<STDIN>".to_string(),
1708 handle_line_numbers: HashMap::new(),
1709 flip_flop_active: Vec::new(),
1710 flip_flop_exclusive_left_line: Vec::new(),
1711 flip_flop_sequence: Vec::new(),
1712 flip_flop_last_dot: Vec::new(),
1713 flip_flop_tree: HashMap::new(),
1714 sigint_pending_caret: Cell::new(false),
1715 auto_split: self.auto_split,
1716 field_separator: self.field_separator.clone(),
1717 begin_blocks: self.begin_blocks.clone(),
1718 unit_check_blocks: self.unit_check_blocks.clone(),
1719 check_blocks: self.check_blocks.clone(),
1720 init_blocks: self.init_blocks.clone(),
1721 end_blocks: self.end_blocks.clone(),
1722 warnings: self.warnings,
1723 output_autoflush: self.output_autoflush,
1724 default_print_handle: self.default_print_handle.clone(),
1725 suppress_stdout: self.suppress_stdout,
1726 child_exit_status: self.child_exit_status,
1727 last_match: self.last_match.clone(),
1728 prematch: self.prematch.clone(),
1729 postmatch: self.postmatch.clone(),
1730 last_paren_match: self.last_paren_match.clone(),
1731 list_separator: self.list_separator.clone(),
1732 script_start_time: self.script_start_time,
1733 compile_hints: self.compile_hints,
1734 warning_bits: self.warning_bits,
1735 global_phase: self.global_phase.clone(),
1736 subscript_sep: self.subscript_sep.clone(),
1737 inplace_edit: self.inplace_edit.clone(),
1738 debug_flags: self.debug_flags,
1739 perl_debug_flags: self.perl_debug_flags,
1740 eval_nesting: self.eval_nesting,
1741 argv_current_file: String::new(),
1742 diamond_next_idx: 0,
1743 diamond_reader: None,
1744 strict_refs: self.strict_refs,
1745 strict_subs: self.strict_subs,
1746 strict_vars: self.strict_vars,
1747 utf8_pragma: self.utf8_pragma,
1748 open_pragma_utf8: self.open_pragma_utf8,
1749 feature_bits: self.feature_bits,
1750 num_threads: 0,
1751 regex_cache: self.regex_cache.clone(),
1752 regex_last: self.regex_last.clone(),
1753 regex_match_memo: self.regex_match_memo.clone(),
1754 regex_capture_scope_fresh: false,
1755 regex_pos: self.regex_pos.clone(),
1756 state_vars: self.state_vars.clone(),
1757 state_bindings_stack: Vec::new(),
1758 rand_rng: self.rand_rng.clone(),
1759 dir_handles: HashMap::new(),
1760 io_file_slots: HashMap::new(),
1761 pipe_children: HashMap::new(),
1762 socket_handles: HashMap::new(),
1763 wantarray_kind: self.wantarray_kind,
1764 profiler: None,
1765 module_export_lists: self.module_export_lists.clone(),
1766 virtual_modules: self.virtual_modules.clone(),
1767 tied_hashes: self.tied_hashes.clone(),
1768 tied_scalars: self.tied_scalars.clone(),
1769 tied_arrays: self.tied_arrays.clone(),
1770 overload_table: self.overload_table.clone(),
1771 format_templates: self.format_templates.clone(),
1772 special_caret_scalars: self.special_caret_scalars.clone(),
1773 format_page_number: self.format_page_number,
1774 format_lines_per_page: self.format_lines_per_page,
1775 format_lines_left: self.format_lines_left,
1776 format_line_break_chars: self.format_line_break_chars.clone(),
1777 format_top_name: self.format_top_name.clone(),
1778 accumulator_format: self.accumulator_format.clone(),
1779 max_system_fd: self.max_system_fd,
1780 emergency_memory: self.emergency_memory.clone(),
1781 last_subpattern_name: self.last_subpattern_name.clone(),
1782 inc_hook_index: self.inc_hook_index,
1783 multiline_match: self.multiline_match,
1784 executable_path: self.executable_path.clone(),
1785 formfeed_string: self.formfeed_string.clone(),
1786 glob_handle_alias: self.glob_handle_alias.clone(),
1787 glob_restore_frames: self.glob_restore_frames.clone(),
1788 special_var_restore_frames: self.special_var_restore_frames.clone(),
1789 reflection_hashes_ready: self.reflection_hashes_ready,
1790 english_enabled: self.english_enabled,
1791 english_no_match_vars: self.english_no_match_vars,
1792 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1793 english_lexical_scalars: self.english_lexical_scalars.clone(),
1794 our_lexical_scalars: self.our_lexical_scalars.clone(),
1795 vm_jit_enabled: self.vm_jit_enabled,
1796 disasm_bytecode: self.disasm_bytecode,
1797 pec_precompiled_chunk: None,
1799 pec_cache_fingerprint: None,
1800 in_generator: false,
1801 line_mode_skip_main: false,
1802 line_mode_eof_pending: false,
1803 line_mode_stdin_pending: VecDeque::new(),
1804 rate_limit_slots: Vec::new(),
1805 log_level_override: self.log_level_override,
1806 current_sub_stack: Vec::new(),
1807 debugger: None,
1808 debug_call_stack: Vec::new(),
1809 }
1810 }
1811
1812 pub(crate) fn parallel_thread_count(&mut self) -> usize {
1814 if self.num_threads == 0 {
1815 self.num_threads = rayon::current_num_threads();
1816 }
1817 self.num_threads
1818 }
1819
1820 pub(crate) fn eval_par_list_call(
1822 &mut self,
1823 name: &str,
1824 args: &[PerlValue],
1825 ctx: WantarrayCtx,
1826 line: usize,
1827 ) -> PerlResult<PerlValue> {
1828 match name {
1829 "puniq" => {
1830 let (list_src, show_prog) = match args.len() {
1831 0 => return Err(PerlError::runtime("puniq: expected LIST", line)),
1832 1 => (&args[0], false),
1833 2 => (&args[0], args[1].is_true()),
1834 _ => {
1835 return Err(PerlError::runtime(
1836 "puniq: expected LIST [, progress => EXPR]",
1837 line,
1838 ));
1839 }
1840 };
1841 let list = list_src.to_list();
1842 let n_threads = self.parallel_thread_count();
1843 let pmap_progress = PmapProgress::new(show_prog, list.len());
1844 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
1845 pmap_progress.finish();
1846 if ctx == WantarrayCtx::List {
1847 Ok(PerlValue::array(out))
1848 } else {
1849 Ok(PerlValue::integer(out.len() as i64))
1850 }
1851 }
1852 "pfirst" => {
1853 let (code_val, list_src, show_prog) = match args.len() {
1854 2 => (&args[0], &args[1], false),
1855 3 => (&args[0], &args[1], args[2].is_true()),
1856 _ => {
1857 return Err(PerlError::runtime(
1858 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
1859 line,
1860 ));
1861 }
1862 };
1863 let Some(sub) = code_val.as_code_ref() else {
1864 return Err(PerlError::runtime(
1865 "pfirst: first argument must be a code reference",
1866 line,
1867 ));
1868 };
1869 let sub = sub.clone();
1870 let list = list_src.to_list();
1871 if list.is_empty() {
1872 return Ok(PerlValue::UNDEF);
1873 }
1874 let pmap_progress = PmapProgress::new(show_prog, list.len());
1875 let subs = self.subs.clone();
1876 let (scope_capture, atomic_arrays, atomic_hashes) =
1877 self.scope.capture_with_atomics();
1878 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
1879 let mut local_interp = Interpreter::new();
1880 local_interp.subs = subs.clone();
1881 local_interp.scope.restore_capture(&scope_capture);
1882 local_interp
1883 .scope
1884 .restore_atomics(&atomic_arrays, &atomic_hashes);
1885 local_interp.enable_parallel_guard();
1886 local_interp.scope.set_topic(item);
1887 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1888 Ok(v) => v.is_true(),
1889 Err(_) => false,
1890 }
1891 });
1892 pmap_progress.finish();
1893 Ok(out.unwrap_or(PerlValue::UNDEF))
1894 }
1895 "pany" => {
1896 let (code_val, list_src, show_prog) = match args.len() {
1897 2 => (&args[0], &args[1], false),
1898 3 => (&args[0], &args[1], args[2].is_true()),
1899 _ => {
1900 return Err(PerlError::runtime(
1901 "pany: expected BLOCK, LIST [, progress => EXPR]",
1902 line,
1903 ));
1904 }
1905 };
1906 let Some(sub) = code_val.as_code_ref() else {
1907 return Err(PerlError::runtime(
1908 "pany: first argument must be a code reference",
1909 line,
1910 ));
1911 };
1912 let sub = sub.clone();
1913 let list = list_src.to_list();
1914 let pmap_progress = PmapProgress::new(show_prog, list.len());
1915 let subs = self.subs.clone();
1916 let (scope_capture, atomic_arrays, atomic_hashes) =
1917 self.scope.capture_with_atomics();
1918 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
1919 let mut local_interp = Interpreter::new();
1920 local_interp.subs = subs.clone();
1921 local_interp.scope.restore_capture(&scope_capture);
1922 local_interp
1923 .scope
1924 .restore_atomics(&atomic_arrays, &atomic_hashes);
1925 local_interp.enable_parallel_guard();
1926 local_interp.scope.set_topic(item);
1927 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1928 Ok(v) => v.is_true(),
1929 Err(_) => false,
1930 }
1931 });
1932 pmap_progress.finish();
1933 Ok(PerlValue::integer(if b { 1 } else { 0 }))
1934 }
1935 _ => Err(PerlError::runtime(
1936 format!("internal: unknown par_list builtin {name}"),
1937 line,
1938 )),
1939 }
1940 }
1941
1942 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
1943 #[cfg(unix)]
1944 if let Some(sig) = s.signal() {
1945 return sig as i64 & 0x7f;
1946 }
1947 let code = s.code().unwrap_or(0) as i64;
1948 code << 8
1949 }
1950
1951 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
1952 self.child_exit_status = self.encode_exit_status(s);
1953 }
1954
1955 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
1957 self.errno = e.to_string();
1958 self.errno_code = e.raw_os_error().unwrap_or(0);
1959 }
1960
1961 pub(crate) fn ssh_builtin_execute(&mut self, args: &[PerlValue]) -> PerlResult<PerlValue> {
1972 use std::process::Command;
1973 let mut cmd = Command::new("ssh");
1974 #[cfg(unix)]
1975 {
1976 use libc::geteuid;
1977 let home_for_ssh = if unsafe { geteuid() } == 0 {
1978 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
1979 } else {
1980 None
1981 };
1982 if let Some(h) = home_for_ssh {
1983 cmd.env("HOME", h);
1984 } else if std::env::var_os("HOME").is_none() {
1985 if let Some(h) = pw_home_dir_for_current_uid() {
1986 cmd.env("HOME", h);
1987 }
1988 }
1989 }
1990 for a in args {
1991 cmd.arg(a.to_string());
1992 }
1993 match cmd.status() {
1994 Ok(s) => {
1995 self.record_child_exit_status(s);
1996 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
1997 }
1998 Err(e) => {
1999 self.apply_io_error_to_errno(&e);
2000 Ok(PerlValue::integer(-1))
2001 }
2002 }
2003 }
2004
2005 pub(crate) fn set_eval_error(&mut self, msg: String) {
2007 self.eval_error = msg;
2008 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2009 self.eval_error_value = None;
2010 }
2011
2012 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &PerlError) {
2013 self.eval_error = e.to_string();
2014 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2015 self.eval_error_value = e.die_value.clone();
2016 }
2017
2018 pub(crate) fn clear_eval_error(&mut self) {
2019 self.eval_error = String::new();
2020 self.eval_error_code = 0;
2021 self.eval_error_value = None;
2022 }
2023
2024 fn bump_line_for_handle(&mut self, handle_key: &str) {
2026 self.last_readline_handle = handle_key.to_string();
2027 *self
2028 .handle_line_numbers
2029 .entry(handle_key.to_string())
2030 .or_insert(0) += 1;
2031 }
2032
2033 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
2035 if name.starts_with('^') {
2036 return name.to_string();
2037 }
2038 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
2039 let pkg = self.current_package();
2040 if !pkg.is_empty() && pkg != "main" {
2041 return format!("{}::{}", pkg, name);
2042 }
2043 }
2044 name.to_string()
2045 }
2046
2047 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
2049 if name.contains("::") {
2050 return name.to_string();
2051 }
2052 let pkg = self.current_package();
2053 if pkg.is_empty() || pkg == "main" {
2054 format!("main::{}", name)
2055 } else {
2056 format!("{}::{}", pkg, name)
2057 }
2058 }
2059
2060 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
2062 if name.contains("::") {
2063 return name.to_string();
2064 }
2065 for (lex, our) in self
2066 .english_lexical_scalars
2067 .iter()
2068 .zip(self.our_lexical_scalars.iter())
2069 .rev()
2070 {
2071 if lex.contains(name) {
2072 if our.contains(name) {
2073 return self.stash_scalar_name_for_package(name);
2074 }
2075 return name.to_string();
2076 }
2077 }
2078 name.to_string()
2079 }
2080
2081 pub(crate) fn tie_execute(
2083 &mut self,
2084 target_kind: u8,
2085 target_name: &str,
2086 class_and_args: Vec<PerlValue>,
2087 line: usize,
2088 ) -> PerlResult<PerlValue> {
2089 let mut it = class_and_args.into_iter();
2090 let class = it.next().unwrap_or(PerlValue::UNDEF);
2091 let pkg = class.to_string();
2092 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
2093 let tie_ctor = match target_kind {
2094 0 => "TIESCALAR",
2095 1 => "TIEARRAY",
2096 2 => "TIEHASH",
2097 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
2098 };
2099 let tie_fn = format!("{}::{}", pkg, tie_ctor);
2100 let sub = self
2101 .subs
2102 .get(&tie_fn)
2103 .cloned()
2104 .ok_or_else(|| PerlError::runtime(format!("tie: cannot find &{}", tie_fn), line))?;
2105 let mut call_args = vec![PerlValue::string(pkg.clone())];
2106 call_args.extend(it);
2107 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
2108 Ok(v) => v,
2109 Err(FlowOrError::Flow(_)) => PerlValue::UNDEF,
2110 Err(FlowOrError::Error(e)) => return Err(e),
2111 };
2112 match target_kind {
2113 0 => {
2114 self.tied_scalars.insert(target_name.to_string(), obj);
2115 }
2116 1 => {
2117 let key = self.stash_array_name_for_package(target_name);
2118 self.tied_arrays.insert(key, obj);
2119 }
2120 2 => {
2121 self.tied_hashes.insert(target_name.to_string(), obj);
2122 }
2123 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
2124 }
2125 Ok(PerlValue::UNDEF)
2126 }
2127
2128 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
2130 let key = format!("{}::ISA", class);
2131 self.scope
2132 .get_array(&key)
2133 .into_iter()
2134 .map(|v| v.to_string())
2135 .collect()
2136 }
2137
2138 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
2139 let p = |c: &str| self.parents_of_class(c);
2140 linearize_c3(class, &p, 0)
2141 }
2142
2143 pub(crate) fn resolve_method_full_name(
2145 &self,
2146 invocant_class: &str,
2147 method: &str,
2148 super_mode: bool,
2149 ) -> Option<String> {
2150 let mro = self.mro_linearize(invocant_class);
2151 let start = if super_mode {
2155 mro.iter()
2156 .position(|p| p == invocant_class)
2157 .map(|i| i + 1)
2158 .unwrap_or(1)
2161 } else {
2162 0
2163 };
2164 for pkg in mro.iter().skip(start) {
2165 if pkg == "UNIVERSAL" {
2166 continue;
2167 }
2168 let fq = format!("{}::{}", pkg, method);
2169 if self.subs.contains_key(&fq) {
2170 return Some(fq);
2171 }
2172 }
2173 mro.iter()
2174 .skip(start)
2175 .find(|p| *p != "UNIVERSAL")
2176 .map(|pkg| format!("{}::{}", pkg, method))
2177 }
2178
2179 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
2180 if let Some(alias) = self.glob_handle_alias.get(name) {
2181 return alias.clone();
2182 }
2183 if let Some(var_name) = name.strip_prefix('$') {
2186 let val = self.scope.get_scalar(var_name);
2187 let s = val.to_string();
2188 if !s.is_empty() {
2189 return self.resolve_io_handle_name(&s);
2190 }
2191 }
2192 name.to_string()
2193 }
2194
2195 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2197 if name.contains("::") {
2198 name.to_string()
2199 } else {
2200 self.qualify_sub_key(name)
2201 }
2202 }
2203
2204 pub(crate) fn copy_typeglob_slots(
2206 &mut self,
2207 lhs: &str,
2208 rhs: &str,
2209 line: usize,
2210 ) -> PerlResult<()> {
2211 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2212 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2213 match self.subs.get(&rhs_sub).cloned() {
2214 Some(s) => {
2215 self.subs.insert(lhs_sub, s);
2216 }
2217 None => {
2218 self.subs.remove(&lhs_sub);
2219 }
2220 }
2221 let sv = self.scope.get_scalar(rhs);
2222 self.scope
2223 .set_scalar(lhs, sv.clone())
2224 .map_err(|e| e.at_line(line))?;
2225 let lhs_an = self.stash_array_name_for_package(lhs);
2226 let rhs_an = self.stash_array_name_for_package(rhs);
2227 let av = self.scope.get_array(&rhs_an);
2228 self.scope
2229 .set_array(&lhs_an, av.clone())
2230 .map_err(|e| e.at_line(line))?;
2231 let hv = self.scope.get_hash(rhs);
2232 self.scope
2233 .set_hash(lhs, hv.clone())
2234 .map_err(|e| e.at_line(line))?;
2235 match self.glob_handle_alias.get(rhs).cloned() {
2236 Some(t) => {
2237 self.glob_handle_alias.insert(lhs.to_string(), t);
2238 }
2239 None => {
2240 self.glob_handle_alias.remove(lhs);
2241 }
2242 }
2243 Ok(())
2244 }
2245
2246 pub(crate) fn install_format_decl(
2248 &mut self,
2249 basename: &str,
2250 lines: &[String],
2251 line: usize,
2252 ) -> PerlResult<()> {
2253 let pkg = self.current_package();
2254 let key = format!("{}::{}", pkg, basename);
2255 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2256 self.format_templates.insert(key, Arc::new(tmpl));
2257 Ok(())
2258 }
2259
2260 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2262 let pkg = self.current_package();
2263 let ent = self.overload_table.entry(pkg).or_default();
2264 for (k, v) in pairs {
2265 ent.insert(k.clone(), v.clone());
2266 }
2267 }
2268
2269 pub(crate) fn local_declare_typeglob(
2272 &mut self,
2273 lhs: &str,
2274 rhs: Option<&str>,
2275 line: usize,
2276 ) -> PerlResult<()> {
2277 let old = self.glob_handle_alias.remove(lhs);
2278 let Some(frame) = self.glob_restore_frames.last_mut() else {
2279 return Err(PerlError::runtime(
2280 "internal: no glob restore frame for local *GLOB",
2281 line,
2282 ));
2283 };
2284 frame.push((lhs.to_string(), old));
2285 if let Some(r) = rhs {
2286 self.glob_handle_alias
2287 .insert(lhs.to_string(), r.to_string());
2288 }
2289 Ok(())
2290 }
2291
2292 pub(crate) fn scope_push_hook(&mut self) {
2293 self.scope.push_frame();
2294 self.glob_restore_frames.push(Vec::new());
2295 self.special_var_restore_frames.push(Vec::new());
2296 self.english_lexical_scalars.push(HashSet::new());
2297 self.our_lexical_scalars.push(HashSet::new());
2298 self.state_bindings_stack.push(Vec::new());
2299 }
2300
2301 #[inline]
2302 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2303 if let Some(s) = self.english_lexical_scalars.last_mut() {
2304 s.insert(name.to_string());
2305 }
2306 }
2307
2308 #[inline]
2309 fn note_our_scalar(&mut self, bare_name: &str) {
2310 if let Some(s) = self.our_lexical_scalars.last_mut() {
2311 s.insert(bare_name.to_string());
2312 }
2313 }
2314
2315 pub(crate) fn scope_pop_hook(&mut self) {
2316 if !self.scope.can_pop_frame() {
2317 return;
2318 }
2319 let defers = self.scope.take_defers();
2323 for coderef in defers {
2324 if let Some(sub) = coderef.as_code_ref() {
2325 let saved_wa = self.wantarray_kind;
2329 self.wantarray_kind = WantarrayCtx::Void;
2330 let _ = self.exec_block_no_scope(&sub.body);
2331 self.wantarray_kind = saved_wa;
2332 }
2333 }
2334 if let Some(bindings) = self.state_bindings_stack.pop() {
2336 for (var_name, state_key) in &bindings {
2337 let val = self.scope.get_scalar(var_name).clone();
2338 self.state_vars.insert(state_key.clone(), val);
2339 }
2340 }
2341 if let Some(entries) = self.special_var_restore_frames.pop() {
2344 for (name, old) in entries.into_iter().rev() {
2345 let _ = self.set_special_var(&name, &old);
2346 }
2347 }
2348 if let Some(entries) = self.glob_restore_frames.pop() {
2349 for (name, old) in entries.into_iter().rev() {
2350 match old {
2351 Some(s) => {
2352 self.glob_handle_alias.insert(name, s);
2353 }
2354 None => {
2355 self.glob_handle_alias.remove(&name);
2356 }
2357 }
2358 }
2359 }
2360 self.scope.pop_frame();
2361 let _ = self.english_lexical_scalars.pop();
2362 let _ = self.our_lexical_scalars.pop();
2363 }
2364
2365 #[inline]
2368 pub(crate) fn enable_parallel_guard(&mut self) {
2369 self.scope.set_parallel_guard(true);
2370 }
2371
2372 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2375 self.begin_blocks.clear();
2376 self.unit_check_blocks.clear();
2377 self.check_blocks.clear();
2378 self.init_blocks.clear();
2379 self.end_blocks.clear();
2380 }
2381
2382 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2388 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2389 self.scope_pop_hook();
2390 }
2391 }
2392
2393 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> PerlResult<()> {
2400 self.touch_env_hash("SIG");
2401 let v = self.scope.get_hash_element("SIG", sig);
2402 if v.is_undef() {
2403 return Self::default_sig_action(sig);
2404 }
2405 if let Some(s) = v.as_str() {
2406 if s == "IGNORE" {
2407 return Ok(());
2408 }
2409 if s == "DEFAULT" {
2410 return Self::default_sig_action(sig);
2411 }
2412 }
2413 if let Some(sub) = v.as_code_ref() {
2414 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2415 Ok(_) => Ok(()),
2416 Err(FlowOrError::Flow(_)) => Ok(()),
2417 Err(FlowOrError::Error(e)) => Err(e),
2418 }
2419 } else {
2420 Self::default_sig_action(sig)
2421 }
2422 }
2423
2424 #[inline]
2426 fn default_sig_action(sig: &str) -> PerlResult<()> {
2427 match sig {
2428 "INT" => std::process::exit(130),
2430 "TERM" => std::process::exit(143),
2431 "ALRM" => std::process::exit(142),
2432 "CHLD" => Ok(()),
2434 _ => Ok(()),
2435 }
2436 }
2437
2438 pub fn materialize_env_if_needed(&mut self) {
2441 if self.env_materialized {
2442 return;
2443 }
2444 self.env = std::env::vars()
2445 .map(|(k, v)| (k, PerlValue::string(v)))
2446 .collect();
2447 self.scope
2448 .set_hash("ENV", self.env.clone())
2449 .expect("set %ENV");
2450 self.env_materialized = true;
2451 }
2452
2453 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2455 self.materialize_env_if_needed();
2456 if let Some(x) = self.log_level_override {
2457 return x;
2458 }
2459 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2460 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2461 }
2462
2463 pub(crate) fn no_color_effective(&mut self) -> bool {
2465 self.materialize_env_if_needed();
2466 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2467 if v.is_undef() {
2468 return false;
2469 }
2470 !v.to_string().is_empty()
2471 }
2472
2473 #[inline]
2474 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2475 if hash_name == "ENV" {
2476 self.materialize_env_if_needed();
2477 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2478 match hash_name {
2479 "b"
2480 | "pc"
2481 | "e"
2482 | "a"
2483 | "d"
2484 | "c"
2485 | "p"
2486 | "all"
2487 | "stryke::builtins"
2488 | "stryke::perl_compats"
2489 | "stryke::extensions"
2490 | "stryke::aliases"
2491 | "stryke::descriptions"
2492 | "stryke::categories"
2493 | "stryke::primaries"
2494 | "stryke::all" => {
2495 self.ensure_reflection_hashes();
2496 }
2497 _ => {}
2498 }
2499 }
2500 }
2501
2502 pub(crate) fn exists_arrow_hash_element(
2504 &self,
2505 container: PerlValue,
2506 key: &str,
2507 line: usize,
2508 ) -> PerlResult<bool> {
2509 if let Some(r) = container.as_hash_ref() {
2510 return Ok(r.read().contains_key(key));
2511 }
2512 if let Some(b) = container.as_blessed_ref() {
2513 let data = b.data.read();
2514 if let Some(r) = data.as_hash_ref() {
2515 return Ok(r.read().contains_key(key));
2516 }
2517 if let Some(hm) = data.as_hash_map() {
2518 return Ok(hm.contains_key(key));
2519 }
2520 return Err(PerlError::runtime(
2521 "exists argument is not a HASH reference",
2522 line,
2523 ));
2524 }
2525 Err(PerlError::runtime(
2526 "exists argument is not a HASH reference",
2527 line,
2528 ))
2529 }
2530
2531 pub(crate) fn delete_arrow_hash_element(
2533 &self,
2534 container: PerlValue,
2535 key: &str,
2536 line: usize,
2537 ) -> PerlResult<PerlValue> {
2538 if let Some(r) = container.as_hash_ref() {
2539 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2540 }
2541 if let Some(b) = container.as_blessed_ref() {
2542 let mut data = b.data.write();
2543 if let Some(r) = data.as_hash_ref() {
2544 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2545 }
2546 if let Some(mut map) = data.as_hash_map() {
2547 let v = map.shift_remove(key).unwrap_or(PerlValue::UNDEF);
2548 *data = PerlValue::hash(map);
2549 return Ok(v);
2550 }
2551 return Err(PerlError::runtime(
2552 "delete argument is not a HASH reference",
2553 line,
2554 ));
2555 }
2556 Err(PerlError::runtime(
2557 "delete argument is not a HASH reference",
2558 line,
2559 ))
2560 }
2561
2562 pub(crate) fn exists_arrow_array_element(
2564 &self,
2565 container: PerlValue,
2566 idx: i64,
2567 line: usize,
2568 ) -> PerlResult<bool> {
2569 if let Some(a) = container.as_array_ref() {
2570 let arr = a.read();
2571 let i = if idx < 0 {
2572 (arr.len() as i64 + idx) as usize
2573 } else {
2574 idx as usize
2575 };
2576 return Ok(i < arr.len());
2577 }
2578 Err(PerlError::runtime(
2579 "exists argument is not an ARRAY reference",
2580 line,
2581 ))
2582 }
2583
2584 pub(crate) fn delete_arrow_array_element(
2586 &self,
2587 container: PerlValue,
2588 idx: i64,
2589 line: usize,
2590 ) -> PerlResult<PerlValue> {
2591 if let Some(a) = container.as_array_ref() {
2592 let mut arr = a.write();
2593 let i = if idx < 0 {
2594 (arr.len() as i64 + idx) as usize
2595 } else {
2596 idx as usize
2597 };
2598 if i >= arr.len() {
2599 return Ok(PerlValue::UNDEF);
2600 }
2601 let old = arr.get(i).cloned().unwrap_or(PerlValue::UNDEF);
2602 arr[i] = PerlValue::UNDEF;
2603 return Ok(old);
2604 }
2605 Err(PerlError::runtime(
2606 "delete argument is not an ARRAY reference",
2607 line,
2608 ))
2609 }
2610
2611 pub(crate) fn inc_directories(&self) -> Vec<String> {
2613 let mut v: Vec<String> = self
2614 .scope
2615 .get_array("INC")
2616 .into_iter()
2617 .map(|x| x.to_string())
2618 .filter(|s| !s.is_empty())
2619 .collect();
2620 if v.is_empty() {
2621 v.push(".".to_string());
2622 }
2623 v
2624 }
2625
2626 #[inline]
2627 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
2628 matches!(
2629 name,
2630 "_" | "0"
2631 | "!"
2632 | "@"
2633 | "/"
2634 | "\\"
2635 | ","
2636 | "."
2637 | "__PACKAGE__"
2638 | "$$"
2639 | "|"
2640 | "?"
2641 | "\""
2642 | "&"
2643 | "`"
2644 | "'"
2645 | "+"
2646 | "<"
2647 | ">"
2648 | "("
2649 | ")"
2650 | "]"
2651 | ";"
2652 | "ARGV"
2653 | "%"
2654 | "="
2655 | "-"
2656 | ":"
2657 | "*"
2658 | "INC"
2659 ) || name.chars().all(|c| c.is_ascii_digit())
2660 || name.starts_with('^')
2661 || (name.starts_with('#') && name.len() > 1)
2662 }
2663
2664 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2665 if !self.strict_vars
2666 || Self::strict_scalar_exempt(name)
2667 || name.contains("::")
2668 || self.scope.scalar_binding_exists(name)
2669 {
2670 return Ok(());
2671 }
2672 Err(PerlError::runtime(
2673 format!(
2674 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
2675 name, name
2676 ),
2677 line,
2678 )
2679 .into())
2680 }
2681
2682 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2683 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
2684 return Ok(());
2685 }
2686 Err(PerlError::runtime(
2687 format!(
2688 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
2689 name, name
2690 ),
2691 line,
2692 )
2693 .into())
2694 }
2695
2696 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2697 if !self.strict_vars
2699 || name.contains("::")
2700 || self.scope.hash_binding_exists(name)
2701 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
2702 {
2703 return Ok(());
2704 }
2705 Err(PerlError::runtime(
2706 format!(
2707 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
2708 name, name
2709 ),
2710 line,
2711 )
2712 .into())
2713 }
2714
2715 fn looks_like_version_only(spec: &str) -> bool {
2716 let t = spec.trim();
2717 !t.is_empty()
2718 && !t.contains('/')
2719 && !t.contains('\\')
2720 && !t.contains("::")
2721 && t.chars()
2722 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
2723 && t.chars().any(|c| c.is_ascii_digit())
2724 }
2725
2726 fn module_spec_to_relpath(spec: &str) -> String {
2727 let t = spec.trim();
2728 if t.contains("::") {
2729 format!("{}.pm", t.replace("::", "/"))
2730 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
2731 t.replace('\\', "/")
2732 } else {
2733 format!("{}.pm", t)
2734 }
2735 }
2736
2737 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
2740 if name.contains("::") {
2741 return name.to_string();
2742 }
2743 let pkg = self.current_package();
2744 if pkg.is_empty() || pkg == "main" {
2745 name.to_string()
2746 } else {
2747 format!("{}::{}", pkg, name)
2748 }
2749 }
2750
2751 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
2753 let mut msg = format!("Undefined subroutine &{}", name);
2754 if self.strict_subs {
2755 msg.push_str(
2756 " (strict subs: declare the sub or use a fully qualified name before calling)",
2757 );
2758 }
2759 msg
2760 }
2761
2762 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
2764 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
2765 if self.strict_subs {
2766 msg.push_str(
2767 " (strict subs: declare the sub or use a fully qualified name before calling)",
2768 );
2769 }
2770 msg
2771 }
2772
2773 fn import_alias_key(&self, short: &str) -> String {
2775 self.qualify_sub_key(short)
2776 }
2777
2778 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
2780 if imports.len() == 1 {
2781 match &imports[0].kind {
2782 ExprKind::QW(ws) => return ws.is_empty(),
2783 ExprKind::List(xs) => return xs.is_empty(),
2785 _ => {}
2786 }
2787 }
2788 false
2789 }
2790
2791 fn apply_module_import(
2793 &mut self,
2794 module: &str,
2795 imports: &[Expr],
2796 line: usize,
2797 ) -> PerlResult<()> {
2798 if imports.is_empty() {
2799 return self.import_all_from_module(module, line);
2800 }
2801 if Self::is_explicit_empty_import_list(imports) {
2802 return Ok(());
2803 }
2804 let names = Self::pragma_import_strings(imports, line)?;
2805 if names.is_empty() {
2806 return Ok(());
2807 }
2808 for name in names {
2809 self.import_one_symbol(module, &name, line)?;
2810 }
2811 Ok(())
2812 }
2813
2814 fn import_all_from_module(&mut self, module: &str, line: usize) -> PerlResult<()> {
2815 if module == "List::Util" {
2816 crate::list_util::ensure_list_util(self);
2817 }
2818 if let Some(lists) = self.module_export_lists.get(module) {
2819 let export: Vec<String> = lists.export.clone();
2820 for short in export {
2821 self.import_named_sub(module, &short, line)?;
2822 }
2823 return Ok(());
2824 }
2825 let prefix = format!("{}::", module);
2827 let keys: Vec<String> = self
2828 .subs
2829 .keys()
2830 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
2831 .cloned()
2832 .collect();
2833 for k in keys {
2834 let short = k[prefix.len()..].to_string();
2835 if let Some(sub) = self.subs.get(&k).cloned() {
2836 let alias = self.import_alias_key(&short);
2837 self.subs.insert(alias, sub);
2838 }
2839 }
2840 Ok(())
2841 }
2842
2843 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> PerlResult<()> {
2845 if module == "List::Util" {
2846 crate::list_util::ensure_list_util(self);
2847 }
2848 let qual = format!("{}::{}", module, short);
2849 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
2850 PerlError::runtime(
2851 format!(
2852 "`{}` is not defined in module `{}` (expected `{}`)",
2853 short, module, qual
2854 ),
2855 line,
2856 )
2857 })?;
2858 let alias = self.import_alias_key(short);
2859 self.subs.insert(alias, sub);
2860 Ok(())
2861 }
2862
2863 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> PerlResult<()> {
2864 if let Some(lists) = self.module_export_lists.get(module) {
2865 let allowed: HashSet<&str> = lists
2866 .export
2867 .iter()
2868 .map(|s| s.as_str())
2869 .chain(lists.export_ok.iter().map(|s| s.as_str()))
2870 .collect();
2871 if !allowed.contains(export) {
2872 return Err(PerlError::runtime(
2873 format!(
2874 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
2875 export, module
2876 ),
2877 line,
2878 ));
2879 }
2880 }
2881 self.import_named_sub(module, export, line)
2882 }
2883
2884 fn record_exporter_our_array_name(&mut self, name: &str, items: &[PerlValue]) {
2886 if name != "EXPORT" && name != "EXPORT_OK" {
2887 return;
2888 }
2889 let pkg = self.current_package();
2890 if pkg.is_empty() || pkg == "main" {
2891 return;
2892 }
2893 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
2894 let ent = self.module_export_lists.entry(pkg).or_default();
2895 if name == "EXPORT" {
2896 ent.export = names;
2897 } else {
2898 ent.export_ok = names;
2899 }
2900 }
2901
2902 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
2906 let key = self.qualify_sub_key(name);
2907 let Some(sub) = self.subs.get(&key).cloned() else {
2908 return;
2909 };
2910 let captured = self.scope.capture();
2911 let closure_env = if captured.is_empty() {
2912 None
2913 } else {
2914 Some(captured)
2915 };
2916 let mut new_sub = (*sub).clone();
2917 new_sub.closure_env = closure_env;
2918 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
2919 self.subs.insert(key, Arc::new(new_sub));
2920 }
2921
2922 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<PerlSub>> {
2923 if let Some(s) = self.subs.get(name) {
2924 return Some(s.clone());
2925 }
2926 if !name.contains("::") {
2927 let pkg = self.current_package();
2928 if !pkg.is_empty() && pkg != "main" {
2929 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
2930 q.push_str(&pkg);
2931 q.push_str("::");
2932 q.push_str(name);
2933 return self.subs.get(&q).cloned();
2934 }
2935 }
2936 None
2937 }
2938
2939 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
2942 if let Some(first) = imports.first() {
2943 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
2944 return &imports[1..];
2945 }
2946 }
2947 imports
2948 }
2949
2950 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> PerlResult<Vec<String>> {
2952 let mut out = Vec::new();
2953 for e in imports {
2954 match &e.kind {
2955 ExprKind::String(s) => out.push(s.clone()),
2956 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
2957 ExprKind::Integer(n) => out.push(n.to_string()),
2958 ExprKind::InterpolatedString(parts) => {
2961 let mut s = String::new();
2962 for p in parts {
2963 match p {
2964 StringPart::Literal(l) => s.push_str(l),
2965 StringPart::ScalarVar(v) => {
2966 s.push('$');
2967 s.push_str(v);
2968 }
2969 StringPart::ArrayVar(v) => {
2970 s.push('@');
2971 s.push_str(v);
2972 }
2973 _ => {
2974 return Err(PerlError::runtime(
2975 "pragma import must be a compile-time string, qw(), or integer",
2976 e.line.max(default_line),
2977 ));
2978 }
2979 }
2980 }
2981 out.push(s);
2982 }
2983 _ => {
2984 return Err(PerlError::runtime(
2985 "pragma import must be a compile-time string, qw(), or integer",
2986 e.line.max(default_line),
2987 ));
2988 }
2989 }
2990 }
2991 Ok(out)
2992 }
2993
2994 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2995 if imports.is_empty() {
2996 self.strict_refs = true;
2997 self.strict_subs = true;
2998 self.strict_vars = true;
2999 return Ok(());
3000 }
3001 let names = Self::pragma_import_strings(imports, line)?;
3002 for name in names {
3003 match name.as_str() {
3004 "refs" => self.strict_refs = true,
3005 "subs" => self.strict_subs = true,
3006 "vars" => self.strict_vars = true,
3007 _ => {
3008 return Err(PerlError::runtime(
3009 format!("Unknown strict mode `{}`", name),
3010 line,
3011 ));
3012 }
3013 }
3014 }
3015 Ok(())
3016 }
3017
3018 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3019 if imports.is_empty() {
3020 self.strict_refs = false;
3021 self.strict_subs = false;
3022 self.strict_vars = false;
3023 return Ok(());
3024 }
3025 let names = Self::pragma_import_strings(imports, line)?;
3026 for name in names {
3027 match name.as_str() {
3028 "refs" => self.strict_refs = false,
3029 "subs" => self.strict_subs = false,
3030 "vars" => self.strict_vars = false,
3031 _ => {
3032 return Err(PerlError::runtime(
3033 format!("Unknown strict mode `{}`", name),
3034 line,
3035 ));
3036 }
3037 }
3038 }
3039 Ok(())
3040 }
3041
3042 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3043 let items = Self::pragma_import_strings(imports, line)?;
3044 if items.is_empty() {
3045 return Err(PerlError::runtime(
3046 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
3047 line,
3048 ));
3049 }
3050 for item in items {
3051 let s = item.trim();
3052 if let Some(rest) = s.strip_prefix(':') {
3053 self.apply_feature_bundle(rest, line)?;
3054 } else {
3055 self.apply_feature_name(s, true, line)?;
3056 }
3057 }
3058 Ok(())
3059 }
3060
3061 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3062 if imports.is_empty() {
3063 self.feature_bits = 0;
3064 return Ok(());
3065 }
3066 let items = Self::pragma_import_strings(imports, line)?;
3067 for item in items {
3068 let s = item.trim();
3069 if let Some(rest) = s.strip_prefix(':') {
3070 self.clear_feature_bundle(rest);
3071 } else {
3072 self.apply_feature_name(s, false, line)?;
3073 }
3074 }
3075 Ok(())
3076 }
3077
3078 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> PerlResult<()> {
3079 let key = v.trim();
3080 match key {
3081 "5.10" | "5.010" | "5.10.0" => {
3082 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3083 }
3084 "5.12" | "5.012" | "5.12.0" => {
3085 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3086 }
3087 _ => {
3088 return Err(PerlError::runtime(
3089 format!("unsupported feature bundle :{}", key),
3090 line,
3091 ));
3092 }
3093 }
3094 Ok(())
3095 }
3096
3097 fn clear_feature_bundle(&mut self, v: &str) {
3098 let key = v.trim();
3099 if matches!(
3100 key,
3101 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
3102 ) {
3103 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
3104 }
3105 }
3106
3107 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> PerlResult<()> {
3108 let bit = match name {
3109 "say" => FEAT_SAY,
3110 "state" => FEAT_STATE,
3111 "switch" => FEAT_SWITCH,
3112 "unicode_strings" => FEAT_UNICODE_STRINGS,
3113 "postderef"
3117 | "postderef_qq"
3118 | "evalbytes"
3119 | "current_sub"
3120 | "fc"
3121 | "lexical_subs"
3122 | "signatures"
3123 | "refaliasing"
3124 | "bitwise"
3125 | "isa"
3126 | "indirect"
3127 | "multidimensional"
3128 | "bareword_filehandles"
3129 | "try"
3130 | "defer"
3131 | "extra_paired_delimiters"
3132 | "module_true"
3133 | "class"
3134 | "array_base" => return Ok(()),
3135 _ => {
3136 return Err(PerlError::runtime(
3137 format!("unknown feature `{}`", name),
3138 line,
3139 ));
3140 }
3141 };
3142 if enable {
3143 self.feature_bits |= bit;
3144 } else {
3145 self.feature_bits &= !bit;
3146 }
3147 Ok(())
3148 }
3149
3150 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> PerlResult<PerlValue> {
3152 let t = spec.trim();
3153 if t.is_empty() {
3154 return Err(PerlError::runtime("require: empty argument", line));
3155 }
3156 match t {
3157 "strict" => {
3158 self.apply_use_strict(&[], line)?;
3159 return Ok(PerlValue::integer(1));
3160 }
3161 "utf8" => {
3162 self.utf8_pragma = true;
3163 return Ok(PerlValue::integer(1));
3164 }
3165 "feature" | "v5" => {
3166 return Ok(PerlValue::integer(1));
3167 }
3168 "warnings" => {
3169 self.warnings = true;
3170 return Ok(PerlValue::integer(1));
3171 }
3172 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
3173 return Ok(PerlValue::integer(1));
3174 }
3175 _ => {}
3176 }
3177 let p = Path::new(t);
3178 if p.is_absolute() {
3179 return self.require_absolute_path(p, line);
3180 }
3181 if t.starts_with("./") || t.starts_with("../") {
3182 return self.require_relative_path(p, line);
3183 }
3184 if Self::looks_like_version_only(t) {
3185 return Ok(PerlValue::integer(1));
3186 }
3187 let relpath = Self::module_spec_to_relpath(t);
3188 self.require_from_inc(&relpath, line)
3189 }
3190
3191 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> PerlResult<()> {
3193 let v = self.scope.get_hash_element("^HOOK", key);
3194 if v.is_undef() {
3195 return Ok(());
3196 }
3197 let Some(sub) = v.as_code_ref() else {
3198 return Ok(());
3199 };
3200 let r = self.call_sub(
3201 sub.as_ref(),
3202 vec![PerlValue::string(path.to_string())],
3203 WantarrayCtx::Scalar,
3204 line,
3205 );
3206 match r {
3207 Ok(_) => Ok(()),
3208 Err(FlowOrError::Error(e)) => Err(e),
3209 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3210 Err(FlowOrError::Flow(other)) => Err(PerlError::runtime(
3211 format!(
3212 "require hook {:?} returned unexpected control flow: {:?}",
3213 key, other
3214 ),
3215 line,
3216 )),
3217 }
3218 }
3219
3220 fn require_absolute_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3221 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3222 let key = canon.to_string_lossy().into_owned();
3223 if self.scope.exists_hash_element("INC", &key) {
3224 return Ok(PerlValue::integer(1));
3225 }
3226 self.invoke_require_hook("require__before", &key, line)?;
3227 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3228 PerlError::runtime(
3229 format!("Can't open {} for reading: {}", canon.display(), e),
3230 line,
3231 )
3232 })?;
3233 let code = crate::data_section::strip_perl_end_marker(&code);
3234 self.scope
3235 .set_hash_element("INC", &key, PerlValue::string(key.clone()))?;
3236 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3237 let r = crate::parse_and_run_string_in_file(code, self, &key);
3238 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3239 r?;
3240 self.invoke_require_hook("require__after", &key, line)?;
3241 Ok(PerlValue::integer(1))
3242 }
3243
3244 fn require_relative_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3245 if !path.exists() {
3246 return Err(PerlError::runtime(
3247 format!(
3248 "Can't locate {} (relative path does not exist)",
3249 path.display()
3250 ),
3251 line,
3252 ));
3253 }
3254 self.require_absolute_path(path, line)
3255 }
3256
3257 fn require_from_inc(&mut self, relpath: &str, line: usize) -> PerlResult<PerlValue> {
3258 if self.scope.exists_hash_element("INC", relpath) {
3259 return Ok(PerlValue::integer(1));
3260 }
3261 self.invoke_require_hook("require__before", relpath, line)?;
3262
3263 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3265 let code = crate::data_section::strip_perl_end_marker(&code);
3266 self.scope.set_hash_element(
3267 "INC",
3268 relpath,
3269 PerlValue::string(format!("(virtual)/{}", relpath)),
3270 )?;
3271 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3272 let r = crate::parse_and_run_string_in_file(code, self, relpath);
3273 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3274 r?;
3275 self.invoke_require_hook("require__after", relpath, line)?;
3276 return Ok(PerlValue::integer(1));
3277 }
3278
3279 for dir in self.inc_directories() {
3280 let full = Path::new(&dir).join(relpath);
3281 if full.is_file() {
3282 let code = read_file_text_perl_compat(&full).map_err(|e| {
3283 PerlError::runtime(
3284 format!("Can't open {} for reading: {}", full.display(), e),
3285 line,
3286 )
3287 })?;
3288 let code = crate::data_section::strip_perl_end_marker(&code);
3289 let abs = full.canonicalize().unwrap_or(full);
3290 let abs_s = abs.to_string_lossy().into_owned();
3291 self.scope
3292 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3293 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3294 let r = crate::parse_and_run_string_in_file(code, self, &abs_s);
3295 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3296 r?;
3297 self.invoke_require_hook("require__after", relpath, line)?;
3298 return Ok(PerlValue::integer(1));
3299 }
3300 }
3301 Err(PerlError::runtime(
3302 format!(
3303 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3304 relpath
3305 ),
3306 line,
3307 ))
3308 }
3309
3310 pub fn register_virtual_module(&mut self, path: String, source: String) {
3312 self.virtual_modules.insert(path, source);
3313 }
3314
3315 pub(crate) fn exec_use_stmt(
3317 &mut self,
3318 module: &str,
3319 imports: &[Expr],
3320 line: usize,
3321 ) -> PerlResult<()> {
3322 match module {
3323 "strict" => self.apply_use_strict(imports, line),
3324 "utf8" => {
3325 if !imports.is_empty() {
3326 return Err(PerlError::runtime("use utf8 takes no arguments", line));
3327 }
3328 self.utf8_pragma = true;
3329 Ok(())
3330 }
3331 "feature" => self.apply_use_feature(imports, line),
3332 "v5" => Ok(()),
3333 "warnings" => {
3334 self.warnings = true;
3335 Ok(())
3336 }
3337 "English" => {
3338 self.english_enabled = true;
3339 let args = Self::pragma_import_strings(imports, line)?;
3340 let no_match = args.iter().any(|a| a == "-no_match_vars");
3341 if !no_match {
3345 self.english_match_vars_ever_enabled = true;
3346 }
3347 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3348 Ok(())
3349 }
3350 "Env" => self.apply_use_env(imports, line),
3351 "open" => self.apply_use_open(imports, line),
3352 "constant" => self.apply_use_constant(imports, line),
3353 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3354 _ => {
3355 self.require_execute(module, line)?;
3356 let imports = Self::imports_after_leading_use_version(imports);
3357 self.apply_module_import(module, imports, line)?;
3358 Ok(())
3359 }
3360 }
3361 }
3362
3363 pub(crate) fn exec_no_stmt(
3365 &mut self,
3366 module: &str,
3367 imports: &[Expr],
3368 line: usize,
3369 ) -> PerlResult<()> {
3370 match module {
3371 "strict" => self.apply_no_strict(imports, line),
3372 "utf8" => {
3373 if !imports.is_empty() {
3374 return Err(PerlError::runtime("no utf8 takes no arguments", line));
3375 }
3376 self.utf8_pragma = false;
3377 Ok(())
3378 }
3379 "feature" => self.apply_no_feature(imports, line),
3380 "v5" => Ok(()),
3381 "warnings" => {
3382 self.warnings = false;
3383 Ok(())
3384 }
3385 "English" => {
3386 self.english_enabled = false;
3387 if !self.english_match_vars_ever_enabled {
3390 self.english_no_match_vars = false;
3391 }
3392 Ok(())
3393 }
3394 "open" => {
3395 self.open_pragma_utf8 = false;
3396 Ok(())
3397 }
3398 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3399 _ => Ok(()),
3400 }
3401 }
3402
3403 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3405 let names = Self::pragma_import_strings(imports, line)?;
3406 for n in names {
3407 let key = n.trim_start_matches('@');
3408 if key.eq_ignore_ascii_case("PATH") {
3409 let path_env = std::env::var("PATH").unwrap_or_default();
3410 let path_vec: Vec<PerlValue> = std::env::split_paths(&path_env)
3411 .map(|p| PerlValue::string(p.to_string_lossy().into_owned()))
3412 .collect();
3413 let aname = self.stash_array_name_for_package("PATH");
3414 self.scope.declare_array(&aname, path_vec);
3415 }
3416 }
3417 Ok(())
3418 }
3419
3420 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3422 let items = Self::pragma_import_strings(imports, line)?;
3423 for item in items {
3424 let s = item.trim();
3425 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3426 self.open_pragma_utf8 = true;
3427 continue;
3428 }
3429 if let Some(rest) = s.strip_prefix(":encoding(") {
3430 if let Some(inner) = rest.strip_suffix(')') {
3431 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3432 self.open_pragma_utf8 = true;
3433 }
3434 }
3435 }
3436 }
3437 Ok(())
3438 }
3439
3440 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3442 if imports.is_empty() {
3443 return Ok(());
3444 }
3445 if imports.len() == 1 {
3447 match &imports[0].kind {
3448 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
3449 _ => {}
3450 }
3451 }
3452 for imp in imports {
3453 match &imp.kind {
3454 ExprKind::List(items) => {
3455 if items.len() % 2 != 0 {
3456 return Err(PerlError::runtime(
3457 format!(
3458 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
3459 items.len()
3460 ),
3461 line,
3462 ));
3463 }
3464 let mut i = 0;
3465 while i < items.len() {
3466 let name = match &items[i].kind {
3467 ExprKind::String(s) => s.clone(),
3468 _ => {
3469 return Err(PerlError::runtime(
3470 "use constant: constant name must be a string literal",
3471 line,
3472 ));
3473 }
3474 };
3475 let val = match self.eval_expr(&items[i + 1]) {
3476 Ok(v) => v,
3477 Err(FlowOrError::Error(e)) => return Err(e),
3478 Err(FlowOrError::Flow(_)) => {
3479 return Err(PerlError::runtime(
3480 "use constant: unexpected control flow in initializer",
3481 line,
3482 ));
3483 }
3484 };
3485 self.install_constant_sub(&name, &val, line)?;
3486 i += 2;
3487 }
3488 }
3489 _ => {
3490 return Err(PerlError::runtime(
3491 "use constant: expected list of NAME => VALUE pairs",
3492 line,
3493 ));
3494 }
3495 }
3496 }
3497 Ok(())
3498 }
3499
3500 fn install_constant_sub(&mut self, name: &str, val: &PerlValue, line: usize) -> PerlResult<()> {
3501 let key = self.qualify_sub_key(name);
3502 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
3503 let body = vec![Statement {
3504 label: None,
3505 kind: StmtKind::Return(Some(ret_expr)),
3506 line,
3507 }];
3508 self.subs.insert(
3509 key.clone(),
3510 Arc::new(PerlSub {
3511 name: key,
3512 params: vec![],
3513 body,
3514 prototype: None,
3515 closure_env: None,
3516 fib_like: None,
3517 }),
3518 );
3519 Ok(())
3520 }
3521
3522 fn perl_value_to_const_literal_expr(&self, v: &PerlValue, line: usize) -> PerlResult<Expr> {
3524 if v.is_undef() {
3525 return Ok(Expr {
3526 kind: ExprKind::Undef,
3527 line,
3528 });
3529 }
3530 if let Some(n) = v.as_integer() {
3531 return Ok(Expr {
3532 kind: ExprKind::Integer(n),
3533 line,
3534 });
3535 }
3536 if let Some(f) = v.as_float() {
3537 return Ok(Expr {
3538 kind: ExprKind::Float(f),
3539 line,
3540 });
3541 }
3542 if let Some(s) = v.as_str() {
3543 return Ok(Expr {
3544 kind: ExprKind::String(s),
3545 line,
3546 });
3547 }
3548 if let Some(arr) = v.as_array_vec() {
3549 let mut elems = Vec::with_capacity(arr.len());
3550 for e in &arr {
3551 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3552 }
3553 return Ok(Expr {
3554 kind: ExprKind::ArrayRef(elems),
3555 line,
3556 });
3557 }
3558 if let Some(h) = v.as_hash_map() {
3559 let mut pairs = Vec::with_capacity(h.len());
3560 for (k, vv) in h.iter() {
3561 pairs.push((
3562 Expr {
3563 kind: ExprKind::String(k.clone()),
3564 line,
3565 },
3566 self.perl_value_to_const_literal_expr(vv, line)?,
3567 ));
3568 }
3569 return Ok(Expr {
3570 kind: ExprKind::HashRef(pairs),
3571 line,
3572 });
3573 }
3574 if let Some(aref) = v.as_array_ref() {
3575 let arr = aref.read();
3576 let mut elems = Vec::with_capacity(arr.len());
3577 for e in arr.iter() {
3578 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3579 }
3580 return Ok(Expr {
3581 kind: ExprKind::ArrayRef(elems),
3582 line,
3583 });
3584 }
3585 if let Some(href) = v.as_hash_ref() {
3586 let h = href.read();
3587 let mut pairs = Vec::with_capacity(h.len());
3588 for (k, vv) in h.iter() {
3589 pairs.push((
3590 Expr {
3591 kind: ExprKind::String(k.clone()),
3592 line,
3593 },
3594 self.perl_value_to_const_literal_expr(vv, line)?,
3595 ));
3596 }
3597 return Ok(Expr {
3598 kind: ExprKind::HashRef(pairs),
3599 line,
3600 });
3601 }
3602 Err(PerlError::runtime(
3603 format!("use constant: unsupported value type ({v:?})"),
3604 line,
3605 ))
3606 }
3607
3608 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> PerlResult<()> {
3610 if crate::list_util::program_needs_list_util(program) {
3611 crate::list_util::ensure_list_util(self);
3612 }
3613 for stmt in &program.statements {
3614 match &stmt.kind {
3615 StmtKind::Package { name } => {
3616 let _ = self
3617 .scope
3618 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
3619 }
3620 StmtKind::SubDecl {
3621 name,
3622 params,
3623 body,
3624 prototype,
3625 } => {
3626 let key = self.qualify_sub_key(name);
3627 let mut sub = PerlSub {
3628 name: name.clone(),
3629 params: params.clone(),
3630 body: body.clone(),
3631 closure_env: None,
3632 prototype: prototype.clone(),
3633 fib_like: None,
3634 };
3635 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
3636 self.subs.insert(key, Arc::new(sub));
3637 }
3638 StmtKind::UsePerlVersion { .. } => {}
3639 StmtKind::Use { module, imports } => {
3640 self.exec_use_stmt(module, imports, stmt.line)?;
3641 }
3642 StmtKind::UseOverload { pairs } => {
3643 self.install_use_overload_pairs(pairs);
3644 }
3645 StmtKind::FormatDecl { name, lines } => {
3646 self.install_format_decl(name, lines, stmt.line)?;
3647 }
3648 StmtKind::No { module, imports } => {
3649 self.exec_no_stmt(module, imports, stmt.line)?;
3650 }
3651 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
3652 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
3653 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
3654 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
3655 StmtKind::End(block) => self.end_blocks.push(block.clone()),
3656 _ => {}
3657 }
3658 }
3659 Ok(())
3660 }
3661
3662 pub fn install_data_handle(&mut self, data: Vec<u8>) {
3664 self.input_handles.insert(
3665 "DATA".to_string(),
3666 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
3667 );
3668 }
3669
3670 pub(crate) fn open_builtin_execute(
3676 &mut self,
3677 handle_name: String,
3678 mode_s: String,
3679 file_opt: Option<String>,
3680 line: usize,
3681 ) -> PerlResult<PerlValue> {
3682 let (actual_mode, path) = if let Some(f) = file_opt {
3687 (mode_s, f)
3688 } else {
3689 let trimmed = mode_s.trim();
3690 if let Some(rest) = trimmed.strip_prefix('|') {
3691 ("|-".to_string(), rest.trim_start().to_string())
3692 } else if trimmed.ends_with('|') {
3693 let mut cmd = trimmed.to_string();
3694 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
3696 } else if let Some(rest) = trimmed.strip_prefix(">>") {
3697 (">>".to_string(), rest.trim().to_string())
3698 } else if let Some(rest) = trimmed.strip_prefix('>') {
3699 (">".to_string(), rest.trim().to_string())
3700 } else if let Some(rest) = trimmed.strip_prefix('<') {
3701 ("<".to_string(), rest.trim().to_string())
3702 } else {
3703 ("<".to_string(), trimmed.to_string())
3704 }
3705 };
3706 let handle_return = handle_name.clone();
3707 match actual_mode.as_str() {
3708 "-|" => {
3709 let mut cmd = piped_shell_command(&path);
3710 cmd.stdout(Stdio::piped());
3711 let mut child = cmd.spawn().map_err(|e| {
3712 self.apply_io_error_to_errno(&e);
3713 PerlError::runtime(format!("Can't open pipe from command: {}", e), line)
3714 })?;
3715 let stdout = child
3716 .stdout
3717 .take()
3718 .ok_or_else(|| PerlError::runtime("pipe: child has no stdout", line))?;
3719 self.input_handles
3720 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
3721 self.pipe_children.insert(handle_name, child);
3722 }
3723 "|-" => {
3724 let mut cmd = piped_shell_command(&path);
3725 cmd.stdin(Stdio::piped());
3726 let mut child = cmd.spawn().map_err(|e| {
3727 self.apply_io_error_to_errno(&e);
3728 PerlError::runtime(format!("Can't open pipe to command: {}", e), line)
3729 })?;
3730 let stdin = child
3731 .stdin
3732 .take()
3733 .ok_or_else(|| PerlError::runtime("pipe: child has no stdin", line))?;
3734 self.output_handles
3735 .insert(handle_name.clone(), Box::new(stdin));
3736 self.pipe_children.insert(handle_name, child);
3737 }
3738 "<" => {
3739 let file = std::fs::File::open(&path).map_err(|e| {
3740 self.apply_io_error_to_errno(&e);
3741 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3742 })?;
3743 let shared = Arc::new(Mutex::new(file));
3744 self.io_file_slots
3745 .insert(handle_name.clone(), Arc::clone(&shared));
3746 self.input_handles.insert(
3747 handle_name.clone(),
3748 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
3749 );
3750 }
3751 ">" => {
3752 let file = std::fs::File::create(&path).map_err(|e| {
3753 self.apply_io_error_to_errno(&e);
3754 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3755 })?;
3756 let shared = Arc::new(Mutex::new(file));
3757 self.io_file_slots
3758 .insert(handle_name.clone(), Arc::clone(&shared));
3759 self.output_handles.insert(
3760 handle_name.clone(),
3761 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3762 );
3763 }
3764 ">>" => {
3765 let file = std::fs::OpenOptions::new()
3766 .append(true)
3767 .create(true)
3768 .open(&path)
3769 .map_err(|e| {
3770 self.apply_io_error_to_errno(&e);
3771 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3772 })?;
3773 let shared = Arc::new(Mutex::new(file));
3774 self.io_file_slots
3775 .insert(handle_name.clone(), Arc::clone(&shared));
3776 self.output_handles.insert(
3777 handle_name.clone(),
3778 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3779 );
3780 }
3781 _ => {
3782 return Err(PerlError::runtime(
3783 format!("Unknown open mode '{}'", actual_mode),
3784 line,
3785 ));
3786 }
3787 }
3788 Ok(PerlValue::io_handle(handle_return))
3789 }
3790
3791 pub(crate) fn eval_chunk_by_builtin(
3795 &mut self,
3796 key_spec: &Expr,
3797 list_expr: &Expr,
3798 ctx: WantarrayCtx,
3799 line: usize,
3800 ) -> ExecResult {
3801 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
3802 let chunks = match &key_spec.kind {
3803 ExprKind::CodeRef { .. } => {
3804 let cr = self.eval_expr(key_spec)?;
3805 let Some(sub) = cr.as_code_ref() else {
3806 return Err(PerlError::runtime(
3807 "group_by/chunk_by: first argument must be { BLOCK }",
3808 line,
3809 )
3810 .into());
3811 };
3812 let sub = sub.clone();
3813 let mut chunks: Vec<PerlValue> = Vec::new();
3814 let mut run: Vec<PerlValue> = Vec::new();
3815 let mut prev_key: Option<PerlValue> = None;
3816 for item in list {
3817 self.scope.set_topic(item.clone());
3818 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
3819 Ok(k) => k,
3820 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
3821 Err(FlowOrError::Flow(Flow::Return(v))) => v,
3822 Err(_) => PerlValue::UNDEF,
3823 };
3824 match &prev_key {
3825 None => {
3826 run.push(item);
3827 prev_key = Some(key);
3828 }
3829 Some(pk) => {
3830 if key.str_eq(pk) {
3831 run.push(item);
3832 } else {
3833 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3834 std::mem::take(&mut run),
3835 ))));
3836 run.push(item);
3837 prev_key = Some(key);
3838 }
3839 }
3840 }
3841 }
3842 if !run.is_empty() {
3843 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3844 }
3845 chunks
3846 }
3847 _ => {
3848 let mut chunks: Vec<PerlValue> = Vec::new();
3849 let mut run: Vec<PerlValue> = Vec::new();
3850 let mut prev_key: Option<PerlValue> = None;
3851 for item in list {
3852 self.scope.set_topic(item.clone());
3853 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
3854 match &prev_key {
3855 None => {
3856 run.push(item);
3857 prev_key = Some(key);
3858 }
3859 Some(pk) => {
3860 if key.str_eq(pk) {
3861 run.push(item);
3862 } else {
3863 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3864 std::mem::take(&mut run),
3865 ))));
3866 run.push(item);
3867 prev_key = Some(key);
3868 }
3869 }
3870 }
3871 }
3872 if !run.is_empty() {
3873 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3874 }
3875 chunks
3876 }
3877 };
3878 Ok(match ctx {
3879 WantarrayCtx::List => PerlValue::array(chunks),
3880 WantarrayCtx::Scalar => PerlValue::integer(chunks.len() as i64),
3881 WantarrayCtx::Void => PerlValue::UNDEF,
3882 })
3883 }
3884
3885 pub(crate) fn list_higher_order_block_builtin(
3887 &mut self,
3888 name: &str,
3889 args: &[PerlValue],
3890 line: usize,
3891 ) -> PerlResult<PerlValue> {
3892 match self.list_higher_order_block_builtin_exec(name, args, line) {
3893 Ok(v) => Ok(v),
3894 Err(FlowOrError::Error(e)) => Err(e),
3895 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
3896 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
3897 format!("{name}: unsupported control flow in block"),
3898 line,
3899 )),
3900 }
3901 }
3902
3903 fn list_higher_order_block_builtin_exec(
3904 &mut self,
3905 name: &str,
3906 args: &[PerlValue],
3907 line: usize,
3908 ) -> ExecResult {
3909 if args.is_empty() {
3910 return Err(
3911 PerlError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
3912 );
3913 }
3914 let Some(sub) = args[0].as_code_ref() else {
3915 return Err(PerlError::runtime(
3916 format!("{name}: first argument must be {{ BLOCK }}"),
3917 line,
3918 )
3919 .into());
3920 };
3921 let sub = sub.clone();
3922 let items: Vec<PerlValue> = args[1..].to_vec();
3923 if matches!(name, "tap" | "peek") && items.len() == 1 {
3924 if let Some(p) = items[0].as_pipeline() {
3925 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
3926 return Ok(PerlValue::pipeline(Arc::clone(&p)));
3927 }
3928 let v = &items[0];
3929 if v.is_iterator() || v.as_array_vec().is_some() {
3930 let source = crate::map_stream::into_pull_iter(v.clone());
3931 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
3932 return Ok(PerlValue::iterator(Arc::new(
3933 crate::map_stream::TapIterator::new(
3934 source,
3935 sub,
3936 self.subs.clone(),
3937 capture,
3938 atomic_arrays,
3939 atomic_hashes,
3940 ),
3941 )));
3942 }
3943 }
3944 let wa = self.wantarray_kind;
3949 match name {
3950 "take_while" => {
3951 let mut out = Vec::new();
3952 for item in items {
3953 self.scope_push_hook();
3954 self.scope.set_topic(item.clone());
3955 let pred = self.exec_block(&sub.body)?;
3956 self.scope_pop_hook();
3957 if !pred.is_true() {
3958 break;
3959 }
3960 out.push(item);
3961 }
3962 Ok(match wa {
3963 WantarrayCtx::List => PerlValue::array(out),
3964 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3965 WantarrayCtx::Void => PerlValue::UNDEF,
3966 })
3967 }
3968 "drop_while" | "skip_while" => {
3969 let mut i = 0usize;
3970 while i < items.len() {
3971 self.scope_push_hook();
3972 self.scope.set_topic(items[i].clone());
3973 let pred = self.exec_block(&sub.body)?;
3974 self.scope_pop_hook();
3975 if !pred.is_true() {
3976 break;
3977 }
3978 i += 1;
3979 }
3980 let rest = items[i..].to_vec();
3981 Ok(match wa {
3982 WantarrayCtx::List => PerlValue::array(rest),
3983 WantarrayCtx::Scalar => PerlValue::integer(rest.len() as i64),
3984 WantarrayCtx::Void => PerlValue::UNDEF,
3985 })
3986 }
3987 "reject" => {
3988 let mut out = Vec::new();
3989 for item in items {
3990 self.scope_push_hook();
3991 self.scope.set_topic(item.clone());
3992 let pred = self.exec_block(&sub.body)?;
3993 self.scope_pop_hook();
3994 if !pred.is_true() {
3995 out.push(item);
3996 }
3997 }
3998 Ok(match wa {
3999 WantarrayCtx::List => PerlValue::array(out),
4000 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4001 WantarrayCtx::Void => PerlValue::UNDEF,
4002 })
4003 }
4004 "tap" | "peek" => {
4005 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
4006 Ok(match wa {
4007 WantarrayCtx::List => PerlValue::array(items),
4008 WantarrayCtx::Scalar => PerlValue::integer(items.len() as i64),
4009 WantarrayCtx::Void => PerlValue::UNDEF,
4010 })
4011 }
4012 "partition" => {
4013 let mut yes = Vec::new();
4014 let mut no = Vec::new();
4015 for item in items {
4016 self.scope.set_topic(item.clone());
4017 let pred = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4018 if pred.is_true() {
4019 yes.push(item);
4020 } else {
4021 no.push(item);
4022 }
4023 }
4024 let yes_ref = PerlValue::array_ref(Arc::new(RwLock::new(yes)));
4025 let no_ref = PerlValue::array_ref(Arc::new(RwLock::new(no)));
4026 Ok(match wa {
4027 WantarrayCtx::List => PerlValue::array(vec![yes_ref, no_ref]),
4028 WantarrayCtx::Scalar => PerlValue::integer(2),
4029 WantarrayCtx::Void => PerlValue::UNDEF,
4030 })
4031 }
4032 "min_by" => {
4033 let mut best: Option<(PerlValue, PerlValue)> = None;
4034 for item in items {
4035 self.scope.set_topic(item.clone());
4036 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4037 best = Some(match best {
4038 None => (item, key),
4039 Some((bv, bk)) => {
4040 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
4041 (item, key)
4042 } else {
4043 (bv, bk)
4044 }
4045 }
4046 });
4047 }
4048 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
4049 }
4050 "max_by" => {
4051 let mut best: Option<(PerlValue, PerlValue)> = None;
4052 for item in items {
4053 self.scope.set_topic(item.clone());
4054 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4055 best = Some(match best {
4056 None => (item, key),
4057 Some((bv, bk)) => {
4058 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
4059 (item, key)
4060 } else {
4061 (bv, bk)
4062 }
4063 }
4064 });
4065 }
4066 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
4067 }
4068 "zip_with" => {
4069 let flat: Vec<PerlValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
4072 let refs: Vec<Vec<PerlValue>> = flat
4073 .iter()
4074 .map(|el| {
4075 if let Some(ar) = el.as_array_ref() {
4076 ar.read().clone()
4077 } else if let Some(name) = el.as_array_binding_name() {
4078 self.scope.get_array(&name)
4079 } else {
4080 vec![el.clone()]
4081 }
4082 })
4083 .collect();
4084 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
4085 let mut out = Vec::with_capacity(max_len);
4086 for i in 0..max_len {
4087 let pair: Vec<PerlValue> = refs
4088 .iter()
4089 .map(|l| l.get(i).cloned().unwrap_or(PerlValue::UNDEF))
4090 .collect();
4091 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
4092 out.push(result);
4093 }
4094 Ok(match wa {
4095 WantarrayCtx::List => PerlValue::array(out),
4096 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4097 WantarrayCtx::Void => PerlValue::UNDEF,
4098 })
4099 }
4100 "count_by" => {
4101 let mut counts = indexmap::IndexMap::new();
4102 for item in items {
4103 self.scope.set_topic(item.clone());
4104 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4105 let k = key.to_string();
4106 let entry = counts.entry(k).or_insert(PerlValue::integer(0));
4107 *entry = PerlValue::integer(entry.to_int() + 1);
4108 }
4109 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(counts))))
4110 }
4111 _ => Err(PerlError::runtime(
4112 format!("internal: unknown list block builtin `{name}`"),
4113 line,
4114 )
4115 .into()),
4116 }
4117 }
4118
4119 pub(crate) fn builtin_rmdir_execute(
4121 &mut self,
4122 args: &[PerlValue],
4123 _line: usize,
4124 ) -> PerlResult<PerlValue> {
4125 let mut count = 0i64;
4126 for a in args {
4127 let p = a.to_string();
4128 if p.is_empty() {
4129 continue;
4130 }
4131 if std::fs::remove_dir(&p).is_ok() {
4132 count += 1;
4133 }
4134 }
4135 Ok(PerlValue::integer(count))
4136 }
4137
4138 pub(crate) fn builtin_touch_execute(
4140 &mut self,
4141 args: &[PerlValue],
4142 _line: usize,
4143 ) -> PerlResult<PerlValue> {
4144 let paths: Vec<String> = args.iter().map(|v| v.to_string()).collect();
4145 Ok(PerlValue::integer(crate::perl_fs::touch_paths(&paths)))
4146 }
4147
4148 pub(crate) fn builtin_utime_execute(
4150 &mut self,
4151 args: &[PerlValue],
4152 line: usize,
4153 ) -> PerlResult<PerlValue> {
4154 if args.len() < 3 {
4155 return Err(PerlError::runtime(
4156 "utime requires at least three arguments (atime, mtime, files...)",
4157 line,
4158 ));
4159 }
4160 let at = args[0].to_int();
4161 let mt = args[1].to_int();
4162 let paths: Vec<String> = args.iter().skip(2).map(|v| v.to_string()).collect();
4163 let n = crate::perl_fs::utime_paths(at, mt, &paths);
4164 #[cfg(not(unix))]
4165 if !paths.is_empty() && n == 0 {
4166 return Err(PerlError::runtime(
4167 "utime is not supported on this platform",
4168 line,
4169 ));
4170 }
4171 Ok(PerlValue::integer(n))
4172 }
4173
4174 pub(crate) fn builtin_umask_execute(
4176 &mut self,
4177 args: &[PerlValue],
4178 line: usize,
4179 ) -> PerlResult<PerlValue> {
4180 #[cfg(unix)]
4181 {
4182 let _ = line;
4183 if args.is_empty() {
4184 let cur = unsafe { libc::umask(0) };
4185 unsafe { libc::umask(cur) };
4186 return Ok(PerlValue::integer(cur as i64));
4187 }
4188 let new_m = args[0].to_int() as libc::mode_t;
4189 let old = unsafe { libc::umask(new_m) };
4190 Ok(PerlValue::integer(old as i64))
4191 }
4192 #[cfg(not(unix))]
4193 {
4194 let _ = args;
4195 Err(PerlError::runtime(
4196 "umask is not supported on this platform",
4197 line,
4198 ))
4199 }
4200 }
4201
4202 pub(crate) fn builtin_getcwd_execute(
4204 &mut self,
4205 args: &[PerlValue],
4206 line: usize,
4207 ) -> PerlResult<PerlValue> {
4208 if !args.is_empty() {
4209 return Err(PerlError::runtime("getcwd takes no arguments", line));
4210 }
4211 match std::env::current_dir() {
4212 Ok(p) => Ok(PerlValue::string(p.to_string_lossy().into_owned())),
4213 Err(e) => {
4214 self.apply_io_error_to_errno(&e);
4215 Ok(PerlValue::UNDEF)
4216 }
4217 }
4218 }
4219
4220 pub(crate) fn builtin_realpath_execute(
4222 &mut self,
4223 args: &[PerlValue],
4224 line: usize,
4225 ) -> PerlResult<PerlValue> {
4226 let path = args
4227 .first()
4228 .ok_or_else(|| PerlError::runtime("realpath: need path", line))?
4229 .to_string();
4230 if path.is_empty() {
4231 return Err(PerlError::runtime("realpath: need path", line));
4232 }
4233 match crate::perl_fs::realpath_resolved(&path) {
4234 Ok(s) => Ok(PerlValue::string(s)),
4235 Err(e) => {
4236 self.apply_io_error_to_errno(&e);
4237 Ok(PerlValue::UNDEF)
4238 }
4239 }
4240 }
4241
4242 pub(crate) fn builtin_pipe_execute(
4244 &mut self,
4245 args: &[PerlValue],
4246 line: usize,
4247 ) -> PerlResult<PerlValue> {
4248 if args.len() != 2 {
4249 return Err(PerlError::runtime(
4250 "pipe requires exactly two arguments",
4251 line,
4252 ));
4253 }
4254 #[cfg(unix)]
4255 {
4256 use std::fs::File;
4257 use std::os::unix::io::FromRawFd;
4258
4259 let read_name = args[0].to_string();
4260 let write_name = args[1].to_string();
4261 if read_name.is_empty() || write_name.is_empty() {
4262 return Err(PerlError::runtime("pipe: invalid handle name", line));
4263 }
4264 let mut fds = [0i32; 2];
4265 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
4266 let e = std::io::Error::last_os_error();
4267 self.apply_io_error_to_errno(&e);
4268 return Ok(PerlValue::integer(0));
4269 }
4270 let read_file = unsafe { File::from_raw_fd(fds[0]) };
4271 let write_file = unsafe { File::from_raw_fd(fds[1]) };
4272
4273 let read_shared = Arc::new(Mutex::new(read_file));
4274 let write_shared = Arc::new(Mutex::new(write_file));
4275
4276 self.close_builtin_execute(read_name.clone()).ok();
4277 self.close_builtin_execute(write_name.clone()).ok();
4278
4279 self.io_file_slots
4280 .insert(read_name.clone(), Arc::clone(&read_shared));
4281 self.input_handles.insert(
4282 read_name,
4283 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
4284 );
4285
4286 self.io_file_slots
4287 .insert(write_name.clone(), Arc::clone(&write_shared));
4288 self.output_handles
4289 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
4290
4291 Ok(PerlValue::integer(1))
4292 }
4293 #[cfg(not(unix))]
4294 {
4295 let _ = args;
4296 Err(PerlError::runtime(
4297 "pipe is not supported on this platform",
4298 line,
4299 ))
4300 }
4301 }
4302
4303 pub(crate) fn close_builtin_execute(&mut self, name: String) -> PerlResult<PerlValue> {
4304 self.output_handles.remove(&name);
4305 self.input_handles.remove(&name);
4306 self.io_file_slots.remove(&name);
4307 if let Some(mut child) = self.pipe_children.remove(&name) {
4308 if let Ok(st) = child.wait() {
4309 self.record_child_exit_status(st);
4310 }
4311 }
4312 Ok(PerlValue::integer(1))
4313 }
4314
4315 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
4316 self.input_handles.contains_key(name)
4317 }
4318
4319 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
4323 self.line_mode_eof_pending
4324 }
4325
4326 pub(crate) fn eof_builtin_execute(
4330 &self,
4331 args: &[PerlValue],
4332 line: usize,
4333 ) -> PerlResult<PerlValue> {
4334 match args.len() {
4335 0 => Ok(PerlValue::integer(if self.eof_without_arg_is_true() {
4336 1
4337 } else {
4338 0
4339 })),
4340 1 => {
4341 let name = args[0].to_string();
4342 let at_eof = !self.has_input_handle(&name);
4343 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
4344 }
4345 _ => Err(PerlError::runtime("eof: too many arguments", line)),
4346 }
4347 }
4348
4349 pub(crate) fn study_return_value(s: &str) -> PerlValue {
4352 if s.is_empty() {
4353 PerlValue::string(String::new())
4354 } else {
4355 PerlValue::integer(1)
4356 }
4357 }
4358
4359 pub(crate) fn readline_builtin_execute(
4360 &mut self,
4361 handle: Option<&str>,
4362 ) -> PerlResult<PerlValue> {
4363 if handle.is_none() {
4365 let argv = self.scope.get_array("ARGV");
4366 if !argv.is_empty() {
4367 loop {
4368 if self.diamond_reader.is_none() {
4369 while self.diamond_next_idx < argv.len() {
4370 let path = argv[self.diamond_next_idx].to_string();
4371 self.diamond_next_idx += 1;
4372 match File::open(&path) {
4373 Ok(f) => {
4374 self.argv_current_file = path;
4375 self.diamond_reader = Some(BufReader::new(f));
4376 break;
4377 }
4378 Err(e) => {
4379 self.apply_io_error_to_errno(&e);
4380 }
4381 }
4382 }
4383 if self.diamond_reader.is_none() {
4384 return Ok(PerlValue::UNDEF);
4385 }
4386 }
4387 let mut line_str = String::new();
4388 let read_result: Result<usize, io::Error> =
4389 if let Some(reader) = self.diamond_reader.as_mut() {
4390 if self.open_pragma_utf8 {
4391 let mut buf = Vec::new();
4392 reader.read_until(b'\n', &mut buf).inspect(|n| {
4393 if *n > 0 {
4394 line_str = String::from_utf8_lossy(&buf).into_owned();
4395 }
4396 })
4397 } else {
4398 let mut buf = Vec::new();
4399 match reader.read_until(b'\n', &mut buf) {
4400 Ok(n) => {
4401 if n > 0 {
4402 line_str =
4403 crate::perl_decode::decode_utf8_or_latin1_read_until(
4404 &buf,
4405 );
4406 }
4407 Ok(n)
4408 }
4409 Err(e) => Err(e),
4410 }
4411 }
4412 } else {
4413 unreachable!()
4414 };
4415 match read_result {
4416 Ok(0) => {
4417 self.diamond_reader = None;
4418 continue;
4419 }
4420 Ok(_) => {
4421 self.bump_line_for_handle(&self.argv_current_file.clone());
4422 return Ok(PerlValue::string(line_str));
4423 }
4424 Err(e) => {
4425 self.apply_io_error_to_errno(&e);
4426 self.diamond_reader = None;
4427 continue;
4428 }
4429 }
4430 }
4431 } else {
4432 self.argv_current_file.clear();
4433 }
4434 }
4435
4436 let handle_name = handle.unwrap_or("STDIN");
4437 let mut line_str = String::new();
4438 if handle_name == "STDIN" {
4439 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
4440 self.last_stdin_die_bracket = if handle.is_none() {
4441 "<>".to_string()
4442 } else {
4443 "<STDIN>".to_string()
4444 };
4445 self.bump_line_for_handle("STDIN");
4446 return Ok(PerlValue::string(queued));
4447 }
4448 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4449 let mut buf = Vec::new();
4450 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
4451 if *n > 0 {
4452 line_str = String::from_utf8_lossy(&buf).into_owned();
4453 }
4454 })
4455 } else {
4456 let mut buf = Vec::new();
4457 let mut lock = io::stdin().lock();
4458 match lock.read_until(b'\n', &mut buf) {
4459 Ok(n) => {
4460 if n > 0 {
4461 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4462 }
4463 Ok(n)
4464 }
4465 Err(e) => Err(e),
4466 }
4467 };
4468 match r {
4469 Ok(0) => Ok(PerlValue::UNDEF),
4470 Ok(_) => {
4471 self.last_stdin_die_bracket = if handle.is_none() {
4472 "<>".to_string()
4473 } else {
4474 "<STDIN>".to_string()
4475 };
4476 self.bump_line_for_handle("STDIN");
4477 Ok(PerlValue::string(line_str))
4478 }
4479 Err(e) => {
4480 self.apply_io_error_to_errno(&e);
4481 Ok(PerlValue::UNDEF)
4482 }
4483 }
4484 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
4485 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4486 let mut buf = Vec::new();
4487 reader.read_until(b'\n', &mut buf).inspect(|n| {
4488 if *n > 0 {
4489 line_str = String::from_utf8_lossy(&buf).into_owned();
4490 }
4491 })
4492 } else {
4493 let mut buf = Vec::new();
4494 match reader.read_until(b'\n', &mut buf) {
4495 Ok(n) => {
4496 if n > 0 {
4497 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4498 }
4499 Ok(n)
4500 }
4501 Err(e) => Err(e),
4502 }
4503 };
4504 match r {
4505 Ok(0) => Ok(PerlValue::UNDEF),
4506 Ok(_) => {
4507 self.bump_line_for_handle(handle_name);
4508 Ok(PerlValue::string(line_str))
4509 }
4510 Err(e) => {
4511 self.apply_io_error_to_errno(&e);
4512 Ok(PerlValue::UNDEF)
4513 }
4514 }
4515 } else {
4516 Ok(PerlValue::UNDEF)
4517 }
4518 }
4519
4520 pub(crate) fn readline_builtin_execute_list(
4522 &mut self,
4523 handle: Option<&str>,
4524 ) -> PerlResult<PerlValue> {
4525 let mut lines = Vec::new();
4526 loop {
4527 let v = self.readline_builtin_execute(handle)?;
4528 if v.is_undef() {
4529 break;
4530 }
4531 lines.push(v);
4532 }
4533 Ok(PerlValue::array(lines))
4534 }
4535
4536 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> PerlValue {
4537 match std::fs::read_dir(path) {
4538 Ok(rd) => {
4539 let entries: Vec<String> = rd
4540 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
4541 .collect();
4542 self.dir_handles
4543 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
4544 PerlValue::integer(1)
4545 }
4546 Err(e) => {
4547 self.apply_io_error_to_errno(&e);
4548 PerlValue::integer(0)
4549 }
4550 }
4551 }
4552
4553 pub(crate) fn readdir_handle(&mut self, handle: &str) -> PerlValue {
4554 if let Some(dh) = self.dir_handles.get_mut(handle) {
4555 if dh.pos < dh.entries.len() {
4556 let s = dh.entries[dh.pos].clone();
4557 dh.pos += 1;
4558 PerlValue::string(s)
4559 } else {
4560 PerlValue::UNDEF
4561 }
4562 } else {
4563 PerlValue::UNDEF
4564 }
4565 }
4566
4567 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> PerlValue {
4569 if let Some(dh) = self.dir_handles.get_mut(handle) {
4570 let rest: Vec<PerlValue> = dh.entries[dh.pos..]
4571 .iter()
4572 .cloned()
4573 .map(PerlValue::string)
4574 .collect();
4575 dh.pos = dh.entries.len();
4576 PerlValue::array(rest)
4577 } else {
4578 PerlValue::array(Vec::new())
4579 }
4580 }
4581
4582 pub(crate) fn closedir_handle(&mut self, handle: &str) -> PerlValue {
4583 PerlValue::integer(if self.dir_handles.remove(handle).is_some() {
4584 1
4585 } else {
4586 0
4587 })
4588 }
4589
4590 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> PerlValue {
4591 if let Some(dh) = self.dir_handles.get_mut(handle) {
4592 dh.pos = 0;
4593 PerlValue::integer(1)
4594 } else {
4595 PerlValue::integer(0)
4596 }
4597 }
4598
4599 pub(crate) fn telldir_handle(&mut self, handle: &str) -> PerlValue {
4600 self.dir_handles
4601 .get(handle)
4602 .map(|dh| PerlValue::integer(dh.pos as i64))
4603 .unwrap_or(PerlValue::UNDEF)
4604 }
4605
4606 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> PerlValue {
4607 if let Some(dh) = self.dir_handles.get_mut(handle) {
4608 dh.pos = pos.min(dh.entries.len());
4609 PerlValue::integer(1)
4610 } else {
4611 PerlValue::integer(0)
4612 }
4613 }
4614
4615 #[inline]
4620 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
4621 crate::special_vars::is_regex_match_scalar_name(name)
4622 }
4623
4624 #[inline]
4628 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
4629 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
4630 self.regex_capture_scope_fresh = false;
4631 }
4632 }
4633
4634 pub(crate) fn apply_regex_captures(
4635 &mut self,
4636 haystack: &str,
4637 offset: usize,
4638 re: &PerlCompiledRegex,
4639 caps: &PerlCaptures<'_>,
4640 capture_all: CaptureAllMode,
4641 ) -> Result<(), FlowOrError> {
4642 let m0 = caps.get(0).expect("regex capture 0");
4643 let s0 = offset + m0.start;
4644 let e0 = offset + m0.end;
4645 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
4646 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
4647 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
4648 let mut last_paren = String::new();
4649 for i in 1..caps.len() {
4650 if let Some(m) = caps.get(i) {
4651 last_paren = m.text.to_string();
4652 }
4653 }
4654 self.last_paren_match = last_paren;
4655 self.last_subpattern_name = String::new();
4656 for n in re.capture_names().flatten() {
4657 if caps.name(n).is_some() {
4658 self.last_subpattern_name = n.to_string();
4659 }
4660 }
4661 self.scope
4662 .set_scalar("&", PerlValue::string(self.last_match.clone()))?;
4663 self.scope
4664 .set_scalar("`", PerlValue::string(self.prematch.clone()))?;
4665 self.scope
4666 .set_scalar("'", PerlValue::string(self.postmatch.clone()))?;
4667 self.scope
4668 .set_scalar("+", PerlValue::string(self.last_paren_match.clone()))?;
4669 for i in 1..caps.len() {
4670 if let Some(m) = caps.get(i) {
4671 self.scope
4672 .set_scalar(&i.to_string(), PerlValue::string(m.text.to_string()))?;
4673 }
4674 }
4675 let mut start_arr = vec![PerlValue::integer(s0 as i64)];
4676 let mut end_arr = vec![PerlValue::integer(e0 as i64)];
4677 for i in 1..caps.len() {
4678 if let Some(m) = caps.get(i) {
4679 start_arr.push(PerlValue::integer((offset + m.start) as i64));
4680 end_arr.push(PerlValue::integer((offset + m.end) as i64));
4681 } else {
4682 start_arr.push(PerlValue::integer(-1));
4683 end_arr.push(PerlValue::integer(-1));
4684 }
4685 }
4686 self.scope.set_array("-", start_arr)?;
4687 self.scope.set_array("+", end_arr)?;
4688 let mut named = IndexMap::new();
4689 for name in re.capture_names().flatten() {
4690 if let Some(m) = caps.name(name) {
4691 named.insert(name.to_string(), PerlValue::string(m.text.to_string()));
4692 }
4693 }
4694 self.scope.set_hash("+", named.clone())?;
4695 let mut named_minus = IndexMap::new();
4697 for (name, val) in &named {
4698 named_minus.insert(
4699 name.clone(),
4700 PerlValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
4701 );
4702 }
4703 self.scope.set_hash("-", named_minus)?;
4704 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
4705 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
4706 match capture_all {
4707 CaptureAllMode::Empty => {
4708 self.scope.set_array("^CAPTURE_ALL", vec![])?;
4709 }
4710 CaptureAllMode::Append => {
4711 let mut rows = self.scope.get_array("^CAPTURE_ALL");
4712 rows.push(PerlValue::array(cap_flat));
4713 self.scope.set_array("^CAPTURE_ALL", rows)?;
4714 }
4715 CaptureAllMode::Skip => {}
4716 }
4717 Ok(())
4718 }
4719
4720 pub(crate) fn clear_flip_flop_state(&mut self) {
4721 self.flip_flop_active.clear();
4722 self.flip_flop_exclusive_left_line.clear();
4723 self.flip_flop_sequence.clear();
4724 self.flip_flop_last_dot.clear();
4725 self.flip_flop_tree.clear();
4726 }
4727
4728 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
4729 self.flip_flop_active.resize(slots as usize, false);
4730 self.flip_flop_active.fill(false);
4731 self.flip_flop_exclusive_left_line
4732 .resize(slots as usize, None);
4733 self.flip_flop_exclusive_left_line.fill(None);
4734 self.flip_flop_sequence.resize(slots as usize, 0);
4735 self.flip_flop_sequence.fill(0);
4736 self.flip_flop_last_dot.resize(slots as usize, None);
4737 self.flip_flop_last_dot.fill(None);
4738 }
4739
4740 #[inline]
4744 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
4745 if self.last_readline_handle.is_empty() {
4746 self.line_number
4747 } else {
4748 *self
4749 .handle_line_numbers
4750 .get(&self.last_readline_handle)
4751 .unwrap_or(&0)
4752 }
4753 }
4754
4755 pub(crate) fn scalar_flip_flop_eval(
4764 &mut self,
4765 left: i64,
4766 right: i64,
4767 slot: usize,
4768 exclusive: bool,
4769 ) -> PerlResult<PerlValue> {
4770 if self.flip_flop_active.len() <= slot {
4771 self.flip_flop_active.resize(slot + 1, false);
4772 }
4773 if self.flip_flop_exclusive_left_line.len() <= slot {
4774 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4775 }
4776 if self.flip_flop_sequence.len() <= slot {
4777 self.flip_flop_sequence.resize(slot + 1, 0);
4778 }
4779 if self.flip_flop_last_dot.len() <= slot {
4780 self.flip_flop_last_dot.resize(slot + 1, None);
4781 }
4782 let dot = self.scalar_flipflop_dot_line();
4783 let active = &mut self.flip_flop_active[slot];
4784 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4785 let seq = &mut self.flip_flop_sequence[slot];
4786 let last_dot = &mut self.flip_flop_last_dot[slot];
4787 if !*active {
4788 if dot == left {
4789 *active = true;
4790 *seq = 1;
4791 *last_dot = Some(dot);
4792 if exclusive {
4793 *excl_left = Some(dot);
4794 } else {
4795 *excl_left = None;
4796 if dot == right {
4797 *active = false;
4798 return Ok(PerlValue::string(format!("{}E0", *seq)));
4799 }
4800 }
4801 return Ok(PerlValue::string(seq.to_string()));
4802 }
4803 *last_dot = Some(dot);
4804 return Ok(PerlValue::string(String::new()));
4805 }
4806 if *last_dot != Some(dot) {
4809 *seq += 1;
4810 *last_dot = Some(dot);
4811 }
4812 let cur_seq = *seq;
4813 if let Some(ll) = *excl_left {
4814 if dot == right && dot > ll {
4815 *active = false;
4816 *excl_left = None;
4817 *seq = 0;
4818 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4819 }
4820 } else if dot == right {
4821 *active = false;
4822 *seq = 0;
4823 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4824 }
4825 Ok(PerlValue::string(cur_seq.to_string()))
4826 }
4827
4828 fn regex_flip_flop_transition(
4829 active: &mut bool,
4830 excl_left: &mut Option<i64>,
4831 exclusive: bool,
4832 dot: i64,
4833 left_m: bool,
4834 right_m: bool,
4835 ) -> i64 {
4836 if !*active {
4837 if left_m {
4838 *active = true;
4839 if exclusive {
4840 *excl_left = Some(dot);
4841 } else {
4842 *excl_left = None;
4843 if right_m {
4844 *active = false;
4845 }
4846 }
4847 return 1;
4848 }
4849 return 0;
4850 }
4851 if let Some(ll) = *excl_left {
4852 if right_m && dot > ll {
4853 *active = false;
4854 *excl_left = None;
4855 }
4856 } else if right_m {
4857 *active = false;
4858 }
4859 1
4860 }
4861
4862 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
4867 &mut self,
4868 left_pat: &str,
4869 left_flags: &str,
4870 right_pat: &str,
4871 right_flags: &str,
4872 slot: usize,
4873 exclusive: bool,
4874 line: usize,
4875 ) -> PerlResult<PerlValue> {
4876 let dot = self.scalar_flipflop_dot_line();
4877 let subject = self.scope.get_scalar("_").to_string();
4878 let left_re = self
4879 .compile_regex(left_pat, left_flags, line)
4880 .map_err(|e| match e {
4881 FlowOrError::Error(err) => err,
4882 FlowOrError::Flow(_) => {
4883 PerlError::runtime("unexpected flow in regex flip-flop", line)
4884 }
4885 })?;
4886 let right_re = self
4887 .compile_regex(right_pat, right_flags, line)
4888 .map_err(|e| match e {
4889 FlowOrError::Error(err) => err,
4890 FlowOrError::Flow(_) => {
4891 PerlError::runtime("unexpected flow in regex flip-flop", line)
4892 }
4893 })?;
4894 let left_m = left_re.is_match(&subject);
4895 let right_m = right_re.is_match(&subject);
4896 if self.flip_flop_active.len() <= slot {
4897 self.flip_flop_active.resize(slot + 1, false);
4898 }
4899 if self.flip_flop_exclusive_left_line.len() <= slot {
4900 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4901 }
4902 let active = &mut self.flip_flop_active[slot];
4903 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4904 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4905 active, excl_left, exclusive, dot, left_m, right_m,
4906 )))
4907 }
4908
4909 pub(crate) fn regex_flip_flop_eval_dynamic_right(
4911 &mut self,
4912 left_pat: &str,
4913 left_flags: &str,
4914 slot: usize,
4915 exclusive: bool,
4916 line: usize,
4917 right_m: bool,
4918 ) -> PerlResult<PerlValue> {
4919 let dot = self.scalar_flipflop_dot_line();
4920 let subject = self.scope.get_scalar("_").to_string();
4921 let left_re = self
4922 .compile_regex(left_pat, left_flags, line)
4923 .map_err(|e| match e {
4924 FlowOrError::Error(err) => err,
4925 FlowOrError::Flow(_) => {
4926 PerlError::runtime("unexpected flow in regex flip-flop", line)
4927 }
4928 })?;
4929 let left_m = left_re.is_match(&subject);
4930 if self.flip_flop_active.len() <= slot {
4931 self.flip_flop_active.resize(slot + 1, false);
4932 }
4933 if self.flip_flop_exclusive_left_line.len() <= slot {
4934 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4935 }
4936 let active = &mut self.flip_flop_active[slot];
4937 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4938 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4939 active, excl_left, exclusive, dot, left_m, right_m,
4940 )))
4941 }
4942
4943 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
4945 &mut self,
4946 left_pat: &str,
4947 left_flags: &str,
4948 slot: usize,
4949 exclusive: bool,
4950 line: usize,
4951 rhs_line: i64,
4952 ) -> PerlResult<PerlValue> {
4953 let dot = self.scalar_flipflop_dot_line();
4954 let subject = self.scope.get_scalar("_").to_string();
4955 let left_re = self
4956 .compile_regex(left_pat, left_flags, line)
4957 .map_err(|e| match e {
4958 FlowOrError::Error(err) => err,
4959 FlowOrError::Flow(_) => {
4960 PerlError::runtime("unexpected flow in regex flip-flop", line)
4961 }
4962 })?;
4963 let left_m = left_re.is_match(&subject);
4964 let right_m = dot == rhs_line;
4965 if self.flip_flop_active.len() <= slot {
4966 self.flip_flop_active.resize(slot + 1, false);
4967 }
4968 if self.flip_flop_exclusive_left_line.len() <= slot {
4969 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4970 }
4971 let active = &mut self.flip_flop_active[slot];
4972 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4973 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4974 active, excl_left, exclusive, dot, left_m, right_m,
4975 )))
4976 }
4977
4978 pub(crate) fn regex_eof_flip_flop_eval(
4983 &mut self,
4984 left_pat: &str,
4985 left_flags: &str,
4986 slot: usize,
4987 exclusive: bool,
4988 line: usize,
4989 ) -> PerlResult<PerlValue> {
4990 let dot = self.scalar_flipflop_dot_line();
4991 let subject = self.scope.get_scalar("_").to_string();
4992 let left_re = self
4993 .compile_regex(left_pat, left_flags, line)
4994 .map_err(|e| match e {
4995 FlowOrError::Error(err) => err,
4996 FlowOrError::Flow(_) => {
4997 PerlError::runtime("unexpected flow in regex/eof flip-flop", line)
4998 }
4999 })?;
5000 let left_m = left_re.is_match(&subject);
5001 let right_m = self.eof_without_arg_is_true();
5002 if self.flip_flop_active.len() <= slot {
5003 self.flip_flop_active.resize(slot + 1, false);
5004 }
5005 if self.flip_flop_exclusive_left_line.len() <= slot {
5006 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5007 }
5008 let active = &mut self.flip_flop_active[slot];
5009 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5010 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5011 active, excl_left, exclusive, dot, left_m, right_m,
5012 )))
5013 }
5014
5015 pub(crate) fn chomp_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
5017 let mut s = val.to_string();
5018 let removed = if s.ends_with('\n') {
5019 s.pop();
5020 1i64
5021 } else {
5022 0i64
5023 };
5024 self.assign_value(target, PerlValue::string(s))?;
5025 Ok(PerlValue::integer(removed))
5026 }
5027
5028 pub(crate) fn chop_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
5030 let mut s = val.to_string();
5031 let chopped = s
5032 .pop()
5033 .map(|c| PerlValue::string(c.to_string()))
5034 .unwrap_or(PerlValue::UNDEF);
5035 self.assign_value(target, PerlValue::string(s))?;
5036 Ok(chopped)
5037 }
5038
5039 pub(crate) fn regex_match_execute(
5041 &mut self,
5042 s: String,
5043 pattern: &str,
5044 flags: &str,
5045 scalar_g: bool,
5046 pos_key: &str,
5047 line: usize,
5048 ) -> ExecResult {
5049 if !flags.contains('g') && !scalar_g {
5057 let memo_hit = {
5058 if let Some(ref mem) = self.regex_match_memo {
5059 mem.pattern == pattern
5060 && mem.flags == flags
5061 && mem.multiline == self.multiline_match
5062 && mem.haystack == s
5063 } else {
5064 false
5065 }
5066 };
5067 if memo_hit {
5068 if self.regex_capture_scope_fresh {
5069 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
5070 }
5071 let (memo_s, memo_result) = {
5074 let mem = self.regex_match_memo.as_ref().expect("memo");
5075 (mem.haystack.clone(), mem.result.clone())
5076 };
5077 let re = self.compile_regex(pattern, flags, line)?;
5078 if let Some(caps) = re.captures(&memo_s) {
5079 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
5080 }
5081 self.regex_capture_scope_fresh = true;
5082 return Ok(memo_result);
5083 }
5084 }
5085 let re = self.compile_regex(pattern, flags, line)?;
5086 if flags.contains('g') && scalar_g {
5087 let key = pos_key.to_string();
5088 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
5089 if start == 0 {
5090 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5091 }
5092 if start > s.len() {
5093 self.regex_pos.insert(key, None);
5094 return Ok(PerlValue::integer(0));
5095 }
5096 let sub = s.get(start..).unwrap_or("");
5097 if let Some(caps) = re.captures(sub) {
5098 let overall = caps.get(0).expect("capture 0");
5099 let abs_end = start + overall.end;
5100 self.regex_pos.insert(key, Some(abs_end));
5101 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
5102 Ok(PerlValue::integer(1))
5103 } else {
5104 self.regex_pos.insert(key, None);
5105 Ok(PerlValue::integer(0))
5106 }
5107 } else if flags.contains('g') {
5108 let mut rows = Vec::new();
5109 let mut last_caps: Option<PerlCaptures<'_>> = None;
5110 for caps in re.captures_iter(&s) {
5111 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5112 &caps,
5113 )));
5114 last_caps = Some(caps);
5115 }
5116 self.scope.set_array("^CAPTURE_ALL", rows)?;
5117 let matches: Vec<PerlValue> = match &*re {
5118 PerlCompiledRegex::Rust(r) => r
5119 .find_iter(&s)
5120 .map(|m| PerlValue::string(m.as_str().to_string()))
5121 .collect(),
5122 PerlCompiledRegex::Fancy(r) => r
5123 .find_iter(&s)
5124 .filter_map(|m| m.ok())
5125 .map(|m| PerlValue::string(m.as_str().to_string()))
5126 .collect(),
5127 PerlCompiledRegex::Pcre2(r) => r
5128 .find_iter(s.as_bytes())
5129 .filter_map(|m| m.ok())
5130 .map(|m| {
5131 let t = s.get(m.start()..m.end()).unwrap_or("");
5132 PerlValue::string(t.to_string())
5133 })
5134 .collect(),
5135 };
5136 if matches.is_empty() {
5137 Ok(PerlValue::integer(0))
5138 } else {
5139 if let Some(caps) = last_caps {
5140 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
5141 }
5142 Ok(PerlValue::array(matches))
5143 }
5144 } else if let Some(caps) = re.captures(&s) {
5145 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
5146 let result = PerlValue::integer(1);
5147 self.regex_match_memo = Some(RegexMatchMemo {
5148 pattern: pattern.to_string(),
5149 flags: flags.to_string(),
5150 multiline: self.multiline_match,
5151 haystack: s,
5152 result: result.clone(),
5153 });
5154 self.regex_capture_scope_fresh = true;
5155 Ok(result)
5156 } else {
5157 let result = PerlValue::integer(0);
5158 self.regex_match_memo = Some(RegexMatchMemo {
5160 pattern: pattern.to_string(),
5161 flags: flags.to_string(),
5162 multiline: self.multiline_match,
5163 haystack: s,
5164 result: result.clone(),
5165 });
5166 Ok(result)
5169 }
5170 }
5171
5172 pub(crate) fn expand_env_braces_in_subst(
5176 &mut self,
5177 raw: &str,
5178 line: usize,
5179 ) -> PerlResult<String> {
5180 self.materialize_env_if_needed();
5181 let mut out = String::new();
5182 let mut rest = raw;
5183 while let Some(idx) = rest.find("$ENV{") {
5184 out.push_str(&rest[..idx]);
5185 let after = &rest[idx + 5..];
5186 let end = after
5187 .find('}')
5188 .ok_or_else(|| PerlError::runtime("Unclosed $ENV{...} in s///", line))?;
5189 let key = &after[..end];
5190 let val = self.scope.get_hash_element("ENV", key);
5191 out.push_str(&val.to_string());
5192 rest = &after[end + 1..];
5193 }
5194 out.push_str(rest);
5195 Ok(out)
5196 }
5197
5198 pub(crate) fn regex_subst_execute(
5204 &mut self,
5205 s: String,
5206 pattern: &str,
5207 replacement: &str,
5208 flags: &str,
5209 target: &Expr,
5210 line: usize,
5211 ) -> ExecResult {
5212 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
5213 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
5214 let re = self.compile_regex(&pattern, &re_flags, line)?;
5215 if flags.contains('e') {
5216 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
5217 }
5218 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
5219 let replacement = self.interpolate_replacement_string(&replacement);
5220 let replacement = normalize_replacement_backrefs(&replacement);
5221 let last_caps = if flags.contains('g') {
5222 let mut rows = Vec::new();
5223 let mut last = None;
5224 for caps in re.captures_iter(&s) {
5225 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5226 &caps,
5227 )));
5228 last = Some(caps);
5229 }
5230 self.scope.set_array("^CAPTURE_ALL", rows)?;
5231 last
5232 } else {
5233 re.captures(&s)
5234 };
5235 if let Some(caps) = last_caps {
5236 let mode = if flags.contains('g') {
5237 CaptureAllMode::Skip
5238 } else {
5239 CaptureAllMode::Empty
5240 };
5241 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
5242 }
5243 let (new_s, count) = if flags.contains('g') {
5244 let count = re.find_iter_count(&s);
5245 (re.replace_all(&s, replacement.as_str()), count)
5246 } else {
5247 let count = if re.is_match(&s) { 1 } else { 0 };
5248 (re.replace(&s, replacement.as_str()), count)
5249 };
5250 if flags.contains('r') {
5251 Ok(PerlValue::string(new_s))
5253 } else {
5254 self.assign_value(target, PerlValue::string(new_s))?;
5255 Ok(PerlValue::integer(count as i64))
5256 }
5257 }
5258
5259 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
5262 let prep_source = |raw: &str| -> String {
5263 let mut code = raw.trim().to_string();
5264 if !code.ends_with(';') {
5265 code.push(';');
5266 }
5267 code
5268 };
5269 let mut cur = prep_source(replacement);
5270 let mut last = PerlValue::UNDEF;
5271 for round in 0..e_count {
5272 last = crate::parse_and_run_string(&cur, self)?;
5273 if round + 1 < e_count {
5274 cur = prep_source(&last.to_string());
5275 }
5276 }
5277 Ok(last)
5278 }
5279
5280 fn regex_subst_execute_eval(
5281 &mut self,
5282 s: String,
5283 re: &PerlCompiledRegex,
5284 replacement: &str,
5285 flags: &str,
5286 target: &Expr,
5287 line: usize,
5288 ) -> ExecResult {
5289 let e_count = flags.chars().filter(|c| *c == 'e').count();
5290 if e_count == 0 {
5291 return Err(PerlError::runtime("s///e: internal error (no e flag)", line).into());
5292 }
5293
5294 if flags.contains('g') {
5295 let mut rows = Vec::new();
5296 let mut out = String::new();
5297 let mut last = 0usize;
5298 let mut count = 0usize;
5299 for caps in re.captures_iter(&s) {
5300 let m0 = caps.get(0).expect("regex capture 0");
5301 out.push_str(&s[last..m0.start]);
5302 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5303 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5304 out.push_str(&repl_val.to_string());
5305 last = m0.end;
5306 count += 1;
5307 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5308 &caps,
5309 )));
5310 }
5311 self.scope.set_array("^CAPTURE_ALL", rows)?;
5312 out.push_str(&s[last..]);
5313 if flags.contains('r') {
5314 return Ok(PerlValue::string(out));
5315 }
5316 self.assign_value(target, PerlValue::string(out))?;
5317 return Ok(PerlValue::integer(count as i64));
5318 }
5319 if let Some(caps) = re.captures(&s) {
5320 let m0 = caps.get(0).expect("regex capture 0");
5321 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5322 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5323 let mut out = String::new();
5324 out.push_str(&s[..m0.start]);
5325 out.push_str(&repl_val.to_string());
5326 out.push_str(&s[m0.end..]);
5327 if flags.contains('r') {
5328 return Ok(PerlValue::string(out));
5329 }
5330 self.assign_value(target, PerlValue::string(out))?;
5331 return Ok(PerlValue::integer(1));
5332 }
5333 if flags.contains('r') {
5334 return Ok(PerlValue::string(s));
5335 }
5336 self.assign_value(target, PerlValue::string(s))?;
5337 Ok(PerlValue::integer(0))
5338 }
5339
5340 pub(crate) fn regex_transliterate_execute(
5342 &mut self,
5343 s: String,
5344 from: &str,
5345 to: &str,
5346 flags: &str,
5347 target: &Expr,
5348 line: usize,
5349 ) -> ExecResult {
5350 let _ = line;
5351 let from_chars = Self::tr_expand_ranges(from);
5352 let to_chars = Self::tr_expand_ranges(to);
5353 let delete_mode = flags.contains('d');
5354 let mut count = 0i64;
5355 let new_s: String = s
5356 .chars()
5357 .filter_map(|c| {
5358 if let Some(pos) = from_chars.iter().position(|&fc| fc == c) {
5359 count += 1;
5360 if delete_mode {
5361 if pos < to_chars.len() {
5363 Some(to_chars[pos])
5364 } else {
5365 None }
5367 } else {
5368 Some(to_chars.get(pos).or(to_chars.last()).copied().unwrap_or(c))
5370 }
5371 } else {
5372 Some(c)
5373 }
5374 })
5375 .collect();
5376 if flags.contains('r') {
5377 Ok(PerlValue::string(new_s))
5379 } else {
5380 self.assign_value(target, PerlValue::string(new_s))?;
5381 Ok(PerlValue::integer(count))
5382 }
5383 }
5384
5385 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
5388 let raw: Vec<char> = spec.chars().collect();
5389 let mut out = Vec::with_capacity(raw.len());
5390 let mut i = 0;
5391 while i < raw.len() {
5392 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
5393 let start = raw[i] as u32;
5394 let end = raw[i + 2] as u32;
5395 for code in start..=end {
5396 if let Some(c) = char::from_u32(code) {
5397 out.push(c);
5398 }
5399 }
5400 i += 3;
5401 } else {
5402 out.push(raw[i]);
5403 i += 1;
5404 }
5405 }
5406 out
5407 }
5408
5409 pub(crate) fn splice_builtin_execute(
5411 &mut self,
5412 args: &[PerlValue],
5413 line: usize,
5414 ) -> PerlResult<PerlValue> {
5415 if args.is_empty() {
5416 return Err(PerlError::runtime("splice: missing array", line));
5417 }
5418 let arr_name = args[0].to_string();
5419 let arr_len = self.scope.array_len(&arr_name);
5420 let offset_val = args
5421 .get(1)
5422 .cloned()
5423 .unwrap_or_else(|| PerlValue::integer(0));
5424 let length_val = match args.get(2) {
5425 None => PerlValue::UNDEF,
5426 Some(v) => v.clone(),
5427 };
5428 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
5429 let rep_vals: Vec<PerlValue> = args.iter().skip(3).cloned().collect();
5430 let arr = self.scope.get_array_mut(&arr_name)?;
5431 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
5432 for (i, v) in rep_vals.into_iter().enumerate() {
5433 arr.insert(off + i, v);
5434 }
5435 Ok(match self.wantarray_kind {
5436 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
5437 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
5438 })
5439 }
5440
5441 pub(crate) fn unshift_builtin_execute(
5443 &mut self,
5444 args: &[PerlValue],
5445 line: usize,
5446 ) -> PerlResult<PerlValue> {
5447 if args.is_empty() {
5448 return Err(PerlError::runtime("unshift: missing array", line));
5449 }
5450 let arr_name = args[0].to_string();
5451 let mut flat_vals: Vec<PerlValue> = Vec::new();
5452 for a in args.iter().skip(1) {
5453 if let Some(items) = a.as_array_vec() {
5454 flat_vals.extend(items);
5455 } else {
5456 flat_vals.push(a.clone());
5457 }
5458 }
5459 let arr = self.scope.get_array_mut(&arr_name)?;
5460 for (i, v) in flat_vals.into_iter().enumerate() {
5461 arr.insert(i, v);
5462 }
5463 Ok(PerlValue::integer(arr.len() as i64))
5464 }
5465
5466 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
5469 if upper == 0.0 {
5470 self.rand_rng.gen_range(0.0..1.0)
5471 } else if upper > 0.0 {
5472 self.rand_rng.gen_range(0.0..upper)
5473 } else {
5474 self.rand_rng.gen_range(upper..0.0)
5475 }
5476 }
5477
5478 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
5480 let n = if let Some(s) = seed {
5481 s as i64
5482 } else {
5483 std::time::SystemTime::now()
5484 .duration_since(std::time::UNIX_EPOCH)
5485 .map(|d| d.as_secs() as i64)
5486 .unwrap_or(1)
5487 };
5488 let mag = n.unsigned_abs();
5489 self.rand_rng = StdRng::seed_from_u64(mag);
5490 n.abs()
5491 }
5492
5493 pub fn set_file(&mut self, file: &str) {
5494 self.file = file.to_string();
5495 }
5496
5497 pub fn repl_completion_names(&self) -> Vec<String> {
5499 let mut v = self.scope.repl_binding_names();
5500 v.extend(self.subs.keys().cloned());
5501 v.sort();
5502 v.dedup();
5503 v
5504 }
5505
5506 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
5508 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
5509 subs.sort();
5510 let mut classes: HashSet<String> = HashSet::new();
5511 for k in &subs {
5512 if let Some((pkg, rest)) = k.split_once("::") {
5513 if !rest.contains("::") {
5514 classes.insert(pkg.to_string());
5515 }
5516 }
5517 }
5518 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
5519 for bn in self.scope.repl_binding_names() {
5520 if let Some(r) = bn.strip_prefix('$') {
5521 let v = self.scope.get_scalar(r);
5522 if let Some(b) = v.as_blessed_ref() {
5523 blessed_scalars.insert(r.to_string(), b.class.clone());
5524 classes.insert(b.class.clone());
5525 }
5526 }
5527 }
5528 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
5529 for c in classes {
5530 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
5531 }
5532 ReplCompletionSnapshot {
5533 subs,
5534 blessed_scalars,
5535 isa_for_class,
5536 }
5537 }
5538
5539 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
5540 if n == 0 {
5541 return Err(FlowOrError::Error(PerlError::runtime(
5542 "bench: iteration count must be positive",
5543 line,
5544 )));
5545 }
5546 let mut samples = Vec::with_capacity(n);
5547 for _ in 0..n {
5548 let start = std::time::Instant::now();
5549 self.exec_block(body)?;
5550 samples.push(start.elapsed().as_secs_f64() * 1000.0);
5551 }
5552 let mut sorted = samples.clone();
5553 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
5554 let min_ms = sorted[0];
5555 let mean = samples.iter().sum::<f64>() / n as f64;
5556 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
5557 .saturating_sub(1)
5558 .min(n - 1);
5559 let p99_ms = sorted[p99_idx];
5560 Ok(PerlValue::string(format!(
5561 "bench: n={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
5562 n, min_ms, mean, p99_ms
5563 )))
5564 }
5565
5566 pub fn execute(&mut self, program: &Program) -> PerlResult<PerlValue> {
5567 if self.line_mode_skip_main {
5570 return self.execute_tree(program);
5571 }
5572 if let Some(result) = crate::try_vm_execute(program, self) {
5575 return result;
5576 }
5577
5578 self.execute_tree(program)
5580 }
5581
5582 pub fn run_end_blocks(&mut self) -> PerlResult<()> {
5584 self.global_phase = "END".to_string();
5585 let ends = std::mem::take(&mut self.end_blocks);
5586 for block in &ends {
5587 self.exec_block(block).map_err(|e| match e {
5588 FlowOrError::Error(e) => e,
5589 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in END", 0),
5590 })?;
5591 }
5592 Ok(())
5593 }
5594
5595 pub fn run_global_teardown(&mut self) -> PerlResult<()> {
5598 self.global_phase = "DESTRUCT".to_string();
5599 self.drain_pending_destroys(0)
5600 }
5601
5602 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> PerlResult<()> {
5604 loop {
5605 let batch = crate::pending_destroy::take_queue();
5606 if batch.is_empty() {
5607 break;
5608 }
5609 for (class, payload) in batch {
5610 let fq = format!("{}::DESTROY", class);
5611 let Some(sub) = self.subs.get(&fq).cloned() else {
5612 continue;
5613 };
5614 let inv = PerlValue::blessed(Arc::new(
5615 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
5616 ));
5617 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
5618 Ok(_) => {}
5619 Err(FlowOrError::Error(e)) => return Err(e),
5620 Err(FlowOrError::Flow(Flow::Return(_))) => {}
5621 Err(FlowOrError::Flow(other)) => {
5622 return Err(PerlError::runtime(
5623 format!("DESTROY: unexpected control flow ({other:?})"),
5624 line,
5625 ));
5626 }
5627 }
5628 }
5629 }
5630 Ok(())
5631 }
5632
5633 pub fn execute_tree(&mut self, program: &Program) -> PerlResult<PerlValue> {
5635 self.global_phase = "RUN".to_string();
5637 self.clear_flip_flop_state();
5638 self.prepare_program_top_level(program)?;
5640
5641 let begins = std::mem::take(&mut self.begin_blocks);
5643 if !begins.is_empty() {
5644 self.global_phase = "START".to_string();
5645 }
5646 for block in &begins {
5647 self.exec_block(block).map_err(|e| match e {
5648 FlowOrError::Error(e) => e,
5649 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in BEGIN", 0),
5650 })?;
5651 }
5652
5653 let ucs = std::mem::take(&mut self.unit_check_blocks);
5656 for block in ucs.iter().rev() {
5657 self.exec_block(block).map_err(|e| match e {
5658 FlowOrError::Error(e) => e,
5659 FlowOrError::Flow(_) => {
5660 PerlError::runtime("Unexpected flow control in UNITCHECK", 0)
5661 }
5662 })?;
5663 }
5664
5665 let checks = std::mem::take(&mut self.check_blocks);
5667 if !checks.is_empty() {
5668 self.global_phase = "CHECK".to_string();
5669 }
5670 for block in checks.iter().rev() {
5671 self.exec_block(block).map_err(|e| match e {
5672 FlowOrError::Error(e) => e,
5673 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in CHECK", 0),
5674 })?;
5675 }
5676
5677 let inits = std::mem::take(&mut self.init_blocks);
5679 if !inits.is_empty() {
5680 self.global_phase = "INIT".to_string();
5681 }
5682 for block in &inits {
5683 self.exec_block(block).map_err(|e| match e {
5684 FlowOrError::Error(e) => e,
5685 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in INIT", 0),
5686 })?;
5687 }
5688
5689 self.global_phase = "RUN".to_string();
5690
5691 if self.line_mode_skip_main {
5692 return Ok(PerlValue::UNDEF);
5695 }
5696
5697 let mut last = PerlValue::UNDEF;
5699 for stmt in &program.statements {
5700 match &stmt.kind {
5701 StmtKind::Begin(_)
5702 | StmtKind::UnitCheck(_)
5703 | StmtKind::Check(_)
5704 | StmtKind::Init(_)
5705 | StmtKind::End(_)
5706 | StmtKind::UsePerlVersion { .. }
5707 | StmtKind::Use { .. }
5708 | StmtKind::No { .. }
5709 | StmtKind::FormatDecl { .. } => continue,
5710 _ => {
5711 match self.exec_statement(stmt) {
5712 Ok(val) => last = val,
5713 Err(FlowOrError::Error(e)) => {
5714 self.global_phase = "END".to_string();
5716 let ends = std::mem::take(&mut self.end_blocks);
5717 for block in &ends {
5718 let _ = self.exec_block(block);
5719 }
5720 return Err(e);
5721 }
5722 Err(FlowOrError::Flow(Flow::Return(v))) => {
5723 last = v;
5724 break;
5725 }
5726 Err(FlowOrError::Flow(_)) => {}
5727 }
5728 }
5729 }
5730 }
5731
5732 self.global_phase = "END".to_string();
5734 let ends = std::mem::take(&mut self.end_blocks);
5735 for block in &ends {
5736 let _ = self.exec_block(block);
5737 }
5738
5739 self.drain_pending_destroys(0)?;
5740 Ok(last)
5741 }
5742
5743 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
5744 self.exec_block_with_tail(block, WantarrayCtx::Void)
5745 }
5746
5747 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5750 let uses_goto = block
5751 .iter()
5752 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
5753 if uses_goto {
5754 self.scope_push_hook();
5755 let r = self.exec_block_with_goto_tail(block, tail);
5756 self.scope_pop_hook();
5757 r
5758 } else {
5759 self.scope_push_hook();
5760 let result = self.exec_block_no_scope_with_tail(block, tail);
5761 self.scope_pop_hook();
5762 result
5763 }
5764 }
5765
5766 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5767 let mut map: HashMap<String, usize> = HashMap::new();
5768 for (i, s) in block.iter().enumerate() {
5769 if let Some(l) = &s.label {
5770 map.insert(l.clone(), i);
5771 }
5772 }
5773 let mut pc = 0usize;
5774 let mut last = PerlValue::UNDEF;
5775 let last_idx = block.len().saturating_sub(1);
5776 while pc < block.len() {
5777 if let StmtKind::Goto { target } = &block[pc].kind {
5778 let line = block[pc].line;
5779 let name = self.eval_expr(target)?.to_string();
5780 pc = *map.get(&name).ok_or_else(|| {
5781 FlowOrError::Error(PerlError::runtime(
5782 format!("goto: unknown label {}", name),
5783 line,
5784 ))
5785 })?;
5786 continue;
5787 }
5788 let v = if pc == last_idx {
5789 match &block[pc].kind {
5790 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
5791 _ => self.exec_statement(&block[pc])?,
5792 }
5793 } else {
5794 self.exec_statement(&block[pc])?
5795 };
5796 last = v;
5797 pc += 1;
5798 }
5799 Ok(last)
5800 }
5801
5802 #[inline]
5805 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
5806 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
5807 }
5808
5809 pub(crate) fn exec_block_no_scope_with_tail(
5810 &mut self,
5811 block: &Block,
5812 tail: WantarrayCtx,
5813 ) -> ExecResult {
5814 if block.is_empty() {
5815 return Ok(PerlValue::UNDEF);
5816 }
5817 let last_i = block.len() - 1;
5818 for (i, stmt) in block.iter().enumerate() {
5819 if i < last_i {
5820 self.exec_statement(stmt)?;
5821 } else {
5822 return match &stmt.kind {
5823 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
5824 _ => self.exec_statement(stmt),
5825 };
5826 }
5827 }
5828 Ok(PerlValue::UNDEF)
5829 }
5830
5831 pub(crate) fn spawn_async_block(&self, block: &Block) -> PerlValue {
5833 use parking_lot::Mutex as ParkMutex;
5834
5835 let block = block.clone();
5836 let subs = self.subs.clone();
5837 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5838 let result = Arc::new(ParkMutex::new(None));
5839 let join = Arc::new(ParkMutex::new(None));
5840 let result2 = result.clone();
5841 let h = std::thread::spawn(move || {
5842 let mut interp = Interpreter::new();
5843 interp.subs = subs;
5844 interp.scope.restore_capture(&scalars);
5845 interp.scope.restore_atomics(&aar, &ahash);
5846 interp.enable_parallel_guard();
5847 let r = match interp.exec_block(&block) {
5848 Ok(v) => Ok(v),
5849 Err(FlowOrError::Error(e)) => Err(e),
5850 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5851 Err(PerlError::runtime("yield inside async/spawn block", 0))
5852 }
5853 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5854 };
5855 *result2.lock() = Some(r);
5856 });
5857 *join.lock() = Some(h);
5858 PerlValue::async_task(Arc::new(PerlAsyncTask { result, join }))
5859 }
5860
5861 pub(crate) fn eval_timeout_block(
5863 &mut self,
5864 body: &Block,
5865 secs: f64,
5866 line: usize,
5867 ) -> ExecResult {
5868 use std::sync::mpsc::channel;
5869 use std::time::Duration;
5870
5871 let block = body.clone();
5872 let subs = self.subs.clone();
5873 let struct_defs = self.struct_defs.clone();
5874 let enum_defs = self.enum_defs.clone();
5875 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5876 self.materialize_env_if_needed();
5877 let env = self.env.clone();
5878 let argv = self.argv.clone();
5879 let inc = self.scope.get_array("INC");
5880 let (tx, rx) = channel::<PerlResult<PerlValue>>();
5881 let _handle = std::thread::spawn(move || {
5882 let mut interp = Interpreter::new();
5883 interp.subs = subs;
5884 interp.struct_defs = struct_defs;
5885 interp.enum_defs = enum_defs;
5886 interp.env = env.clone();
5887 interp.argv = argv.clone();
5888 interp.scope.declare_array(
5889 "ARGV",
5890 argv.iter().map(|s| PerlValue::string(s.clone())).collect(),
5891 );
5892 for (k, v) in env {
5893 interp
5894 .scope
5895 .set_hash_element("ENV", &k, v)
5896 .expect("set ENV in timeout thread");
5897 }
5898 interp.scope.declare_array("INC", inc);
5899 interp.scope.restore_capture(&scalars);
5900 interp.scope.restore_atomics(&aar, &ahash);
5901 interp.enable_parallel_guard();
5902 let out: PerlResult<PerlValue> = match interp.exec_block(&block) {
5903 Ok(v) => Ok(v),
5904 Err(FlowOrError::Error(e)) => Err(e),
5905 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5906 Err(PerlError::runtime("yield inside eval_timeout block", 0))
5907 }
5908 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5909 };
5910 let _ = tx.send(out);
5911 });
5912 let dur = Duration::from_secs_f64(secs.max(0.0));
5913 match rx.recv_timeout(dur) {
5914 Ok(Ok(v)) => Ok(v),
5915 Ok(Err(e)) => Err(FlowOrError::Error(e)),
5916 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(PerlError::runtime(
5917 format!(
5918 "eval_timeout: exceeded {} second(s) (worker continues in background)",
5919 secs
5920 ),
5921 line,
5922 )
5923 .into()),
5924 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(PerlError::runtime(
5925 "eval_timeout: worker thread panicked or disconnected",
5926 line,
5927 )
5928 .into()),
5929 }
5930 }
5931
5932 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
5933 let mut last = PerlValue::UNDEF;
5934 for stmt in body {
5935 match &stmt.kind {
5936 StmtKind::When { cond, body: wb } => {
5937 if self.when_matches(cond)? {
5938 return self.exec_block_smart(wb);
5939 }
5940 }
5941 StmtKind::DefaultCase { body: db } => {
5942 return self.exec_block_smart(db);
5943 }
5944 _ => {
5945 last = self.exec_statement(stmt)?;
5946 }
5947 }
5948 }
5949 Ok(last)
5950 }
5951
5952 pub(crate) fn exec_given_with_topic_value(
5954 &mut self,
5955 topic: PerlValue,
5956 body: &Block,
5957 ) -> ExecResult {
5958 self.scope_push_hook();
5959 self.scope.declare_scalar("_", topic);
5960 self.english_note_lexical_scalar("_");
5961 let r = self.exec_given_body(body);
5962 self.scope_pop_hook();
5963 r
5964 }
5965
5966 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
5967 let t = self.eval_expr(topic)?;
5968 self.exec_given_with_topic_value(t, body)
5969 }
5970
5971 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5973 let topic = self.scope.get_scalar("_");
5974 let line = cond.line;
5975 match &cond.kind {
5976 ExprKind::Regex(pattern, flags) => {
5977 let re = self.compile_regex(pattern, flags, line)?;
5978 let s = topic.to_string();
5979 Ok(re.is_match(&s))
5980 }
5981 ExprKind::String(s) => Ok(topic.to_string() == *s),
5982 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
5983 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
5984 _ => {
5985 let c = self.eval_expr(cond)?;
5986 Ok(self.smartmatch_when(&topic, &c))
5987 }
5988 }
5989 }
5990
5991 fn smartmatch_when(&self, topic: &PerlValue, c: &PerlValue) -> bool {
5992 if let Some(re) = c.as_regex() {
5993 return re.is_match(&topic.to_string());
5994 }
5995 topic.to_string() == c.to_string()
5996 }
5997
5998 pub(crate) fn eval_boolean_rvalue_condition(
6000 &mut self,
6001 cond: &Expr,
6002 ) -> Result<bool, FlowOrError> {
6003 match &cond.kind {
6004 ExprKind::Regex(pattern, flags) => {
6005 let topic = self.scope.get_scalar("_");
6006 let line = cond.line;
6007 let s = topic.to_string();
6008 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
6009 Ok(v.is_true())
6010 }
6011 ExprKind::ReadLine(_) => {
6013 let v = self.eval_expr(cond)?;
6014 self.scope.set_topic(v.clone());
6015 Ok(!v.is_undef())
6016 }
6017 _ => {
6018 let v = self.eval_expr(cond)?;
6019 Ok(v.is_true())
6020 }
6021 }
6022 }
6023
6024 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6026 self.eval_boolean_rvalue_condition(cond)
6027 }
6028
6029 pub(crate) fn eval_algebraic_match(
6030 &mut self,
6031 subject: &Expr,
6032 arms: &[MatchArm],
6033 line: usize,
6034 ) -> ExecResult {
6035 let val = self.eval_algebraic_match_subject(subject, line)?;
6036 self.eval_algebraic_match_with_subject_value(val, arms, line)
6037 }
6038
6039 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
6041 match &subject.kind {
6042 ExprKind::ArrayVar(name) => {
6043 self.check_strict_array_var(name, line)?;
6044 let aname = self.stash_array_name_for_package(name);
6045 Ok(PerlValue::array_binding_ref(aname))
6046 }
6047 ExprKind::HashVar(name) => {
6048 self.check_strict_hash_var(name, line)?;
6049 self.touch_env_hash(name);
6050 Ok(PerlValue::hash_binding_ref(name.clone()))
6051 }
6052 _ => self.eval_expr(subject),
6053 }
6054 }
6055
6056 pub(crate) fn eval_algebraic_match_with_subject_value(
6058 &mut self,
6059 val: PerlValue,
6060 arms: &[MatchArm],
6061 line: usize,
6062 ) -> ExecResult {
6063 if let Some(e) = val.as_enum_inst() {
6065 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
6066 if !has_catchall {
6067 let covered: Vec<String> = arms
6068 .iter()
6069 .filter_map(|a| {
6070 if let MatchPattern::Value(expr) = &a.pattern {
6071 if let ExprKind::FuncCall { name, .. } = &expr.kind {
6072 return name.rsplit_once("::").map(|(_, v)| v.to_string());
6073 }
6074 }
6075 None
6076 })
6077 .collect();
6078 let missing: Vec<&str> = e
6079 .def
6080 .variants
6081 .iter()
6082 .filter(|v| !covered.contains(&v.name))
6083 .map(|v| v.name.as_str())
6084 .collect();
6085 if !missing.is_empty() {
6086 return Err(PerlError::runtime(
6087 format!(
6088 "non-exhaustive match on enum `{}`: missing variant(s) {}",
6089 e.def.name,
6090 missing.join(", ")
6091 ),
6092 line,
6093 )
6094 .into());
6095 }
6096 }
6097 }
6098 for arm in arms {
6099 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
6100 let re = self.compile_regex(pattern, flags, line)?;
6101 let s = val.to_string();
6102 if let Some(caps) = re.captures(&s) {
6103 self.scope_push_hook();
6104 self.scope.declare_scalar("_", val.clone());
6105 self.english_note_lexical_scalar("_");
6106 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
6107 let guard_ok = if let Some(g) = &arm.guard {
6108 self.eval_expr(g)?.is_true()
6109 } else {
6110 true
6111 };
6112 if !guard_ok {
6113 self.scope_pop_hook();
6114 continue;
6115 }
6116 let out = self.eval_expr(&arm.body);
6117 self.scope_pop_hook();
6118 return out;
6119 }
6120 continue;
6121 }
6122 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
6123 self.scope_push_hook();
6124 self.scope.declare_scalar("_", val.clone());
6125 self.english_note_lexical_scalar("_");
6126 for b in bindings {
6127 match b {
6128 PatternBinding::Scalar(name, v) => {
6129 self.scope.declare_scalar(&name, v);
6130 self.english_note_lexical_scalar(&name);
6131 }
6132 PatternBinding::Array(name, elems) => {
6133 self.scope.declare_array(&name, elems);
6134 }
6135 }
6136 }
6137 let guard_ok = if let Some(g) = &arm.guard {
6138 self.eval_expr(g)?.is_true()
6139 } else {
6140 true
6141 };
6142 if !guard_ok {
6143 self.scope_pop_hook();
6144 continue;
6145 }
6146 let out = self.eval_expr(&arm.body);
6147 self.scope_pop_hook();
6148 return out;
6149 }
6150 }
6151 Err(PerlError::runtime(
6152 "match: no arm matched the value (add a `_` catch-all)",
6153 line,
6154 )
6155 .into())
6156 }
6157
6158 fn parse_duration_seconds(pv: &PerlValue) -> Option<f64> {
6159 let s = pv.to_string();
6160 let s = s.trim();
6161 if let Some(rest) = s.strip_suffix("ms") {
6162 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
6163 }
6164 if let Some(rest) = s.strip_suffix('s') {
6165 return rest.trim().parse::<f64>().ok();
6166 }
6167 if let Some(rest) = s.strip_suffix('m') {
6168 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
6169 }
6170 s.parse::<f64>().ok()
6171 }
6172
6173 fn eval_retry_block(
6174 &mut self,
6175 body: &Block,
6176 times: &Expr,
6177 backoff: RetryBackoff,
6178 _line: usize,
6179 ) -> ExecResult {
6180 let max = self.eval_expr(times)?.to_int().max(1) as usize;
6181 let base_ms: u64 = 10;
6182 let mut attempt = 0usize;
6183 loop {
6184 attempt += 1;
6185 match self.exec_block(body) {
6186 Ok(v) => return Ok(v),
6187 Err(FlowOrError::Error(e)) => {
6188 if attempt >= max {
6189 return Err(FlowOrError::Error(e));
6190 }
6191 let delay_ms = match backoff {
6192 RetryBackoff::None => 0,
6193 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
6194 RetryBackoff::Exponential => {
6195 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
6196 }
6197 };
6198 if delay_ms > 0 {
6199 std::thread::sleep(Duration::from_millis(delay_ms));
6200 }
6201 }
6202 Err(e) => return Err(e),
6203 }
6204 }
6205 }
6206
6207 fn eval_rate_limit_block(
6208 &mut self,
6209 slot: u32,
6210 max: &Expr,
6211 window: &Expr,
6212 body: &Block,
6213 _line: usize,
6214 ) -> ExecResult {
6215 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
6216 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
6217 .filter(|s| *s > 0.0)
6218 .unwrap_or(1.0);
6219 let window_d = Duration::from_secs_f64(window_sec);
6220 let slot = slot as usize;
6221 while self.rate_limit_slots.len() <= slot {
6222 self.rate_limit_slots.push(VecDeque::new());
6223 }
6224 {
6225 let dq = &mut self.rate_limit_slots[slot];
6226 loop {
6227 let now = Instant::now();
6228 while let Some(t0) = dq.front().copied() {
6229 if now.duration_since(t0) >= window_d {
6230 dq.pop_front();
6231 } else {
6232 break;
6233 }
6234 }
6235 if dq.len() < max_n || max_n == 0 {
6236 break;
6237 }
6238 let t0 = dq.front().copied().unwrap();
6239 let wait = window_d.saturating_sub(now.duration_since(t0));
6240 if wait.is_zero() {
6241 dq.pop_front();
6242 continue;
6243 }
6244 std::thread::sleep(wait);
6245 }
6246 dq.push_back(Instant::now());
6247 }
6248 self.exec_block(body)
6249 }
6250
6251 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
6252 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
6253 .filter(|s| *s > 0.0)
6254 .unwrap_or(1.0);
6255 loop {
6256 match self.exec_block(body) {
6257 Ok(_) => {}
6258 Err(e) => return Err(e),
6259 }
6260 std::thread::sleep(Duration::from_secs_f64(sec));
6261 }
6262 }
6263
6264 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> PerlResult<PerlValue> {
6266 let pair = |value: PerlValue, more: i64| {
6267 PerlValue::array_ref(Arc::new(RwLock::new(vec![value, PerlValue::integer(more)])))
6268 };
6269 let mut exhausted = gen.exhausted.lock();
6270 if *exhausted {
6271 return Ok(pair(PerlValue::UNDEF, 0));
6272 }
6273 let mut pc = gen.pc.lock();
6274 let mut scope_started = gen.scope_started.lock();
6275 if *pc >= gen.block.len() {
6276 if *scope_started {
6277 self.scope_pop_hook();
6278 *scope_started = false;
6279 }
6280 *exhausted = true;
6281 return Ok(pair(PerlValue::UNDEF, 0));
6282 }
6283 if !*scope_started {
6284 self.scope_push_hook();
6285 *scope_started = true;
6286 }
6287 self.in_generator = true;
6288 while *pc < gen.block.len() {
6289 let stmt = &gen.block[*pc];
6290 match self.exec_statement(stmt) {
6291 Ok(_) => {
6292 *pc += 1;
6293 }
6294 Err(FlowOrError::Flow(Flow::Yield(v))) => {
6295 *pc += 1;
6296 self.in_generator = false;
6297 if *scope_started {
6300 self.scope_pop_hook();
6301 *scope_started = false;
6302 }
6303 return Ok(pair(v, 1));
6304 }
6305 Err(e) => {
6306 self.in_generator = false;
6307 if *scope_started {
6308 self.scope_pop_hook();
6309 *scope_started = false;
6310 }
6311 return Err(match e {
6312 FlowOrError::Error(ee) => ee,
6313 FlowOrError::Flow(Flow::Yield(_)) => {
6314 unreachable!("yield handled above")
6315 }
6316 FlowOrError::Flow(flow) => PerlError::runtime(
6317 format!("unexpected control flow in generator: {:?}", flow),
6318 0,
6319 ),
6320 });
6321 }
6322 }
6323 }
6324 self.in_generator = false;
6325 if *scope_started {
6326 self.scope_pop_hook();
6327 *scope_started = false;
6328 }
6329 *exhausted = true;
6330 Ok(pair(PerlValue::UNDEF, 0))
6331 }
6332
6333 fn match_pattern_try(
6334 &mut self,
6335 subject: &PerlValue,
6336 pattern: &MatchPattern,
6337 line: usize,
6338 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6339 match pattern {
6340 MatchPattern::Any => Ok(Some(vec![])),
6341 MatchPattern::Regex { .. } => {
6342 unreachable!("regex arms are handled in eval_algebraic_match")
6343 }
6344 MatchPattern::Value(expr) => {
6345 if self.match_pattern_value_alternation(subject, expr, line)? {
6346 Ok(Some(vec![]))
6347 } else {
6348 Ok(None)
6349 }
6350 }
6351 MatchPattern::Array(elems) => {
6352 let Some(arr) = self.match_subject_as_array(subject) else {
6353 return Ok(None);
6354 };
6355 self.match_array_pattern_elems(&arr, elems, line)
6356 }
6357 MatchPattern::Hash(pairs) => {
6358 let Some(h) = self.match_subject_as_hash(subject) else {
6359 return Ok(None);
6360 };
6361 self.match_hash_pattern_pairs(&h, pairs, line)
6362 }
6363 MatchPattern::OptionSome(name) => {
6364 let Some(arr) = self.match_subject_as_array(subject) else {
6365 return Ok(None);
6366 };
6367 if arr.len() < 2 {
6368 return Ok(None);
6369 }
6370 if !arr[1].is_true() {
6371 return Ok(None);
6372 }
6373 Ok(Some(vec![PatternBinding::Scalar(
6374 name.clone(),
6375 arr[0].clone(),
6376 )]))
6377 }
6378 }
6379 }
6380
6381 fn match_pattern_value_alternation(
6384 &mut self,
6385 subject: &PerlValue,
6386 expr: &Expr,
6387 _line: usize,
6388 ) -> Result<bool, FlowOrError> {
6389 if let ExprKind::BinOp {
6390 left,
6391 op: BinOp::BitOr,
6392 right,
6393 } = &expr.kind
6394 {
6395 if self.match_pattern_value_alternation(subject, left, _line)? {
6396 return Ok(true);
6397 }
6398 return self.match_pattern_value_alternation(subject, right, _line);
6399 }
6400 let pv = self.eval_expr(expr)?;
6401 Ok(self.smartmatch_when(subject, &pv))
6402 }
6403
6404 fn match_subject_as_array(&self, v: &PerlValue) -> Option<Vec<PerlValue>> {
6406 if let Some(a) = v.as_array_vec() {
6407 return Some(a);
6408 }
6409 if let Some(r) = v.as_array_ref() {
6410 return Some(r.read().clone());
6411 }
6412 if let Some(name) = v.as_array_binding_name() {
6413 return Some(self.scope.get_array(&name));
6414 }
6415 None
6416 }
6417
6418 fn match_subject_as_hash(&mut self, v: &PerlValue) -> Option<IndexMap<String, PerlValue>> {
6419 if let Some(h) = v.as_hash_map() {
6420 return Some(h);
6421 }
6422 if let Some(r) = v.as_hash_ref() {
6423 return Some(r.read().clone());
6424 }
6425 if let Some(name) = v.as_hash_binding_name() {
6426 self.touch_env_hash(&name);
6427 return Some(self.scope.get_hash(&name));
6428 }
6429 None
6430 }
6431
6432 pub(crate) fn hash_slice_deref_values(
6435 &mut self,
6436 container: &PerlValue,
6437 key_values: &[PerlValue],
6438 line: usize,
6439 ) -> Result<PerlValue, FlowOrError> {
6440 let h = if let Some(m) = self.match_subject_as_hash(container) {
6441 m
6442 } else {
6443 return Err(PerlError::runtime(
6444 "Hash slice dereference needs a hash or hash reference value",
6445 line,
6446 )
6447 .into());
6448 };
6449 let mut result = Vec::new();
6450 for kv in key_values {
6451 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6452 vv.iter().map(|x| x.to_string()).collect()
6453 } else {
6454 vec![kv.to_string()]
6455 };
6456 for k in key_strings {
6457 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6458 }
6459 }
6460 Ok(PerlValue::array(result))
6461 }
6462
6463 pub(crate) fn assign_hash_slice_one_key(
6466 &mut self,
6467 container: PerlValue,
6468 key: &str,
6469 val: PerlValue,
6470 line: usize,
6471 ) -> Result<PerlValue, FlowOrError> {
6472 if let Some(r) = container.as_hash_ref() {
6473 r.write().insert(key.to_string(), val);
6474 return Ok(PerlValue::UNDEF);
6475 }
6476 if let Some(name) = container.as_hash_binding_name() {
6477 self.touch_env_hash(&name);
6478 self.scope
6479 .set_hash_element(&name, key, val)
6480 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6481 return Ok(PerlValue::UNDEF);
6482 }
6483 if let Some(s) = container.as_str() {
6484 self.touch_env_hash(&s);
6485 if self.strict_refs {
6486 return Err(PerlError::runtime(
6487 format!(
6488 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6489 s
6490 ),
6491 line,
6492 )
6493 .into());
6494 }
6495 self.scope
6496 .set_hash_element(&s, key, val)
6497 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6498 return Ok(PerlValue::UNDEF);
6499 }
6500 Err(PerlError::runtime(
6501 "Hash slice assignment needs a hash or hash reference value",
6502 line,
6503 )
6504 .into())
6505 }
6506
6507 pub(crate) fn assign_named_hash_slice(
6510 &mut self,
6511 hash: &str,
6512 key_values: Vec<PerlValue>,
6513 val: PerlValue,
6514 line: usize,
6515 ) -> Result<PerlValue, FlowOrError> {
6516 self.touch_env_hash(hash);
6517 let mut ks: Vec<String> = Vec::new();
6518 for kv in key_values {
6519 if let Some(vv) = kv.as_array_vec() {
6520 ks.extend(vv.iter().map(|x| x.to_string()));
6521 } else {
6522 ks.push(kv.to_string());
6523 }
6524 }
6525 if ks.is_empty() {
6526 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6527 }
6528 let items = val.to_list();
6529 for (i, k) in ks.iter().enumerate() {
6530 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6531 self.scope
6532 .set_hash_element(hash, k, v)
6533 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6534 }
6535 Ok(PerlValue::UNDEF)
6536 }
6537
6538 pub(crate) fn assign_hash_slice_deref(
6540 &mut self,
6541 container: PerlValue,
6542 key_values: Vec<PerlValue>,
6543 val: PerlValue,
6544 line: usize,
6545 ) -> Result<PerlValue, FlowOrError> {
6546 let mut ks: Vec<String> = Vec::new();
6547 for kv in key_values {
6548 if let Some(vv) = kv.as_array_vec() {
6549 ks.extend(vv.iter().map(|x| x.to_string()));
6550 } else {
6551 ks.push(kv.to_string());
6552 }
6553 }
6554 if ks.is_empty() {
6555 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6556 }
6557 let items = val.to_list();
6558 if let Some(r) = container.as_hash_ref() {
6559 let mut h = r.write();
6560 for (i, k) in ks.iter().enumerate() {
6561 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6562 h.insert(k.clone(), v);
6563 }
6564 return Ok(PerlValue::UNDEF);
6565 }
6566 if let Some(name) = container.as_hash_binding_name() {
6567 self.touch_env_hash(&name);
6568 for (i, k) in ks.iter().enumerate() {
6569 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6570 self.scope
6571 .set_hash_element(&name, k, v)
6572 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6573 }
6574 return Ok(PerlValue::UNDEF);
6575 }
6576 if let Some(s) = container.as_str() {
6577 if self.strict_refs {
6578 return Err(PerlError::runtime(
6579 format!(
6580 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6581 s
6582 ),
6583 line,
6584 )
6585 .into());
6586 }
6587 self.touch_env_hash(&s);
6588 for (i, k) in ks.iter().enumerate() {
6589 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6590 self.scope
6591 .set_hash_element(&s, k, v)
6592 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6593 }
6594 return Ok(PerlValue::UNDEF);
6595 }
6596 Err(PerlError::runtime(
6597 "Hash slice assignment needs a hash or hash reference value",
6598 line,
6599 )
6600 .into())
6601 }
6602
6603 pub(crate) fn compound_assign_hash_slice_deref(
6606 &mut self,
6607 container: PerlValue,
6608 key_values: Vec<PerlValue>,
6609 op: BinOp,
6610 rhs: PerlValue,
6611 line: usize,
6612 ) -> Result<PerlValue, FlowOrError> {
6613 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6614 let last_old = old_list
6615 .to_list()
6616 .last()
6617 .cloned()
6618 .unwrap_or(PerlValue::UNDEF);
6619 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6620 let mut ks: Vec<String> = Vec::new();
6621 for kv in &key_values {
6622 if let Some(vv) = kv.as_array_vec() {
6623 ks.extend(vv.iter().map(|x| x.to_string()));
6624 } else {
6625 ks.push(kv.to_string());
6626 }
6627 }
6628 if ks.is_empty() {
6629 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6630 }
6631 let last_key = ks.last().expect("non-empty ks");
6632 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6633 Ok(new_val)
6634 }
6635
6636 pub(crate) fn hash_slice_deref_inc_dec(
6642 &mut self,
6643 container: PerlValue,
6644 key_values: Vec<PerlValue>,
6645 kind: u8,
6646 line: usize,
6647 ) -> Result<PerlValue, FlowOrError> {
6648 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6649 let last_old = old_list
6650 .to_list()
6651 .last()
6652 .cloned()
6653 .unwrap_or(PerlValue::UNDEF);
6654 let new_val = if kind & 1 == 0 {
6655 PerlValue::integer(last_old.to_int() + 1)
6656 } else {
6657 PerlValue::integer(last_old.to_int() - 1)
6658 };
6659 let mut ks: Vec<String> = Vec::new();
6660 for kv in &key_values {
6661 if let Some(vv) = kv.as_array_vec() {
6662 ks.extend(vv.iter().map(|x| x.to_string()));
6663 } else {
6664 ks.push(kv.to_string());
6665 }
6666 }
6667 let last_key = ks.last().ok_or_else(|| {
6668 PerlError::runtime("Hash slice increment needs at least one key", line)
6669 })?;
6670 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6671 Ok(if kind < 2 { new_val } else { last_old })
6672 }
6673
6674 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[PerlValue]) -> PerlValue {
6675 self.touch_env_hash(hash);
6676 let h = self.scope.get_hash(hash);
6677 let mut result = Vec::new();
6678 for kv in key_values {
6679 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6680 vv.iter().map(|x| x.to_string()).collect()
6681 } else {
6682 vec![kv.to_string()]
6683 };
6684 for k in key_strings {
6685 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6686 }
6687 }
6688 PerlValue::array(result)
6689 }
6690
6691 pub(crate) fn compound_assign_named_hash_slice(
6693 &mut self,
6694 hash: &str,
6695 key_values: Vec<PerlValue>,
6696 op: BinOp,
6697 rhs: PerlValue,
6698 line: usize,
6699 ) -> Result<PerlValue, FlowOrError> {
6700 let old_list = self.hash_slice_named_values(hash, &key_values);
6701 let last_old = old_list
6702 .to_list()
6703 .last()
6704 .cloned()
6705 .unwrap_or(PerlValue::UNDEF);
6706 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6707 let mut ks: Vec<String> = Vec::new();
6708 for kv in &key_values {
6709 if let Some(vv) = kv.as_array_vec() {
6710 ks.extend(vv.iter().map(|x| x.to_string()));
6711 } else {
6712 ks.push(kv.to_string());
6713 }
6714 }
6715 if ks.is_empty() {
6716 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6717 }
6718 let last_key = ks.last().expect("non-empty ks");
6719 let container = PerlValue::string(hash.to_string());
6720 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6721 Ok(new_val)
6722 }
6723
6724 pub(crate) fn named_hash_slice_inc_dec(
6726 &mut self,
6727 hash: &str,
6728 key_values: Vec<PerlValue>,
6729 kind: u8,
6730 line: usize,
6731 ) -> Result<PerlValue, FlowOrError> {
6732 let old_list = self.hash_slice_named_values(hash, &key_values);
6733 let last_old = old_list
6734 .to_list()
6735 .last()
6736 .cloned()
6737 .unwrap_or(PerlValue::UNDEF);
6738 let new_val = if kind & 1 == 0 {
6739 PerlValue::integer(last_old.to_int() + 1)
6740 } else {
6741 PerlValue::integer(last_old.to_int() - 1)
6742 };
6743 let mut ks: Vec<String> = Vec::new();
6744 for kv in &key_values {
6745 if let Some(vv) = kv.as_array_vec() {
6746 ks.extend(vv.iter().map(|x| x.to_string()));
6747 } else {
6748 ks.push(kv.to_string());
6749 }
6750 }
6751 let last_key = ks.last().ok_or_else(|| {
6752 PerlError::runtime("Hash slice increment needs at least one key", line)
6753 })?;
6754 let container = PerlValue::string(hash.to_string());
6755 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6756 Ok(if kind < 2 { new_val } else { last_old })
6757 }
6758
6759 fn match_array_pattern_elems(
6760 &mut self,
6761 arr: &[PerlValue],
6762 elems: &[MatchArrayElem],
6763 line: usize,
6764 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6765 let has_rest = elems
6766 .iter()
6767 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
6768 let mut binds: Vec<PatternBinding> = Vec::new();
6769 let mut idx = 0usize;
6770 for (i, elem) in elems.iter().enumerate() {
6771 match elem {
6772 MatchArrayElem::Rest => {
6773 if i != elems.len() - 1 {
6774 return Err(PerlError::runtime(
6775 "internal: `*` must be last in array match pattern",
6776 line,
6777 )
6778 .into());
6779 }
6780 return Ok(Some(binds));
6781 }
6782 MatchArrayElem::RestBind(name) => {
6783 if i != elems.len() - 1 {
6784 return Err(PerlError::runtime(
6785 "internal: `@name` rest bind must be last in array match pattern",
6786 line,
6787 )
6788 .into());
6789 }
6790 let tail = arr[idx..].to_vec();
6791 binds.push(PatternBinding::Array(name.clone(), tail));
6792 return Ok(Some(binds));
6793 }
6794 MatchArrayElem::CaptureScalar(name) => {
6795 if idx >= arr.len() {
6796 return Ok(None);
6797 }
6798 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
6799 idx += 1;
6800 }
6801 MatchArrayElem::Expr(e) => {
6802 if idx >= arr.len() {
6803 return Ok(None);
6804 }
6805 let expected = self.eval_expr(e)?;
6806 if !self.smartmatch_when(&arr[idx], &expected) {
6807 return Ok(None);
6808 }
6809 idx += 1;
6810 }
6811 }
6812 }
6813 if !has_rest && idx != arr.len() {
6814 return Ok(None);
6815 }
6816 Ok(Some(binds))
6817 }
6818
6819 fn match_hash_pattern_pairs(
6820 &mut self,
6821 h: &IndexMap<String, PerlValue>,
6822 pairs: &[MatchHashPair],
6823 _line: usize,
6824 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6825 let mut binds = Vec::new();
6826 for pair in pairs {
6827 match pair {
6828 MatchHashPair::KeyOnly { key } => {
6829 let ks = self.eval_expr(key)?.to_string();
6830 if !h.contains_key(&ks) {
6831 return Ok(None);
6832 }
6833 }
6834 MatchHashPair::Capture { key, name } => {
6835 let ks = self.eval_expr(key)?.to_string();
6836 let Some(v) = h.get(&ks) else {
6837 return Ok(None);
6838 };
6839 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
6840 }
6841 }
6842 }
6843 Ok(Some(binds))
6844 }
6845
6846 #[inline]
6848 fn block_needs_scope(block: &Block) -> bool {
6849 block.iter().any(|s| match &s.kind {
6850 StmtKind::My(_)
6851 | StmtKind::Our(_)
6852 | StmtKind::Local(_)
6853 | StmtKind::State(_)
6854 | StmtKind::LocalExpr { .. } => true,
6855 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
6856 _ => false,
6857 })
6858 }
6859
6860 #[inline]
6862 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
6863 if Self::block_needs_scope(block) {
6864 self.exec_block(block)
6865 } else {
6866 self.exec_block_no_scope(block)
6867 }
6868 }
6869
6870 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
6871 let t0 = self.profiler.is_some().then(std::time::Instant::now);
6872 let r = self.exec_statement_inner(stmt);
6873 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
6874 prof.on_line(&self.file, stmt.line, t0.elapsed());
6875 }
6876 r
6877 }
6878
6879 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
6880 if let Err(e) = crate::perl_signal::poll(self) {
6881 return Err(FlowOrError::Error(e));
6882 }
6883 if let Err(e) = self.drain_pending_destroys(stmt.line) {
6884 return Err(FlowOrError::Error(e));
6885 }
6886 match &stmt.kind {
6887 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
6888 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
6889 StmtKind::If {
6890 condition,
6891 body,
6892 elsifs,
6893 else_block,
6894 } => {
6895 if self.eval_boolean_rvalue_condition(condition)? {
6896 return self.exec_block(body);
6897 }
6898 for (c, b) in elsifs {
6899 if self.eval_boolean_rvalue_condition(c)? {
6900 return self.exec_block(b);
6901 }
6902 }
6903 if let Some(eb) = else_block {
6904 return self.exec_block(eb);
6905 }
6906 Ok(PerlValue::UNDEF)
6907 }
6908 StmtKind::Unless {
6909 condition,
6910 body,
6911 else_block,
6912 } => {
6913 if !self.eval_boolean_rvalue_condition(condition)? {
6914 return self.exec_block(body);
6915 }
6916 if let Some(eb) = else_block {
6917 return self.exec_block(eb);
6918 }
6919 Ok(PerlValue::UNDEF)
6920 }
6921 StmtKind::While {
6922 condition,
6923 body,
6924 label,
6925 continue_block,
6926 } => {
6927 'outer: loop {
6928 if !self.eval_boolean_rvalue_condition(condition)? {
6929 break;
6930 }
6931 'inner: loop {
6932 match self.exec_block_smart(body) {
6933 Ok(_) => break 'inner,
6934 Err(FlowOrError::Flow(Flow::Last(ref l)))
6935 if l == label || l.is_none() =>
6936 {
6937 break 'outer;
6938 }
6939 Err(FlowOrError::Flow(Flow::Next(ref l)))
6940 if l == label || l.is_none() =>
6941 {
6942 if let Some(cb) = continue_block {
6943 let _ = self.exec_block_smart(cb);
6944 }
6945 continue 'outer;
6946 }
6947 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6948 if l == label || l.is_none() =>
6949 {
6950 continue 'inner;
6951 }
6952 Err(e) => return Err(e),
6953 }
6954 }
6955 if let Some(cb) = continue_block {
6956 let _ = self.exec_block_smart(cb);
6957 }
6958 }
6959 Ok(PerlValue::UNDEF)
6960 }
6961 StmtKind::Until {
6962 condition,
6963 body,
6964 label,
6965 continue_block,
6966 } => {
6967 'outer: loop {
6968 if self.eval_boolean_rvalue_condition(condition)? {
6969 break;
6970 }
6971 'inner: loop {
6972 match self.exec_block(body) {
6973 Ok(_) => break 'inner,
6974 Err(FlowOrError::Flow(Flow::Last(ref l)))
6975 if l == label || l.is_none() =>
6976 {
6977 break 'outer;
6978 }
6979 Err(FlowOrError::Flow(Flow::Next(ref l)))
6980 if l == label || l.is_none() =>
6981 {
6982 if let Some(cb) = continue_block {
6983 let _ = self.exec_block_smart(cb);
6984 }
6985 continue 'outer;
6986 }
6987 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6988 if l == label || l.is_none() =>
6989 {
6990 continue 'inner;
6991 }
6992 Err(e) => return Err(e),
6993 }
6994 }
6995 if let Some(cb) = continue_block {
6996 let _ = self.exec_block_smart(cb);
6997 }
6998 }
6999 Ok(PerlValue::UNDEF)
7000 }
7001 StmtKind::DoWhile { body, condition } => {
7002 loop {
7003 self.exec_block(body)?;
7004 if !self.eval_boolean_rvalue_condition(condition)? {
7005 break;
7006 }
7007 }
7008 Ok(PerlValue::UNDEF)
7009 }
7010 StmtKind::For {
7011 init,
7012 condition,
7013 step,
7014 body,
7015 label,
7016 continue_block,
7017 } => {
7018 self.scope_push_hook();
7019 if let Some(init) = init {
7020 self.exec_statement(init)?;
7021 }
7022 'outer: loop {
7023 if let Some(cond) = condition {
7024 if !self.eval_boolean_rvalue_condition(cond)? {
7025 break;
7026 }
7027 }
7028 'inner: loop {
7029 match self.exec_block_smart(body) {
7030 Ok(_) => break 'inner,
7031 Err(FlowOrError::Flow(Flow::Last(ref l)))
7032 if l == label || l.is_none() =>
7033 {
7034 break 'outer;
7035 }
7036 Err(FlowOrError::Flow(Flow::Next(ref l)))
7037 if l == label || l.is_none() =>
7038 {
7039 if let Some(cb) = continue_block {
7040 let _ = self.exec_block_smart(cb);
7041 }
7042 if let Some(step) = step {
7043 self.eval_expr(step)?;
7044 }
7045 continue 'outer;
7046 }
7047 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7048 if l == label || l.is_none() =>
7049 {
7050 continue 'inner;
7051 }
7052 Err(e) => {
7053 self.scope_pop_hook();
7054 return Err(e);
7055 }
7056 }
7057 }
7058 if let Some(cb) = continue_block {
7059 let _ = self.exec_block_smart(cb);
7060 }
7061 if let Some(step) = step {
7062 self.eval_expr(step)?;
7063 }
7064 }
7065 self.scope_pop_hook();
7066 Ok(PerlValue::UNDEF)
7067 }
7068 StmtKind::Foreach {
7069 var,
7070 list,
7071 body,
7072 label,
7073 continue_block,
7074 } => {
7075 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
7076 let items = list_val.to_list();
7077 self.scope_push_hook();
7078 self.scope.declare_scalar(var, PerlValue::UNDEF);
7079 self.english_note_lexical_scalar(var);
7080 let mut i = 0usize;
7081 'outer: while i < items.len() {
7082 self.scope
7083 .set_scalar(var, items[i].clone())
7084 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
7085 'inner: loop {
7086 match self.exec_block_smart(body) {
7087 Ok(_) => break 'inner,
7088 Err(FlowOrError::Flow(Flow::Last(ref l)))
7089 if l == label || l.is_none() =>
7090 {
7091 break 'outer;
7092 }
7093 Err(FlowOrError::Flow(Flow::Next(ref l)))
7094 if l == label || l.is_none() =>
7095 {
7096 if let Some(cb) = continue_block {
7097 let _ = self.exec_block_smart(cb);
7098 }
7099 i += 1;
7100 continue 'outer;
7101 }
7102 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7103 if l == label || l.is_none() =>
7104 {
7105 continue 'inner;
7106 }
7107 Err(e) => {
7108 self.scope_pop_hook();
7109 return Err(e);
7110 }
7111 }
7112 }
7113 if let Some(cb) = continue_block {
7114 let _ = self.exec_block_smart(cb);
7115 }
7116 i += 1;
7117 }
7118 self.scope_pop_hook();
7119 Ok(PerlValue::UNDEF)
7120 }
7121 StmtKind::SubDecl {
7122 name,
7123 params,
7124 body,
7125 prototype,
7126 } => {
7127 let key = self.qualify_sub_key(name);
7128 let captured = self.scope.capture();
7129 let closure_env = if captured.is_empty() {
7130 None
7131 } else {
7132 Some(captured)
7133 };
7134 let mut sub = PerlSub {
7135 name: name.clone(),
7136 params: params.clone(),
7137 body: body.clone(),
7138 closure_env,
7139 prototype: prototype.clone(),
7140 fib_like: None,
7141 };
7142 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
7143 self.subs.insert(key, Arc::new(sub));
7144 Ok(PerlValue::UNDEF)
7145 }
7146 StmtKind::StructDecl { def } => {
7147 if self.struct_defs.contains_key(&def.name) {
7148 return Err(PerlError::runtime(
7149 format!("duplicate struct `{}`", def.name),
7150 stmt.line,
7151 )
7152 .into());
7153 }
7154 self.struct_defs
7155 .insert(def.name.clone(), Arc::new(def.clone()));
7156 Ok(PerlValue::UNDEF)
7157 }
7158 StmtKind::EnumDecl { def } => {
7159 if self.enum_defs.contains_key(&def.name) {
7160 return Err(PerlError::runtime(
7161 format!("duplicate enum `{}`", def.name),
7162 stmt.line,
7163 )
7164 .into());
7165 }
7166 self.enum_defs
7167 .insert(def.name.clone(), Arc::new(def.clone()));
7168 Ok(PerlValue::UNDEF)
7169 }
7170 StmtKind::ClassDecl { def } => {
7171 if self.class_defs.contains_key(&def.name) {
7172 return Err(PerlError::runtime(
7173 format!("duplicate class `{}`", def.name),
7174 stmt.line,
7175 )
7176 .into());
7177 }
7178 for parent_name in &def.extends {
7180 if let Some(parent_def) = self.class_defs.get(parent_name) {
7181 if parent_def.is_final {
7182 return Err(PerlError::runtime(
7183 format!("cannot extend final class `{}`", parent_name),
7184 stmt.line,
7185 )
7186 .into());
7187 }
7188 for m in &def.methods {
7190 if let Some(parent_method) = parent_def.method(&m.name) {
7191 if parent_method.is_final {
7192 return Err(PerlError::runtime(
7193 format!(
7194 "cannot override final method `{}` from class `{}`",
7195 m.name, parent_name
7196 ),
7197 stmt.line,
7198 )
7199 .into());
7200 }
7201 }
7202 }
7203 }
7204 }
7205 let mut def = def.clone();
7207 for trait_name in &def.implements.clone() {
7208 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
7209 for required in trait_def.required_methods() {
7210 let has_method = def.methods.iter().any(|m| m.name == required.name);
7211 if !has_method {
7212 return Err(PerlError::runtime(
7213 format!(
7214 "class `{}` implements trait `{}` but does not define required method `{}`",
7215 def.name, trait_name, required.name
7216 ),
7217 stmt.line,
7218 )
7219 .into());
7220 }
7221 }
7222 for tm in &trait_def.methods {
7224 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
7225 def.methods.push(tm.clone());
7226 }
7227 }
7228 }
7229 }
7230 if !def.is_abstract {
7233 for parent_name in &def.extends.clone() {
7234 if let Some(parent_def) = self.class_defs.get(parent_name) {
7235 if parent_def.is_abstract {
7236 for m in &parent_def.methods {
7237 if m.body.is_none()
7238 && !def.methods.iter().any(|dm| dm.name == m.name)
7239 {
7240 return Err(PerlError::runtime(
7241 format!(
7242 "class `{}` must implement abstract method `{}` from `{}`",
7243 def.name, m.name, parent_name
7244 ),
7245 stmt.line,
7246 )
7247 .into());
7248 }
7249 }
7250 }
7251 }
7252 }
7253 }
7254 for sf in &def.static_fields {
7256 let val = if let Some(ref expr) = sf.default {
7257 self.eval_expr(expr)?
7258 } else {
7259 PerlValue::UNDEF
7260 };
7261 let key = format!("{}::{}", def.name, sf.name);
7262 self.scope.declare_scalar(&key, val);
7263 }
7264 for m in &def.methods {
7266 if let Some(ref body) = m.body {
7267 let fq = format!("{}::{}", def.name, m.name);
7268 let sub = Arc::new(PerlSub {
7269 name: fq.clone(),
7270 params: m.params.clone(),
7271 body: body.clone(),
7272 closure_env: None,
7273 prototype: None,
7274 fib_like: None,
7275 });
7276 self.subs.insert(fq, sub);
7277 }
7278 }
7279 if !def.extends.is_empty() {
7281 let isa_key = format!("{}::ISA", def.name);
7282 let parents: Vec<PerlValue> = def
7283 .extends
7284 .iter()
7285 .map(|p| PerlValue::string(p.clone()))
7286 .collect();
7287 self.scope.declare_array(&isa_key, parents);
7288 }
7289 self.class_defs.insert(def.name.clone(), Arc::new(def));
7290 Ok(PerlValue::UNDEF)
7291 }
7292 StmtKind::TraitDecl { def } => {
7293 if self.trait_defs.contains_key(&def.name) {
7294 return Err(PerlError::runtime(
7295 format!("duplicate trait `{}`", def.name),
7296 stmt.line,
7297 )
7298 .into());
7299 }
7300 self.trait_defs
7301 .insert(def.name.clone(), Arc::new(def.clone()));
7302 Ok(PerlValue::UNDEF)
7303 }
7304 StmtKind::My(decls) | StmtKind::Our(decls) => {
7305 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
7306 if decls.len() > 1 && decls[0].initializer.is_some() {
7309 let val = self.eval_expr_ctx(
7310 decls[0].initializer.as_ref().unwrap(),
7311 WantarrayCtx::List,
7312 )?;
7313 let items = val.to_list();
7314 let mut idx = 0;
7315 for decl in decls {
7316 match decl.sigil {
7317 Sigil::Scalar => {
7318 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7319 let skey = if is_our {
7320 self.stash_scalar_name_for_package(&decl.name)
7321 } else {
7322 decl.name.clone()
7323 };
7324 self.scope.declare_scalar_frozen(
7325 &skey,
7326 v,
7327 decl.frozen,
7328 decl.type_annotation.clone(),
7329 )?;
7330 self.english_note_lexical_scalar(&decl.name);
7331 if is_our {
7332 self.note_our_scalar(&decl.name);
7333 }
7334 idx += 1;
7335 }
7336 Sigil::Array => {
7337 let rest: Vec<PerlValue> = items[idx..].to_vec();
7339 idx = items.len();
7340 if is_our {
7341 self.record_exporter_our_array_name(&decl.name, &rest);
7342 }
7343 let aname = self.stash_array_name_for_package(&decl.name);
7344 self.scope.declare_array(&aname, rest);
7345 }
7346 Sigil::Hash => {
7347 let rest: Vec<PerlValue> = items[idx..].to_vec();
7348 idx = items.len();
7349 let mut map = IndexMap::new();
7350 let mut i = 0;
7351 while i + 1 < rest.len() {
7352 map.insert(rest[i].to_string(), rest[i + 1].clone());
7353 i += 2;
7354 }
7355 self.scope.declare_hash(&decl.name, map);
7356 }
7357 Sigil::Typeglob => {
7358 return Err(PerlError::runtime(
7359 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
7360 stmt.line,
7361 )
7362 .into());
7363 }
7364 }
7365 }
7366 } else {
7367 for decl in decls {
7369 let compound_init = decl
7373 .initializer
7374 .as_ref()
7375 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
7376
7377 if compound_init {
7378 match decl.sigil {
7379 Sigil::Typeglob => {
7380 return Err(PerlError::runtime(
7381 "compound assignment on typeglob declaration is not supported",
7382 stmt.line,
7383 )
7384 .into());
7385 }
7386 Sigil::Scalar => {
7387 let skey = if is_our {
7388 self.stash_scalar_name_for_package(&decl.name)
7389 } else {
7390 decl.name.clone()
7391 };
7392 self.scope.declare_scalar_frozen(
7393 &skey,
7394 PerlValue::UNDEF,
7395 decl.frozen,
7396 decl.type_annotation.clone(),
7397 )?;
7398 self.english_note_lexical_scalar(&decl.name);
7399 if is_our {
7400 self.note_our_scalar(&decl.name);
7401 }
7402 let init = decl.initializer.as_ref().unwrap();
7403 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7404 }
7405 Sigil::Array => {
7406 let aname = self.stash_array_name_for_package(&decl.name);
7407 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
7408 let init = decl.initializer.as_ref().unwrap();
7409 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7410 if is_our {
7411 let items = self.scope.get_array(&aname);
7412 self.record_exporter_our_array_name(&decl.name, &items);
7413 }
7414 }
7415 Sigil::Hash => {
7416 self.scope.declare_hash_frozen(
7417 &decl.name,
7418 IndexMap::new(),
7419 decl.frozen,
7420 );
7421 let init = decl.initializer.as_ref().unwrap();
7422 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7423 }
7424 }
7425 continue;
7426 }
7427
7428 let val = if let Some(init) = &decl.initializer {
7429 let ctx = match decl.sigil {
7430 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7431 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7432 };
7433 self.eval_expr_ctx(init, ctx)?
7434 } else {
7435 PerlValue::UNDEF
7436 };
7437 match decl.sigil {
7438 Sigil::Typeglob => {
7439 return Err(PerlError::runtime(
7440 "`my *FH` / typeglob declaration is not supported",
7441 stmt.line,
7442 )
7443 .into());
7444 }
7445 Sigil::Scalar => {
7446 let skey = if is_our {
7447 self.stash_scalar_name_for_package(&decl.name)
7448 } else {
7449 decl.name.clone()
7450 };
7451 self.scope.declare_scalar_frozen(
7452 &skey,
7453 val,
7454 decl.frozen,
7455 decl.type_annotation.clone(),
7456 )?;
7457 self.english_note_lexical_scalar(&decl.name);
7458 if is_our {
7459 self.note_our_scalar(&decl.name);
7460 }
7461 }
7462 Sigil::Array => {
7463 let items = val.to_list();
7464 if is_our {
7465 self.record_exporter_our_array_name(&decl.name, &items);
7466 }
7467 let aname = self.stash_array_name_for_package(&decl.name);
7468 self.scope.declare_array_frozen(&aname, items, decl.frozen);
7469 }
7470 Sigil::Hash => {
7471 let items = val.to_list();
7472 let mut map = IndexMap::new();
7473 let mut i = 0;
7474 while i + 1 < items.len() {
7475 let k = items[i].to_string();
7476 let v = items[i + 1].clone();
7477 map.insert(k, v);
7478 i += 2;
7479 }
7480 self.scope.declare_hash_frozen(&decl.name, map, decl.frozen);
7481 }
7482 }
7483 }
7484 }
7485 Ok(PerlValue::UNDEF)
7486 }
7487 StmtKind::State(decls) => {
7488 for decl in decls {
7491 let state_key = format!("{}:{}", stmt.line, decl.name);
7492 match decl.sigil {
7493 Sigil::Scalar => {
7494 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
7495 self.scope.declare_scalar(&decl.name, prev);
7497 } else {
7498 let val = if let Some(init) = &decl.initializer {
7500 self.eval_expr(init)?
7501 } else {
7502 PerlValue::UNDEF
7503 };
7504 self.state_vars.insert(state_key.clone(), val.clone());
7505 self.scope.declare_scalar(&decl.name, val);
7506 }
7507 if let Some(frame) = self.state_bindings_stack.last_mut() {
7509 frame.push((decl.name.clone(), state_key));
7510 }
7511 }
7512 _ => {
7513 let val = if let Some(init) = &decl.initializer {
7515 self.eval_expr(init)?
7516 } else {
7517 PerlValue::UNDEF
7518 };
7519 match decl.sigil {
7520 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
7521 Sigil::Hash => {
7522 let items = val.to_list();
7523 let mut map = IndexMap::new();
7524 let mut i = 0;
7525 while i + 1 < items.len() {
7526 map.insert(items[i].to_string(), items[i + 1].clone());
7527 i += 2;
7528 }
7529 self.scope.declare_hash(&decl.name, map);
7530 }
7531 _ => {}
7532 }
7533 }
7534 }
7535 }
7536 Ok(PerlValue::UNDEF)
7537 }
7538 StmtKind::Local(decls) => {
7539 if decls.len() > 1 && decls[0].initializer.is_some() {
7540 let val = self.eval_expr_ctx(
7541 decls[0].initializer.as_ref().unwrap(),
7542 WantarrayCtx::List,
7543 )?;
7544 let items = val.to_list();
7545 let mut idx = 0;
7546 for decl in decls {
7547 match decl.sigil {
7548 Sigil::Scalar => {
7549 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7550 idx += 1;
7551 self.scope.local_set_scalar(&decl.name, v)?;
7552 }
7553 Sigil::Array => {
7554 let rest: Vec<PerlValue> = items[idx..].to_vec();
7555 idx = items.len();
7556 self.scope.local_set_array(&decl.name, rest)?;
7557 }
7558 Sigil::Hash => {
7559 let rest: Vec<PerlValue> = items[idx..].to_vec();
7560 idx = items.len();
7561 if decl.name == "ENV" {
7562 self.materialize_env_if_needed();
7563 }
7564 let mut map = IndexMap::new();
7565 let mut i = 0;
7566 while i + 1 < rest.len() {
7567 map.insert(rest[i].to_string(), rest[i + 1].clone());
7568 i += 2;
7569 }
7570 self.scope.local_set_hash(&decl.name, map)?;
7571 }
7572 Sigil::Typeglob => {
7573 return Err(PerlError::runtime(
7574 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
7575 stmt.line,
7576 )
7577 .into());
7578 }
7579 }
7580 }
7581 Ok(val)
7582 } else {
7583 let mut last_val = PerlValue::UNDEF;
7584 for decl in decls {
7585 let val = if let Some(init) = &decl.initializer {
7586 let ctx = match decl.sigil {
7587 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7588 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7589 };
7590 self.eval_expr_ctx(init, ctx)?
7591 } else {
7592 PerlValue::UNDEF
7593 };
7594 last_val = val.clone();
7595 match decl.sigil {
7596 Sigil::Typeglob => {
7597 let old = self.glob_handle_alias.remove(&decl.name);
7598 if let Some(frame) = self.glob_restore_frames.last_mut() {
7599 frame.push((decl.name.clone(), old));
7600 }
7601 if let Some(init) = &decl.initializer {
7602 if let ExprKind::Typeglob(rhs) = &init.kind {
7603 self.glob_handle_alias
7604 .insert(decl.name.clone(), rhs.clone());
7605 } else {
7606 return Err(PerlError::runtime(
7607 "local *GLOB = *OTHER — right side must be a typeglob",
7608 stmt.line,
7609 )
7610 .into());
7611 }
7612 }
7613 }
7614 Sigil::Scalar => {
7615 if Self::is_special_scalar_name_for_set(&decl.name) {
7621 let old = self.get_special_var(&decl.name);
7622 if let Some(frame) = self.special_var_restore_frames.last_mut()
7623 {
7624 frame.push((decl.name.clone(), old));
7625 }
7626 self.set_special_var(&decl.name, &val)
7627 .map_err(|e| e.at_line(stmt.line))?;
7628 }
7629 self.scope.local_set_scalar(&decl.name, val)?;
7630 }
7631 Sigil::Array => {
7632 self.scope.local_set_array(&decl.name, val.to_list())?;
7633 }
7634 Sigil::Hash => {
7635 if decl.name == "ENV" {
7636 self.materialize_env_if_needed();
7637 }
7638 let items = val.to_list();
7639 let mut map = IndexMap::new();
7640 let mut i = 0;
7641 while i + 1 < items.len() {
7642 let k = items[i].to_string();
7643 let v = items[i + 1].clone();
7644 map.insert(k, v);
7645 i += 2;
7646 }
7647 self.scope.local_set_hash(&decl.name, map)?;
7648 }
7649 }
7650 }
7651 Ok(last_val)
7652 }
7653 }
7654 StmtKind::LocalExpr {
7655 target,
7656 initializer,
7657 } => {
7658 let rhs_name = |init: &Expr| -> PerlResult<Option<String>> {
7659 match &init.kind {
7660 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
7661 _ => Err(PerlError::runtime(
7662 "local *GLOB = *OTHER — right side must be a typeglob",
7663 stmt.line,
7664 )),
7665 }
7666 };
7667 match &target.kind {
7668 ExprKind::Typeglob(name) => {
7669 let rhs = if let Some(init) = initializer {
7670 rhs_name(init)?
7671 } else {
7672 None
7673 };
7674 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
7675 return Ok(PerlValue::UNDEF);
7676 }
7677 ExprKind::Deref {
7678 expr,
7679 kind: Sigil::Typeglob,
7680 } => {
7681 let lhs = self.eval_expr(expr)?.to_string();
7682 let rhs = if let Some(init) = initializer {
7683 rhs_name(init)?
7684 } else {
7685 None
7686 };
7687 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7688 return Ok(PerlValue::UNDEF);
7689 }
7690 ExprKind::TypeglobExpr(e) => {
7691 let lhs = self.eval_expr(e)?.to_string();
7692 let rhs = if let Some(init) = initializer {
7693 rhs_name(init)?
7694 } else {
7695 None
7696 };
7697 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7698 return Ok(PerlValue::UNDEF);
7699 }
7700 _ => {}
7701 }
7702 let val = if let Some(init) = initializer {
7703 let ctx = match &target.kind {
7704 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
7705 _ => WantarrayCtx::Scalar,
7706 };
7707 self.eval_expr_ctx(init, ctx)?
7708 } else {
7709 PerlValue::UNDEF
7710 };
7711 match &target.kind {
7712 ExprKind::ScalarVar(name) => {
7713 if Self::is_special_scalar_name_for_set(name) {
7716 let old = self.get_special_var(name);
7717 if let Some(frame) = self.special_var_restore_frames.last_mut() {
7718 frame.push((name.clone(), old));
7719 }
7720 self.set_special_var(name, &val)
7721 .map_err(|e| e.at_line(stmt.line))?;
7722 }
7723 self.scope.local_set_scalar(name, val.clone())?;
7724 }
7725 ExprKind::ArrayVar(name) => {
7726 self.scope.local_set_array(name, val.to_list())?;
7727 }
7728 ExprKind::HashVar(name) => {
7729 if name == "ENV" {
7730 self.materialize_env_if_needed();
7731 }
7732 let items = val.to_list();
7733 let mut map = IndexMap::new();
7734 let mut i = 0;
7735 while i + 1 < items.len() {
7736 map.insert(items[i].to_string(), items[i + 1].clone());
7737 i += 2;
7738 }
7739 self.scope.local_set_hash(name, map)?;
7740 }
7741 ExprKind::HashElement { hash, key } => {
7742 let ks = self.eval_expr(key)?.to_string();
7743 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
7744 }
7745 ExprKind::ArrayElement { array, index } => {
7746 self.check_strict_array_var(array, stmt.line)?;
7747 let aname = self.stash_array_name_for_package(array);
7748 let idx = self.eval_expr(index)?.to_int();
7749 self.scope
7750 .local_set_array_element(&aname, idx, val.clone())?;
7751 }
7752 _ => {
7753 return Err(PerlError::runtime(
7754 format!(
7755 "local on this lvalue is not supported yet ({:?})",
7756 target.kind
7757 ),
7758 stmt.line,
7759 )
7760 .into());
7761 }
7762 }
7763 Ok(val)
7764 }
7765 StmtKind::MySync(decls) => {
7766 for decl in decls {
7767 let val = if let Some(init) = &decl.initializer {
7768 self.eval_expr(init)?
7769 } else {
7770 PerlValue::UNDEF
7771 };
7772 match decl.sigil {
7773 Sigil::Typeglob => {
7774 return Err(PerlError::runtime(
7775 "`mysync` does not support typeglob variables",
7776 stmt.line,
7777 )
7778 .into());
7779 }
7780 Sigil::Scalar => {
7781 let stored = if val.is_mysync_deque_or_heap() {
7784 val
7785 } else {
7786 PerlValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(val)))
7787 };
7788 self.scope.declare_scalar(&decl.name, stored);
7789 }
7790 Sigil::Array => {
7791 self.scope.declare_atomic_array(&decl.name, val.to_list());
7792 }
7793 Sigil::Hash => {
7794 let items = val.to_list();
7795 let mut map = IndexMap::new();
7796 let mut i = 0;
7797 while i + 1 < items.len() {
7798 map.insert(items[i].to_string(), items[i + 1].clone());
7799 i += 2;
7800 }
7801 self.scope.declare_atomic_hash(&decl.name, map);
7802 }
7803 }
7804 }
7805 Ok(PerlValue::UNDEF)
7806 }
7807 StmtKind::Package { name } => {
7808 let _ = self
7810 .scope
7811 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
7812 Ok(PerlValue::UNDEF)
7813 }
7814 StmtKind::UsePerlVersion { .. } => Ok(PerlValue::UNDEF),
7815 StmtKind::Use { .. } => {
7816 Ok(PerlValue::UNDEF)
7818 }
7819 StmtKind::UseOverload { pairs } => {
7820 self.install_use_overload_pairs(pairs);
7821 Ok(PerlValue::UNDEF)
7822 }
7823 StmtKind::No { .. } => {
7824 Ok(PerlValue::UNDEF)
7826 }
7827 StmtKind::Return(val) => {
7828 let v = if let Some(e) = val {
7829 self.eval_expr_ctx(e, self.wantarray_kind)?
7833 } else {
7834 PerlValue::UNDEF
7835 };
7836 Err(Flow::Return(v).into())
7837 }
7838 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
7839 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
7840 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
7841 StmtKind::Block(block) => self.exec_block(block),
7842 StmtKind::Begin(_)
7843 | StmtKind::UnitCheck(_)
7844 | StmtKind::Check(_)
7845 | StmtKind::Init(_)
7846 | StmtKind::End(_) => Ok(PerlValue::UNDEF),
7847 StmtKind::Empty => Ok(PerlValue::UNDEF),
7848 StmtKind::Goto { target } => {
7849 if let ExprKind::SubroutineRef(name) = &target.kind {
7851 return Err(Flow::GotoSub(name.clone()).into());
7852 }
7853 Err(PerlError::runtime("goto reached outside goto-aware block", stmt.line).into())
7854 }
7855 StmtKind::EvalTimeout { timeout, body } => {
7856 let secs = self.eval_expr(timeout)?.to_number();
7857 self.eval_timeout_block(body, secs, stmt.line)
7858 }
7859 StmtKind::Tie {
7860 target,
7861 class,
7862 args,
7863 } => {
7864 let kind = match &target {
7865 TieTarget::Scalar(_) => 0u8,
7866 TieTarget::Array(_) => 1u8,
7867 TieTarget::Hash(_) => 2u8,
7868 };
7869 let name = match &target {
7870 TieTarget::Scalar(s) => s.as_str(),
7871 TieTarget::Array(a) => a.as_str(),
7872 TieTarget::Hash(h) => h.as_str(),
7873 };
7874 let mut vals = vec![self.eval_expr(class)?];
7875 for a in args {
7876 vals.push(self.eval_expr(a)?);
7877 }
7878 self.tie_execute(kind, name, vals, stmt.line)
7879 .map_err(Into::into)
7880 }
7881 StmtKind::TryCatch {
7882 try_block,
7883 catch_var,
7884 catch_block,
7885 finally_block,
7886 } => match self.exec_block(try_block) {
7887 Ok(v) => {
7888 if let Some(fb) = finally_block {
7889 self.exec_block(fb)?;
7890 }
7891 Ok(v)
7892 }
7893 Err(FlowOrError::Error(e)) => {
7894 if matches!(e.kind, ErrorKind::Exit(_)) {
7895 return Err(FlowOrError::Error(e));
7896 }
7897 self.scope_push_hook();
7898 self.scope
7899 .declare_scalar(catch_var, PerlValue::string(e.to_string()));
7900 self.english_note_lexical_scalar(catch_var);
7901 let r = self.exec_block(catch_block);
7902 self.scope_pop_hook();
7903 if let Some(fb) = finally_block {
7904 self.exec_block(fb)?;
7905 }
7906 r
7907 }
7908 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
7909 },
7910 StmtKind::Given { topic, body } => self.exec_given(topic, body),
7911 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(PerlError::runtime(
7912 "when/default may only appear inside a given block",
7913 stmt.line,
7914 )
7915 .into()),
7916 StmtKind::FormatDecl { .. } => {
7917 Ok(PerlValue::UNDEF)
7919 }
7920 StmtKind::Continue(block) => self.exec_block_smart(block),
7921 }
7922 }
7923
7924 #[inline]
7925 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
7926 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
7927 }
7928
7929 pub(crate) fn scalar_compound_assign_scalar_target(
7933 &mut self,
7934 name: &str,
7935 op: BinOp,
7936 rhs: PerlValue,
7937 ) -> Result<PerlValue, PerlError> {
7938 if op == BinOp::Concat {
7939 return self.scope.scalar_concat_inplace(name, &rhs);
7940 }
7941 Ok(self
7942 .scope
7943 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs)))
7944 }
7945
7946 fn compound_scalar_binop(old: &PerlValue, op: BinOp, rhs: &PerlValue) -> PerlValue {
7947 match op {
7948 BinOp::Add => {
7949 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7950 PerlValue::integer(a.wrapping_add(b))
7951 } else {
7952 PerlValue::float(old.to_number() + rhs.to_number())
7953 }
7954 }
7955 BinOp::Sub => {
7956 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7957 PerlValue::integer(a.wrapping_sub(b))
7958 } else {
7959 PerlValue::float(old.to_number() - rhs.to_number())
7960 }
7961 }
7962 BinOp::Mul => {
7963 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7964 PerlValue::integer(a.wrapping_mul(b))
7965 } else {
7966 PerlValue::float(old.to_number() * rhs.to_number())
7967 }
7968 }
7969 BinOp::BitAnd => {
7970 if let Some(s) = crate::value::set_intersection(old, rhs) {
7971 s
7972 } else {
7973 PerlValue::integer(old.to_int() & rhs.to_int())
7974 }
7975 }
7976 BinOp::BitOr => {
7977 if let Some(s) = crate::value::set_union(old, rhs) {
7978 s
7979 } else {
7980 PerlValue::integer(old.to_int() | rhs.to_int())
7981 }
7982 }
7983 BinOp::BitXor => PerlValue::integer(old.to_int() ^ rhs.to_int()),
7984 BinOp::ShiftLeft => PerlValue::integer(old.to_int() << rhs.to_int()),
7985 BinOp::ShiftRight => PerlValue::integer(old.to_int() >> rhs.to_int()),
7986 BinOp::Div => PerlValue::float(old.to_number() / rhs.to_number()),
7987 BinOp::Mod => PerlValue::float(old.to_number() % rhs.to_number()),
7988 BinOp::Pow => PerlValue::float(old.to_number().powf(rhs.to_number())),
7989 BinOp::LogOr => {
7990 if old.is_true() {
7991 old.clone()
7992 } else {
7993 rhs.clone()
7994 }
7995 }
7996 BinOp::DefinedOr => {
7997 if !old.is_undef() {
7998 old.clone()
7999 } else {
8000 rhs.clone()
8001 }
8002 }
8003 BinOp::LogAnd => {
8004 if old.is_true() {
8005 rhs.clone()
8006 } else {
8007 old.clone()
8008 }
8009 }
8010 _ => PerlValue::float(old.to_number() + rhs.to_number()),
8011 }
8012 }
8013
8014 fn eval_hash_slice_key_components(
8018 &mut self,
8019 key_expr: &Expr,
8020 ) -> Result<Vec<String>, FlowOrError> {
8021 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
8022 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8023 } else {
8024 self.eval_expr(key_expr)?
8025 };
8026 if let Some(vv) = v.as_array_vec() {
8027 Ok(vv.iter().map(|x| x.to_string()).collect())
8028 } else {
8029 Ok(vec![v.to_string()])
8030 }
8031 }
8032
8033 pub(crate) fn symbolic_deref(
8035 &mut self,
8036 val: PerlValue,
8037 kind: Sigil,
8038 line: usize,
8039 ) -> ExecResult {
8040 match kind {
8041 Sigil::Scalar => {
8042 if let Some(name) = val.as_scalar_binding_name() {
8043 return Ok(self.get_special_var(&name));
8044 }
8045 if let Some(r) = val.as_scalar_ref() {
8046 return Ok(r.read().clone());
8047 }
8048 if let Some(r) = val.as_array_ref() {
8050 return Ok(PerlValue::array(r.read().clone()));
8051 }
8052 if let Some(name) = val.as_array_binding_name() {
8053 return Ok(PerlValue::array(self.scope.get_array(&name)));
8054 }
8055 if let Some(r) = val.as_hash_ref() {
8056 return Ok(PerlValue::hash(r.read().clone()));
8057 }
8058 if let Some(name) = val.as_hash_binding_name() {
8059 self.touch_env_hash(&name);
8060 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
8061 }
8062 if let Some(s) = val.as_str() {
8063 if self.strict_refs {
8064 return Err(PerlError::runtime(
8065 format!(
8066 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
8067 s
8068 ),
8069 line,
8070 )
8071 .into());
8072 }
8073 return Ok(self.get_special_var(&s));
8074 }
8075 Err(PerlError::runtime("Can't dereference non-reference as scalar", line).into())
8076 }
8077 Sigil::Array => {
8078 if let Some(r) = val.as_array_ref() {
8079 return Ok(PerlValue::array(r.read().clone()));
8080 }
8081 if let Some(name) = val.as_array_binding_name() {
8082 return Ok(PerlValue::array(self.scope.get_array(&name)));
8083 }
8084 if let Some(s) = val.as_str() {
8085 if self.strict_refs {
8086 return Err(PerlError::runtime(
8087 format!(
8088 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8089 s
8090 ),
8091 line,
8092 )
8093 .into());
8094 }
8095 return Ok(PerlValue::array(self.scope.get_array(&s)));
8096 }
8097 Err(PerlError::runtime("Can't dereference non-reference as array", line).into())
8098 }
8099 Sigil::Hash => {
8100 if let Some(r) = val.as_hash_ref() {
8101 return Ok(PerlValue::hash(r.read().clone()));
8102 }
8103 if let Some(name) = val.as_hash_binding_name() {
8104 self.touch_env_hash(&name);
8105 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
8106 }
8107 if let Some(s) = val.as_str() {
8108 if self.strict_refs {
8109 return Err(PerlError::runtime(
8110 format!(
8111 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8112 s
8113 ),
8114 line,
8115 )
8116 .into());
8117 }
8118 self.touch_env_hash(&s);
8119 return Ok(PerlValue::hash(self.scope.get_hash(&s)));
8120 }
8121 Err(PerlError::runtime("Can't dereference non-reference as hash", line).into())
8122 }
8123 Sigil::Typeglob => {
8124 if let Some(s) = val.as_str() {
8125 return Ok(PerlValue::string(self.resolve_io_handle_name(&s)));
8126 }
8127 Err(PerlError::runtime("Can't dereference non-reference as typeglob", line).into())
8128 }
8129 }
8130 }
8131
8132 #[inline]
8135 pub(crate) fn peel_array_ref_for_list_join(&self, v: PerlValue) -> PerlValue {
8136 if let Some(r) = v.as_array_ref() {
8137 return PerlValue::array(r.read().clone());
8138 }
8139 v
8140 }
8141
8142 pub(crate) fn make_array_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
8144 if let Some(a) = val.as_array_ref() {
8145 return Ok(PerlValue::array_ref(Arc::clone(&a)));
8146 }
8147 if let Some(name) = val.as_array_binding_name() {
8148 return Ok(PerlValue::array_binding_ref(name));
8149 }
8150 if let Some(s) = val.as_str() {
8151 if self.strict_refs {
8152 return Err(PerlError::runtime(
8153 format!(
8154 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8155 s
8156 ),
8157 line,
8158 )
8159 .into());
8160 }
8161 return Ok(PerlValue::array_binding_ref(s.to_string()));
8162 }
8163 if let Some(r) = val.as_scalar_ref() {
8164 let inner = r.read().clone();
8165 return self.make_array_ref_alias(inner, line);
8166 }
8167 Err(PerlError::runtime("Can't make array reference from value", line).into())
8168 }
8169
8170 pub(crate) fn make_hash_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
8172 if let Some(h) = val.as_hash_ref() {
8173 return Ok(PerlValue::hash_ref(Arc::clone(&h)));
8174 }
8175 if let Some(name) = val.as_hash_binding_name() {
8176 return Ok(PerlValue::hash_binding_ref(name));
8177 }
8178 if let Some(s) = val.as_str() {
8179 if self.strict_refs {
8180 return Err(PerlError::runtime(
8181 format!(
8182 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8183 s
8184 ),
8185 line,
8186 )
8187 .into());
8188 }
8189 return Ok(PerlValue::hash_binding_ref(s.to_string()));
8190 }
8191 if let Some(r) = val.as_scalar_ref() {
8192 let inner = r.read().clone();
8193 return self.make_hash_ref_alias(inner, line);
8194 }
8195 Err(PerlError::runtime("Can't make hash reference from value", line).into())
8196 }
8197
8198 pub(crate) fn process_case_escapes(s: &str) -> String {
8201 if !s.contains('\\') {
8203 return s.to_string();
8204 }
8205 let mut result = String::with_capacity(s.len());
8206 let mut chars = s.chars().peekable();
8207 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
8211 if c == '\\' {
8212 match chars.peek() {
8213 Some(&'U') => {
8214 chars.next();
8215 mode = Some('U');
8216 continue;
8217 }
8218 Some(&'L') => {
8219 chars.next();
8220 mode = Some('L');
8221 continue;
8222 }
8223 Some(&'Q') => {
8224 chars.next();
8225 mode = Some('Q');
8226 continue;
8227 }
8228 Some(&'E') => {
8229 chars.next();
8230 mode = None;
8231 next_char_mod = None;
8232 continue;
8233 }
8234 Some(&'u') => {
8235 chars.next();
8236 next_char_mod = Some('u');
8237 continue;
8238 }
8239 Some(&'l') => {
8240 chars.next();
8241 next_char_mod = Some('l');
8242 continue;
8243 }
8244 _ => {}
8245 }
8246 }
8247
8248 let ch = c;
8249
8250 if let Some(m) = next_char_mod.take() {
8252 let transformed = match m {
8253 'u' => ch.to_uppercase().next().unwrap_or(ch),
8254 'l' => ch.to_lowercase().next().unwrap_or(ch),
8255 _ => ch,
8256 };
8257 result.push(transformed);
8258 } else {
8259 match mode {
8261 Some('U') => {
8262 for uc in ch.to_uppercase() {
8263 result.push(uc);
8264 }
8265 }
8266 Some('L') => {
8267 for lc in ch.to_lowercase() {
8268 result.push(lc);
8269 }
8270 }
8271 Some('Q') => {
8272 if !ch.is_ascii_alphanumeric() && ch != '_' {
8273 result.push('\\');
8274 }
8275 result.push(ch);
8276 }
8277 None | Some(_) => {
8278 result.push(ch);
8279 }
8280 }
8281 }
8282 }
8283 result
8284 }
8285
8286 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
8287 let line = expr.line;
8288 match &expr.kind {
8289 ExprKind::Integer(n) => Ok(PerlValue::integer(*n)),
8290 ExprKind::Float(f) => Ok(PerlValue::float(*f)),
8291 ExprKind::String(s) => {
8292 let processed = Self::process_case_escapes(s);
8293 Ok(PerlValue::string(processed))
8294 }
8295 ExprKind::Bareword(s) => {
8296 if s == "__PACKAGE__" {
8297 return Ok(PerlValue::string(self.current_package()));
8298 }
8299 if let Some(sub) = self.resolve_sub_by_name(s) {
8300 return self.call_sub(&sub, vec![], ctx, line);
8301 }
8302 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
8304 return r.map_err(Into::into);
8305 }
8306 Ok(PerlValue::string(s.clone()))
8307 }
8308 ExprKind::Undef => Ok(PerlValue::UNDEF),
8309 ExprKind::MagicConst(MagicConstKind::File) => Ok(PerlValue::string(self.file.clone())),
8310 ExprKind::MagicConst(MagicConstKind::Line) => Ok(PerlValue::integer(expr.line as i64)),
8311 ExprKind::MagicConst(MagicConstKind::Sub) => {
8312 if let Some(sub) = self.current_sub_stack.last().cloned() {
8313 Ok(PerlValue::code_ref(sub))
8314 } else {
8315 Ok(PerlValue::UNDEF)
8316 }
8317 }
8318 ExprKind::Regex(pattern, flags) => {
8319 if ctx == WantarrayCtx::Void {
8320 let topic = self.scope.get_scalar("_");
8322 let s = topic.to_string();
8323 self.regex_match_execute(s, pattern, flags, false, "_", line)
8324 } else {
8325 let re = self.compile_regex(pattern, flags, line)?;
8326 Ok(PerlValue::regex(re, pattern.clone(), flags.clone()))
8327 }
8328 }
8329 ExprKind::QW(words) => Ok(PerlValue::array(
8330 words.iter().map(|w| PerlValue::string(w.clone())).collect(),
8331 )),
8332
8333 ExprKind::InterpolatedString(parts) => {
8335 let mut raw_result = String::new();
8336 for part in parts {
8337 match part {
8338 StringPart::Literal(s) => raw_result.push_str(s),
8339 StringPart::ScalarVar(name) => {
8340 self.check_strict_scalar_var(name, line)?;
8341 let val = self.get_special_var(name);
8342 let s = self.stringify_value(val, line)?;
8343 raw_result.push_str(&s);
8344 }
8345 StringPart::ArrayVar(name) => {
8346 self.check_strict_array_var(name, line)?;
8347 let aname = self.stash_array_name_for_package(name);
8348 let arr = self.scope.get_array(&aname);
8349 let mut parts = Vec::with_capacity(arr.len());
8350 for v in &arr {
8351 parts.push(self.stringify_value(v.clone(), line)?);
8352 }
8353 let sep = self.list_separator.clone();
8354 raw_result.push_str(&parts.join(&sep));
8355 }
8356 StringPart::Expr(e) => {
8357 if let ExprKind::ArraySlice { array, .. } = &e.kind {
8358 self.check_strict_array_var(array, line)?;
8359 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8360 let val = self.peel_array_ref_for_list_join(val);
8361 let list = val.to_list();
8362 let sep = self.list_separator.clone();
8363 let mut parts = Vec::with_capacity(list.len());
8364 for v in list {
8365 parts.push(self.stringify_value(v, line)?);
8366 }
8367 raw_result.push_str(&parts.join(&sep));
8368 } else if let ExprKind::Deref {
8369 kind: Sigil::Array, ..
8370 } = &e.kind
8371 {
8372 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8373 let val = self.peel_array_ref_for_list_join(val);
8374 let list = val.to_list();
8375 let sep = self.list_separator.clone();
8376 let mut parts = Vec::with_capacity(list.len());
8377 for v in list {
8378 parts.push(self.stringify_value(v, line)?);
8379 }
8380 raw_result.push_str(&parts.join(&sep));
8381 } else {
8382 let val = self.eval_expr(e)?;
8383 let s = self.stringify_value(val, line)?;
8384 raw_result.push_str(&s);
8385 }
8386 }
8387 }
8388 }
8389 let result = Self::process_case_escapes(&raw_result);
8390 Ok(PerlValue::string(result))
8391 }
8392
8393 ExprKind::ScalarVar(name) => {
8395 self.check_strict_scalar_var(name, line)?;
8396 let stor = self.tree_scalar_storage_name(name);
8397 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
8398 let class = obj
8399 .as_blessed_ref()
8400 .map(|b| b.class.clone())
8401 .unwrap_or_default();
8402 let full = format!("{}::FETCH", class);
8403 if let Some(sub) = self.subs.get(&full).cloned() {
8404 return self.call_sub(&sub, vec![obj], ctx, line);
8405 }
8406 }
8407 Ok(self.get_special_var(&stor))
8408 }
8409 ExprKind::ArrayVar(name) => {
8410 self.check_strict_array_var(name, line)?;
8411 let aname = self.stash_array_name_for_package(name);
8412 let arr = self.scope.get_array(&aname);
8413 if ctx == WantarrayCtx::List {
8414 Ok(PerlValue::array(arr))
8415 } else {
8416 Ok(PerlValue::integer(arr.len() as i64))
8417 }
8418 }
8419 ExprKind::HashVar(name) => {
8420 self.check_strict_hash_var(name, line)?;
8421 self.touch_env_hash(name);
8422 let h = self.scope.get_hash(name);
8423 let pv = PerlValue::hash(h);
8424 if ctx == WantarrayCtx::List {
8425 Ok(pv)
8426 } else {
8427 Ok(pv.scalar_context())
8428 }
8429 }
8430 ExprKind::Typeglob(name) => {
8431 let n = self.resolve_io_handle_name(name);
8432 Ok(PerlValue::string(n))
8433 }
8434 ExprKind::TypeglobExpr(e) => {
8435 let name = self.eval_expr(e)?.to_string();
8436 let n = self.resolve_io_handle_name(&name);
8437 Ok(PerlValue::string(n))
8438 }
8439 ExprKind::ArrayElement { array, index } => {
8440 self.check_strict_array_var(array, line)?;
8441 let idx = self.eval_expr(index)?.to_int();
8442 let aname = self.stash_array_name_for_package(array);
8443 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
8444 let class = obj
8445 .as_blessed_ref()
8446 .map(|b| b.class.clone())
8447 .unwrap_or_default();
8448 let full = format!("{}::FETCH", class);
8449 if let Some(sub) = self.subs.get(&full).cloned() {
8450 let arg_vals = vec![obj, PerlValue::integer(idx)];
8451 return self.call_sub(&sub, arg_vals, ctx, line);
8452 }
8453 }
8454 Ok(self.scope.get_array_element(&aname, idx))
8455 }
8456 ExprKind::HashElement { hash, key } => {
8457 self.check_strict_hash_var(hash, line)?;
8458 let k = self.eval_expr(key)?.to_string();
8459 self.touch_env_hash(hash);
8460 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
8461 let class = obj
8462 .as_blessed_ref()
8463 .map(|b| b.class.clone())
8464 .unwrap_or_default();
8465 let full = format!("{}::FETCH", class);
8466 if let Some(sub) = self.subs.get(&full).cloned() {
8467 let arg_vals = vec![obj, PerlValue::string(k)];
8468 return self.call_sub(&sub, arg_vals, ctx, line);
8469 }
8470 }
8471 Ok(self.scope.get_hash_element(hash, &k))
8472 }
8473 ExprKind::ArraySlice { array, indices } => {
8474 self.check_strict_array_var(array, line)?;
8475 let aname = self.stash_array_name_for_package(array);
8476 let flat = self.flatten_array_slice_index_specs(indices)?;
8477 let mut result = Vec::with_capacity(flat.len());
8478 for idx in flat {
8479 result.push(self.scope.get_array_element(&aname, idx));
8480 }
8481 Ok(PerlValue::array(result))
8482 }
8483 ExprKind::HashSlice { hash, keys } => {
8484 self.check_strict_hash_var(hash, line)?;
8485 self.touch_env_hash(hash);
8486 let mut result = Vec::new();
8487 for key_expr in keys {
8488 for k in self.eval_hash_slice_key_components(key_expr)? {
8489 result.push(self.scope.get_hash_element(hash, &k));
8490 }
8491 }
8492 Ok(PerlValue::array(result))
8493 }
8494 ExprKind::HashSliceDeref { container, keys } => {
8495 let hv = self.eval_expr(container)?;
8496 let mut key_vals = Vec::with_capacity(keys.len());
8497 for key_expr in keys {
8498 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
8499 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8500 } else {
8501 self.eval_expr(key_expr)?
8502 };
8503 key_vals.push(v);
8504 }
8505 self.hash_slice_deref_values(&hv, &key_vals, line)
8506 }
8507 ExprKind::AnonymousListSlice { source, indices } => {
8508 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
8509 let items = list_val.to_list();
8510 let flat = self.flatten_array_slice_index_specs(indices)?;
8511 let mut out = Vec::with_capacity(flat.len());
8512 for idx in flat {
8513 let i = if idx < 0 {
8514 (items.len() as i64 + idx) as usize
8515 } else {
8516 idx as usize
8517 };
8518 out.push(items.get(i).cloned().unwrap_or(PerlValue::UNDEF));
8519 }
8520 let arr = PerlValue::array(out);
8521 if ctx != WantarrayCtx::List {
8522 let v = arr.to_list();
8523 Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF))
8524 } else {
8525 Ok(arr)
8526 }
8527 }
8528
8529 ExprKind::ScalarRef(inner) => match &inner.kind {
8531 ExprKind::ScalarVar(name) => Ok(PerlValue::scalar_binding_ref(name.clone())),
8532 ExprKind::ArrayVar(name) => {
8533 self.check_strict_array_var(name, line)?;
8534 let aname = self.stash_array_name_for_package(name);
8535 Ok(PerlValue::array_binding_ref(aname))
8536 }
8537 ExprKind::HashVar(name) => {
8538 self.check_strict_hash_var(name, line)?;
8539 Ok(PerlValue::hash_binding_ref(name.clone()))
8540 }
8541 ExprKind::Deref {
8542 expr: e,
8543 kind: Sigil::Array,
8544 } => {
8545 let v = self.eval_expr(e)?;
8546 self.make_array_ref_alias(v, line)
8547 }
8548 ExprKind::Deref {
8549 expr: e,
8550 kind: Sigil::Hash,
8551 } => {
8552 let v = self.eval_expr(e)?;
8553 self.make_hash_ref_alias(v, line)
8554 }
8555 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
8556 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8557 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8558 }
8559 ExprKind::HashSliceDeref { .. } => {
8560 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8561 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8562 }
8563 _ => {
8564 let val = self.eval_expr(inner)?;
8565 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8566 }
8567 },
8568 ExprKind::ArrayRef(elems) => {
8569 let mut arr = Vec::with_capacity(elems.len());
8573 for e in elems {
8574 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8575 if let Some(vec) = v.as_array_vec() {
8576 arr.extend(vec);
8577 } else {
8578 arr.push(v);
8579 }
8580 }
8581 Ok(PerlValue::array_ref(Arc::new(RwLock::new(arr))))
8582 }
8583 ExprKind::HashRef(pairs) => {
8584 let mut map = IndexMap::new();
8587 for (k, v) in pairs {
8588 let key_str = self.eval_expr(k)?.to_string();
8589 if key_str == "__HASH_SPREAD__" {
8590 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8592 let items = spread.to_list();
8593 let mut i = 0;
8594 while i + 1 < items.len() {
8595 map.insert(items[i].to_string(), items[i + 1].clone());
8596 i += 2;
8597 }
8598 } else {
8599 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8600 map.insert(key_str, val);
8601 }
8602 }
8603 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map))))
8604 }
8605 ExprKind::CodeRef { params, body } => {
8606 let captured = self.scope.capture();
8607 Ok(PerlValue::code_ref(Arc::new(PerlSub {
8608 name: "__ANON__".to_string(),
8609 params: params.clone(),
8610 body: body.clone(),
8611 closure_env: Some(captured),
8612 prototype: None,
8613 fib_like: None,
8614 })))
8615 }
8616 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
8617 ExprKind::SubroutineCodeRef(name) => {
8618 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
8619 PerlError::runtime(self.undefined_subroutine_resolve_message(name), line)
8620 })?;
8621 Ok(PerlValue::code_ref(sub))
8622 }
8623 ExprKind::DynamicSubCodeRef(expr) => {
8624 let name = self.eval_expr(expr)?.to_string();
8625 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
8626 PerlError::runtime(self.undefined_subroutine_resolve_message(&name), line)
8627 })?;
8628 Ok(PerlValue::code_ref(sub))
8629 }
8630 ExprKind::Deref { expr, kind } => {
8631 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
8632 let val = self.eval_expr(expr)?;
8633 let n = self.array_deref_len(val, line)?;
8634 return Ok(PerlValue::integer(n));
8635 }
8636 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
8637 let val = self.eval_expr(expr)?;
8638 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
8639 return Ok(h.scalar_context());
8640 }
8641 let val = self.eval_expr(expr)?;
8642 self.symbolic_deref(val, *kind, line)
8643 }
8644 ExprKind::ArrowDeref { expr, index, kind } => {
8645 match kind {
8646 DerefKind::Array => {
8647 let container = self.eval_arrow_array_base(expr, line)?;
8648 if let ExprKind::List(indices) = &index.kind {
8649 let mut out = Vec::with_capacity(indices.len());
8650 for ix in indices {
8651 let idx = self.eval_expr(ix)?.to_int();
8652 out.push(self.read_arrow_array_element(
8653 container.clone(),
8654 idx,
8655 line,
8656 )?);
8657 }
8658 let arr = PerlValue::array(out);
8659 if ctx != WantarrayCtx::List {
8660 let v = arr.to_list();
8661 return Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF));
8662 }
8663 return Ok(arr);
8664 }
8665 let idx = self.eval_expr(index)?.to_int();
8666 self.read_arrow_array_element(container, idx, line)
8667 }
8668 DerefKind::Hash => {
8669 let val = self.eval_arrow_hash_base(expr, line)?;
8670 let key = self.eval_expr(index)?.to_string();
8671 self.read_arrow_hash_element(val, key.as_str(), line)
8672 }
8673 DerefKind::Call => {
8674 let val = self.eval_expr(expr)?;
8676 if let ExprKind::List(ref arg_exprs) = index.kind {
8677 let mut args = Vec::new();
8678 for a in arg_exprs {
8679 args.push(self.eval_expr(a)?);
8680 }
8681 if let Some(sub) = val.as_code_ref() {
8682 return self.call_sub(&sub, args, ctx, line);
8683 }
8684 Err(PerlError::runtime("Not a code reference", line).into())
8685 } else {
8686 Err(PerlError::runtime("Invalid call deref", line).into())
8687 }
8688 }
8689 }
8690 }
8691
8692 ExprKind::BinOp { left, op, right } => {
8694 match op {
8696 BinOp::BindMatch => {
8697 let lv = self.eval_expr(left)?;
8698 let rv = self.eval_expr(right)?;
8699 let s = lv.to_string();
8700 let pat = rv.to_string();
8701 return self.regex_match_execute(s, &pat, "", false, "_", line);
8702 }
8703 BinOp::BindNotMatch => {
8704 let lv = self.eval_expr(left)?;
8705 let rv = self.eval_expr(right)?;
8706 let s = lv.to_string();
8707 let pat = rv.to_string();
8708 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
8709 return Ok(PerlValue::integer(if m.is_true() { 0 } else { 1 }));
8710 }
8711 BinOp::LogAnd | BinOp::LogAndWord => {
8712 match &left.kind {
8713 ExprKind::Regex(_, _) => {
8714 if !self.eval_boolean_rvalue_condition(left)? {
8715 return Ok(PerlValue::string(String::new()));
8716 }
8717 }
8718 _ => {
8719 let lv = self.eval_expr(left)?;
8720 if !lv.is_true() {
8721 return Ok(lv);
8722 }
8723 }
8724 }
8725 return match &right.kind {
8726 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8727 if self.eval_boolean_rvalue_condition(right)? {
8728 1
8729 } else {
8730 0
8731 },
8732 )),
8733 _ => self.eval_expr(right),
8734 };
8735 }
8736 BinOp::LogOr | BinOp::LogOrWord => {
8737 match &left.kind {
8738 ExprKind::Regex(_, _) => {
8739 if self.eval_boolean_rvalue_condition(left)? {
8740 return Ok(PerlValue::integer(1));
8741 }
8742 }
8743 _ => {
8744 let lv = self.eval_expr(left)?;
8745 if lv.is_true() {
8746 return Ok(lv);
8747 }
8748 }
8749 }
8750 return match &right.kind {
8751 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8752 if self.eval_boolean_rvalue_condition(right)? {
8753 1
8754 } else {
8755 0
8756 },
8757 )),
8758 _ => self.eval_expr(right),
8759 };
8760 }
8761 BinOp::DefinedOr => {
8762 let lv = self.eval_expr(left)?;
8763 if !lv.is_undef() {
8764 return Ok(lv);
8765 }
8766 return self.eval_expr(right);
8767 }
8768 _ => {}
8769 }
8770 let lv = self.eval_expr(left)?;
8771 let rv = self.eval_expr(right)?;
8772 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
8773 return r;
8774 }
8775 self.eval_binop(*op, &lv, &rv, line)
8776 }
8777
8778 ExprKind::UnaryOp { op, expr } => match op {
8780 UnaryOp::PreIncrement => {
8781 if let ExprKind::ScalarVar(name) = &expr.kind {
8782 self.check_strict_scalar_var(name, line)?;
8783 let n = self.english_scalar_name(name);
8784 return Ok(self
8785 .scope
8786 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() + 1)));
8787 }
8788 if let ExprKind::Deref { kind, .. } = &expr.kind {
8789 if matches!(kind, Sigil::Array | Sigil::Hash) {
8790 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8791 *kind, true, true, line,
8792 ));
8793 }
8794 }
8795 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8796 let href = self.eval_expr(container)?;
8797 let mut key_vals = Vec::with_capacity(keys.len());
8798 for key_expr in keys {
8799 key_vals.push(self.eval_expr(key_expr)?);
8800 }
8801 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
8802 }
8803 if let ExprKind::ArrowDeref {
8804 expr: arr_expr,
8805 index,
8806 kind: DerefKind::Array,
8807 } = &expr.kind
8808 {
8809 if let ExprKind::List(indices) = &index.kind {
8810 let container = self.eval_arrow_array_base(arr_expr, line)?;
8811 let mut idxs = Vec::with_capacity(indices.len());
8812 for ix in indices {
8813 idxs.push(self.eval_expr(ix)?.to_int());
8814 }
8815 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
8816 }
8817 }
8818 let val = self.eval_expr(expr)?;
8819 let new_val = PerlValue::integer(val.to_int() + 1);
8820 self.assign_value(expr, new_val.clone())?;
8821 Ok(new_val)
8822 }
8823 UnaryOp::PreDecrement => {
8824 if let ExprKind::ScalarVar(name) = &expr.kind {
8825 self.check_strict_scalar_var(name, line)?;
8826 let n = self.english_scalar_name(name);
8827 return Ok(self
8828 .scope
8829 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() - 1)));
8830 }
8831 if let ExprKind::Deref { kind, .. } = &expr.kind {
8832 if matches!(kind, Sigil::Array | Sigil::Hash) {
8833 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8834 *kind, true, false, line,
8835 ));
8836 }
8837 }
8838 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8839 let href = self.eval_expr(container)?;
8840 let mut key_vals = Vec::with_capacity(keys.len());
8841 for key_expr in keys {
8842 key_vals.push(self.eval_expr(key_expr)?);
8843 }
8844 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
8845 }
8846 if let ExprKind::ArrowDeref {
8847 expr: arr_expr,
8848 index,
8849 kind: DerefKind::Array,
8850 } = &expr.kind
8851 {
8852 if let ExprKind::List(indices) = &index.kind {
8853 let container = self.eval_arrow_array_base(arr_expr, line)?;
8854 let mut idxs = Vec::with_capacity(indices.len());
8855 for ix in indices {
8856 idxs.push(self.eval_expr(ix)?.to_int());
8857 }
8858 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
8859 }
8860 }
8861 let val = self.eval_expr(expr)?;
8862 let new_val = PerlValue::integer(val.to_int() - 1);
8863 self.assign_value(expr, new_val.clone())?;
8864 Ok(new_val)
8865 }
8866 _ => {
8867 match op {
8868 UnaryOp::LogNot | UnaryOp::LogNotWord => {
8869 if let ExprKind::Regex(pattern, flags) = &expr.kind {
8870 let topic = self.scope.get_scalar("_");
8871 let rl = expr.line;
8872 let s = topic.to_string();
8873 let v =
8874 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
8875 return Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }));
8876 }
8877 }
8878 _ => {}
8879 }
8880 let val = self.eval_expr(expr)?;
8881 match op {
8882 UnaryOp::Negate => {
8883 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
8884 return r;
8885 }
8886 if let Some(n) = val.as_integer() {
8887 Ok(PerlValue::integer(-n))
8888 } else {
8889 Ok(PerlValue::float(-val.to_number()))
8890 }
8891 }
8892 UnaryOp::LogNot => {
8893 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8894 let pv = r?;
8895 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8896 }
8897 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8898 }
8899 UnaryOp::BitNot => Ok(PerlValue::integer(!val.to_int())),
8900 UnaryOp::LogNotWord => {
8901 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8902 let pv = r?;
8903 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8904 }
8905 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8906 }
8907 UnaryOp::Ref => {
8908 if let ExprKind::ScalarVar(name) = &expr.kind {
8909 return Ok(PerlValue::scalar_binding_ref(name.clone()));
8910 }
8911 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8912 }
8913 _ => unreachable!(),
8914 }
8915 }
8916 },
8917
8918 ExprKind::PostfixOp { expr, op } => {
8919 if let ExprKind::ScalarVar(name) = &expr.kind {
8922 self.check_strict_scalar_var(name, line)?;
8923 let n = self.english_scalar_name(name);
8924 let f: fn(&PerlValue) -> PerlValue = match op {
8925 PostfixOp::Increment => |v| PerlValue::integer(v.to_int() + 1),
8926 PostfixOp::Decrement => |v| PerlValue::integer(v.to_int() - 1),
8927 };
8928 return Ok(self.scope.atomic_mutate_post(n, f));
8929 }
8930 if let ExprKind::Deref { kind, .. } = &expr.kind {
8931 if matches!(kind, Sigil::Array | Sigil::Hash) {
8932 let is_inc = matches!(op, PostfixOp::Increment);
8933 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8934 *kind, false, is_inc, line,
8935 ));
8936 }
8937 }
8938 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8939 let href = self.eval_expr(container)?;
8940 let mut key_vals = Vec::with_capacity(keys.len());
8941 for key_expr in keys {
8942 key_vals.push(self.eval_expr(key_expr)?);
8943 }
8944 let kind_byte = match op {
8945 PostfixOp::Increment => 2u8,
8946 PostfixOp::Decrement => 3u8,
8947 };
8948 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
8949 }
8950 if let ExprKind::ArrowDeref {
8951 expr: arr_expr,
8952 index,
8953 kind: DerefKind::Array,
8954 } = &expr.kind
8955 {
8956 if let ExprKind::List(indices) = &index.kind {
8957 let container = self.eval_arrow_array_base(arr_expr, line)?;
8958 let mut idxs = Vec::with_capacity(indices.len());
8959 for ix in indices {
8960 idxs.push(self.eval_expr(ix)?.to_int());
8961 }
8962 let kind_byte = match op {
8963 PostfixOp::Increment => 2u8,
8964 PostfixOp::Decrement => 3u8,
8965 };
8966 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
8967 }
8968 }
8969 let val = self.eval_expr(expr)?;
8970 let old = val.clone();
8971 let new_val = match op {
8972 PostfixOp::Increment => PerlValue::integer(val.to_int() + 1),
8973 PostfixOp::Decrement => PerlValue::integer(val.to_int() - 1),
8974 };
8975 self.assign_value(expr, new_val)?;
8976 Ok(old)
8977 }
8978
8979 ExprKind::Assign { target, value } => {
8981 if let ExprKind::Typeglob(lhs) = &target.kind {
8982 if let ExprKind::Typeglob(rhs) = &value.kind {
8983 self.copy_typeglob_slots(lhs, rhs, line)?;
8984 return self.eval_expr(value);
8985 }
8986 }
8987 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
8988 self.assign_value(target, val.clone())?;
8989 Ok(val)
8990 }
8991 ExprKind::CompoundAssign { target, op, value } => {
8992 if let ExprKind::ScalarVar(name) = &target.kind {
8995 self.check_strict_scalar_var(name, line)?;
8996 let n = self.english_scalar_name(name);
8997 let op = *op;
8998 let rhs = match op {
8999 BinOp::LogOr => {
9000 let old = self.scope.get_scalar(n);
9001 if old.is_true() {
9002 return Ok(old);
9003 }
9004 self.eval_expr(value)?
9005 }
9006 BinOp::DefinedOr => {
9007 let old = self.scope.get_scalar(n);
9008 if !old.is_undef() {
9009 return Ok(old);
9010 }
9011 self.eval_expr(value)?
9012 }
9013 BinOp::LogAnd => {
9014 let old = self.scope.get_scalar(n);
9015 if !old.is_true() {
9016 return Ok(old);
9017 }
9018 self.eval_expr(value)?
9019 }
9020 _ => self.eval_expr(value)?,
9021 };
9022 return Ok(self.scalar_compound_assign_scalar_target(n, op, rhs)?);
9023 }
9024 let rhs = self.eval_expr(value)?;
9025 if let ExprKind::HashElement { hash, key } = &target.kind {
9027 self.check_strict_hash_var(hash, line)?;
9028 let k = self.eval_expr(key)?.to_string();
9029 let op = *op;
9030 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
9031 BinOp::Add => {
9032 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9033 PerlValue::integer(a.wrapping_add(b))
9034 } else {
9035 PerlValue::float(old.to_number() + rhs.to_number())
9036 }
9037 }
9038 BinOp::Sub => {
9039 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9040 PerlValue::integer(a.wrapping_sub(b))
9041 } else {
9042 PerlValue::float(old.to_number() - rhs.to_number())
9043 }
9044 }
9045 BinOp::Concat => {
9046 let mut s = old.to_string();
9047 rhs.append_to(&mut s);
9048 PerlValue::string(s)
9049 }
9050 _ => PerlValue::float(old.to_number() + rhs.to_number()),
9051 })?);
9052 }
9053 if let ExprKind::ArrayElement { array, index } = &target.kind {
9055 self.check_strict_array_var(array, line)?;
9056 let idx = self.eval_expr(index)?.to_int();
9057 let op = *op;
9058 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
9059 BinOp::Add => {
9060 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9061 PerlValue::integer(a.wrapping_add(b))
9062 } else {
9063 PerlValue::float(old.to_number() + rhs.to_number())
9064 }
9065 }
9066 _ => PerlValue::float(old.to_number() + rhs.to_number()),
9067 })?);
9068 }
9069 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
9070 let href = self.eval_expr(container)?;
9071 let mut key_vals = Vec::with_capacity(keys.len());
9072 for key_expr in keys {
9073 key_vals.push(self.eval_expr(key_expr)?);
9074 }
9075 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
9076 }
9077 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
9078 if let ExprKind::Deref {
9079 expr: inner,
9080 kind: Sigil::Array,
9081 } = &source.kind
9082 {
9083 let container = self.eval_arrow_array_base(inner, line)?;
9084 let idxs = self.flatten_array_slice_index_specs(indices)?;
9085 return self
9086 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
9087 }
9088 }
9089 if let ExprKind::ArrowDeref {
9090 expr: arr_expr,
9091 index,
9092 kind: DerefKind::Array,
9093 } = &target.kind
9094 {
9095 if let ExprKind::List(indices) = &index.kind {
9096 let container = self.eval_arrow_array_base(arr_expr, line)?;
9097 let mut idxs = Vec::with_capacity(indices.len());
9098 for ix in indices {
9099 idxs.push(self.eval_expr(ix)?.to_int());
9100 }
9101 return self
9102 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
9103 }
9104 }
9105 let old = self.eval_expr(target)?;
9106 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
9107 self.assign_value(target, new_val.clone())?;
9108 Ok(new_val)
9109 }
9110
9111 ExprKind::Ternary {
9113 condition,
9114 then_expr,
9115 else_expr,
9116 } => {
9117 if self.eval_boolean_rvalue_condition(condition)? {
9118 self.eval_expr(then_expr)
9119 } else {
9120 self.eval_expr(else_expr)
9121 }
9122 }
9123
9124 ExprKind::Range {
9126 from,
9127 to,
9128 exclusive,
9129 step,
9130 } => {
9131 if ctx == WantarrayCtx::List {
9132 let f = self.eval_expr(from)?;
9133 let t = self.eval_expr(to)?;
9134 if let Some(s) = step {
9135 let step_val = self.eval_expr(s)?.to_int();
9136 let from_i = f.to_int();
9137 let to_i = t.to_int();
9138 let list = if step_val == 0 {
9139 vec![]
9140 } else if step_val > 0 {
9141 (from_i..=to_i)
9142 .step_by(step_val as usize)
9143 .map(PerlValue::integer)
9144 .collect()
9145 } else {
9146 std::iter::successors(Some(from_i), |&x| {
9147 let next = x - step_val.abs();
9148 if next >= to_i {
9149 Some(next)
9150 } else {
9151 None
9152 }
9153 })
9154 .map(PerlValue::integer)
9155 .collect()
9156 };
9157 Ok(PerlValue::array(list))
9158 } else {
9159 let list = perl_list_range_expand(f, t);
9160 Ok(PerlValue::array(list))
9161 }
9162 } else {
9163 let key = std::ptr::from_ref(expr) as usize;
9164 match (&from.kind, &to.kind) {
9165 (
9166 ExprKind::Regex(left_pat, left_flags),
9167 ExprKind::Regex(right_pat, right_flags),
9168 ) => {
9169 let dot = self.scalar_flipflop_dot_line();
9170 let subject = self.scope.get_scalar("_").to_string();
9171 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9172 |e| match e {
9173 FlowOrError::Error(err) => err,
9174 FlowOrError::Flow(_) => PerlError::runtime(
9175 "unexpected flow in regex flip-flop",
9176 line,
9177 ),
9178 },
9179 )?;
9180 let right_re = self
9181 .compile_regex(right_pat, right_flags, line)
9182 .map_err(|e| match e {
9183 FlowOrError::Error(err) => err,
9184 FlowOrError::Flow(_) => PerlError::runtime(
9185 "unexpected flow in regex flip-flop",
9186 line,
9187 ),
9188 })?;
9189 let left_m = left_re.is_match(&subject);
9190 let right_m = right_re.is_match(&subject);
9191 let st = self.flip_flop_tree.entry(key).or_default();
9192 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9193 &mut st.active,
9194 &mut st.exclusive_left_line,
9195 *exclusive,
9196 dot,
9197 left_m,
9198 right_m,
9199 )))
9200 }
9201 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
9202 let dot = self.scalar_flipflop_dot_line();
9203 let subject = self.scope.get_scalar("_").to_string();
9204 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9205 |e| match e {
9206 FlowOrError::Error(err) => err,
9207 FlowOrError::Flow(_) => PerlError::runtime(
9208 "unexpected flow in regex/eof flip-flop",
9209 line,
9210 ),
9211 },
9212 )?;
9213 let left_m = left_re.is_match(&subject);
9214 let right_m = self.eof_without_arg_is_true();
9215 let st = self.flip_flop_tree.entry(key).or_default();
9216 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9217 &mut st.active,
9218 &mut st.exclusive_left_line,
9219 *exclusive,
9220 dot,
9221 left_m,
9222 right_m,
9223 )))
9224 }
9225 (
9226 ExprKind::Regex(left_pat, left_flags),
9227 ExprKind::Integer(_) | ExprKind::Float(_),
9228 ) => {
9229 let dot = self.scalar_flipflop_dot_line();
9230 let right = self.eval_expr(to)?.to_int();
9231 let subject = self.scope.get_scalar("_").to_string();
9232 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9233 |e| match e {
9234 FlowOrError::Error(err) => err,
9235 FlowOrError::Flow(_) => PerlError::runtime(
9236 "unexpected flow in regex flip-flop",
9237 line,
9238 ),
9239 },
9240 )?;
9241 let left_m = left_re.is_match(&subject);
9242 let right_m = dot == right;
9243 let st = self.flip_flop_tree.entry(key).or_default();
9244 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9245 &mut st.active,
9246 &mut st.exclusive_left_line,
9247 *exclusive,
9248 dot,
9249 left_m,
9250 right_m,
9251 )))
9252 }
9253 (ExprKind::Regex(left_pat, left_flags), _) => {
9254 if let ExprKind::Eof(Some(_)) = &to.kind {
9255 return Err(FlowOrError::Error(PerlError::runtime(
9256 "regex flip-flop with eof(HANDLE) is not supported",
9257 line,
9258 )));
9259 }
9260 let dot = self.scalar_flipflop_dot_line();
9261 let subject = self.scope.get_scalar("_").to_string();
9262 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9263 |e| match e {
9264 FlowOrError::Error(err) => err,
9265 FlowOrError::Flow(_) => PerlError::runtime(
9266 "unexpected flow in regex flip-flop",
9267 line,
9268 ),
9269 },
9270 )?;
9271 let left_m = left_re.is_match(&subject);
9272 let right_m = self.eval_boolean_rvalue_condition(to)?;
9273 let st = self.flip_flop_tree.entry(key).or_default();
9274 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9275 &mut st.active,
9276 &mut st.exclusive_left_line,
9277 *exclusive,
9278 dot,
9279 left_m,
9280 right_m,
9281 )))
9282 }
9283 _ => {
9284 let left = self.eval_expr(from)?.to_int();
9285 let right = self.eval_expr(to)?.to_int();
9286 let dot = self.scalar_flipflop_dot_line();
9287 let st = self.flip_flop_tree.entry(key).or_default();
9288 if !st.active {
9289 if dot == left {
9290 st.active = true;
9291 if *exclusive {
9292 st.exclusive_left_line = Some(dot);
9293 } else {
9294 st.exclusive_left_line = None;
9295 if dot == right {
9296 st.active = false;
9297 }
9298 }
9299 return Ok(PerlValue::integer(1));
9300 }
9301 return Ok(PerlValue::integer(0));
9302 }
9303 if let Some(ll) = st.exclusive_left_line {
9304 if dot == right && dot > ll {
9305 st.active = false;
9306 st.exclusive_left_line = None;
9307 }
9308 } else if dot == right {
9309 st.active = false;
9310 }
9311 Ok(PerlValue::integer(1))
9312 }
9313 }
9314 }
9315 }
9316
9317 ExprKind::Repeat { expr, count } => {
9319 let val = self.eval_expr(expr)?;
9320 let n = self.eval_expr(count)?.to_int().max(0) as usize;
9321 if let Some(s) = val.as_str() {
9322 Ok(PerlValue::string(s.repeat(n)))
9323 } else if let Some(a) = val.as_array_vec() {
9324 let mut result = Vec::with_capacity(a.len() * n);
9325 for _ in 0..n {
9326 result.extend(a.iter().cloned());
9327 }
9328 Ok(PerlValue::array(result))
9329 } else {
9330 Ok(PerlValue::string(val.to_string().repeat(n)))
9331 }
9332 }
9333
9334 ExprKind::MyExpr { keyword, decls } => {
9339 let stmt_kind = match keyword.as_str() {
9342 "my" => StmtKind::My(decls.clone()),
9343 "our" => StmtKind::Our(decls.clone()),
9344 "state" => StmtKind::State(decls.clone()),
9345 "local" => StmtKind::Local(decls.clone()),
9346 _ => StmtKind::My(decls.clone()),
9347 };
9348 let stmt = Statement {
9349 label: None,
9350 kind: stmt_kind,
9351 line,
9352 };
9353 self.exec_statement(&stmt)?;
9354 let first = decls.first().ok_or_else(|| {
9358 FlowOrError::Error(PerlError::runtime("MyExpr: empty decl list", line))
9359 })?;
9360 Ok(match first.sigil {
9361 Sigil::Scalar => self.scope.get_scalar(&first.name),
9362 Sigil::Array => PerlValue::array(self.scope.get_array(&first.name)),
9363 Sigil::Hash => {
9364 let h = self.scope.get_hash(&first.name);
9365 let mut flat: Vec<PerlValue> = Vec::with_capacity(h.len() * 2);
9366 for (k, v) in h {
9367 flat.push(PerlValue::string(k));
9368 flat.push(v);
9369 }
9370 PerlValue::array(flat)
9371 }
9372 Sigil::Typeglob => PerlValue::UNDEF,
9373 })
9374 }
9375
9376 ExprKind::FuncCall { name, args } => {
9378 if matches!(name.as_str(), "read" | "CORE::read") && args.len() >= 3 {
9380 let fh_val = self.eval_expr(&args[0])?;
9381 let fh = fh_val
9382 .as_io_handle_name()
9383 .unwrap_or_else(|| fh_val.to_string());
9384 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
9385 let offset = if args.len() > 3 {
9386 self.eval_expr(&args[3])?.to_int().max(0) as usize
9387 } else {
9388 0
9389 };
9390 let var_name = match &args[1].kind {
9392 ExprKind::ScalarVar(n) => n.clone(),
9393 _ => self.eval_expr(&args[1])?.to_string(),
9394 };
9395 let mut buf = vec![0u8; len];
9396 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
9397 slot.lock().read(&mut buf).unwrap_or(0)
9398 } else if fh == "STDIN" {
9399 std::io::stdin().read(&mut buf).unwrap_or(0)
9400 } else {
9401 return Err(PerlError::runtime(
9402 format!("read: unopened handle {}", fh),
9403 line,
9404 )
9405 .into());
9406 };
9407 buf.truncate(n);
9408 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
9409 if offset > 0 {
9410 let mut existing = self.scope.get_scalar(&var_name).to_string();
9411 while existing.len() < offset {
9412 existing.push('\0');
9413 }
9414 existing.push_str(&read_str);
9415 let _ = self
9416 .scope
9417 .set_scalar(&var_name, PerlValue::string(existing));
9418 } else {
9419 let _ = self
9420 .scope
9421 .set_scalar(&var_name, PerlValue::string(read_str));
9422 }
9423 return Ok(PerlValue::integer(n as i64));
9424 }
9425 if matches!(name.as_str(), "group_by" | "chunk_by") {
9426 if args.len() != 2 {
9427 return Err(PerlError::runtime(
9428 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
9429 line,
9430 )
9431 .into());
9432 }
9433 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
9434 }
9435 if matches!(name.as_str(), "puniq" | "pfirst" | "pany") {
9436 let mut arg_vals = Vec::with_capacity(args.len());
9437 for a in args {
9438 arg_vals.push(self.eval_expr(a)?);
9439 }
9440 let saved_wa = self.wantarray_kind;
9441 self.wantarray_kind = ctx;
9442 let r = self.eval_par_list_call(name.as_str(), &arg_vals, ctx, line);
9443 self.wantarray_kind = saved_wa;
9444 return r.map_err(Into::into);
9445 }
9446 let arg_vals = if matches!(name.as_str(), "any" | "all" | "none" | "first")
9447 || matches!(
9448 name.as_str(),
9449 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9450 )
9451 || matches!(
9452 name.as_str(),
9453 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
9454 ) {
9455 if args.len() != 2 {
9456 return Err(PerlError::runtime(
9457 format!("{}: expected BLOCK, LIST", name),
9458 line,
9459 )
9460 .into());
9461 }
9462 let cr = self.eval_expr(&args[0])?;
9463 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
9464 let mut v = vec![cr];
9465 v.extend(list_src.to_list());
9466 v
9467 } else if matches!(
9468 name.as_str(),
9469 "zip" | "List::Util::zip" | "List::Util::zip_longest"
9470 ) {
9471 let mut v = Vec::with_capacity(args.len());
9472 for a in args {
9473 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9474 }
9475 v
9476 } else if matches!(
9477 name.as_str(),
9478 "uniq"
9479 | "distinct"
9480 | "uniqstr"
9481 | "uniqint"
9482 | "uniqnum"
9483 | "flatten"
9484 | "set"
9485 | "list_count"
9486 | "list_size"
9487 | "count"
9488 | "size"
9489 | "cnt"
9490 | "with_index"
9491 | "List::Util::uniq"
9492 | "List::Util::uniqstr"
9493 | "List::Util::uniqint"
9494 | "List::Util::uniqnum"
9495 | "shuffle"
9496 | "List::Util::shuffle"
9497 | "sum"
9498 | "sum0"
9499 | "product"
9500 | "min"
9501 | "max"
9502 | "minstr"
9503 | "maxstr"
9504 | "mean"
9505 | "median"
9506 | "mode"
9507 | "stddev"
9508 | "variance"
9509 | "List::Util::sum"
9510 | "List::Util::sum0"
9511 | "List::Util::product"
9512 | "List::Util::min"
9513 | "List::Util::max"
9514 | "List::Util::minstr"
9515 | "List::Util::maxstr"
9516 | "List::Util::mean"
9517 | "List::Util::median"
9518 | "List::Util::mode"
9519 | "List::Util::stddev"
9520 | "List::Util::variance"
9521 | "pairs"
9522 | "unpairs"
9523 | "pairkeys"
9524 | "pairvalues"
9525 | "List::Util::pairs"
9526 | "List::Util::unpairs"
9527 | "List::Util::pairkeys"
9528 | "List::Util::pairvalues"
9529 ) {
9530 let mut list_out = Vec::new();
9534 if args.len() == 1 {
9535 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
9536 } else {
9537 for a in args {
9538 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
9539 }
9540 }
9541 list_out
9542 } else if matches!(
9543 name.as_str(),
9544 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail"
9545 ) {
9546 if args.is_empty() {
9547 return Err(PerlError::runtime(
9548 "take/head/tail/drop/List::Util::head|tail: need LIST..., N or unary N",
9549 line,
9550 )
9551 .into());
9552 }
9553 let mut arg_vals = Vec::with_capacity(args.len());
9554 if args.len() == 1 {
9555 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9557 } else {
9558 for a in &args[..args.len() - 1] {
9559 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9560 }
9561 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
9562 }
9563 arg_vals
9564 } else if matches!(
9565 name.as_str(),
9566 "chunked" | "List::Util::chunked" | "windowed" | "List::Util::windowed"
9567 ) {
9568 let mut list_out = Vec::new();
9569 match args.len() {
9570 0 => {
9571 return Err(PerlError::runtime(
9572 format!("{name}: expected (LIST, N) or unary N after |>"),
9573 line,
9574 )
9575 .into());
9576 }
9577 1 => {
9578 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9580 }
9581 2 => {
9582 list_out.extend(
9583 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
9584 );
9585 list_out.push(self.eval_expr(&args[1])?);
9586 }
9587 _ => {
9588 return Err(PerlError::runtime(
9589 format!(
9590 "{name}: expected exactly (LIST, N); use one list expression then size"
9591 ),
9592 line,
9593 )
9594 .into());
9595 }
9596 }
9597 list_out
9598 } else {
9599 let mut arg_vals = Vec::with_capacity(args.len());
9602 for a in args {
9603 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
9604 if let Some(items) = v.as_array_vec() {
9605 arg_vals.extend(items);
9606 } else {
9607 arg_vals.push(v);
9608 }
9609 }
9610 arg_vals
9611 };
9612 let saved_wa = self.wantarray_kind;
9614 self.wantarray_kind = ctx;
9615 if !crate::compat_mode() {
9618 if matches!(
9619 name.as_str(),
9620 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9621 ) {
9622 let r =
9623 self.list_higher_order_block_builtin(name.as_str(), &arg_vals, line);
9624 self.wantarray_kind = saved_wa;
9625 return r.map_err(Into::into);
9626 }
9627 if let Some(r) =
9628 crate::builtins::try_builtin(self, name.as_str(), &arg_vals, line)
9629 {
9630 self.wantarray_kind = saved_wa;
9631 return r.map_err(Into::into);
9632 }
9633 }
9634 if let Some(sub) = self.resolve_sub_by_name(name) {
9635 self.wantarray_kind = saved_wa;
9636 let args = self.with_topic_default_args(arg_vals);
9637 return self.call_sub(&sub, args, ctx, line);
9638 }
9639 if crate::compat_mode() {
9641 if matches!(
9642 name.as_str(),
9643 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9644 ) {
9645 let r =
9646 self.list_higher_order_block_builtin(name.as_str(), &arg_vals, line);
9647 self.wantarray_kind = saved_wa;
9648 return r.map_err(Into::into);
9649 }
9650 if let Some(r) =
9651 crate::builtins::try_builtin(self, name.as_str(), &arg_vals, line)
9652 {
9653 self.wantarray_kind = saved_wa;
9654 return r.map_err(Into::into);
9655 }
9656 }
9657 self.wantarray_kind = saved_wa;
9658 self.call_named_sub(name, arg_vals, line, ctx)
9659 }
9660 ExprKind::IndirectCall {
9661 target,
9662 args,
9663 ampersand: _,
9664 pass_caller_arglist,
9665 } => {
9666 let tval = self.eval_expr(target)?;
9667 let arg_vals = if *pass_caller_arglist {
9668 self.scope.get_array("_")
9669 } else {
9670 let mut v = Vec::with_capacity(args.len());
9671 for a in args {
9672 v.push(self.eval_expr(a)?);
9673 }
9674 v
9675 };
9676 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
9677 }
9678 ExprKind::MethodCall {
9679 object,
9680 method,
9681 args,
9682 super_call,
9683 } => {
9684 let obj = self.eval_expr(object)?;
9685 let mut arg_vals = vec![obj.clone()];
9686 for a in args {
9687 arg_vals.push(self.eval_expr(a)?);
9688 }
9689 if let Some(r) =
9690 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
9691 {
9692 return r.map_err(Into::into);
9693 }
9694 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
9695 return r.map_err(Into::into);
9696 }
9697 let class = if let Some(b) = obj.as_blessed_ref() {
9699 b.class.clone()
9700 } else if let Some(s) = obj.as_str() {
9701 s } else {
9703 return Err(PerlError::runtime("Can't call method on non-object", line).into());
9704 };
9705 if method == "VERSION" && !*super_call {
9706 if let Some(ver) = self.package_version_scalar(class.as_str())? {
9707 return Ok(ver);
9708 }
9709 }
9710 if !*super_call {
9712 match method.as_str() {
9713 "isa" => {
9714 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9715 let mro = self.mro_linearize(&class);
9716 let result = mro.iter().any(|c| c == &target);
9717 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9718 }
9719 "can" => {
9720 let target_method =
9721 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9722 let found = self
9723 .resolve_method_full_name(&class, &target_method, false)
9724 .and_then(|fq| self.subs.get(&fq))
9725 .is_some();
9726 if found {
9727 return Ok(PerlValue::code_ref(Arc::new(PerlSub {
9728 name: target_method,
9729 params: vec![],
9730 body: vec![],
9731 closure_env: None,
9732 prototype: None,
9733 fib_like: None,
9734 })));
9735 } else {
9736 return Ok(PerlValue::UNDEF);
9737 }
9738 }
9739 "DOES" => {
9740 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9741 let mro = self.mro_linearize(&class);
9742 let result = mro.iter().any(|c| c == &target);
9743 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9744 }
9745 _ => {}
9746 }
9747 }
9748 let full_name = self
9749 .resolve_method_full_name(&class, method, *super_call)
9750 .ok_or_else(|| {
9751 PerlError::runtime(
9752 format!(
9753 "Can't locate method \"{}\" for invocant \"{}\"",
9754 method, class
9755 ),
9756 line,
9757 )
9758 })?;
9759 if let Some(sub) = self.subs.get(&full_name).cloned() {
9760 self.call_sub(&sub, arg_vals, ctx, line)
9761 } else if method == "new" && !*super_call {
9762 self.builtin_new(&class, arg_vals, line)
9764 } else if let Some(r) =
9765 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
9766 {
9767 r
9768 } else {
9769 Err(PerlError::runtime(
9770 format!(
9771 "Can't locate method \"{}\" in package \"{}\"",
9772 method, class
9773 ),
9774 line,
9775 )
9776 .into())
9777 }
9778 }
9779
9780 ExprKind::Print { handle, args } => {
9782 self.exec_print(handle.as_deref(), args, false, line)
9783 }
9784 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
9785 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
9786 ExprKind::Die(args) => {
9787 if args.is_empty() {
9788 let current = self.scope.get_scalar("@");
9790 let msg = if current.is_undef() || current.to_string().is_empty() {
9791 let mut m = "Died".to_string();
9792 m.push_str(&self.die_warn_at_suffix(line));
9793 m.push('\n');
9794 m
9795 } else {
9796 current.to_string()
9797 };
9798 return Err(PerlError::die(msg, line).into());
9799 }
9800 if args.len() == 1 {
9802 let v = self.eval_expr(&args[0])?;
9803 if v.as_hash_ref().is_some()
9804 || v.as_blessed_ref().is_some()
9805 || v.as_array_ref().is_some()
9806 || v.as_code_ref().is_some()
9807 {
9808 let msg = v.to_string();
9809 return Err(PerlError::die_with_value(v, msg, line).into());
9810 }
9811 }
9812 let mut msg = String::new();
9813 for a in args {
9814 let v = self.eval_expr(a)?;
9815 msg.push_str(&v.to_string());
9816 }
9817 if msg.is_empty() {
9818 msg = "Died".to_string();
9819 }
9820 if !msg.ends_with('\n') {
9821 msg.push_str(&self.die_warn_at_suffix(line));
9822 msg.push('\n');
9823 }
9824 Err(PerlError::die(msg, line).into())
9825 }
9826 ExprKind::Warn(args) => {
9827 let mut msg = String::new();
9828 for a in args {
9829 let v = self.eval_expr(a)?;
9830 msg.push_str(&v.to_string());
9831 }
9832 if msg.is_empty() {
9833 msg = "Warning: something's wrong".to_string();
9834 }
9835 if !msg.ends_with('\n') {
9836 msg.push_str(&self.die_warn_at_suffix(line));
9837 msg.push('\n');
9838 }
9839 eprint!("{}", msg);
9840 Ok(PerlValue::integer(1))
9841 }
9842
9843 ExprKind::Match {
9845 expr,
9846 pattern,
9847 flags,
9848 scalar_g,
9849 delim: _,
9850 } => {
9851 let val = self.eval_expr(expr)?;
9852 if val.is_iterator() {
9853 let source = crate::map_stream::into_pull_iter(val);
9854 let re = self.compile_regex(pattern, flags, line)?;
9855 let global = flags.contains('g');
9856 if global {
9857 return Ok(PerlValue::iterator(std::sync::Arc::new(
9858 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
9859 )));
9860 } else {
9861 return Ok(PerlValue::iterator(std::sync::Arc::new(
9862 crate::map_stream::MatchStreamIterator::new(source, re),
9863 )));
9864 }
9865 }
9866 let s = val.to_string();
9867 let pos_key = match &expr.kind {
9868 ExprKind::ScalarVar(n) => n.as_str(),
9869 _ => "_",
9870 };
9871 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
9872 }
9873 ExprKind::Substitution {
9874 expr,
9875 pattern,
9876 replacement,
9877 flags,
9878 delim: _,
9879 } => {
9880 let val = self.eval_expr(expr)?;
9881 if val.is_iterator() {
9882 let source = crate::map_stream::into_pull_iter(val);
9883 let re = self.compile_regex(pattern, flags, line)?;
9884 let global = flags.contains('g');
9885 return Ok(PerlValue::iterator(std::sync::Arc::new(
9886 crate::map_stream::SubstStreamIterator::new(
9887 source,
9888 re,
9889 normalize_replacement_backrefs(replacement),
9890 global,
9891 ),
9892 )));
9893 }
9894 let s = val.to_string();
9895 self.regex_subst_execute(
9896 s,
9897 pattern,
9898 replacement.as_str(),
9899 flags.as_str(),
9900 expr,
9901 line,
9902 )
9903 }
9904 ExprKind::Transliterate {
9905 expr,
9906 from,
9907 to,
9908 flags,
9909 delim: _,
9910 } => {
9911 let val = self.eval_expr(expr)?;
9912 if val.is_iterator() {
9913 let source = crate::map_stream::into_pull_iter(val);
9914 return Ok(PerlValue::iterator(std::sync::Arc::new(
9915 crate::map_stream::TransliterateStreamIterator::new(
9916 source, from, to, flags,
9917 ),
9918 )));
9919 }
9920 let s = val.to_string();
9921 self.regex_transliterate_execute(
9922 s,
9923 from.as_str(),
9924 to.as_str(),
9925 flags.as_str(),
9926 expr,
9927 line,
9928 )
9929 }
9930
9931 ExprKind::MapExpr {
9933 block,
9934 list,
9935 flatten_array_refs,
9936 stream,
9937 } => {
9938 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9939 if *stream {
9940 let out =
9941 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
9942 if ctx == WantarrayCtx::List {
9943 return Ok(out);
9944 }
9945 return Ok(PerlValue::integer(out.to_list().len() as i64));
9946 }
9947 let items = list_val.to_list();
9948 if items.len() == 1 {
9949 if let Some(p) = items[0].as_pipeline() {
9950 if *flatten_array_refs {
9951 return Err(PerlError::runtime(
9952 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
9953 line,
9954 )
9955 .into());
9956 }
9957 let sub = self.anon_coderef_from_block(block);
9958 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
9959 return Ok(PerlValue::pipeline(Arc::clone(&p)));
9960 }
9961 }
9962 let mut result = Vec::new();
9967 for item in items {
9968 self.scope.set_topic(item);
9969 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
9970 result.extend(val.map_flatten_outputs(*flatten_array_refs));
9971 }
9972 if ctx == WantarrayCtx::List {
9973 Ok(PerlValue::array(result))
9974 } else {
9975 Ok(PerlValue::integer(result.len() as i64))
9976 }
9977 }
9978 ExprKind::ForEachExpr { block, list } => {
9979 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9980 if list_val.is_iterator() {
9982 let iter = list_val.into_iterator();
9983 let mut count = 0i64;
9984 while let Some(item) = iter.next_item() {
9985 count += 1;
9986 self.scope.set_topic(item);
9987 self.exec_block(block)?;
9988 }
9989 return Ok(PerlValue::integer(count));
9990 }
9991 let items = list_val.to_list();
9992 let count = items.len();
9993 for item in items {
9994 self.scope.set_topic(item);
9995 self.exec_block(block)?;
9996 }
9997 Ok(PerlValue::integer(count as i64))
9998 }
9999 ExprKind::MapExprComma {
10000 expr,
10001 list,
10002 flatten_array_refs,
10003 stream,
10004 } => {
10005 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10006 if *stream {
10007 let out =
10008 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
10009 if ctx == WantarrayCtx::List {
10010 return Ok(out);
10011 }
10012 return Ok(PerlValue::integer(out.to_list().len() as i64));
10013 }
10014 let items = list_val.to_list();
10015 let mut result = Vec::new();
10016 for item in items {
10017 self.scope.set_topic(item.clone());
10018 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10019 result.extend(val.map_flatten_outputs(*flatten_array_refs));
10020 }
10021 if ctx == WantarrayCtx::List {
10022 Ok(PerlValue::array(result))
10023 } else {
10024 Ok(PerlValue::integer(result.len() as i64))
10025 }
10026 }
10027 ExprKind::GrepExpr {
10028 block,
10029 list,
10030 keyword,
10031 } => {
10032 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10033 if keyword.is_stream() {
10034 let out = self.filter_stream_block_output(list_val, block, line)?;
10035 if ctx == WantarrayCtx::List {
10036 return Ok(out);
10037 }
10038 return Ok(PerlValue::integer(out.to_list().len() as i64));
10039 }
10040 let items = list_val.to_list();
10041 if items.len() == 1 {
10042 if let Some(p) = items[0].as_pipeline() {
10043 let sub = self.anon_coderef_from_block(block);
10044 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
10045 return Ok(PerlValue::pipeline(Arc::clone(&p)));
10046 }
10047 }
10048 let mut result = Vec::new();
10049 for item in items {
10050 self.scope.set_topic(item.clone());
10051 let val = self.exec_block(block)?;
10052 let keep = if let Some(re) = val.as_regex() {
10055 re.is_match(&item.to_string())
10056 } else {
10057 val.is_true()
10058 };
10059 if keep {
10060 result.push(item);
10061 }
10062 }
10063 if ctx == WantarrayCtx::List {
10064 Ok(PerlValue::array(result))
10065 } else {
10066 Ok(PerlValue::integer(result.len() as i64))
10067 }
10068 }
10069 ExprKind::GrepExprComma {
10070 expr,
10071 list,
10072 keyword,
10073 } => {
10074 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10075 if keyword.is_stream() {
10076 let out = self.filter_stream_expr_output(list_val, expr, line)?;
10077 if ctx == WantarrayCtx::List {
10078 return Ok(out);
10079 }
10080 return Ok(PerlValue::integer(out.to_list().len() as i64));
10081 }
10082 let items = list_val.to_list();
10083 let mut result = Vec::new();
10084 for item in items {
10085 self.scope.set_topic(item.clone());
10086 let val = self.eval_expr(expr)?;
10087 let keep = if let Some(re) = val.as_regex() {
10088 re.is_match(&item.to_string())
10089 } else {
10090 val.is_true()
10091 };
10092 if keep {
10093 result.push(item);
10094 }
10095 }
10096 if ctx == WantarrayCtx::List {
10097 Ok(PerlValue::array(result))
10098 } else {
10099 Ok(PerlValue::integer(result.len() as i64))
10100 }
10101 }
10102 ExprKind::SortExpr { cmp, list } => {
10103 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10104 let mut items = list_val.to_list();
10105 match cmp {
10106 Some(SortComparator::Code(code_expr)) => {
10107 let sub = self.eval_expr(code_expr)?;
10108 let Some(sub) = sub.as_code_ref() else {
10109 return Err(PerlError::runtime(
10110 "sort: comparator must be a code reference",
10111 line,
10112 )
10113 .into());
10114 };
10115 let sub = sub.clone();
10116 items.sort_by(|a, b| {
10117 let _ = self.scope.set_scalar("a", a.clone());
10118 let _ = self.scope.set_scalar("b", b.clone());
10119 let _ = self.scope.set_scalar("_0", a.clone());
10120 let _ = self.scope.set_scalar("_1", b.clone());
10121 match self.call_sub(&sub, vec![], ctx, line) {
10122 Ok(v) => {
10123 let n = v.to_int();
10124 if n < 0 {
10125 Ordering::Less
10126 } else if n > 0 {
10127 Ordering::Greater
10128 } else {
10129 Ordering::Equal
10130 }
10131 }
10132 Err(_) => Ordering::Equal,
10133 }
10134 });
10135 }
10136 Some(SortComparator::Block(cmp_block)) => {
10137 if let Some(mode) = detect_sort_block_fast(cmp_block) {
10138 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
10139 } else {
10140 let cmp_block = cmp_block.clone();
10141 items.sort_by(|a, b| {
10142 let _ = self.scope.set_scalar("a", a.clone());
10143 let _ = self.scope.set_scalar("b", b.clone());
10144 let _ = self.scope.set_scalar("_0", a.clone());
10145 let _ = self.scope.set_scalar("_1", b.clone());
10146 match self.exec_block(&cmp_block) {
10147 Ok(v) => {
10148 let n = v.to_int();
10149 if n < 0 {
10150 Ordering::Less
10151 } else if n > 0 {
10152 Ordering::Greater
10153 } else {
10154 Ordering::Equal
10155 }
10156 }
10157 Err(_) => Ordering::Equal,
10158 }
10159 });
10160 }
10161 }
10162 None => {
10163 items.sort_by_key(|a| a.to_string());
10164 }
10165 }
10166 Ok(PerlValue::array(items))
10167 }
10168 ExprKind::Rev(expr) => {
10169 let val = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
10171 if val.is_iterator() {
10172 return Ok(PerlValue::iterator(Arc::new(
10173 crate::value::RevIterator::new(val.into_iterator()),
10174 )));
10175 }
10176 if let Some(s) = crate::value::set_payload(&val) {
10177 let mut out = crate::value::PerlSet::new();
10178 for (k, v) in s.iter().rev() {
10179 out.insert(k.clone(), v.clone());
10180 }
10181 return Ok(PerlValue::set(Arc::new(out)));
10182 }
10183 if let Some(ar) = val.as_array_ref() {
10184 let items: Vec<_> = ar.read().iter().rev().cloned().collect();
10185 return Ok(PerlValue::array_ref(Arc::new(parking_lot::RwLock::new(
10186 items,
10187 ))));
10188 }
10189 if let Some(hr) = val.as_hash_ref() {
10190 let mut out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
10191 for (k, v) in hr.read().iter() {
10192 out.insert(v.to_string(), PerlValue::string(k.clone()));
10193 }
10194 return Ok(PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(out))));
10195 }
10196 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10198 if let Some(hm) = val.as_hash_map() {
10199 let mut out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
10200 for (k, v) in hm.iter() {
10201 out.insert(v.to_string(), PerlValue::string(k.clone()));
10202 }
10203 return Ok(PerlValue::hash(out));
10204 }
10205 if val.as_array_vec().is_some() {
10206 let mut items = val.to_list();
10207 items.reverse();
10208 Ok(PerlValue::array(items))
10209 } else {
10210 let items = val.to_list();
10211 if items.len() > 1 {
10212 let mut items = items;
10213 items.reverse();
10214 Ok(PerlValue::array(items))
10215 } else {
10216 let s = val.to_string();
10217 Ok(PerlValue::string(s.chars().rev().collect()))
10218 }
10219 }
10220 }
10221 ExprKind::ReverseExpr(list) => {
10222 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10223 match ctx {
10224 WantarrayCtx::List => {
10225 let mut items = val.to_list();
10226 items.reverse();
10227 Ok(PerlValue::array(items))
10228 }
10229 _ => {
10230 let items = val.to_list();
10231 let s: String = items.iter().map(|v| v.to_string()).collect();
10232 Ok(PerlValue::string(s.chars().rev().collect()))
10233 }
10234 }
10235 }
10236
10237 ExprKind::ParLinesExpr {
10239 path,
10240 callback,
10241 progress,
10242 } => self.eval_par_lines_expr(
10243 path.as_ref(),
10244 callback.as_ref(),
10245 progress.as_deref(),
10246 line,
10247 ),
10248 ExprKind::ParWalkExpr {
10249 path,
10250 callback,
10251 progress,
10252 } => {
10253 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
10254 }
10255 ExprKind::PwatchExpr { path, callback } => {
10256 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
10257 }
10258 ExprKind::PMapExpr {
10259 block,
10260 list,
10261 progress,
10262 flat_outputs,
10263 on_cluster,
10264 stream,
10265 } => {
10266 let show_progress = progress
10267 .as_ref()
10268 .map(|p| self.eval_expr(p))
10269 .transpose()?
10270 .map(|v| v.is_true())
10271 .unwrap_or(false);
10272 let list_val = self.eval_expr(list)?;
10273 if let Some(cluster_e) = on_cluster {
10274 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
10275 return self.eval_pmap_remote(
10276 cluster_val,
10277 list_val,
10278 show_progress,
10279 block,
10280 *flat_outputs,
10281 line,
10282 );
10283 }
10284 if *stream {
10285 let source = crate::map_stream::into_pull_iter(list_val);
10286 let sub = self.anon_coderef_from_block(block);
10287 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
10288 return Ok(PerlValue::iterator(Arc::new(
10289 crate::map_stream::PMapStreamIterator::new(
10290 source,
10291 sub,
10292 self.subs.clone(),
10293 capture,
10294 atomic_arrays,
10295 atomic_hashes,
10296 *flat_outputs,
10297 ),
10298 )));
10299 }
10300 let items = list_val.to_list();
10301 let block = block.clone();
10302 let subs = self.subs.clone();
10303 let (scope_capture, atomic_arrays, atomic_hashes) =
10304 self.scope.capture_with_atomics();
10305 let pmap_progress = PmapProgress::new(show_progress, items.len());
10306
10307 if *flat_outputs {
10308 let mut indexed: Vec<(usize, Vec<PerlValue>)> = items
10309 .into_par_iter()
10310 .enumerate()
10311 .map(|(i, item)| {
10312 let mut local_interp = Interpreter::new();
10313 local_interp.subs = subs.clone();
10314 local_interp.scope.restore_capture(&scope_capture);
10315 local_interp
10316 .scope
10317 .restore_atomics(&atomic_arrays, &atomic_hashes);
10318 local_interp.enable_parallel_guard();
10319 local_interp.scope.set_topic(item);
10320 let val = match local_interp.exec_block(&block) {
10321 Ok(val) => val,
10322 Err(_) => PerlValue::UNDEF,
10323 };
10324 let chunk = val.map_flatten_outputs(true);
10325 pmap_progress.tick();
10326 (i, chunk)
10327 })
10328 .collect();
10329 pmap_progress.finish();
10330 indexed.sort_by_key(|(i, _)| *i);
10331 let results: Vec<PerlValue> =
10332 indexed.into_iter().flat_map(|(_, v)| v).collect();
10333 Ok(PerlValue::array(results))
10334 } else {
10335 let results: Vec<PerlValue> = items
10336 .into_par_iter()
10337 .map(|item| {
10338 let mut local_interp = Interpreter::new();
10339 local_interp.subs = subs.clone();
10340 local_interp.scope.restore_capture(&scope_capture);
10341 local_interp
10342 .scope
10343 .restore_atomics(&atomic_arrays, &atomic_hashes);
10344 local_interp.enable_parallel_guard();
10345 local_interp.scope.set_topic(item);
10346 let val = match local_interp.exec_block(&block) {
10347 Ok(val) => val,
10348 Err(_) => PerlValue::UNDEF,
10349 };
10350 pmap_progress.tick();
10351 val
10352 })
10353 .collect();
10354 pmap_progress.finish();
10355 Ok(PerlValue::array(results))
10356 }
10357 }
10358 ExprKind::PMapChunkedExpr {
10359 chunk_size,
10360 block,
10361 list,
10362 progress,
10363 } => {
10364 let show_progress = progress
10365 .as_ref()
10366 .map(|p| self.eval_expr(p))
10367 .transpose()?
10368 .map(|v| v.is_true())
10369 .unwrap_or(false);
10370 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
10371 let list_val = self.eval_expr(list)?;
10372 let items = list_val.to_list();
10373 let block = block.clone();
10374 let subs = self.subs.clone();
10375 let (scope_capture, atomic_arrays, atomic_hashes) =
10376 self.scope.capture_with_atomics();
10377
10378 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = items
10379 .chunks(chunk_n)
10380 .enumerate()
10381 .map(|(i, c)| (i, c.to_vec()))
10382 .collect();
10383
10384 let n_chunks = indexed_chunks.len();
10385 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
10386
10387 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
10388 .into_par_iter()
10389 .map(|(chunk_idx, chunk)| {
10390 let mut local_interp = Interpreter::new();
10391 local_interp.subs = subs.clone();
10392 local_interp.scope.restore_capture(&scope_capture);
10393 local_interp
10394 .scope
10395 .restore_atomics(&atomic_arrays, &atomic_hashes);
10396 local_interp.enable_parallel_guard();
10397 let mut out = Vec::with_capacity(chunk.len());
10398 for item in chunk {
10399 local_interp.scope.set_topic(item);
10400 match local_interp.exec_block(&block) {
10401 Ok(val) => out.push(val),
10402 Err(_) => out.push(PerlValue::UNDEF),
10403 }
10404 }
10405 pmap_progress.tick();
10406 (chunk_idx, out)
10407 })
10408 .collect();
10409
10410 pmap_progress.finish();
10411 chunk_results.sort_by_key(|(i, _)| *i);
10412 let results: Vec<PerlValue> =
10413 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
10414 Ok(PerlValue::array(results))
10415 }
10416 ExprKind::PGrepExpr {
10417 block,
10418 list,
10419 progress,
10420 stream,
10421 } => {
10422 let show_progress = progress
10423 .as_ref()
10424 .map(|p| self.eval_expr(p))
10425 .transpose()?
10426 .map(|v| v.is_true())
10427 .unwrap_or(false);
10428 let list_val = self.eval_expr(list)?;
10429 if *stream {
10430 let source = crate::map_stream::into_pull_iter(list_val);
10431 let sub = self.anon_coderef_from_block(block);
10432 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
10433 return Ok(PerlValue::iterator(Arc::new(
10434 crate::map_stream::PGrepStreamIterator::new(
10435 source,
10436 sub,
10437 self.subs.clone(),
10438 capture,
10439 atomic_arrays,
10440 atomic_hashes,
10441 ),
10442 )));
10443 }
10444 let items = list_val.to_list();
10445 let block = block.clone();
10446 let subs = self.subs.clone();
10447 let (scope_capture, atomic_arrays, atomic_hashes) =
10448 self.scope.capture_with_atomics();
10449 let pmap_progress = PmapProgress::new(show_progress, items.len());
10450
10451 let results: Vec<PerlValue> = items
10452 .into_par_iter()
10453 .filter_map(|item| {
10454 let mut local_interp = Interpreter::new();
10455 local_interp.subs = subs.clone();
10456 local_interp.scope.restore_capture(&scope_capture);
10457 local_interp
10458 .scope
10459 .restore_atomics(&atomic_arrays, &atomic_hashes);
10460 local_interp.enable_parallel_guard();
10461 local_interp.scope.set_topic(item.clone());
10462 let keep = match local_interp.exec_block(&block) {
10463 Ok(val) => val.is_true(),
10464 Err(_) => false,
10465 };
10466 pmap_progress.tick();
10467 if keep {
10468 Some(item)
10469 } else {
10470 None
10471 }
10472 })
10473 .collect();
10474 pmap_progress.finish();
10475 Ok(PerlValue::array(results))
10476 }
10477 ExprKind::PForExpr {
10478 block,
10479 list,
10480 progress,
10481 } => {
10482 let show_progress = progress
10483 .as_ref()
10484 .map(|p| self.eval_expr(p))
10485 .transpose()?
10486 .map(|v| v.is_true())
10487 .unwrap_or(false);
10488 let list_val = self.eval_expr(list)?;
10489 let items = list_val.to_list();
10490 let block = block.clone();
10491 let subs = self.subs.clone();
10492 let (scope_capture, atomic_arrays, atomic_hashes) =
10493 self.scope.capture_with_atomics();
10494
10495 let pmap_progress = PmapProgress::new(show_progress, items.len());
10496 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10497 items.into_par_iter().for_each(|item| {
10498 if first_err.lock().is_some() {
10499 return;
10500 }
10501 let mut local_interp = Interpreter::new();
10502 local_interp.subs = subs.clone();
10503 local_interp.scope.restore_capture(&scope_capture);
10504 local_interp
10505 .scope
10506 .restore_atomics(&atomic_arrays, &atomic_hashes);
10507 local_interp.enable_parallel_guard();
10508 local_interp.scope.set_topic(item);
10509 match local_interp.exec_block(&block) {
10510 Ok(_) => {}
10511 Err(e) => {
10512 let stryke = match e {
10513 FlowOrError::Error(stryke) => stryke,
10514 FlowOrError::Flow(_) => PerlError::runtime(
10515 "return/last/next/redo not supported inside pfor block",
10516 line,
10517 ),
10518 };
10519 let mut g = first_err.lock();
10520 if g.is_none() {
10521 *g = Some(stryke);
10522 }
10523 }
10524 }
10525 pmap_progress.tick();
10526 });
10527 pmap_progress.finish();
10528 if let Some(e) = first_err.lock().take() {
10529 return Err(FlowOrError::Error(e));
10530 }
10531 Ok(PerlValue::UNDEF)
10532 }
10533 ExprKind::FanExpr {
10534 count,
10535 block,
10536 progress,
10537 capture,
10538 } => {
10539 let show_progress = progress
10540 .as_ref()
10541 .map(|p| self.eval_expr(p))
10542 .transpose()?
10543 .map(|v| v.is_true())
10544 .unwrap_or(false);
10545 let n = match count {
10546 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
10547 None => self.parallel_thread_count(),
10548 };
10549 let block = block.clone();
10550 let subs = self.subs.clone();
10551 let (scope_capture, atomic_arrays, atomic_hashes) =
10552 self.scope.capture_with_atomics();
10553
10554 let fan_progress = FanProgress::new(show_progress, n);
10555 if *capture {
10556 if n == 0 {
10557 return Ok(PerlValue::array(Vec::new()));
10558 }
10559 let pairs: Vec<(usize, ExecResult)> = (0..n)
10560 .into_par_iter()
10561 .map(|i| {
10562 fan_progress.start_worker(i);
10563 let mut local_interp = Interpreter::new();
10564 local_interp.subs = subs.clone();
10565 local_interp.suppress_stdout = show_progress;
10566 local_interp.scope.restore_capture(&scope_capture);
10567 local_interp
10568 .scope
10569 .restore_atomics(&atomic_arrays, &atomic_hashes);
10570 local_interp.enable_parallel_guard();
10571 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10572 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10573 let res = local_interp.exec_block(&block);
10574 crate::parallel_trace::fan_worker_set_index(None);
10575 fan_progress.finish_worker(i);
10576 (i, res)
10577 })
10578 .collect();
10579 fan_progress.finish();
10580 let mut pairs = pairs;
10581 pairs.sort_by_key(|(i, _)| *i);
10582 let mut out = Vec::with_capacity(n);
10583 for (_, r) in pairs {
10584 match r {
10585 Ok(v) => out.push(v),
10586 Err(e) => return Err(e),
10587 }
10588 }
10589 return Ok(PerlValue::array(out));
10590 }
10591 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10592 (0..n).into_par_iter().for_each(|i| {
10593 if first_err.lock().is_some() {
10594 return;
10595 }
10596 fan_progress.start_worker(i);
10597 let mut local_interp = Interpreter::new();
10598 local_interp.subs = subs.clone();
10599 local_interp.suppress_stdout = show_progress;
10600 local_interp.scope.restore_capture(&scope_capture);
10601 local_interp
10602 .scope
10603 .restore_atomics(&atomic_arrays, &atomic_hashes);
10604 local_interp.enable_parallel_guard();
10605 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10606 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10607 match local_interp.exec_block(&block) {
10608 Ok(_) => {}
10609 Err(e) => {
10610 let stryke = match e {
10611 FlowOrError::Error(stryke) => stryke,
10612 FlowOrError::Flow(_) => PerlError::runtime(
10613 "return/last/next/redo not supported inside fan block",
10614 line,
10615 ),
10616 };
10617 let mut g = first_err.lock();
10618 if g.is_none() {
10619 *g = Some(stryke);
10620 }
10621 }
10622 }
10623 crate::parallel_trace::fan_worker_set_index(None);
10624 fan_progress.finish_worker(i);
10625 });
10626 fan_progress.finish();
10627 if let Some(e) = first_err.lock().take() {
10628 return Err(FlowOrError::Error(e));
10629 }
10630 Ok(PerlValue::UNDEF)
10631 }
10632 ExprKind::RetryBlock {
10633 body,
10634 times,
10635 backoff,
10636 } => self.eval_retry_block(body, times, *backoff, line),
10637 ExprKind::RateLimitBlock {
10638 slot,
10639 max,
10640 window,
10641 body,
10642 } => self.eval_rate_limit_block(*slot, max, window, body, line),
10643 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
10644 ExprKind::GenBlock { body } => {
10645 let g = Arc::new(PerlGenerator {
10646 block: body.clone(),
10647 pc: Mutex::new(0),
10648 scope_started: Mutex::new(false),
10649 exhausted: Mutex::new(false),
10650 });
10651 Ok(PerlValue::generator(g))
10652 }
10653 ExprKind::Yield(e) => {
10654 if !self.in_generator {
10655 return Err(PerlError::runtime("yield outside gen block", line).into());
10656 }
10657 let v = self.eval_expr(e)?;
10658 Err(FlowOrError::Flow(Flow::Yield(v)))
10659 }
10660 ExprKind::AlgebraicMatch { subject, arms } => {
10661 self.eval_algebraic_match(subject, arms, line)
10662 }
10663 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
10664 Ok(self.spawn_async_block(body))
10665 }
10666 ExprKind::Trace { body } => {
10667 crate::parallel_trace::trace_enter();
10668 let out = self.exec_block(body);
10669 crate::parallel_trace::trace_leave();
10670 out
10671 }
10672 ExprKind::Spinner { message, body } => {
10673 use std::io::Write as _;
10674 let msg = self.eval_expr(message)?.to_string();
10675 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
10676 let done2 = done.clone();
10677 let handle = std::thread::spawn(move || {
10678 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
10679 let mut i = 0;
10680 let stderr = std::io::stderr();
10681 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
10682 {
10683 let stdout = std::io::stdout();
10684 let _stdout_lock = stdout.lock();
10685 let mut err = stderr.lock();
10686 let _ = write!(
10687 err,
10688 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
10689 frames[i % frames.len()],
10690 msg
10691 );
10692 let _ = err.flush();
10693 }
10694 std::thread::sleep(std::time::Duration::from_millis(80));
10695 i += 1;
10696 }
10697 let mut err = stderr.lock();
10698 let _ = write!(err, "\r\x1b[2K");
10699 let _ = err.flush();
10700 });
10701 let result = self.exec_block(body);
10702 done.store(true, std::sync::atomic::Ordering::Relaxed);
10703 let _ = handle.join();
10704 result
10705 }
10706 ExprKind::Timer { body } => {
10707 let start = std::time::Instant::now();
10708 self.exec_block(body)?;
10709 let ms = start.elapsed().as_secs_f64() * 1000.0;
10710 Ok(PerlValue::float(ms))
10711 }
10712 ExprKind::Bench { body, times } => {
10713 let n = self.eval_expr(times)?.to_int();
10714 if n < 0 {
10715 return Err(PerlError::runtime(
10716 "bench: iteration count must be non-negative",
10717 line,
10718 )
10719 .into());
10720 }
10721 self.run_bench_block(body, n as usize, line)
10722 }
10723 ExprKind::Await(expr) => {
10724 let v = self.eval_expr(expr)?;
10725 if let Some(t) = v.as_async_task() {
10726 t.await_result().map_err(FlowOrError::from)
10727 } else {
10728 Ok(v)
10729 }
10730 }
10731 ExprKind::Slurp(e) => {
10732 let path = self.eval_expr(e)?.to_string();
10733 read_file_text_perl_compat(&path)
10734 .map(PerlValue::string)
10735 .map_err(|e| {
10736 FlowOrError::Error(PerlError::runtime(format!("slurp: {}", e), line))
10737 })
10738 }
10739 ExprKind::Capture(e) => {
10740 let cmd = self.eval_expr(e)?.to_string();
10741 let output = Command::new("sh")
10742 .arg("-c")
10743 .arg(&cmd)
10744 .output()
10745 .map_err(|e| {
10746 FlowOrError::Error(PerlError::runtime(format!("capture: {}", e), line))
10747 })?;
10748 self.record_child_exit_status(output.status);
10749 let exitcode = output.status.code().unwrap_or(-1) as i64;
10750 let stdout = decode_utf8_or_latin1(&output.stdout);
10751 let stderr = decode_utf8_or_latin1(&output.stderr);
10752 Ok(PerlValue::capture(Arc::new(CaptureResult {
10753 stdout,
10754 stderr,
10755 exitcode,
10756 })))
10757 }
10758 ExprKind::Qx(e) => {
10759 let cmd = self.eval_expr(e)?.to_string();
10760 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)
10761 }
10762 ExprKind::FetchUrl(e) => {
10763 let url = self.eval_expr(e)?.to_string();
10764 ureq::get(&url)
10765 .call()
10766 .map_err(|e| {
10767 FlowOrError::Error(PerlError::runtime(format!("fetch_url: {}", e), line))
10768 })
10769 .and_then(|r| {
10770 r.into_string().map(PerlValue::string).map_err(|e| {
10771 FlowOrError::Error(PerlError::runtime(
10772 format!("fetch_url: {}", e),
10773 line,
10774 ))
10775 })
10776 })
10777 }
10778 ExprKind::Pchannel { capacity } => {
10779 if let Some(c) = capacity {
10780 let n = self.eval_expr(c)?.to_int().max(1) as usize;
10781 Ok(crate::pchannel::create_bounded_pair(n))
10782 } else {
10783 Ok(crate::pchannel::create_pair())
10784 }
10785 }
10786 ExprKind::PSortExpr {
10787 cmp,
10788 list,
10789 progress,
10790 } => {
10791 let show_progress = progress
10792 .as_ref()
10793 .map(|p| self.eval_expr(p))
10794 .transpose()?
10795 .map(|v| v.is_true())
10796 .unwrap_or(false);
10797 let list_val = self.eval_expr(list)?;
10798 let mut items = list_val.to_list();
10799 let pmap_progress = PmapProgress::new(show_progress, 2);
10800 pmap_progress.tick();
10801 if let Some(cmp_block) = cmp {
10802 if let Some(mode) = detect_sort_block_fast(cmp_block) {
10803 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
10804 } else {
10805 let cmp_block = cmp_block.clone();
10806 let subs = self.subs.clone();
10807 let scope_capture = self.scope.capture();
10808 items.par_sort_by(|a, b| {
10809 let mut local_interp = Interpreter::new();
10810 local_interp.subs = subs.clone();
10811 local_interp.scope.restore_capture(&scope_capture);
10812 let _ = local_interp.scope.set_scalar("a", a.clone());
10813 let _ = local_interp.scope.set_scalar("b", b.clone());
10814 let _ = local_interp.scope.set_scalar("_0", a.clone());
10815 let _ = local_interp.scope.set_scalar("_1", b.clone());
10816 match local_interp.exec_block(&cmp_block) {
10817 Ok(v) => {
10818 let n = v.to_int();
10819 if n < 0 {
10820 std::cmp::Ordering::Less
10821 } else if n > 0 {
10822 std::cmp::Ordering::Greater
10823 } else {
10824 std::cmp::Ordering::Equal
10825 }
10826 }
10827 Err(_) => std::cmp::Ordering::Equal,
10828 }
10829 });
10830 }
10831 } else {
10832 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
10833 }
10834 pmap_progress.tick();
10835 pmap_progress.finish();
10836 Ok(PerlValue::array(items))
10837 }
10838
10839 ExprKind::ReduceExpr { block, list } => {
10840 let list_val = self.eval_expr(list)?;
10841 let items = list_val.to_list();
10842 if items.is_empty() {
10843 return Ok(PerlValue::UNDEF);
10844 }
10845 if items.len() == 1 {
10846 return Ok(items.into_iter().next().unwrap());
10847 }
10848 let block = block.clone();
10849 let subs = self.subs.clone();
10850 let scope_capture = self.scope.capture();
10851 let mut acc = items[0].clone();
10852 for b in items.into_iter().skip(1) {
10853 let mut local_interp = Interpreter::new();
10854 local_interp.subs = subs.clone();
10855 local_interp.scope.restore_capture(&scope_capture);
10856 let _ = local_interp.scope.set_scalar("a", acc.clone());
10857 let _ = local_interp.scope.set_scalar("b", b.clone());
10858 let _ = local_interp.scope.set_scalar("_0", acc);
10859 let _ = local_interp.scope.set_scalar("_1", b);
10860 acc = match local_interp.exec_block(&block) {
10861 Ok(val) => val,
10862 Err(_) => PerlValue::UNDEF,
10863 };
10864 }
10865 Ok(acc)
10866 }
10867
10868 ExprKind::PReduceExpr {
10869 block,
10870 list,
10871 progress,
10872 } => {
10873 let show_progress = progress
10874 .as_ref()
10875 .map(|p| self.eval_expr(p))
10876 .transpose()?
10877 .map(|v| v.is_true())
10878 .unwrap_or(false);
10879 let list_val = self.eval_expr(list)?;
10880 let items = list_val.to_list();
10881 if items.is_empty() {
10882 return Ok(PerlValue::UNDEF);
10883 }
10884 if items.len() == 1 {
10885 return Ok(items.into_iter().next().unwrap());
10886 }
10887 let block = block.clone();
10888 let subs = self.subs.clone();
10889 let scope_capture = self.scope.capture();
10890 let pmap_progress = PmapProgress::new(show_progress, items.len());
10891
10892 let result = items
10893 .into_par_iter()
10894 .map(|x| {
10895 pmap_progress.tick();
10896 x
10897 })
10898 .reduce_with(|a, b| {
10899 let mut local_interp = Interpreter::new();
10900 local_interp.subs = subs.clone();
10901 local_interp.scope.restore_capture(&scope_capture);
10902 let _ = local_interp.scope.set_scalar("a", a.clone());
10903 let _ = local_interp.scope.set_scalar("b", b.clone());
10904 let _ = local_interp.scope.set_scalar("_0", a);
10905 let _ = local_interp.scope.set_scalar("_1", b);
10906 match local_interp.exec_block(&block) {
10907 Ok(val) => val,
10908 Err(_) => PerlValue::UNDEF,
10909 }
10910 });
10911 pmap_progress.finish();
10912 Ok(result.unwrap_or(PerlValue::UNDEF))
10913 }
10914
10915 ExprKind::PReduceInitExpr {
10916 init,
10917 block,
10918 list,
10919 progress,
10920 } => {
10921 let show_progress = progress
10922 .as_ref()
10923 .map(|p| self.eval_expr(p))
10924 .transpose()?
10925 .map(|v| v.is_true())
10926 .unwrap_or(false);
10927 let init_val = self.eval_expr(init)?;
10928 let list_val = self.eval_expr(list)?;
10929 let items = list_val.to_list();
10930 if items.is_empty() {
10931 return Ok(init_val);
10932 }
10933 let block = block.clone();
10934 let subs = self.subs.clone();
10935 let scope_capture = self.scope.capture();
10936 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
10937 if items.len() == 1 {
10938 return Ok(fold_preduce_init_step(
10939 &subs,
10940 cap,
10941 &block,
10942 preduce_init_fold_identity(&init_val),
10943 items.into_iter().next().unwrap(),
10944 ));
10945 }
10946 let pmap_progress = PmapProgress::new(show_progress, items.len());
10947 let result = items
10948 .into_par_iter()
10949 .fold(
10950 || preduce_init_fold_identity(&init_val),
10951 |acc, item| {
10952 pmap_progress.tick();
10953 fold_preduce_init_step(&subs, cap, &block, acc, item)
10954 },
10955 )
10956 .reduce(
10957 || preduce_init_fold_identity(&init_val),
10958 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
10959 );
10960 pmap_progress.finish();
10961 Ok(result)
10962 }
10963
10964 ExprKind::PMapReduceExpr {
10965 map_block,
10966 reduce_block,
10967 list,
10968 progress,
10969 } => {
10970 let show_progress = progress
10971 .as_ref()
10972 .map(|p| self.eval_expr(p))
10973 .transpose()?
10974 .map(|v| v.is_true())
10975 .unwrap_or(false);
10976 let list_val = self.eval_expr(list)?;
10977 let items = list_val.to_list();
10978 if items.is_empty() {
10979 return Ok(PerlValue::UNDEF);
10980 }
10981 let map_block = map_block.clone();
10982 let reduce_block = reduce_block.clone();
10983 let subs = self.subs.clone();
10984 let scope_capture = self.scope.capture();
10985 if items.len() == 1 {
10986 let mut local_interp = Interpreter::new();
10987 local_interp.subs = subs.clone();
10988 local_interp.scope.restore_capture(&scope_capture);
10989 local_interp.scope.set_topic(items[0].clone());
10990 return match local_interp.exec_block_no_scope(&map_block) {
10991 Ok(v) => Ok(v),
10992 Err(_) => Ok(PerlValue::UNDEF),
10993 };
10994 }
10995 let pmap_progress = PmapProgress::new(show_progress, items.len());
10996 let result = items
10997 .into_par_iter()
10998 .map(|item| {
10999 let mut local_interp = Interpreter::new();
11000 local_interp.subs = subs.clone();
11001 local_interp.scope.restore_capture(&scope_capture);
11002 local_interp.scope.set_topic(item);
11003 let val = match local_interp.exec_block_no_scope(&map_block) {
11004 Ok(val) => val,
11005 Err(_) => PerlValue::UNDEF,
11006 };
11007 pmap_progress.tick();
11008 val
11009 })
11010 .reduce_with(|a, b| {
11011 let mut local_interp = Interpreter::new();
11012 local_interp.subs = subs.clone();
11013 local_interp.scope.restore_capture(&scope_capture);
11014 let _ = local_interp.scope.set_scalar("a", a.clone());
11015 let _ = local_interp.scope.set_scalar("b", b.clone());
11016 let _ = local_interp.scope.set_scalar("_0", a);
11017 let _ = local_interp.scope.set_scalar("_1", b);
11018 match local_interp.exec_block_no_scope(&reduce_block) {
11019 Ok(val) => val,
11020 Err(_) => PerlValue::UNDEF,
11021 }
11022 });
11023 pmap_progress.finish();
11024 Ok(result.unwrap_or(PerlValue::UNDEF))
11025 }
11026
11027 ExprKind::PcacheExpr {
11028 block,
11029 list,
11030 progress,
11031 } => {
11032 let show_progress = progress
11033 .as_ref()
11034 .map(|p| self.eval_expr(p))
11035 .transpose()?
11036 .map(|v| v.is_true())
11037 .unwrap_or(false);
11038 let list_val = self.eval_expr(list)?;
11039 let items = list_val.to_list();
11040 let block = block.clone();
11041 let subs = self.subs.clone();
11042 let scope_capture = self.scope.capture();
11043 let cache = &*crate::pcache::GLOBAL_PCACHE;
11044 let pmap_progress = PmapProgress::new(show_progress, items.len());
11045 let results: Vec<PerlValue> = items
11046 .into_par_iter()
11047 .map(|item| {
11048 let k = crate::pcache::cache_key(&item);
11049 if let Some(v) = cache.get(&k) {
11050 pmap_progress.tick();
11051 return v.clone();
11052 }
11053 let mut local_interp = Interpreter::new();
11054 local_interp.subs = subs.clone();
11055 local_interp.scope.restore_capture(&scope_capture);
11056 local_interp.scope.set_topic(item.clone());
11057 let val = match local_interp.exec_block_no_scope(&block) {
11058 Ok(v) => v,
11059 Err(_) => PerlValue::UNDEF,
11060 };
11061 cache.insert(k, val.clone());
11062 pmap_progress.tick();
11063 val
11064 })
11065 .collect();
11066 pmap_progress.finish();
11067 Ok(PerlValue::array(results))
11068 }
11069
11070 ExprKind::PselectExpr { receivers, timeout } => {
11071 let mut rx_vals = Vec::with_capacity(receivers.len());
11072 for r in receivers {
11073 rx_vals.push(self.eval_expr(r)?);
11074 }
11075 let dur = if let Some(t) = timeout.as_ref() {
11076 Some(std::time::Duration::from_secs_f64(
11077 self.eval_expr(t)?.to_number().max(0.0),
11078 ))
11079 } else {
11080 None
11081 };
11082 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
11083 &rx_vals, dur, line,
11084 )?)
11085 }
11086
11087 ExprKind::Push { array, values } => {
11089 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
11090 }
11091 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
11092 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
11093 ExprKind::Unshift { array, values } => {
11094 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
11095 }
11096 ExprKind::Splice {
11097 array,
11098 offset,
11099 length,
11100 replacement,
11101 } => self.eval_splice_expr(
11102 array.as_ref(),
11103 offset.as_deref(),
11104 length.as_deref(),
11105 replacement.as_slice(),
11106 ctx,
11107 line,
11108 ),
11109 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
11110 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
11111 ExprKind::Keys(expr) => {
11112 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11113 let keys = Self::keys_from_value(val, line)?;
11114 if ctx == WantarrayCtx::List {
11115 Ok(keys)
11116 } else {
11117 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
11118 Ok(PerlValue::integer(n as i64))
11119 }
11120 }
11121 ExprKind::Values(expr) => {
11122 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11123 let vals = Self::values_from_value(val, line)?;
11124 if ctx == WantarrayCtx::List {
11125 Ok(vals)
11126 } else {
11127 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
11128 Ok(PerlValue::integer(n as i64))
11129 }
11130 }
11131 ExprKind::Each(_) => {
11132 Ok(PerlValue::array(vec![]))
11134 }
11135
11136 ExprKind::Chomp(expr) => {
11138 let val = self.eval_expr(expr)?;
11139 self.chomp_inplace_execute(val, expr)
11140 }
11141 ExprKind::Chop(expr) => {
11142 let val = self.eval_expr(expr)?;
11143 self.chop_inplace_execute(val, expr)
11144 }
11145 ExprKind::Length(expr) => {
11146 let val = self.eval_expr(expr)?;
11147 Ok(if let Some(a) = val.as_array_vec() {
11148 PerlValue::integer(a.len() as i64)
11149 } else if let Some(h) = val.as_hash_map() {
11150 PerlValue::integer(h.len() as i64)
11151 } else if let Some(b) = val.as_bytes_arc() {
11152 PerlValue::integer(b.len() as i64)
11153 } else {
11154 PerlValue::integer(val.to_string().len() as i64)
11155 })
11156 }
11157 ExprKind::Substr {
11158 string,
11159 offset,
11160 length,
11161 replacement,
11162 } => self.eval_substr_expr(
11163 string.as_ref(),
11164 offset.as_ref(),
11165 length.as_deref(),
11166 replacement.as_deref(),
11167 line,
11168 ),
11169 ExprKind::Index {
11170 string,
11171 substr,
11172 position,
11173 } => {
11174 let s = self.eval_expr(string)?.to_string();
11175 let sub = self.eval_expr(substr)?.to_string();
11176 let pos = if let Some(p) = position {
11177 self.eval_expr(p)?.to_int() as usize
11178 } else {
11179 0
11180 };
11181 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
11182 Ok(PerlValue::integer(result))
11183 }
11184 ExprKind::Rindex {
11185 string,
11186 substr,
11187 position,
11188 } => {
11189 let s = self.eval_expr(string)?.to_string();
11190 let sub = self.eval_expr(substr)?.to_string();
11191 let end = if let Some(p) = position {
11192 self.eval_expr(p)?.to_int() as usize + sub.len()
11193 } else {
11194 s.len()
11195 };
11196 let search = &s[..end.min(s.len())];
11197 let result = search.rfind(&sub).map(|i| i as i64).unwrap_or(-1);
11198 Ok(PerlValue::integer(result))
11199 }
11200 ExprKind::Sprintf { format, args } => {
11201 let fmt = self.eval_expr(format)?.to_string();
11202 let mut arg_vals = Vec::new();
11205 for a in args {
11206 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
11207 if let Some(items) = v.as_array_vec() {
11208 arg_vals.extend(items);
11209 } else {
11210 arg_vals.push(v);
11211 }
11212 }
11213 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
11214 Ok(PerlValue::string(s))
11215 }
11216 ExprKind::JoinExpr { separator, list } => {
11217 let sep = self.eval_expr(separator)?.to_string();
11218 let items = if let ExprKind::List(exprs) = &list.kind {
11222 let saved = self.wantarray_kind;
11223 self.wantarray_kind = WantarrayCtx::List;
11224 let mut vals = Vec::new();
11225 for e in exprs {
11226 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
11227 if let Some(items) = v.as_array_vec() {
11228 vals.extend(items);
11229 } else {
11230 vals.push(v);
11231 }
11232 }
11233 self.wantarray_kind = saved;
11234 vals
11235 } else {
11236 let saved = self.wantarray_kind;
11237 self.wantarray_kind = WantarrayCtx::List;
11238 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11239 self.wantarray_kind = saved;
11240 if let Some(items) = v.as_array_vec() {
11241 items
11242 } else {
11243 vec![v]
11244 }
11245 };
11246 let mut strs = Vec::with_capacity(items.len());
11247 for v in &items {
11248 strs.push(self.stringify_value(v.clone(), line)?);
11249 }
11250 Ok(PerlValue::string(strs.join(&sep)))
11251 }
11252 ExprKind::SplitExpr {
11253 pattern,
11254 string,
11255 limit,
11256 } => {
11257 let pat = self.eval_expr(pattern)?.to_string();
11258 let s = self.eval_expr(string)?.to_string();
11259 let lim = if let Some(l) = limit {
11260 self.eval_expr(l)?.to_int() as usize
11261 } else {
11262 0
11263 };
11264 let re = self.compile_regex(&pat, "", line)?;
11265 let parts: Vec<PerlValue> = if lim > 0 {
11266 re.splitn_strings(&s, lim)
11267 .into_iter()
11268 .map(PerlValue::string)
11269 .collect()
11270 } else {
11271 re.split_strings(&s)
11272 .into_iter()
11273 .map(PerlValue::string)
11274 .collect()
11275 };
11276 Ok(PerlValue::array(parts))
11277 }
11278
11279 ExprKind::Abs(expr) => {
11281 let val = self.eval_expr(expr)?;
11282 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
11283 return r;
11284 }
11285 Ok(PerlValue::float(val.to_number().abs()))
11286 }
11287 ExprKind::Int(expr) => {
11288 let val = self.eval_expr(expr)?;
11289 Ok(PerlValue::integer(val.to_number() as i64))
11290 }
11291 ExprKind::Sqrt(expr) => {
11292 let val = self.eval_expr(expr)?;
11293 Ok(PerlValue::float(val.to_number().sqrt()))
11294 }
11295 ExprKind::Sin(expr) => {
11296 let val = self.eval_expr(expr)?;
11297 Ok(PerlValue::float(val.to_number().sin()))
11298 }
11299 ExprKind::Cos(expr) => {
11300 let val = self.eval_expr(expr)?;
11301 Ok(PerlValue::float(val.to_number().cos()))
11302 }
11303 ExprKind::Atan2 { y, x } => {
11304 let yv = self.eval_expr(y)?.to_number();
11305 let xv = self.eval_expr(x)?.to_number();
11306 Ok(PerlValue::float(yv.atan2(xv)))
11307 }
11308 ExprKind::Exp(expr) => {
11309 let val = self.eval_expr(expr)?;
11310 Ok(PerlValue::float(val.to_number().exp()))
11311 }
11312 ExprKind::Log(expr) => {
11313 let val = self.eval_expr(expr)?;
11314 Ok(PerlValue::float(val.to_number().ln()))
11315 }
11316 ExprKind::Rand(upper) => {
11317 let u = match upper {
11318 Some(e) => self.eval_expr(e)?.to_number(),
11319 None => 1.0,
11320 };
11321 Ok(PerlValue::float(self.perl_rand(u)))
11322 }
11323 ExprKind::Srand(seed) => {
11324 let s = match seed {
11325 Some(e) => Some(self.eval_expr(e)?.to_number()),
11326 None => None,
11327 };
11328 Ok(PerlValue::integer(self.perl_srand(s)))
11329 }
11330 ExprKind::Hex(expr) => {
11331 let val = self.eval_expr(expr)?.to_string();
11332 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
11333 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
11334 Ok(PerlValue::integer(n))
11335 }
11336 ExprKind::Oct(expr) => {
11337 let val = self.eval_expr(expr)?.to_string();
11338 let s = val.trim();
11339 let n = if s.starts_with("0x") || s.starts_with("0X") {
11340 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
11341 } else if s.starts_with("0b") || s.starts_with("0B") {
11342 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
11343 } else if s.starts_with("0o") || s.starts_with("0O") {
11344 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
11345 } else {
11346 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
11347 };
11348 Ok(PerlValue::integer(n))
11349 }
11350
11351 ExprKind::Lc(expr) => Ok(PerlValue::string(
11353 self.eval_expr(expr)?.to_string().to_lowercase(),
11354 )),
11355 ExprKind::Uc(expr) => Ok(PerlValue::string(
11356 self.eval_expr(expr)?.to_string().to_uppercase(),
11357 )),
11358 ExprKind::Lcfirst(expr) => {
11359 let s = self.eval_expr(expr)?.to_string();
11360 let mut chars = s.chars();
11361 let result = match chars.next() {
11362 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
11363 None => String::new(),
11364 };
11365 Ok(PerlValue::string(result))
11366 }
11367 ExprKind::Ucfirst(expr) => {
11368 let s = self.eval_expr(expr)?.to_string();
11369 let mut chars = s.chars();
11370 let result = match chars.next() {
11371 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
11372 None => String::new(),
11373 };
11374 Ok(PerlValue::string(result))
11375 }
11376 ExprKind::Fc(expr) => Ok(PerlValue::string(default_case_fold_str(
11377 &self.eval_expr(expr)?.to_string(),
11378 ))),
11379 ExprKind::Crypt { plaintext, salt } => {
11380 let p = self.eval_expr(plaintext)?.to_string();
11381 let sl = self.eval_expr(salt)?.to_string();
11382 Ok(PerlValue::string(perl_crypt(&p, &sl)))
11383 }
11384 ExprKind::Pos(e) => {
11385 let key = match e {
11386 None => "_".to_string(),
11387 Some(expr) => match &expr.kind {
11388 ExprKind::ScalarVar(n) => n.clone(),
11389 _ => self.eval_expr(expr)?.to_string(),
11390 },
11391 };
11392 Ok(self
11393 .regex_pos
11394 .get(&key)
11395 .copied()
11396 .flatten()
11397 .map(|p| PerlValue::integer(p as i64))
11398 .unwrap_or(PerlValue::UNDEF))
11399 }
11400 ExprKind::Study(expr) => {
11401 let s = self.eval_expr(expr)?.to_string();
11402 Ok(Self::study_return_value(&s))
11403 }
11404
11405 ExprKind::Defined(expr) => {
11407 if let ExprKind::SubroutineRef(name) = &expr.kind {
11409 let exists = self.resolve_sub_by_name(name).is_some();
11410 return Ok(PerlValue::integer(if exists { 1 } else { 0 }));
11411 }
11412 let val = self.eval_expr(expr)?;
11413 Ok(PerlValue::integer(if val.is_undef() { 0 } else { 1 }))
11414 }
11415 ExprKind::Ref(expr) => {
11416 let val = self.eval_expr(expr)?;
11417 Ok(val.ref_type())
11418 }
11419 ExprKind::ScalarContext(expr) => {
11420 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11421 Ok(v.scalar_context())
11422 }
11423
11424 ExprKind::Chr(expr) => {
11426 let n = self.eval_expr(expr)?.to_int() as u32;
11427 Ok(PerlValue::string(
11428 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
11429 ))
11430 }
11431 ExprKind::Ord(expr) => {
11432 let s = self.eval_expr(expr)?.to_string();
11433 Ok(PerlValue::integer(
11434 s.chars().next().map(|c| c as i64).unwrap_or(0),
11435 ))
11436 }
11437
11438 ExprKind::OpenMyHandle { .. } => Err(PerlError::runtime(
11440 "internal: `open my $fh` handle used outside open()",
11441 line,
11442 )
11443 .into()),
11444 ExprKind::Open { handle, mode, file } => {
11445 if let ExprKind::OpenMyHandle { name } = &handle.kind {
11446 self.scope
11447 .declare_scalar_frozen(name, PerlValue::UNDEF, false, None)?;
11448 self.english_note_lexical_scalar(name);
11449 let mode_s = self.eval_expr(mode)?.to_string();
11450 let file_opt = if let Some(f) = file {
11451 Some(self.eval_expr(f)?.to_string())
11452 } else {
11453 None
11454 };
11455 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
11456 self.scope.set_scalar(name, ret.clone())?;
11457 return Ok(ret);
11458 }
11459 let handle_s = self.eval_expr(handle)?.to_string();
11460 let handle_name = self.resolve_io_handle_name(&handle_s);
11461 let mode_s = self.eval_expr(mode)?.to_string();
11462 let file_opt = if let Some(f) = file {
11463 Some(self.eval_expr(f)?.to_string())
11464 } else {
11465 None
11466 };
11467 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
11468 .map_err(Into::into)
11469 }
11470 ExprKind::Close(expr) => {
11471 let s = self.eval_expr(expr)?.to_string();
11472 let name = self.resolve_io_handle_name(&s);
11473 self.close_builtin_execute(name).map_err(Into::into)
11474 }
11475 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
11476 self.readline_builtin_execute_list(handle.as_deref())
11477 } else {
11478 self.readline_builtin_execute(handle.as_deref())
11479 }
11480 .map_err(Into::into),
11481 ExprKind::Eof(expr) => match expr {
11482 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
11483 Some(e) => {
11484 let name = self.eval_expr(e)?;
11485 self.eof_builtin_execute(&[name], line).map_err(Into::into)
11486 }
11487 },
11488
11489 ExprKind::Opendir { handle, path } => {
11490 let h = self.eval_expr(handle)?.to_string();
11491 let p = self.eval_expr(path)?.to_string();
11492 Ok(self.opendir_handle(&h, &p))
11493 }
11494 ExprKind::Readdir(e) => {
11495 let h = self.eval_expr(e)?.to_string();
11496 Ok(if ctx == WantarrayCtx::List {
11497 self.readdir_handle_list(&h)
11498 } else {
11499 self.readdir_handle(&h)
11500 })
11501 }
11502 ExprKind::Closedir(e) => {
11503 let h = self.eval_expr(e)?.to_string();
11504 Ok(self.closedir_handle(&h))
11505 }
11506 ExprKind::Rewinddir(e) => {
11507 let h = self.eval_expr(e)?.to_string();
11508 Ok(self.rewinddir_handle(&h))
11509 }
11510 ExprKind::Telldir(e) => {
11511 let h = self.eval_expr(e)?.to_string();
11512 Ok(self.telldir_handle(&h))
11513 }
11514 ExprKind::Seekdir { handle, position } => {
11515 let h = self.eval_expr(handle)?.to_string();
11516 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
11517 Ok(self.seekdir_handle(&h, pos))
11518 }
11519
11520 ExprKind::FileTest { op, expr } => {
11522 let path = self.eval_expr(expr)?.to_string();
11523 if matches!(op, 'M' | 'A' | 'C') {
11525 #[cfg(unix)]
11526 {
11527 return match crate::perl_fs::filetest_age_days(&path, *op) {
11528 Some(days) => Ok(PerlValue::float(days)),
11529 None => Ok(PerlValue::UNDEF),
11530 };
11531 }
11532 #[cfg(not(unix))]
11533 return Ok(PerlValue::UNDEF);
11534 }
11535 if *op == 's' {
11537 return match std::fs::metadata(&path) {
11538 Ok(m) => Ok(PerlValue::integer(m.len() as i64)),
11539 Err(_) => Ok(PerlValue::UNDEF),
11540 };
11541 }
11542 let result = match op {
11543 'e' => std::path::Path::new(&path).exists(),
11544 'f' => std::path::Path::new(&path).is_file(),
11545 'd' => std::path::Path::new(&path).is_dir(),
11546 'l' => std::path::Path::new(&path).is_symlink(),
11547 #[cfg(unix)]
11548 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
11549 #[cfg(not(unix))]
11550 'r' => std::fs::metadata(&path).is_ok(),
11551 #[cfg(unix)]
11552 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
11553 #[cfg(not(unix))]
11554 'w' => std::fs::metadata(&path).is_ok(),
11555 #[cfg(unix)]
11556 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
11557 #[cfg(not(unix))]
11558 'x' => false,
11559 #[cfg(unix)]
11560 'o' => crate::perl_fs::filetest_owned_effective(&path),
11561 #[cfg(not(unix))]
11562 'o' => false,
11563 #[cfg(unix)]
11564 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
11565 #[cfg(not(unix))]
11566 'R' => false,
11567 #[cfg(unix)]
11568 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
11569 #[cfg(not(unix))]
11570 'W' => false,
11571 #[cfg(unix)]
11572 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
11573 #[cfg(not(unix))]
11574 'X' => false,
11575 #[cfg(unix)]
11576 'O' => crate::perl_fs::filetest_owned_real(&path),
11577 #[cfg(not(unix))]
11578 'O' => false,
11579 'z' => std::fs::metadata(&path)
11580 .map(|m| m.len() == 0)
11581 .unwrap_or(true),
11582 't' => crate::perl_fs::filetest_is_tty(&path),
11583 #[cfg(unix)]
11584 'p' => crate::perl_fs::filetest_is_pipe(&path),
11585 #[cfg(not(unix))]
11586 'p' => false,
11587 #[cfg(unix)]
11588 'S' => crate::perl_fs::filetest_is_socket(&path),
11589 #[cfg(not(unix))]
11590 'S' => false,
11591 #[cfg(unix)]
11592 'b' => crate::perl_fs::filetest_is_block_device(&path),
11593 #[cfg(not(unix))]
11594 'b' => false,
11595 #[cfg(unix)]
11596 'c' => crate::perl_fs::filetest_is_char_device(&path),
11597 #[cfg(not(unix))]
11598 'c' => false,
11599 #[cfg(unix)]
11600 'u' => crate::perl_fs::filetest_is_setuid(&path),
11601 #[cfg(not(unix))]
11602 'u' => false,
11603 #[cfg(unix)]
11604 'g' => crate::perl_fs::filetest_is_setgid(&path),
11605 #[cfg(not(unix))]
11606 'g' => false,
11607 #[cfg(unix)]
11608 'k' => crate::perl_fs::filetest_is_sticky(&path),
11609 #[cfg(not(unix))]
11610 'k' => false,
11611 'T' => crate::perl_fs::filetest_is_text(&path),
11612 'B' => crate::perl_fs::filetest_is_binary(&path),
11613 _ => false,
11614 };
11615 Ok(PerlValue::integer(if result { 1 } else { 0 }))
11616 }
11617
11618 ExprKind::System(args) => {
11620 let mut cmd_args = Vec::new();
11621 for a in args {
11622 cmd_args.push(self.eval_expr(a)?.to_string());
11623 }
11624 if cmd_args.is_empty() {
11625 return Ok(PerlValue::integer(-1));
11626 }
11627 let status = Command::new("sh")
11628 .arg("-c")
11629 .arg(cmd_args.join(" "))
11630 .status();
11631 match status {
11632 Ok(s) => {
11633 self.record_child_exit_status(s);
11634 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
11635 }
11636 Err(e) => {
11637 self.apply_io_error_to_errno(&e);
11638 Ok(PerlValue::integer(-1))
11639 }
11640 }
11641 }
11642 ExprKind::Exec(args) => {
11643 let mut cmd_args = Vec::new();
11644 for a in args {
11645 cmd_args.push(self.eval_expr(a)?.to_string());
11646 }
11647 if cmd_args.is_empty() {
11648 return Ok(PerlValue::integer(-1));
11649 }
11650 let status = Command::new("sh")
11651 .arg("-c")
11652 .arg(cmd_args.join(" "))
11653 .status();
11654 match status {
11655 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
11656 Err(e) => {
11657 self.apply_io_error_to_errno(&e);
11658 Ok(PerlValue::integer(-1))
11659 }
11660 }
11661 }
11662 ExprKind::Eval(expr) => {
11663 self.eval_nesting += 1;
11664 let out = match &expr.kind {
11665 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
11666 Ok(v) => {
11667 self.clear_eval_error();
11668 Ok(v)
11669 }
11670 Err(FlowOrError::Error(e)) => {
11671 self.set_eval_error_from_perl_error(&e);
11672 Ok(PerlValue::UNDEF)
11673 }
11674 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
11675 },
11676 _ => {
11677 let code = self.eval_expr(expr)?.to_string();
11678 match crate::parse_and_run_string(&code, self) {
11680 Ok(v) => {
11681 self.clear_eval_error();
11682 Ok(v)
11683 }
11684 Err(e) => {
11685 self.set_eval_error(e.to_string());
11686 Ok(PerlValue::UNDEF)
11687 }
11688 }
11689 }
11690 };
11691 self.eval_nesting -= 1;
11692 out
11693 }
11694 ExprKind::Do(expr) => match &expr.kind {
11695 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
11696 _ => {
11697 let val = self.eval_expr(expr)?;
11698 let filename = val.to_string();
11699 match read_file_text_perl_compat(&filename) {
11700 Ok(code) => {
11701 let code = crate::data_section::strip_perl_end_marker(&code);
11702 match crate::parse_and_run_string_in_file(code, self, &filename) {
11703 Ok(v) => Ok(v),
11704 Err(e) => {
11705 self.set_eval_error(e.to_string());
11706 Ok(PerlValue::UNDEF)
11707 }
11708 }
11709 }
11710 Err(e) => {
11711 self.apply_io_error_to_errno(&e);
11712 Ok(PerlValue::UNDEF)
11713 }
11714 }
11715 }
11716 },
11717 ExprKind::Require(expr) => {
11718 let spec = self.eval_expr(expr)?.to_string();
11719 self.require_execute(&spec, line)
11720 .map_err(FlowOrError::Error)
11721 }
11722 ExprKind::Exit(code) => {
11723 let c = if let Some(e) = code {
11724 self.eval_expr(e)?.to_int() as i32
11725 } else {
11726 0
11727 };
11728 Err(PerlError::new(ErrorKind::Exit(c), "", line, &self.file).into())
11729 }
11730 ExprKind::Chdir(expr) => {
11731 let path = self.eval_expr(expr)?.to_string();
11732 match std::env::set_current_dir(&path) {
11733 Ok(_) => Ok(PerlValue::integer(1)),
11734 Err(e) => {
11735 self.apply_io_error_to_errno(&e);
11736 Ok(PerlValue::integer(0))
11737 }
11738 }
11739 }
11740 ExprKind::Mkdir { path, mode: _ } => {
11741 let p = self.eval_expr(path)?.to_string();
11742 match std::fs::create_dir(&p) {
11743 Ok(_) => Ok(PerlValue::integer(1)),
11744 Err(e) => {
11745 self.apply_io_error_to_errno(&e);
11746 Ok(PerlValue::integer(0))
11747 }
11748 }
11749 }
11750 ExprKind::Unlink(args) => {
11751 let mut count = 0i64;
11752 for a in args {
11753 let path = self.eval_expr(a)?.to_string();
11754 if std::fs::remove_file(&path).is_ok() {
11755 count += 1;
11756 }
11757 }
11758 Ok(PerlValue::integer(count))
11759 }
11760 ExprKind::Rename { old, new } => {
11761 let o = self.eval_expr(old)?.to_string();
11762 let n = self.eval_expr(new)?.to_string();
11763 Ok(crate::perl_fs::rename_paths(&o, &n))
11764 }
11765 ExprKind::Chmod(args) => {
11766 let mode = self.eval_expr(&args[0])?.to_int();
11767 let mut paths = Vec::new();
11768 for a in &args[1..] {
11769 paths.push(self.eval_expr(a)?.to_string());
11770 }
11771 Ok(PerlValue::integer(crate::perl_fs::chmod_paths(
11772 &paths, mode,
11773 )))
11774 }
11775 ExprKind::Chown(args) => {
11776 let uid = self.eval_expr(&args[0])?.to_int();
11777 let gid = self.eval_expr(&args[1])?.to_int();
11778 let mut paths = Vec::new();
11779 for a in &args[2..] {
11780 paths.push(self.eval_expr(a)?.to_string());
11781 }
11782 Ok(PerlValue::integer(crate::perl_fs::chown_paths(
11783 &paths, uid, gid,
11784 )))
11785 }
11786 ExprKind::Stat(e) => {
11787 let path = self.eval_expr(e)?.to_string();
11788 Ok(crate::perl_fs::stat_path(&path, false))
11789 }
11790 ExprKind::Lstat(e) => {
11791 let path = self.eval_expr(e)?.to_string();
11792 Ok(crate::perl_fs::stat_path(&path, true))
11793 }
11794 ExprKind::Link { old, new } => {
11795 let o = self.eval_expr(old)?.to_string();
11796 let n = self.eval_expr(new)?.to_string();
11797 Ok(crate::perl_fs::link_hard(&o, &n))
11798 }
11799 ExprKind::Symlink { old, new } => {
11800 let o = self.eval_expr(old)?.to_string();
11801 let n = self.eval_expr(new)?.to_string();
11802 Ok(crate::perl_fs::link_sym(&o, &n))
11803 }
11804 ExprKind::Readlink(e) => {
11805 let path = self.eval_expr(e)?.to_string();
11806 Ok(crate::perl_fs::read_link(&path))
11807 }
11808 ExprKind::Files(args) => {
11809 let dir = if args.is_empty() {
11810 ".".to_string()
11811 } else {
11812 self.eval_expr(&args[0])?.to_string()
11813 };
11814 Ok(crate::perl_fs::list_files(&dir))
11815 }
11816 ExprKind::Filesf(args) => {
11817 let dir = if args.is_empty() {
11818 ".".to_string()
11819 } else {
11820 self.eval_expr(&args[0])?.to_string()
11821 };
11822 Ok(crate::perl_fs::list_filesf(&dir))
11823 }
11824 ExprKind::FilesfRecursive(args) => {
11825 let dir = if args.is_empty() {
11826 ".".to_string()
11827 } else {
11828 self.eval_expr(&args[0])?.to_string()
11829 };
11830 Ok(PerlValue::iterator(Arc::new(
11831 crate::value::FsWalkIterator::new(&dir, true),
11832 )))
11833 }
11834 ExprKind::Dirs(args) => {
11835 let dir = if args.is_empty() {
11836 ".".to_string()
11837 } else {
11838 self.eval_expr(&args[0])?.to_string()
11839 };
11840 Ok(crate::perl_fs::list_dirs(&dir))
11841 }
11842 ExprKind::DirsRecursive(args) => {
11843 let dir = if args.is_empty() {
11844 ".".to_string()
11845 } else {
11846 self.eval_expr(&args[0])?.to_string()
11847 };
11848 Ok(PerlValue::iterator(Arc::new(
11849 crate::value::FsWalkIterator::new(&dir, false),
11850 )))
11851 }
11852 ExprKind::SymLinks(args) => {
11853 let dir = if args.is_empty() {
11854 ".".to_string()
11855 } else {
11856 self.eval_expr(&args[0])?.to_string()
11857 };
11858 Ok(crate::perl_fs::list_sym_links(&dir))
11859 }
11860 ExprKind::Sockets(args) => {
11861 let dir = if args.is_empty() {
11862 ".".to_string()
11863 } else {
11864 self.eval_expr(&args[0])?.to_string()
11865 };
11866 Ok(crate::perl_fs::list_sockets(&dir))
11867 }
11868 ExprKind::Pipes(args) => {
11869 let dir = if args.is_empty() {
11870 ".".to_string()
11871 } else {
11872 self.eval_expr(&args[0])?.to_string()
11873 };
11874 Ok(crate::perl_fs::list_pipes(&dir))
11875 }
11876 ExprKind::BlockDevices(args) => {
11877 let dir = if args.is_empty() {
11878 ".".to_string()
11879 } else {
11880 self.eval_expr(&args[0])?.to_string()
11881 };
11882 Ok(crate::perl_fs::list_block_devices(&dir))
11883 }
11884 ExprKind::CharDevices(args) => {
11885 let dir = if args.is_empty() {
11886 ".".to_string()
11887 } else {
11888 self.eval_expr(&args[0])?.to_string()
11889 };
11890 Ok(crate::perl_fs::list_char_devices(&dir))
11891 }
11892 ExprKind::Executables(args) => {
11893 let dir = if args.is_empty() {
11894 ".".to_string()
11895 } else {
11896 self.eval_expr(&args[0])?.to_string()
11897 };
11898 Ok(crate::perl_fs::list_executables(&dir))
11899 }
11900 ExprKind::Glob(args) => {
11901 let mut pats = Vec::new();
11902 for a in args {
11903 pats.push(self.eval_expr(a)?.to_string());
11904 }
11905 Ok(crate::perl_fs::glob_patterns(&pats))
11906 }
11907 ExprKind::GlobPar { args, progress } => {
11908 let mut pats = Vec::new();
11909 for a in args {
11910 pats.push(self.eval_expr(a)?.to_string());
11911 }
11912 let show_progress = progress
11913 .as_ref()
11914 .map(|p| self.eval_expr(p))
11915 .transpose()?
11916 .map(|v| v.is_true())
11917 .unwrap_or(false);
11918 if show_progress {
11919 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
11920 } else {
11921 Ok(crate::perl_fs::glob_par_patterns(&pats))
11922 }
11923 }
11924 ExprKind::ParSed { args, progress } => {
11925 let has_progress = progress.is_some();
11926 let mut vals: Vec<PerlValue> = Vec::new();
11927 for a in args {
11928 vals.push(self.eval_expr(a)?);
11929 }
11930 if let Some(p) = progress {
11931 vals.push(self.eval_expr(p.as_ref())?);
11932 }
11933 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
11934 }
11935 ExprKind::Bless { ref_expr, class } => {
11936 let val = self.eval_expr(ref_expr)?;
11937 let class_name = if let Some(c) = class {
11938 self.eval_expr(c)?.to_string()
11939 } else {
11940 self.scope.get_scalar("__PACKAGE__").to_string()
11941 };
11942 Ok(PerlValue::blessed(Arc::new(
11943 crate::value::BlessedRef::new_blessed(class_name, val),
11944 )))
11945 }
11946 ExprKind::Caller(_) => {
11947 Ok(PerlValue::array(vec![
11949 PerlValue::string("main".into()),
11950 PerlValue::string(self.file.clone()),
11951 PerlValue::integer(line as i64),
11952 ]))
11953 }
11954 ExprKind::Wantarray => Ok(match self.wantarray_kind {
11955 WantarrayCtx::Void => PerlValue::UNDEF,
11956 WantarrayCtx::Scalar => PerlValue::integer(0),
11957 WantarrayCtx::List => PerlValue::integer(1),
11958 }),
11959
11960 ExprKind::List(exprs) => {
11961 if ctx == WantarrayCtx::Scalar {
11963 if let Some(last) = exprs.last() {
11964 for e in &exprs[..exprs.len() - 1] {
11966 self.eval_expr(e)?;
11967 }
11968 return self.eval_expr(last);
11969 } else {
11970 return Ok(PerlValue::UNDEF);
11971 }
11972 }
11973 let mut vals = Vec::new();
11974 for e in exprs {
11975 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
11976 if let Some(items) = v.as_array_vec() {
11977 vals.extend(items);
11978 } else {
11979 vals.push(v);
11980 }
11981 }
11982 if vals.len() == 1 {
11983 Ok(vals.pop().unwrap())
11984 } else {
11985 Ok(PerlValue::array(vals))
11986 }
11987 }
11988
11989 ExprKind::PostfixIf { expr, condition } => {
11991 if self.eval_postfix_condition(condition)? {
11992 self.eval_expr(expr)
11993 } else {
11994 Ok(PerlValue::UNDEF)
11995 }
11996 }
11997 ExprKind::PostfixUnless { expr, condition } => {
11998 if !self.eval_postfix_condition(condition)? {
11999 self.eval_expr(expr)
12000 } else {
12001 Ok(PerlValue::UNDEF)
12002 }
12003 }
12004 ExprKind::PostfixWhile { expr, condition } => {
12005 let is_do_block = matches!(
12008 &expr.kind,
12009 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
12010 );
12011 let mut last = PerlValue::UNDEF;
12012 if is_do_block {
12013 loop {
12014 last = self.eval_expr(expr)?;
12015 if !self.eval_postfix_condition(condition)? {
12016 break;
12017 }
12018 }
12019 } else {
12020 loop {
12021 if !self.eval_postfix_condition(condition)? {
12022 break;
12023 }
12024 last = self.eval_expr(expr)?;
12025 }
12026 }
12027 Ok(last)
12028 }
12029 ExprKind::PostfixUntil { expr, condition } => {
12030 let is_do_block = matches!(
12031 &expr.kind,
12032 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
12033 );
12034 let mut last = PerlValue::UNDEF;
12035 if is_do_block {
12036 loop {
12037 last = self.eval_expr(expr)?;
12038 if self.eval_postfix_condition(condition)? {
12039 break;
12040 }
12041 }
12042 } else {
12043 loop {
12044 if self.eval_postfix_condition(condition)? {
12045 break;
12046 }
12047 last = self.eval_expr(expr)?;
12048 }
12049 }
12050 Ok(last)
12051 }
12052 ExprKind::PostfixForeach { expr, list } => {
12053 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
12054 let mut last = PerlValue::UNDEF;
12055 for item in items {
12056 self.scope.set_topic(item);
12057 last = self.eval_expr(expr)?;
12058 }
12059 Ok(last)
12060 }
12061 }
12062 }
12063
12064 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
12067 match op {
12068 BinOp::Add => Some("+"),
12069 BinOp::Sub => Some("-"),
12070 BinOp::Mul => Some("*"),
12071 BinOp::Div => Some("/"),
12072 BinOp::Mod => Some("%"),
12073 BinOp::Pow => Some("**"),
12074 BinOp::Concat => Some("."),
12075 BinOp::StrEq => Some("eq"),
12076 BinOp::NumEq => Some("=="),
12077 BinOp::StrNe => Some("ne"),
12078 BinOp::NumNe => Some("!="),
12079 BinOp::StrLt => Some("lt"),
12080 BinOp::StrGt => Some("gt"),
12081 BinOp::StrLe => Some("le"),
12082 BinOp::StrGe => Some("ge"),
12083 BinOp::NumLt => Some("<"),
12084 BinOp::NumGt => Some(">"),
12085 BinOp::NumLe => Some("<="),
12086 BinOp::NumGe => Some(">="),
12087 BinOp::Spaceship => Some("<=>"),
12088 BinOp::StrCmp => Some("cmp"),
12089 _ => None,
12090 }
12091 }
12092
12093 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
12095 map.get("").or_else(|| map.get("\"\""))
12096 }
12097
12098 pub(crate) fn stringify_value(
12100 &mut self,
12101 v: PerlValue,
12102 line: usize,
12103 ) -> Result<String, FlowOrError> {
12104 if let Some(r) = self.try_overload_stringify(&v, line) {
12105 let pv = r?;
12106 return Ok(pv.to_string());
12107 }
12108 Ok(v.to_string())
12109 }
12110
12111 pub(crate) fn perl_sprintf_stringify(
12113 &mut self,
12114 fmt: &str,
12115 args: &[PerlValue],
12116 line: usize,
12117 ) -> Result<String, FlowOrError> {
12118 perl_sprintf_format_with(fmt, args, |v| self.stringify_value(v.clone(), line))
12119 }
12120
12121 pub(crate) fn render_format_template(
12123 &mut self,
12124 tmpl: &crate::format::FormatTemplate,
12125 line: usize,
12126 ) -> Result<String, FlowOrError> {
12127 use crate::format::{FormatRecord, PictureSegment};
12128 let mut buf = String::new();
12129 for rec in &tmpl.records {
12130 match rec {
12131 FormatRecord::Literal(s) => {
12132 buf.push_str(s);
12133 buf.push('\n');
12134 }
12135 FormatRecord::Picture { segments, exprs } => {
12136 let mut vals: Vec<String> = Vec::new();
12137 for e in exprs {
12138 let v = self.eval_expr(e)?;
12139 vals.push(self.stringify_value(v, line)?);
12140 }
12141 let mut vi = 0usize;
12142 let mut line_out = String::new();
12143 for seg in segments {
12144 match seg {
12145 PictureSegment::Literal(t) => line_out.push_str(t),
12146 PictureSegment::Field {
12147 width,
12148 align,
12149 kind: _,
12150 } => {
12151 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
12152 vi += 1;
12153 line_out.push_str(&crate::format::pad_field(s, *width, *align));
12154 }
12155 }
12156 }
12157 buf.push_str(line_out.trim_end());
12158 buf.push('\n');
12159 }
12160 }
12161 }
12162 Ok(buf)
12163 }
12164
12165 pub(crate) fn resolve_write_output_handle(
12167 &self,
12168 v: &PerlValue,
12169 line: usize,
12170 ) -> PerlResult<String> {
12171 if let Some(n) = v.as_io_handle_name() {
12172 let n = self.resolve_io_handle_name(&n);
12173 if self.is_bound_handle(&n) {
12174 return Ok(n);
12175 }
12176 }
12177 if let Some(s) = v.as_str() {
12178 if self.is_bound_handle(&s) {
12179 return Ok(self.resolve_io_handle_name(&s));
12180 }
12181 }
12182 let s = v.to_string();
12183 if self.is_bound_handle(&s) {
12184 return Ok(self.resolve_io_handle_name(&s));
12185 }
12186 Err(PerlError::runtime(
12187 format!("write: invalid or unopened filehandle {}", s),
12188 line,
12189 ))
12190 }
12191
12192 pub(crate) fn write_format_execute(
12196 &mut self,
12197 args: &[PerlValue],
12198 line: usize,
12199 ) -> PerlResult<PerlValue> {
12200 let handle_name = match args.len() {
12201 0 => self.default_print_handle.clone(),
12202 1 => self.resolve_write_output_handle(&args[0], line)?,
12203 _ => {
12204 return Err(PerlError::runtime("write: too many arguments", line));
12205 }
12206 };
12207 let pkg = self.current_package();
12208 let mut fmt_name = self.scope.get_scalar("~").to_string();
12209 if fmt_name.is_empty() {
12210 fmt_name = "STDOUT".to_string();
12211 }
12212 let key = format!("{}::{}", pkg, fmt_name);
12213 let tmpl = self
12214 .format_templates
12215 .get(&key)
12216 .map(Arc::clone)
12217 .ok_or_else(|| {
12218 PerlError::runtime(
12219 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
12220 line,
12221 )
12222 })?;
12223 let out = self
12224 .render_format_template(&tmpl, line)
12225 .map_err(|e| match e {
12226 FlowOrError::Error(e) => e,
12227 FlowOrError::Flow(_) => PerlError::runtime("write: unexpected control flow", line),
12228 })?;
12229 self.write_formatted_print(handle_name.as_str(), &out, line)?;
12230 Ok(PerlValue::integer(1))
12231 }
12232
12233 pub(crate) fn try_overload_stringify(
12234 &mut self,
12235 v: &PerlValue,
12236 line: usize,
12237 ) -> Option<ExecResult> {
12238 if let Some(c) = v.as_class_inst() {
12240 let method_name = c
12241 .def
12242 .method("stringify")
12243 .or_else(|| c.def.method("\"\""))
12244 .filter(|m| m.body.is_some())?;
12245 let body = method_name.body.clone().unwrap();
12246 let params = method_name.params.clone();
12247 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
12248 }
12249 let br = v.as_blessed_ref()?;
12250 let class = br.class.clone();
12251 let map = self.overload_table.get(&class)?;
12252 let sub_short = Self::overload_stringify_method(map)?;
12253 let fq = format!("{}::{}", class, sub_short);
12254 let sub = self.subs.get(&fq)?.clone();
12255 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
12256 }
12257
12258 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
12260 match key {
12261 "+" => Some("op_add"),
12262 "-" => Some("op_sub"),
12263 "*" => Some("op_mul"),
12264 "/" => Some("op_div"),
12265 "%" => Some("op_mod"),
12266 "**" => Some("op_pow"),
12267 "." => Some("op_concat"),
12268 "==" => Some("op_eq"),
12269 "!=" => Some("op_ne"),
12270 "<" => Some("op_lt"),
12271 ">" => Some("op_gt"),
12272 "<=" => Some("op_le"),
12273 ">=" => Some("op_ge"),
12274 "<=>" => Some("op_spaceship"),
12275 "eq" => Some("op_str_eq"),
12276 "ne" => Some("op_str_ne"),
12277 "lt" => Some("op_str_lt"),
12278 "gt" => Some("op_str_gt"),
12279 "le" => Some("op_str_le"),
12280 "ge" => Some("op_str_ge"),
12281 "cmp" => Some("op_cmp"),
12282 _ => None,
12283 }
12284 }
12285
12286 pub(crate) fn try_overload_binop(
12287 &mut self,
12288 op: BinOp,
12289 lv: &PerlValue,
12290 rv: &PerlValue,
12291 line: usize,
12292 ) -> Option<ExecResult> {
12293 let key = Self::overload_key_for_binop(op)?;
12294 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
12296 (Some(c.def.clone()), lv.clone(), rv.clone())
12297 } else if let Some(c) = rv.as_class_inst() {
12298 (Some(c.def.clone()), rv.clone(), lv.clone())
12299 } else {
12300 (None, lv.clone(), rv.clone())
12301 };
12302 if let Some(ref def) = ci_def {
12303 if let Some(method_name) = Self::overload_method_name_for_key(key) {
12304 if let Some((m, _)) = self.find_class_method(def, method_name) {
12305 if let Some(ref body) = m.body {
12306 let params = m.params.clone();
12307 return Some(self.call_class_method(
12308 body,
12309 ¶ms,
12310 vec![invocant, other],
12311 line,
12312 ));
12313 }
12314 }
12315 }
12316 }
12317 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
12319 (br.class.clone(), lv.clone(), rv.clone())
12320 } else if let Some(br) = rv.as_blessed_ref() {
12321 (br.class.clone(), rv.clone(), lv.clone())
12322 } else {
12323 return None;
12324 };
12325 let map = self.overload_table.get(&class)?;
12326 let sub_short = if let Some(s) = map.get(key) {
12327 s.clone()
12328 } else if let Some(nm) = map.get("nomethod") {
12329 let fq = format!("{}::{}", class, nm);
12330 let sub = self.subs.get(&fq)?.clone();
12331 return Some(self.call_sub(
12332 &sub,
12333 vec![invocant, other, PerlValue::string(key.to_string())],
12334 WantarrayCtx::Scalar,
12335 line,
12336 ));
12337 } else {
12338 return None;
12339 };
12340 let fq = format!("{}::{}", class, sub_short);
12341 let sub = self.subs.get(&fq)?.clone();
12342 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
12343 }
12344
12345 pub(crate) fn try_overload_unary_dispatch(
12347 &mut self,
12348 op_key: &str,
12349 val: &PerlValue,
12350 line: usize,
12351 ) -> Option<ExecResult> {
12352 if let Some(c) = val.as_class_inst() {
12354 let method_name = match op_key {
12355 "neg" => "op_neg",
12356 "bool" => "op_bool",
12357 "abs" => "op_abs",
12358 "0+" => "op_numify",
12359 _ => return None,
12360 };
12361 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
12362 if let Some(ref body) = m.body {
12363 let params = m.params.clone();
12364 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
12365 }
12366 }
12367 return None;
12368 }
12369 let br = val.as_blessed_ref()?;
12371 let class = br.class.clone();
12372 let map = self.overload_table.get(&class)?;
12373 if let Some(s) = map.get(op_key) {
12374 let fq = format!("{}::{}", class, s);
12375 let sub = self.subs.get(&fq)?.clone();
12376 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
12377 }
12378 if let Some(nm) = map.get("nomethod") {
12379 let fq = format!("{}::{}", class, nm);
12380 let sub = self.subs.get(&fq)?.clone();
12381 return Some(self.call_sub(
12382 &sub,
12383 vec![val.clone(), PerlValue::string(op_key.to_string())],
12384 WantarrayCtx::Scalar,
12385 line,
12386 ));
12387 }
12388 None
12389 }
12390
12391 #[inline]
12392 fn eval_binop(
12393 &mut self,
12394 op: BinOp,
12395 lv: &PerlValue,
12396 rv: &PerlValue,
12397 _line: usize,
12398 ) -> ExecResult {
12399 Ok(match op {
12400 BinOp::Add => {
12403 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12404 PerlValue::integer(a.wrapping_add(b))
12405 } else {
12406 PerlValue::float(lv.to_number() + rv.to_number())
12407 }
12408 }
12409 BinOp::Sub => {
12410 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12411 PerlValue::integer(a.wrapping_sub(b))
12412 } else {
12413 PerlValue::float(lv.to_number() - rv.to_number())
12414 }
12415 }
12416 BinOp::Mul => {
12417 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12418 PerlValue::integer(a.wrapping_mul(b))
12419 } else {
12420 PerlValue::float(lv.to_number() * rv.to_number())
12421 }
12422 }
12423 BinOp::Div => {
12424 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12425 if b == 0 {
12426 return Err(PerlError::runtime("Illegal division by zero", _line).into());
12427 }
12428 if a % b == 0 {
12429 PerlValue::integer(a / b)
12430 } else {
12431 PerlValue::float(a as f64 / b as f64)
12432 }
12433 } else {
12434 let d = rv.to_number();
12435 if d == 0.0 {
12436 return Err(PerlError::runtime("Illegal division by zero", _line).into());
12437 }
12438 PerlValue::float(lv.to_number() / d)
12439 }
12440 }
12441 BinOp::Mod => {
12442 let d = rv.to_int();
12443 if d == 0 {
12444 return Err(PerlError::runtime("Illegal modulus zero", _line).into());
12445 }
12446 PerlValue::integer(lv.to_int() % d)
12447 }
12448 BinOp::Pow => {
12449 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12450 let int_pow = (b >= 0)
12451 .then(|| u32::try_from(b).ok())
12452 .flatten()
12453 .and_then(|bu| a.checked_pow(bu))
12454 .map(PerlValue::integer);
12455 int_pow.unwrap_or_else(|| PerlValue::float(lv.to_number().powf(rv.to_number())))
12456 } else {
12457 PerlValue::float(lv.to_number().powf(rv.to_number()))
12458 }
12459 }
12460 BinOp::Concat => {
12461 let mut s = String::new();
12462 lv.append_to(&mut s);
12463 rv.append_to(&mut s);
12464 PerlValue::string(s)
12465 }
12466 BinOp::NumEq => {
12467 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
12469 if a.def.name != b.def.name {
12470 PerlValue::integer(0)
12471 } else {
12472 let av = a.get_values();
12473 let bv = b.get_values();
12474 let eq = av.len() == bv.len()
12475 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
12476 PerlValue::integer(if eq { 1 } else { 0 })
12477 }
12478 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12479 PerlValue::integer(if a == b { 1 } else { 0 })
12480 } else {
12481 PerlValue::integer(if lv.to_number() == rv.to_number() {
12482 1
12483 } else {
12484 0
12485 })
12486 }
12487 }
12488 BinOp::NumNe => {
12489 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12490 PerlValue::integer(if a != b { 1 } else { 0 })
12491 } else {
12492 PerlValue::integer(if lv.to_number() != rv.to_number() {
12493 1
12494 } else {
12495 0
12496 })
12497 }
12498 }
12499 BinOp::NumLt => {
12500 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12501 PerlValue::integer(if a < b { 1 } else { 0 })
12502 } else {
12503 PerlValue::integer(if lv.to_number() < rv.to_number() {
12504 1
12505 } else {
12506 0
12507 })
12508 }
12509 }
12510 BinOp::NumGt => {
12511 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12512 PerlValue::integer(if a > b { 1 } else { 0 })
12513 } else {
12514 PerlValue::integer(if lv.to_number() > rv.to_number() {
12515 1
12516 } else {
12517 0
12518 })
12519 }
12520 }
12521 BinOp::NumLe => {
12522 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12523 PerlValue::integer(if a <= b { 1 } else { 0 })
12524 } else {
12525 PerlValue::integer(if lv.to_number() <= rv.to_number() {
12526 1
12527 } else {
12528 0
12529 })
12530 }
12531 }
12532 BinOp::NumGe => {
12533 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12534 PerlValue::integer(if a >= b { 1 } else { 0 })
12535 } else {
12536 PerlValue::integer(if lv.to_number() >= rv.to_number() {
12537 1
12538 } else {
12539 0
12540 })
12541 }
12542 }
12543 BinOp::Spaceship => {
12544 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12545 PerlValue::integer(if a < b {
12546 -1
12547 } else if a > b {
12548 1
12549 } else {
12550 0
12551 })
12552 } else {
12553 let a = lv.to_number();
12554 let b = rv.to_number();
12555 PerlValue::integer(if a < b {
12556 -1
12557 } else if a > b {
12558 1
12559 } else {
12560 0
12561 })
12562 }
12563 }
12564 BinOp::StrEq => PerlValue::integer(if lv.to_string() == rv.to_string() {
12565 1
12566 } else {
12567 0
12568 }),
12569 BinOp::StrNe => PerlValue::integer(if lv.to_string() != rv.to_string() {
12570 1
12571 } else {
12572 0
12573 }),
12574 BinOp::StrLt => PerlValue::integer(if lv.to_string() < rv.to_string() {
12575 1
12576 } else {
12577 0
12578 }),
12579 BinOp::StrGt => PerlValue::integer(if lv.to_string() > rv.to_string() {
12580 1
12581 } else {
12582 0
12583 }),
12584 BinOp::StrLe => PerlValue::integer(if lv.to_string() <= rv.to_string() {
12585 1
12586 } else {
12587 0
12588 }),
12589 BinOp::StrGe => PerlValue::integer(if lv.to_string() >= rv.to_string() {
12590 1
12591 } else {
12592 0
12593 }),
12594 BinOp::StrCmp => {
12595 let cmp = lv.to_string().cmp(&rv.to_string());
12596 PerlValue::integer(match cmp {
12597 std::cmp::Ordering::Less => -1,
12598 std::cmp::Ordering::Greater => 1,
12599 std::cmp::Ordering::Equal => 0,
12600 })
12601 }
12602 BinOp::BitAnd => {
12603 if let Some(s) = crate::value::set_intersection(lv, rv) {
12604 s
12605 } else {
12606 PerlValue::integer(lv.to_int() & rv.to_int())
12607 }
12608 }
12609 BinOp::BitOr => {
12610 if let Some(s) = crate::value::set_union(lv, rv) {
12611 s
12612 } else {
12613 PerlValue::integer(lv.to_int() | rv.to_int())
12614 }
12615 }
12616 BinOp::BitXor => PerlValue::integer(lv.to_int() ^ rv.to_int()),
12617 BinOp::ShiftLeft => PerlValue::integer(lv.to_int() << rv.to_int()),
12618 BinOp::ShiftRight => PerlValue::integer(lv.to_int() >> rv.to_int()),
12619 BinOp::LogAnd
12621 | BinOp::LogOr
12622 | BinOp::DefinedOr
12623 | BinOp::LogAndWord
12624 | BinOp::LogOrWord => unreachable!(),
12625 BinOp::BindMatch | BinOp::BindNotMatch => {
12626 unreachable!("regex bind handled in eval_expr BinOp arm")
12627 }
12628 })
12629 }
12630
12631 fn err_modify_symbolic_aggregate_deref_inc_dec(
12635 kind: Sigil,
12636 is_pre: bool,
12637 is_inc: bool,
12638 line: usize,
12639 ) -> FlowOrError {
12640 let agg = match kind {
12641 Sigil::Array => "array",
12642 Sigil::Hash => "hash",
12643 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
12644 };
12645 let op = match (is_pre, is_inc) {
12646 (true, true) => "preincrement (++)",
12647 (true, false) => "predecrement (--)",
12648 (false, true) => "postincrement (++)",
12649 (false, false) => "postdecrement (--)",
12650 };
12651 FlowOrError::Error(PerlError::runtime(
12652 format!("Can't modify {agg} dereference in {op}"),
12653 line,
12654 ))
12655 }
12656
12657 pub(crate) fn symbolic_scalar_ref_postfix(
12659 &mut self,
12660 ref_val: PerlValue,
12661 decrement: bool,
12662 line: usize,
12663 ) -> Result<PerlValue, FlowOrError> {
12664 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
12665 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12666 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
12667 Ok(old)
12668 }
12669
12670 pub(crate) fn assign_scalar_ref_deref(
12673 &mut self,
12674 ref_val: PerlValue,
12675 val: PerlValue,
12676 line: usize,
12677 ) -> ExecResult {
12678 if let Some(name) = ref_val.as_scalar_binding_name() {
12679 self.set_special_var(&name, &val)
12680 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12681 return Ok(PerlValue::UNDEF);
12682 }
12683 if let Some(r) = ref_val.as_scalar_ref() {
12684 *r.write() = val;
12685 return Ok(PerlValue::UNDEF);
12686 }
12687 Err(PerlError::runtime("Can't assign to non-scalar reference", line).into())
12688 }
12689
12690 pub(crate) fn assign_symbolic_array_ref_deref(
12692 &mut self,
12693 ref_val: PerlValue,
12694 val: PerlValue,
12695 line: usize,
12696 ) -> ExecResult {
12697 if let Some(a) = ref_val.as_array_ref() {
12698 *a.write() = val.to_list();
12699 return Ok(PerlValue::UNDEF);
12700 }
12701 if let Some(name) = ref_val.as_array_binding_name() {
12702 self.scope
12703 .set_array(&name, val.to_list())
12704 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12705 return Ok(PerlValue::UNDEF);
12706 }
12707 if let Some(s) = ref_val.as_str() {
12708 if self.strict_refs {
12709 return Err(PerlError::runtime(
12710 format!(
12711 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
12712 s
12713 ),
12714 line,
12715 )
12716 .into());
12717 }
12718 self.scope
12719 .set_array(&s, val.to_list())
12720 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12721 return Ok(PerlValue::UNDEF);
12722 }
12723 Err(PerlError::runtime("Can't assign to non-array reference", line).into())
12724 }
12725
12726 pub(crate) fn assign_symbolic_typeglob_ref_deref(
12729 &mut self,
12730 ref_val: PerlValue,
12731 val: PerlValue,
12732 line: usize,
12733 ) -> ExecResult {
12734 let lhs_name = if let Some(s) = ref_val.as_str() {
12735 if self.strict_refs {
12736 return Err(PerlError::runtime(
12737 format!(
12738 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
12739 s
12740 ),
12741 line,
12742 )
12743 .into());
12744 }
12745 s.to_string()
12746 } else {
12747 return Err(
12748 PerlError::runtime("Can't assign to non-glob symbolic reference", line).into(),
12749 );
12750 };
12751 let is_coderef = val.as_code_ref().is_some()
12752 || val
12753 .as_scalar_ref()
12754 .map(|r| r.read().as_code_ref().is_some())
12755 .unwrap_or(false);
12756 if is_coderef {
12757 return self.assign_typeglob_value(&lhs_name, val, line);
12758 }
12759 let rhs_key = val.to_string();
12760 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
12761 .map_err(FlowOrError::Error)?;
12762 Ok(PerlValue::UNDEF)
12763 }
12764
12765 pub(crate) fn assign_symbolic_hash_ref_deref(
12767 &mut self,
12768 ref_val: PerlValue,
12769 val: PerlValue,
12770 line: usize,
12771 ) -> ExecResult {
12772 let items = val.to_list();
12773 let mut map = IndexMap::new();
12774 let mut i = 0;
12775 while i + 1 < items.len() {
12776 map.insert(items[i].to_string(), items[i + 1].clone());
12777 i += 2;
12778 }
12779 if let Some(h) = ref_val.as_hash_ref() {
12780 *h.write() = map;
12781 return Ok(PerlValue::UNDEF);
12782 }
12783 if let Some(name) = ref_val.as_hash_binding_name() {
12784 self.touch_env_hash(&name);
12785 self.scope
12786 .set_hash(&name, map)
12787 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12788 return Ok(PerlValue::UNDEF);
12789 }
12790 if let Some(s) = ref_val.as_str() {
12791 if self.strict_refs {
12792 return Err(PerlError::runtime(
12793 format!(
12794 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
12795 s
12796 ),
12797 line,
12798 )
12799 .into());
12800 }
12801 self.touch_env_hash(&s);
12802 self.scope
12803 .set_hash(&s, map)
12804 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12805 return Ok(PerlValue::UNDEF);
12806 }
12807 Err(PerlError::runtime("Can't assign to non-hash reference", line).into())
12808 }
12809
12810 pub(crate) fn assign_arrow_hash_deref(
12812 &mut self,
12813 container: PerlValue,
12814 key: String,
12815 val: PerlValue,
12816 line: usize,
12817 ) -> ExecResult {
12818 if let Some(b) = container.as_blessed_ref() {
12819 let mut data = b.data.write();
12820 if let Some(r) = data.as_hash_ref() {
12821 r.write().insert(key, val);
12822 return Ok(PerlValue::UNDEF);
12823 }
12824 if let Some(mut map) = data.as_hash_map() {
12825 map.insert(key, val);
12826 *data = PerlValue::hash(map);
12827 return Ok(PerlValue::UNDEF);
12828 }
12829 return Err(PerlError::runtime("Can't assign into non-hash blessed ref", line).into());
12830 }
12831 if let Some(r) = container.as_hash_ref() {
12832 r.write().insert(key, val);
12833 return Ok(PerlValue::UNDEF);
12834 }
12835 if let Some(name) = container.as_hash_binding_name() {
12836 self.touch_env_hash(&name);
12837 self.scope
12838 .set_hash_element(&name, &key, val)
12839 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12840 return Ok(PerlValue::UNDEF);
12841 }
12842 Err(PerlError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
12843 }
12844
12845 pub(crate) fn eval_arrow_array_base(
12848 &mut self,
12849 expr: &Expr,
12850 _line: usize,
12851 ) -> Result<PerlValue, FlowOrError> {
12852 match &expr.kind {
12853 ExprKind::Deref {
12854 expr: inner,
12855 kind: Sigil::Array | Sigil::Scalar,
12856 } => self.eval_expr(inner),
12857 _ => self.eval_expr(expr),
12858 }
12859 }
12860
12861 pub(crate) fn eval_arrow_hash_base(
12863 &mut self,
12864 expr: &Expr,
12865 _line: usize,
12866 ) -> Result<PerlValue, FlowOrError> {
12867 match &expr.kind {
12868 ExprKind::Deref {
12869 expr: inner,
12870 kind: Sigil::Scalar,
12871 } => self.eval_expr(inner),
12872 _ => self.eval_expr(expr),
12873 }
12874 }
12875
12876 pub(crate) fn read_arrow_array_element(
12878 &self,
12879 container: PerlValue,
12880 idx: i64,
12881 line: usize,
12882 ) -> Result<PerlValue, FlowOrError> {
12883 if let Some(a) = container.as_array_ref() {
12884 let arr = a.read();
12885 let i = if idx < 0 {
12886 (arr.len() as i64 + idx) as usize
12887 } else {
12888 idx as usize
12889 };
12890 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12891 }
12892 if let Some(name) = container.as_array_binding_name() {
12893 return Ok(self.scope.get_array_element(&name, idx));
12894 }
12895 if let Some(arr) = container.as_array_vec() {
12896 let i = if idx < 0 {
12897 (arr.len() as i64 + idx) as usize
12898 } else {
12899 idx as usize
12900 };
12901 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12902 }
12903 if let Some(b) = container.as_blessed_ref() {
12907 let inner = b.data.read().clone();
12908 if let Some(a) = inner.as_array_ref() {
12909 let arr = a.read();
12910 let i = if idx < 0 {
12911 (arr.len() as i64 + idx) as usize
12912 } else {
12913 idx as usize
12914 };
12915 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12916 }
12917 }
12918 Err(PerlError::runtime("Can't use arrow deref on non-array-ref", line).into())
12919 }
12920
12921 pub(crate) fn read_arrow_hash_element(
12923 &mut self,
12924 container: PerlValue,
12925 key: &str,
12926 line: usize,
12927 ) -> Result<PerlValue, FlowOrError> {
12928 if let Some(r) = container.as_hash_ref() {
12929 let h = r.read();
12930 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12931 }
12932 if let Some(name) = container.as_hash_binding_name() {
12933 self.touch_env_hash(&name);
12934 return Ok(self.scope.get_hash_element(&name, key));
12935 }
12936 if let Some(b) = container.as_blessed_ref() {
12937 let data = b.data.read();
12938 if let Some(v) = data.hash_get(key) {
12939 return Ok(v);
12940 }
12941 if let Some(r) = data.as_hash_ref() {
12942 let h = r.read();
12943 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12944 }
12945 return Err(PerlError::runtime(
12946 "Can't access hash field on non-hash blessed ref",
12947 line,
12948 )
12949 .into());
12950 }
12951 if let Some(s) = container.as_struct_inst() {
12953 if let Some(idx) = s.def.field_index(key) {
12954 return Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF));
12955 }
12956 return Err(PerlError::runtime(
12957 format!("struct {} has no field `{}`", s.def.name, key),
12958 line,
12959 )
12960 .into());
12961 }
12962 if let Some(c) = container.as_class_inst() {
12964 if let Some(idx) = c.def.field_index(key) {
12965 return Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF));
12966 }
12967 return Err(PerlError::runtime(
12968 format!("class {} has no field `{}`", c.def.name, key),
12969 line,
12970 )
12971 .into());
12972 }
12973 Err(PerlError::runtime("Can't use arrow deref on non-hash-ref", line).into())
12974 }
12975
12976 pub(crate) fn arrow_array_postfix(
12978 &mut self,
12979 container: PerlValue,
12980 idx: i64,
12981 decrement: bool,
12982 line: usize,
12983 ) -> Result<PerlValue, FlowOrError> {
12984 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
12985 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12986 self.assign_arrow_array_deref(container, idx, new_val, line)?;
12987 Ok(old)
12988 }
12989
12990 pub(crate) fn arrow_hash_postfix(
12992 &mut self,
12993 container: PerlValue,
12994 key: String,
12995 decrement: bool,
12996 line: usize,
12997 ) -> Result<PerlValue, FlowOrError> {
12998 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
12999 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
13000 self.assign_arrow_hash_deref(container, key, new_val, line)?;
13001 Ok(old)
13002 }
13003
13004 pub(crate) fn resolve_bareword_rvalue(
13012 &mut self,
13013 name: &str,
13014 want: WantarrayCtx,
13015 line: usize,
13016 ) -> Result<PerlValue, FlowOrError> {
13017 if name == "__PACKAGE__" {
13018 return Ok(PerlValue::string(self.current_package()));
13019 }
13020 if let Some(sub) = self.resolve_sub_by_name(name) {
13021 return self.call_sub(&sub, vec![], want, line);
13022 }
13023 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
13025 return r.map_err(Into::into);
13026 }
13027 Ok(PerlValue::string(name.to_string()))
13028 }
13029
13030 pub(crate) fn arrow_array_slice_values(
13034 &mut self,
13035 container: PerlValue,
13036 indices: &[i64],
13037 line: usize,
13038 ) -> Result<PerlValue, FlowOrError> {
13039 let mut out = Vec::with_capacity(indices.len());
13040 for &idx in indices {
13041 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
13042 out.push(v);
13043 }
13044 Ok(PerlValue::array(out))
13045 }
13046
13047 pub(crate) fn assign_arrow_array_slice(
13051 &mut self,
13052 container: PerlValue,
13053 indices: Vec<i64>,
13054 val: PerlValue,
13055 line: usize,
13056 ) -> Result<PerlValue, FlowOrError> {
13057 if indices.is_empty() {
13058 return Err(PerlError::runtime("assign to empty array slice", line).into());
13059 }
13060 let vals = val.to_list();
13061 for (i, idx) in indices.iter().enumerate() {
13062 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13063 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
13064 }
13065 Ok(PerlValue::UNDEF)
13066 }
13067
13068 pub(crate) fn flatten_array_slice_index_specs(
13070 &mut self,
13071 indices: &[Expr],
13072 ) -> Result<Vec<i64>, FlowOrError> {
13073 let mut out = Vec::new();
13074 for idx_expr in indices {
13075 let v = if matches!(idx_expr.kind, ExprKind::Range { .. }) {
13076 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
13077 } else {
13078 self.eval_expr(idx_expr)?
13079 };
13080 if let Some(list) = v.as_array_vec() {
13081 for idx in list {
13082 out.push(idx.to_int());
13083 }
13084 } else {
13085 out.push(v.to_int());
13086 }
13087 }
13088 Ok(out)
13089 }
13090
13091 pub(crate) fn assign_named_array_slice(
13093 &mut self,
13094 stash_array_name: &str,
13095 indices: Vec<i64>,
13096 val: PerlValue,
13097 line: usize,
13098 ) -> Result<PerlValue, FlowOrError> {
13099 if indices.is_empty() {
13100 return Err(PerlError::runtime("assign to empty array slice", line).into());
13101 }
13102 let vals = val.to_list();
13103 for (i, idx) in indices.iter().enumerate() {
13104 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13105 self.scope
13106 .set_array_element(stash_array_name, *idx, v)
13107 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13108 }
13109 Ok(PerlValue::UNDEF)
13110 }
13111
13112 pub(crate) fn compound_assign_arrow_array_slice(
13115 &mut self,
13116 container: PerlValue,
13117 indices: Vec<i64>,
13118 op: BinOp,
13119 rhs: PerlValue,
13120 line: usize,
13121 ) -> Result<PerlValue, FlowOrError> {
13122 if indices.is_empty() {
13123 return Err(PerlError::runtime("assign to empty array slice", line).into());
13124 }
13125 let last_idx = *indices.last().expect("non-empty indices");
13126 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
13127 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
13128 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
13129 Ok(new_val)
13130 }
13131
13132 pub(crate) fn arrow_array_slice_inc_dec(
13137 &mut self,
13138 container: PerlValue,
13139 indices: Vec<i64>,
13140 kind: u8,
13141 line: usize,
13142 ) -> Result<PerlValue, FlowOrError> {
13143 if indices.is_empty() {
13144 return Err(
13145 PerlError::runtime("array slice increment needs at least one index", line).into(),
13146 );
13147 }
13148 let last_idx = *indices.last().expect("non-empty indices");
13149 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
13150 let new_val = if kind & 1 == 0 {
13151 PerlValue::integer(last_old.to_int() + 1)
13152 } else {
13153 PerlValue::integer(last_old.to_int() - 1)
13154 };
13155 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
13156 Ok(if kind < 2 { new_val } else { last_old })
13157 }
13158
13159 pub(crate) fn named_array_slice_inc_dec(
13162 &mut self,
13163 stash_array_name: &str,
13164 indices: Vec<i64>,
13165 kind: u8,
13166 line: usize,
13167 ) -> Result<PerlValue, FlowOrError> {
13168 let last_idx = *indices.last().ok_or_else(|| {
13169 PerlError::runtime("array slice increment needs at least one index", line)
13170 })?;
13171 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
13172 let new_val = if kind & 1 == 0 {
13173 PerlValue::integer(last_old.to_int() + 1)
13174 } else {
13175 PerlValue::integer(last_old.to_int() - 1)
13176 };
13177 self.scope
13178 .set_array_element(stash_array_name, last_idx, new_val.clone())
13179 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13180 Ok(if kind < 2 { new_val } else { last_old })
13181 }
13182
13183 pub(crate) fn compound_assign_named_array_slice(
13185 &mut self,
13186 stash_array_name: &str,
13187 indices: Vec<i64>,
13188 op: BinOp,
13189 rhs: PerlValue,
13190 line: usize,
13191 ) -> Result<PerlValue, FlowOrError> {
13192 if indices.is_empty() {
13193 return Err(PerlError::runtime("assign to empty array slice", line).into());
13194 }
13195 let last_idx = *indices.last().expect("non-empty indices");
13196 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
13197 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
13198 self.scope
13199 .set_array_element(stash_array_name, last_idx, new_val.clone())
13200 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13201 Ok(new_val)
13202 }
13203
13204 pub(crate) fn assign_arrow_array_deref(
13206 &mut self,
13207 container: PerlValue,
13208 idx: i64,
13209 val: PerlValue,
13210 line: usize,
13211 ) -> ExecResult {
13212 if let Some(a) = container.as_array_ref() {
13213 let mut arr = a.write();
13214 let i = if idx < 0 {
13215 (arr.len() as i64 + idx) as usize
13216 } else {
13217 idx as usize
13218 };
13219 if i >= arr.len() {
13220 arr.resize(i + 1, PerlValue::UNDEF);
13221 }
13222 arr[i] = val;
13223 return Ok(PerlValue::UNDEF);
13224 }
13225 if let Some(name) = container.as_array_binding_name() {
13226 self.scope
13227 .set_array_element(&name, idx, val)
13228 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13229 return Ok(PerlValue::UNDEF);
13230 }
13231 Err(PerlError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
13232 }
13233
13234 pub(crate) fn assign_typeglob_value(
13236 &mut self,
13237 name: &str,
13238 val: PerlValue,
13239 line: usize,
13240 ) -> ExecResult {
13241 let sub = if let Some(c) = val.as_code_ref() {
13242 Some(c)
13243 } else if let Some(r) = val.as_scalar_ref() {
13244 r.read().as_code_ref().map(|c| Arc::clone(&c))
13245 } else {
13246 None
13247 };
13248 if let Some(sub) = sub {
13249 let lhs_sub = self.qualify_typeglob_sub_key(name);
13250 self.subs.insert(lhs_sub, sub);
13251 return Ok(PerlValue::UNDEF);
13252 }
13253 Err(PerlError::runtime(
13254 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
13255 line,
13256 )
13257 .into())
13258 }
13259
13260 fn assign_value(&mut self, target: &Expr, val: PerlValue) -> ExecResult {
13261 match &target.kind {
13262 ExprKind::ScalarVar(name) => {
13263 let stor = self.tree_scalar_storage_name(name);
13264 if self.scope.is_scalar_frozen(&stor) {
13265 return Err(FlowOrError::Error(PerlError::runtime(
13266 format!("Modification of a frozen value: ${}", name),
13267 target.line,
13268 )));
13269 }
13270 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
13271 let class = obj
13272 .as_blessed_ref()
13273 .map(|b| b.class.clone())
13274 .unwrap_or_default();
13275 let full = format!("{}::STORE", class);
13276 if let Some(sub) = self.subs.get(&full).cloned() {
13277 let arg_vals = vec![obj, val];
13278 return match self.call_sub(
13279 &sub,
13280 arg_vals,
13281 WantarrayCtx::Scalar,
13282 target.line,
13283 ) {
13284 Ok(_) => Ok(PerlValue::UNDEF),
13285 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13286 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13287 };
13288 }
13289 }
13290 self.set_special_var(&stor, &val)
13291 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
13292 Ok(PerlValue::UNDEF)
13293 }
13294 ExprKind::ArrayVar(name) => {
13295 if self.scope.is_array_frozen(name) {
13296 return Err(PerlError::runtime(
13297 format!("Modification of a frozen value: @{}", name),
13298 target.line,
13299 )
13300 .into());
13301 }
13302 if self.strict_vars
13303 && !name.contains("::")
13304 && !self.scope.array_binding_exists(name)
13305 {
13306 return Err(PerlError::runtime(
13307 format!(
13308 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
13309 name, name
13310 ),
13311 target.line,
13312 )
13313 .into());
13314 }
13315 self.scope.set_array(name, val.to_list())?;
13316 Ok(PerlValue::UNDEF)
13317 }
13318 ExprKind::HashVar(name) => {
13319 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
13320 {
13321 return Err(PerlError::runtime(
13322 format!(
13323 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13324 name, name
13325 ),
13326 target.line,
13327 )
13328 .into());
13329 }
13330 let items = val.to_list();
13331 let mut map = IndexMap::new();
13332 let mut i = 0;
13333 while i + 1 < items.len() {
13334 map.insert(items[i].to_string(), items[i + 1].clone());
13335 i += 2;
13336 }
13337 self.scope.set_hash(name, map)?;
13338 Ok(PerlValue::UNDEF)
13339 }
13340 ExprKind::ArrayElement { array, index } => {
13341 if self.strict_vars
13342 && !array.contains("::")
13343 && !self.scope.array_binding_exists(array)
13344 {
13345 return Err(PerlError::runtime(
13346 format!(
13347 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
13348 array, array
13349 ),
13350 target.line,
13351 )
13352 .into());
13353 }
13354 if self.scope.is_array_frozen(array) {
13355 return Err(PerlError::runtime(
13356 format!("Modification of a frozen value: @{}", array),
13357 target.line,
13358 )
13359 .into());
13360 }
13361 let idx = self.eval_expr(index)?.to_int();
13362 let aname = self.stash_array_name_for_package(array);
13363 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
13364 let class = obj
13365 .as_blessed_ref()
13366 .map(|b| b.class.clone())
13367 .unwrap_or_default();
13368 let full = format!("{}::STORE", class);
13369 if let Some(sub) = self.subs.get(&full).cloned() {
13370 let arg_vals = vec![obj, PerlValue::integer(idx), val];
13371 return match self.call_sub(
13372 &sub,
13373 arg_vals,
13374 WantarrayCtx::Scalar,
13375 target.line,
13376 ) {
13377 Ok(_) => Ok(PerlValue::UNDEF),
13378 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13379 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13380 };
13381 }
13382 }
13383 self.scope.set_array_element(&aname, idx, val)?;
13384 Ok(PerlValue::UNDEF)
13385 }
13386 ExprKind::ArraySlice { array, indices } => {
13387 if indices.is_empty() {
13388 return Err(
13389 PerlError::runtime("assign to empty array slice", target.line).into(),
13390 );
13391 }
13392 self.check_strict_array_var(array, target.line)?;
13393 if self.scope.is_array_frozen(array) {
13394 return Err(PerlError::runtime(
13395 format!("Modification of a frozen value: @{}", array),
13396 target.line,
13397 )
13398 .into());
13399 }
13400 let aname = self.stash_array_name_for_package(array);
13401 let flat = self.flatten_array_slice_index_specs(indices)?;
13402 self.assign_named_array_slice(&aname, flat, val, target.line)
13403 }
13404 ExprKind::HashElement { hash, key } => {
13405 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
13406 {
13407 return Err(PerlError::runtime(
13408 format!(
13409 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13410 hash, hash
13411 ),
13412 target.line,
13413 )
13414 .into());
13415 }
13416 if self.scope.is_hash_frozen(hash) {
13417 return Err(PerlError::runtime(
13418 format!("Modification of a frozen value: %%{}", hash),
13419 target.line,
13420 )
13421 .into());
13422 }
13423 let k = self.eval_expr(key)?.to_string();
13424 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
13425 let class = obj
13426 .as_blessed_ref()
13427 .map(|b| b.class.clone())
13428 .unwrap_or_default();
13429 let full = format!("{}::STORE", class);
13430 if let Some(sub) = self.subs.get(&full).cloned() {
13431 let arg_vals = vec![obj, PerlValue::string(k), val];
13432 return match self.call_sub(
13433 &sub,
13434 arg_vals,
13435 WantarrayCtx::Scalar,
13436 target.line,
13437 ) {
13438 Ok(_) => Ok(PerlValue::UNDEF),
13439 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13440 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13441 };
13442 }
13443 }
13444 self.scope.set_hash_element(hash, &k, val)?;
13445 Ok(PerlValue::UNDEF)
13446 }
13447 ExprKind::HashSlice { hash, keys } => {
13448 if keys.is_empty() {
13449 return Err(
13450 PerlError::runtime("assign to empty hash slice", target.line).into(),
13451 );
13452 }
13453 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
13454 {
13455 return Err(PerlError::runtime(
13456 format!(
13457 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13458 hash, hash
13459 ),
13460 target.line,
13461 )
13462 .into());
13463 }
13464 if self.scope.is_hash_frozen(hash) {
13465 return Err(PerlError::runtime(
13466 format!("Modification of a frozen value: %%{}", hash),
13467 target.line,
13468 )
13469 .into());
13470 }
13471 let mut key_vals = Vec::with_capacity(keys.len());
13472 for key_expr in keys {
13473 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
13474 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
13475 } else {
13476 self.eval_expr(key_expr)?
13477 };
13478 key_vals.push(v);
13479 }
13480 self.assign_named_hash_slice(hash, key_vals, val, target.line)
13481 }
13482 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
13483 ExprKind::TypeglobExpr(e) => {
13484 let name = self.eval_expr(e)?.to_string();
13485 let synthetic = Expr {
13486 kind: ExprKind::Typeglob(name),
13487 line: target.line,
13488 };
13489 self.assign_value(&synthetic, val)
13490 }
13491 ExprKind::AnonymousListSlice { source, indices } => {
13492 if let ExprKind::Deref {
13493 expr: inner,
13494 kind: Sigil::Array,
13495 } = &source.kind
13496 {
13497 let container = self.eval_arrow_array_base(inner, target.line)?;
13498 let vals = val.to_list();
13499 let n = indices.len().min(vals.len());
13500 for i in 0..n {
13501 let idx = self.eval_expr(&indices[i])?.to_int();
13502 self.assign_arrow_array_deref(
13503 container.clone(),
13504 idx,
13505 vals[i].clone(),
13506 target.line,
13507 )?;
13508 }
13509 return Ok(PerlValue::UNDEF);
13510 }
13511 Err(
13512 PerlError::runtime("assign to list slice: unsupported base", target.line)
13513 .into(),
13514 )
13515 }
13516 ExprKind::ArrowDeref {
13517 expr,
13518 index,
13519 kind: DerefKind::Hash,
13520 } => {
13521 let key = self.eval_expr(index)?.to_string();
13522 let container = self.eval_expr(expr)?;
13523 self.assign_arrow_hash_deref(container, key, val, target.line)
13524 }
13525 ExprKind::ArrowDeref {
13526 expr,
13527 index,
13528 kind: DerefKind::Array,
13529 } => {
13530 let container = self.eval_arrow_array_base(expr, target.line)?;
13531 if let ExprKind::List(indices) = &index.kind {
13532 let vals = val.to_list();
13533 let n = indices.len().min(vals.len());
13534 for i in 0..n {
13535 let idx = self.eval_expr(&indices[i])?.to_int();
13536 self.assign_arrow_array_deref(
13537 container.clone(),
13538 idx,
13539 vals[i].clone(),
13540 target.line,
13541 )?;
13542 }
13543 return Ok(PerlValue::UNDEF);
13544 }
13545 let idx = self.eval_expr(index)?.to_int();
13546 self.assign_arrow_array_deref(container, idx, val, target.line)
13547 }
13548 ExprKind::HashSliceDeref { container, keys } => {
13549 let href = self.eval_expr(container)?;
13550 let mut key_vals = Vec::with_capacity(keys.len());
13551 for key_expr in keys {
13552 key_vals.push(self.eval_expr(key_expr)?);
13553 }
13554 self.assign_hash_slice_deref(href, key_vals, val, target.line)
13555 }
13556 ExprKind::Deref {
13557 expr,
13558 kind: Sigil::Scalar,
13559 } => {
13560 let ref_val = self.eval_expr(expr)?;
13561 self.assign_scalar_ref_deref(ref_val, val, target.line)
13562 }
13563 ExprKind::Deref {
13564 expr,
13565 kind: Sigil::Array,
13566 } => {
13567 let ref_val = self.eval_expr(expr)?;
13568 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
13569 }
13570 ExprKind::Deref {
13571 expr,
13572 kind: Sigil::Hash,
13573 } => {
13574 let ref_val = self.eval_expr(expr)?;
13575 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
13576 }
13577 ExprKind::Deref {
13578 expr,
13579 kind: Sigil::Typeglob,
13580 } => {
13581 let ref_val = self.eval_expr(expr)?;
13582 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
13583 }
13584 ExprKind::Pos(inner) => {
13585 let key = match inner {
13586 None => "_".to_string(),
13587 Some(expr) => match &expr.kind {
13588 ExprKind::ScalarVar(n) => n.clone(),
13589 _ => self.eval_expr(expr)?.to_string(),
13590 },
13591 };
13592 if val.is_undef() {
13593 self.regex_pos.insert(key, None);
13594 } else {
13595 let u = val.to_int().max(0) as usize;
13596 self.regex_pos.insert(key, Some(u));
13597 }
13598 Ok(PerlValue::UNDEF)
13599 }
13600 ExprKind::List(targets) => {
13603 let items = val.to_list();
13604 for (i, t) in targets.iter().enumerate() {
13605 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13606 self.assign_value(t, v)?;
13607 }
13608 Ok(PerlValue::UNDEF)
13609 }
13610 ExprKind::Assign { target, .. } => self.assign_value(target, val),
13613 _ => Ok(PerlValue::UNDEF),
13614 }
13615 }
13616
13617 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
13619 (name.starts_with('#') && name.len() > 1)
13620 || name.starts_with('^')
13621 || matches!(
13622 name,
13623 "$$" | "0"
13624 | "!"
13625 | "@"
13626 | "/"
13627 | "\\"
13628 | ","
13629 | "."
13630 | "]"
13631 | ";"
13632 | "ARGV"
13633 | "^I"
13634 | "^D"
13635 | "^P"
13636 | "^S"
13637 | "^W"
13638 | "^O"
13639 | "^T"
13640 | "^V"
13641 | "^E"
13642 | "^H"
13643 | "^WARNING_BITS"
13644 | "^GLOBAL_PHASE"
13645 | "^MATCH"
13646 | "^PREMATCH"
13647 | "^POSTMATCH"
13648 | "^LAST_SUBMATCH_RESULT"
13649 | "<"
13650 | ">"
13651 | "("
13652 | ")"
13653 | "?"
13654 | "|"
13655 | "\""
13656 | "+"
13657 | "%"
13658 | "="
13659 | "-"
13660 | ":"
13661 | "*"
13662 | "INC"
13663 )
13664 || crate::english::is_known_alias(name)
13665 }
13666
13667 #[inline]
13672 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
13673 if !self.english_enabled {
13674 return name;
13675 }
13676 if self
13677 .english_lexical_scalars
13678 .iter()
13679 .any(|s| s.contains(name))
13680 {
13681 return name;
13682 }
13683 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
13684 return short;
13685 }
13686 name
13687 }
13688
13689 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
13691 name.starts_with('^')
13692 || matches!(
13693 name,
13694 "0" | "/"
13695 | "\\"
13696 | ","
13697 | ";"
13698 | "\""
13699 | "%"
13700 | "="
13701 | "-"
13702 | ":"
13703 | "*"
13704 | "INC"
13705 | "^I"
13706 | "^D"
13707 | "^P"
13708 | "^W"
13709 | "^H"
13710 | "^WARNING_BITS"
13711 | "$$"
13712 | "]"
13713 | "^S"
13714 | "ARGV"
13715 | "|"
13716 | "+"
13717 | "?"
13718 | "!"
13719 | "@"
13720 | "."
13721 )
13722 || crate::english::is_known_alias(name)
13723 }
13724
13725 pub(crate) fn get_special_var(&self, name: &str) -> PerlValue {
13726 let name = if !crate::compat_mode() {
13728 match name {
13729 "NR" => ".",
13730 "RS" => "/",
13731 "OFS" => ",",
13732 "ORS" => "\\",
13733 "NF" => {
13734 let len = self.scope.array_len("F");
13735 return PerlValue::integer(len as i64);
13736 }
13737 _ => self.english_scalar_name(name),
13738 }
13739 } else {
13740 self.english_scalar_name(name)
13741 };
13742 match name {
13743 "$$" => PerlValue::integer(std::process::id() as i64),
13744 "_" => self.scope.get_scalar("_"),
13745 "^MATCH" => PerlValue::string(self.last_match.clone()),
13746 "^PREMATCH" => PerlValue::string(self.prematch.clone()),
13747 "^POSTMATCH" => PerlValue::string(self.postmatch.clone()),
13748 "^LAST_SUBMATCH_RESULT" => PerlValue::string(self.last_paren_match.clone()),
13749 "0" => PerlValue::string(self.program_name.clone()),
13750 "!" => PerlValue::errno_dual(self.errno_code, self.errno.clone()),
13751 "@" => {
13752 if let Some(ref v) = self.eval_error_value {
13753 v.clone()
13754 } else {
13755 PerlValue::errno_dual(self.eval_error_code, self.eval_error.clone())
13756 }
13757 }
13758 "/" => match &self.irs {
13759 Some(s) => PerlValue::string(s.clone()),
13760 None => PerlValue::UNDEF,
13761 },
13762 "\\" => PerlValue::string(self.ors.clone()),
13763 "," => PerlValue::string(self.ofs.clone()),
13764 "." => {
13765 if self.last_readline_handle.is_empty() {
13767 if self.line_number == 0 {
13768 PerlValue::UNDEF
13769 } else {
13770 PerlValue::integer(self.line_number)
13771 }
13772 } else {
13773 PerlValue::integer(
13774 *self
13775 .handle_line_numbers
13776 .get(&self.last_readline_handle)
13777 .unwrap_or(&0),
13778 )
13779 }
13780 }
13781 "]" => PerlValue::float(perl_bracket_version()),
13782 ";" => PerlValue::string(self.subscript_sep.clone()),
13783 "ARGV" => PerlValue::string(self.argv_current_file.clone()),
13784 "^I" => PerlValue::string(self.inplace_edit.clone()),
13785 "^D" => PerlValue::integer(self.debug_flags),
13786 "^P" => PerlValue::integer(self.perl_debug_flags),
13787 "^S" => PerlValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
13788 "^W" => PerlValue::integer(if self.warnings { 1 } else { 0 }),
13789 "^O" => PerlValue::string(perl_osname()),
13790 "^T" => PerlValue::integer(self.script_start_time),
13791 "^V" => PerlValue::string(perl_version_v_string()),
13792 "^E" => PerlValue::string(extended_os_error_string()),
13793 "^H" => PerlValue::integer(self.compile_hints),
13794 "^WARNING_BITS" => PerlValue::integer(self.warning_bits),
13795 "^GLOBAL_PHASE" => PerlValue::string(self.global_phase.clone()),
13796 "<" | ">" => PerlValue::integer(unix_id_for_special(name)),
13797 "(" | ")" => PerlValue::string(unix_group_list_for_special(name)),
13798 "?" => PerlValue::integer(self.child_exit_status),
13799 "|" => PerlValue::integer(if self.output_autoflush { 1 } else { 0 }),
13800 "\"" => PerlValue::string(self.list_separator.clone()),
13801 "+" => PerlValue::string(self.last_paren_match.clone()),
13802 "%" => PerlValue::integer(self.format_page_number),
13803 "=" => PerlValue::integer(self.format_lines_per_page),
13804 "-" => PerlValue::integer(self.format_lines_left),
13805 ":" => PerlValue::string(self.format_line_break_chars.clone()),
13806 "*" => PerlValue::integer(if self.multiline_match { 1 } else { 0 }),
13807 "^" => PerlValue::string(self.format_top_name.clone()),
13808 "INC" => PerlValue::integer(self.inc_hook_index),
13809 "^A" => PerlValue::string(self.accumulator_format.clone()),
13810 "^C" => PerlValue::integer(if self.sigint_pending_caret.replace(false) {
13811 1
13812 } else {
13813 0
13814 }),
13815 "^F" => PerlValue::integer(self.max_system_fd),
13816 "^L" => PerlValue::string(self.formfeed_string.clone()),
13817 "^M" => PerlValue::string(self.emergency_memory.clone()),
13818 "^N" => PerlValue::string(self.last_subpattern_name.clone()),
13819 "^X" => PerlValue::string(self.executable_path.clone()),
13820 "^TAINT" | "^TAINTED" => PerlValue::integer(0),
13822 "^UNICODE" => PerlValue::integer(if self.utf8_pragma { 1 } else { 0 }),
13823 "^OPEN" => PerlValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
13824 "^UTF8LOCALE" => PerlValue::integer(0),
13825 "^UTF8CACHE" => PerlValue::integer(-1),
13826 _ if name.starts_with('^') && name.len() > 1 => self
13827 .special_caret_scalars
13828 .get(name)
13829 .cloned()
13830 .unwrap_or(PerlValue::UNDEF),
13831 _ if name.starts_with('#') && name.len() > 1 => {
13832 let arr = &name[1..];
13833 let aname = self.stash_array_name_for_package(arr);
13834 let len = self.scope.array_len(&aname);
13835 PerlValue::integer(len as i64 - 1)
13836 }
13837 _ => self.scope.get_scalar(name),
13838 }
13839 }
13840
13841 pub(crate) fn set_special_var(&mut self, name: &str, val: &PerlValue) -> Result<(), PerlError> {
13842 let name = self.english_scalar_name(name);
13843 match name {
13844 "!" => {
13845 let code = val.to_int() as i32;
13846 self.errno_code = code;
13847 self.errno = if code == 0 {
13848 String::new()
13849 } else {
13850 std::io::Error::from_raw_os_error(code).to_string()
13851 };
13852 }
13853 "@" => {
13854 if let Some((code, msg)) = val.errno_dual_parts() {
13855 self.eval_error_code = code;
13856 self.eval_error = msg;
13857 } else {
13858 self.eval_error = val.to_string();
13859 let mut code = val.to_int() as i32;
13860 if code == 0 && !self.eval_error.is_empty() {
13861 code = 1;
13862 }
13863 self.eval_error_code = code;
13864 }
13865 }
13866 "." => {
13867 let n = val.to_int();
13870 if self.last_readline_handle.is_empty() {
13871 self.line_number = n;
13872 } else {
13873 self.handle_line_numbers
13874 .insert(self.last_readline_handle.clone(), n);
13875 }
13876 }
13877 "0" => self.program_name = val.to_string(),
13878 "/" => {
13879 self.irs = if val.is_undef() {
13880 None
13881 } else {
13882 Some(val.to_string())
13883 }
13884 }
13885 "\\" => self.ors = val.to_string(),
13886 "," => self.ofs = val.to_string(),
13887 ";" => self.subscript_sep = val.to_string(),
13888 "\"" => self.list_separator = val.to_string(),
13889 "%" => self.format_page_number = val.to_int(),
13890 "=" => self.format_lines_per_page = val.to_int(),
13891 "-" => self.format_lines_left = val.to_int(),
13892 ":" => self.format_line_break_chars = val.to_string(),
13893 "*" => self.multiline_match = val.to_int() != 0,
13894 "^" => self.format_top_name = val.to_string(),
13895 "INC" => self.inc_hook_index = val.to_int(),
13896 "^A" => self.accumulator_format = val.to_string(),
13897 "^F" => self.max_system_fd = val.to_int(),
13898 "^L" => self.formfeed_string = val.to_string(),
13899 "^M" => self.emergency_memory = val.to_string(),
13900 "^I" => self.inplace_edit = val.to_string(),
13901 "^D" => self.debug_flags = val.to_int(),
13902 "^P" => self.perl_debug_flags = val.to_int(),
13903 "^W" => self.warnings = val.to_int() != 0,
13904 "^H" => self.compile_hints = val.to_int(),
13905 "^WARNING_BITS" => self.warning_bits = val.to_int(),
13906 "|" => {
13907 self.output_autoflush = val.to_int() != 0;
13908 if self.output_autoflush {
13909 let _ = io::stdout().flush();
13910 }
13911 }
13912 "$$"
13914 | "]"
13915 | "^S"
13916 | "ARGV"
13917 | "?"
13918 | "^O"
13919 | "^T"
13920 | "^V"
13921 | "^E"
13922 | "^GLOBAL_PHASE"
13923 | "^MATCH"
13924 | "^PREMATCH"
13925 | "^POSTMATCH"
13926 | "^LAST_SUBMATCH_RESULT"
13927 | "^C"
13928 | "^N"
13929 | "^X"
13930 | "^TAINT"
13931 | "^TAINTED"
13932 | "^UNICODE"
13933 | "^UTF8LOCALE"
13934 | "^UTF8CACHE"
13935 | "+"
13936 | "<"
13937 | ">"
13938 | "("
13939 | ")" => {}
13940 _ if name.starts_with('^') && name.len() > 1 => {
13941 self.special_caret_scalars
13942 .insert(name.to_string(), val.clone());
13943 }
13944 _ => self.scope.set_scalar(name, val.clone())?,
13945 }
13946 Ok(())
13947 }
13948
13949 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
13950 match &expr.kind {
13951 ExprKind::ArrayVar(name) => Ok(name.clone()),
13952 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(PerlError::runtime("Expected array", expr.line).into()),
13954 }
13955 }
13956
13957 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
13959 match &expr.kind {
13960 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
13961 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
13962 _ => expr,
13963 }
13964 }
13965
13966 fn try_eval_array_deref_container(
13968 &mut self,
13969 expr: &Expr,
13970 ) -> Result<Option<PerlValue>, FlowOrError> {
13971 let e = Self::peel_array_builtin_operand(expr);
13972 if let ExprKind::Deref {
13973 expr: inner,
13974 kind: Sigil::Array,
13975 } = &e.kind
13976 {
13977 return Ok(Some(self.eval_expr(inner)?));
13978 }
13979 Ok(None)
13980 }
13981
13982 fn current_package(&self) -> String {
13984 let s = self.scope.get_scalar("__PACKAGE__").to_string();
13985 if s.is_empty() {
13986 "main".to_string()
13987 } else {
13988 s
13989 }
13990 }
13991
13992 pub(crate) fn package_version_scalar(
13995 &mut self,
13996 package: &str,
13997 ) -> PerlResult<Option<PerlValue>> {
13998 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
13999 let _ = self
14000 .scope
14001 .set_scalar("__PACKAGE__", PerlValue::string(package.to_string()));
14002 let ver = self.get_special_var("VERSION");
14003 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
14004 Ok(if ver.is_undef() { None } else { Some(ver) })
14005 }
14006
14007 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<PerlSub>> {
14009 let root = if start_package.is_empty() {
14010 "main"
14011 } else {
14012 start_package
14013 };
14014 for pkg in self.mro_linearize(root) {
14015 let key = if pkg == "main" {
14016 "AUTOLOAD".to_string()
14017 } else {
14018 format!("{}::AUTOLOAD", pkg)
14019 };
14020 if let Some(s) = self.subs.get(&key) {
14021 return Some(s.clone());
14022 }
14023 }
14024 None
14025 }
14026
14027 pub(crate) fn try_autoload_call(
14032 &mut self,
14033 missing_name: &str,
14034 args: Vec<PerlValue>,
14035 line: usize,
14036 want: WantarrayCtx,
14037 method_invocant_class: Option<&str>,
14038 ) -> Option<ExecResult> {
14039 let pkg = self.current_package();
14040 let full = if missing_name.contains("::") {
14041 missing_name.to_string()
14042 } else {
14043 format!("{}::{}", pkg, missing_name)
14044 };
14045 let start_pkg = method_invocant_class.unwrap_or_else(|| {
14046 full.rsplit_once("::")
14047 .map(|(p, _)| p)
14048 .filter(|p| !p.is_empty())
14049 .unwrap_or("main")
14050 });
14051 let sub = self.resolve_autoload_sub(start_pkg)?;
14052 if let Err(e) = self
14053 .scope
14054 .set_scalar("AUTOLOAD", PerlValue::string(full.clone()))
14055 {
14056 return Some(Err(e.into()));
14057 }
14058 Some(self.call_sub(&sub, args, want, line))
14059 }
14060
14061 pub(crate) fn with_topic_default_args(&self, args: Vec<PerlValue>) -> Vec<PerlValue> {
14062 if args.is_empty() {
14063 vec![self.scope.get_scalar("_").clone()]
14064 } else {
14065 args
14066 }
14067 }
14068
14069 pub(crate) fn dispatch_indirect_call(
14072 &mut self,
14073 target: PerlValue,
14074 arg_vals: Vec<PerlValue>,
14075 want: WantarrayCtx,
14076 line: usize,
14077 ) -> ExecResult {
14078 if let Some(sub) = target.as_code_ref() {
14079 return self.call_sub(&sub, arg_vals, want, line);
14080 }
14081 if let Some(name) = target.as_str() {
14082 return self.call_named_sub(&name, arg_vals, line, want);
14083 }
14084 Err(PerlError::runtime("Can't use non-code reference as a subroutine", line).into())
14085 }
14086
14087 pub(crate) fn call_bare_list_util(
14093 &mut self,
14094 name: &str,
14095 args: Vec<PerlValue>,
14096 line: usize,
14097 want: WantarrayCtx,
14098 ) -> ExecResult {
14099 crate::list_util::ensure_list_util(self);
14100 let fq = match name {
14101 "uniq" | "distinct" | "uq" => "List::Util::uniq",
14102 "uniqstr" => "List::Util::uniqstr",
14103 "uniqint" => "List::Util::uniqint",
14104 "uniqnum" => "List::Util::uniqnum",
14105 "shuffle" | "shuf" => "List::Util::shuffle",
14106 "sample" => "List::Util::sample",
14107 "chunked" | "chk" => "List::Util::chunked",
14108 "windowed" | "win" => "List::Util::windowed",
14109 "zip" | "zp" => "List::Util::zip",
14110 "zip_longest" => "List::Util::zip_longest",
14111 "zip_shortest" => "List::Util::zip_shortest",
14112 "mesh" => "List::Util::mesh",
14113 "mesh_longest" => "List::Util::mesh_longest",
14114 "mesh_shortest" => "List::Util::mesh_shortest",
14115 "any" => "List::Util::any",
14116 "all" => "List::Util::all",
14117 "none" => "List::Util::none",
14118 "notall" => "List::Util::notall",
14119 "first" | "fst" => "List::Util::first",
14120 "reduce" | "rd" => "List::Util::reduce",
14121 "reductions" => "List::Util::reductions",
14122 "sum" => "List::Util::sum",
14123 "sum0" => "List::Util::sum0",
14124 "product" => "List::Util::product",
14125 "min" => "List::Util::min",
14126 "max" => "List::Util::max",
14127 "minstr" => "List::Util::minstr",
14128 "maxstr" => "List::Util::maxstr",
14129 "mean" => "List::Util::mean",
14130 "median" | "med" => "List::Util::median",
14131 "mode" => "List::Util::mode",
14132 "stddev" | "std" => "List::Util::stddev",
14133 "variance" | "var" => "List::Util::variance",
14134 "pairs" => "List::Util::pairs",
14135 "unpairs" => "List::Util::unpairs",
14136 "pairkeys" => "List::Util::pairkeys",
14137 "pairvalues" => "List::Util::pairvalues",
14138 "pairgrep" => "List::Util::pairgrep",
14139 "pairmap" => "List::Util::pairmap",
14140 "pairfirst" => "List::Util::pairfirst",
14141 _ => {
14142 return Err(PerlError::runtime(
14143 format!("internal: not a bare list-util alias: {name}"),
14144 line,
14145 )
14146 .into());
14147 }
14148 };
14149 let Some(sub) = self.subs.get(fq).cloned() else {
14150 return Err(PerlError::runtime(
14151 format!("internal: missing native stub for {fq}"),
14152 line,
14153 )
14154 .into());
14155 };
14156 let args = self.with_topic_default_args(args);
14157 self.call_sub(&sub, args, want, line)
14158 }
14159
14160 fn call_named_sub(
14161 &mut self,
14162 name: &str,
14163 args: Vec<PerlValue>,
14164 line: usize,
14165 want: WantarrayCtx,
14166 ) -> ExecResult {
14167 if let Some(sub) = self.resolve_sub_by_name(name) {
14168 let args = self.with_topic_default_args(args);
14169 return self.call_sub(&sub, args, want, line);
14170 }
14171 match name {
14172 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
14173 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
14174 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
14175 | "none" | "notall" | "first" | "fst" | "reduce" | "rd" | "reductions" | "sum"
14176 | "sum0" | "product" | "min" | "max" | "minstr" | "maxstr" | "mean" | "median"
14177 | "med" | "mode" | "stddev" | "std" | "variance" | "var" | "pairs" | "unpairs"
14178 | "pairkeys" | "pairvalues" | "pairgrep" | "pairmap" | "pairfirst" => {
14179 self.call_bare_list_util(name, args, line, want)
14180 }
14181 "deque" => {
14182 if !args.is_empty() {
14183 return Err(PerlError::runtime("deque() takes no arguments", line).into());
14184 }
14185 Ok(PerlValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
14186 }
14187 "defer__internal" => {
14188 if args.len() != 1 {
14189 return Err(PerlError::runtime(
14190 "defer__internal expects one coderef argument",
14191 line,
14192 )
14193 .into());
14194 }
14195 self.scope.push_defer(args[0].clone());
14196 Ok(PerlValue::UNDEF)
14197 }
14198 "heap" => {
14199 if args.len() != 1 {
14200 return Err(
14201 PerlError::runtime("heap() expects one comparator sub", line).into(),
14202 );
14203 }
14204 if let Some(sub) = args[0].as_code_ref() {
14205 Ok(PerlValue::heap(Arc::new(Mutex::new(PerlHeap {
14206 items: Vec::new(),
14207 cmp: Arc::clone(&sub),
14208 }))))
14209 } else {
14210 Err(PerlError::runtime("heap() requires a code reference", line).into())
14211 }
14212 }
14213 "pipeline" => {
14214 let mut items = Vec::new();
14215 for v in args {
14216 if let Some(a) = v.as_array_vec() {
14217 items.extend(a);
14218 } else {
14219 items.push(v);
14220 }
14221 }
14222 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
14223 source: items,
14224 ops: Vec::new(),
14225 has_scalar_terminal: false,
14226 par_stream: false,
14227 streaming: false,
14228 streaming_workers: 0,
14229 streaming_buffer: 256,
14230 }))))
14231 }
14232 "par_pipeline" => {
14233 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
14234 return crate::par_pipeline::run_par_pipeline(self, &args, line)
14235 .map_err(Into::into);
14236 }
14237 Ok(self.builtin_par_pipeline_stream(&args, line)?)
14238 }
14239 "par_pipeline_stream" => {
14240 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
14241 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
14242 .map_err(Into::into);
14243 }
14244 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
14245 }
14246 "ppool" => {
14247 if args.len() != 1 {
14248 return Err(PerlError::runtime(
14249 "ppool() expects one argument (worker count)",
14250 line,
14251 )
14252 .into());
14253 }
14254 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
14255 }
14256 "barrier" => {
14257 if args.len() != 1 {
14258 return Err(PerlError::runtime(
14259 "barrier() expects one argument (party count)",
14260 line,
14261 )
14262 .into());
14263 }
14264 let n = args[0].to_int().max(1) as usize;
14265 Ok(PerlValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
14266 }
14267 "cluster" => {
14268 let items = if args.len() == 1 {
14269 args[0].to_list()
14270 } else {
14271 args.to_vec()
14272 };
14273 let c = RemoteCluster::from_list_args(&items)
14274 .map_err(|msg| PerlError::runtime(msg, line))?;
14275 Ok(PerlValue::remote_cluster(Arc::new(c)))
14276 }
14277 _ => {
14278 if let Some(method_name) = name.strip_prefix("static::") {
14280 let self_val = self.scope.get_scalar("self");
14281 if let Some(c) = self_val.as_class_inst() {
14282 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
14283 if let Some(ref body) = m.body {
14284 let params = m.params.clone();
14285 let mut call_args = vec![self_val.clone()];
14286 call_args.extend(args);
14287 return match self.call_class_method(body, ¶ms, call_args, line)
14288 {
14289 Ok(v) => Ok(v),
14290 Err(FlowOrError::Error(e)) => Err(e.into()),
14291 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14292 Err(e) => Err(e),
14293 };
14294 }
14295 }
14296 return Err(PerlError::runtime(
14297 format!(
14298 "static::{} — method not found on class {}",
14299 method_name, c.def.name
14300 ),
14301 line,
14302 )
14303 .into());
14304 }
14305 return Err(PerlError::runtime(
14306 "static:: can only be used inside a class method",
14307 line,
14308 )
14309 .into());
14310 }
14311 if let Some(def) = self.struct_defs.get(name).cloned() {
14313 return self.struct_construct(&def, args, line);
14314 }
14315 if let Some(def) = self.class_defs.get(name).cloned() {
14317 return self.class_construct(&def, args, line);
14318 }
14319 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
14321 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
14322 return self.enum_construct(&def, variant_name, args, line);
14323 }
14324 }
14325 if let Some((class_name, member_name)) = name.rsplit_once("::") {
14327 if let Some(def) = self.class_defs.get(class_name).cloned() {
14328 if let Some(m) = def.method(member_name) {
14330 if m.is_static {
14331 if let Some(ref body) = m.body {
14332 let params = m.params.clone();
14333 return match self.call_static_class_method(
14334 body,
14335 ¶ms,
14336 args.clone(),
14337 line,
14338 ) {
14339 Ok(v) => Ok(v),
14340 Err(FlowOrError::Error(e)) => Err(e.into()),
14341 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14342 Err(e) => Err(e),
14343 };
14344 }
14345 }
14346 }
14347 if def.static_fields.iter().any(|sf| sf.name == member_name) {
14349 let key = format!("{}::{}", class_name, member_name);
14350 match args.len() {
14351 0 => {
14352 let val = self.scope.get_scalar(&key);
14353 return Ok(val);
14354 }
14355 1 => {
14356 let _ = self.scope.set_scalar(&key, args[0].clone());
14357 return Ok(args[0].clone());
14358 }
14359 _ => {
14360 return Err(PerlError::runtime(
14361 format!(
14362 "static field `{}::{}` takes 0 or 1 arguments",
14363 class_name, member_name
14364 ),
14365 line,
14366 )
14367 .into());
14368 }
14369 }
14370 }
14371 }
14372 }
14373 let args = self.with_topic_default_args(args);
14374 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
14375 return r;
14376 }
14377 Err(PerlError::runtime(self.undefined_subroutine_call_message(name), line).into())
14378 }
14379 }
14380 }
14381
14382 pub(crate) fn struct_construct(
14384 &mut self,
14385 def: &Arc<StructDef>,
14386 args: Vec<PerlValue>,
14387 line: usize,
14388 ) -> ExecResult {
14389 let is_named = args.len() >= 2
14392 && args.len().is_multiple_of(2)
14393 && args.iter().step_by(2).all(|v| {
14394 let s = v.to_string();
14395 def.field_index(&s).is_some()
14396 });
14397
14398 let provided = if is_named {
14399 let mut pairs = Vec::new();
14401 let mut i = 0;
14402 while i + 1 < args.len() {
14403 let k = args[i].to_string();
14404 let v = args[i + 1].clone();
14405 pairs.push((k, v));
14406 i += 2;
14407 }
14408 pairs
14409 } else {
14410 def.fields
14412 .iter()
14413 .zip(args.iter())
14414 .map(|(f, v)| (f.name.clone(), v.clone()))
14415 .collect()
14416 };
14417
14418 let mut defaults = Vec::with_capacity(def.fields.len());
14420 for field in &def.fields {
14421 if let Some(ref expr) = field.default {
14422 let val = self.eval_expr(expr)?;
14423 defaults.push(Some(val));
14424 } else {
14425 defaults.push(None);
14426 }
14427 }
14428
14429 Ok(crate::native_data::struct_new_with_defaults(
14430 def, &provided, &defaults, line,
14431 )?)
14432 }
14433
14434 pub(crate) fn class_construct(
14436 &mut self,
14437 def: &Arc<ClassDef>,
14438 args: Vec<PerlValue>,
14439 _line: usize,
14440 ) -> ExecResult {
14441 use crate::value::ClassInstance;
14442
14443 if def.is_abstract {
14445 return Err(PerlError::runtime(
14446 format!("cannot instantiate abstract class `{}`", def.name),
14447 _line,
14448 )
14449 .into());
14450 }
14451
14452 let all_fields = self.collect_class_fields(def);
14454
14455 let is_named = args.len() >= 2
14457 && args.len().is_multiple_of(2)
14458 && args.iter().step_by(2).all(|v| {
14459 let s = v.to_string();
14460 all_fields.iter().any(|(name, _, _)| name == &s)
14461 });
14462
14463 let provided: Vec<(String, PerlValue)> = if is_named {
14464 let mut pairs = Vec::new();
14465 let mut i = 0;
14466 while i + 1 < args.len() {
14467 let k = args[i].to_string();
14468 let v = args[i + 1].clone();
14469 pairs.push((k, v));
14470 i += 2;
14471 }
14472 pairs
14473 } else {
14474 all_fields
14475 .iter()
14476 .zip(args.iter())
14477 .map(|((name, _, _), v)| (name.clone(), v.clone()))
14478 .collect()
14479 };
14480
14481 let mut values = Vec::with_capacity(all_fields.len());
14483 for (name, default, ty) in &all_fields {
14484 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
14485 val.clone()
14486 } else if let Some(ref expr) = default {
14487 self.eval_expr(expr)?
14488 } else {
14489 PerlValue::UNDEF
14490 };
14491 ty.check_value(&val).map_err(|msg| {
14492 PerlError::type_error(
14493 format!("class {} field `{}`: {}", def.name, name, msg),
14494 _line,
14495 )
14496 })?;
14497 values.push(val);
14498 }
14499
14500 let isa_chain = self.mro_linearize(&def.name);
14502 let instance = PerlValue::class_inst(Arc::new(ClassInstance::new_with_isa(
14503 Arc::clone(def),
14504 values,
14505 isa_chain,
14506 )));
14507
14508 let build_chain = self.collect_build_chain(def);
14510 if !build_chain.is_empty() {
14511 for (body, params) in &build_chain {
14512 let call_args = vec![instance.clone()];
14513 match self.call_class_method(body, params, call_args, _line) {
14514 Ok(_) => {}
14515 Err(FlowOrError::Flow(Flow::Return(_))) => {}
14516 Err(e) => return Err(e),
14517 }
14518 }
14519 }
14520
14521 Ok(instance)
14522 }
14523
14524 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14526 let mut chain = Vec::new();
14527 for parent_name in &def.extends {
14529 if let Some(parent_def) = self.class_defs.get(parent_name) {
14530 chain.extend(self.collect_build_chain(parent_def));
14531 }
14532 }
14533 if let Some(m) = def.method("BUILD") {
14535 if let Some(ref body) = m.body {
14536 chain.push((body.clone(), m.params.clone()));
14537 }
14538 }
14539 chain
14540 }
14541
14542 fn collect_class_fields(
14545 &self,
14546 def: &ClassDef,
14547 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
14548 self.collect_class_fields_full(def)
14549 .into_iter()
14550 .map(|(name, default, ty, _, _)| (name, default, ty))
14551 .collect()
14552 }
14553
14554 fn collect_class_fields_full(
14556 &self,
14557 def: &ClassDef,
14558 ) -> Vec<(
14559 String,
14560 Option<Expr>,
14561 crate::ast::PerlTypeName,
14562 crate::ast::Visibility,
14563 String,
14564 )> {
14565 let mut all_fields = Vec::new();
14566
14567 for parent_name in &def.extends {
14568 if let Some(parent_def) = self.class_defs.get(parent_name) {
14569 let parent_fields = self.collect_class_fields_full(parent_def);
14570 all_fields.extend(parent_fields);
14571 }
14572 }
14573
14574 for field in &def.fields {
14575 all_fields.push((
14576 field.name.clone(),
14577 field.default.clone(),
14578 field.ty.clone(),
14579 field.visibility,
14580 def.name.clone(),
14581 ));
14582 }
14583
14584 all_fields
14585 }
14586
14587 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
14589 for parent_name in &def.extends {
14591 if let Some(parent_def) = self.class_defs.get(parent_name) {
14592 self.collect_class_method_names(parent_def, names);
14593 }
14594 }
14595 for m in &def.methods {
14597 if !m.is_static && !names.contains(&m.name) {
14598 names.push(m.name.clone());
14599 }
14600 }
14601 }
14602
14603 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14605 let mut chain = Vec::new();
14606 if let Some(m) = def.method("DESTROY") {
14608 if let Some(ref body) = m.body {
14609 chain.push((body.clone(), m.params.clone()));
14610 }
14611 }
14612 for parent_name in &def.extends {
14614 if let Some(parent_def) = self.class_defs.get(parent_name) {
14615 chain.extend(self.collect_destroy_chain(parent_def));
14616 }
14617 }
14618 chain
14619 }
14620
14621 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
14623 if let Some(def) = self.class_defs.get(child) {
14624 for parent in &def.extends {
14625 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
14626 return true;
14627 }
14628 }
14629 }
14630 false
14631 }
14632
14633 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
14635 if let Some(m) = def.method(method) {
14637 return Some((m.clone(), def.name.clone()));
14638 }
14639 for parent_name in &def.extends {
14641 if let Some(parent_def) = self.class_defs.get(parent_name) {
14642 if let Some(result) = self.find_class_method(parent_def, method) {
14643 return Some(result);
14644 }
14645 }
14646 }
14647 None
14648 }
14649
14650 pub(crate) fn enum_construct(
14652 &mut self,
14653 def: &Arc<EnumDef>,
14654 variant_name: &str,
14655 args: Vec<PerlValue>,
14656 line: usize,
14657 ) -> ExecResult {
14658 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
14659 FlowOrError::Error(PerlError::runtime(
14660 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
14661 line,
14662 ))
14663 })?;
14664 let variant = &def.variants[variant_idx];
14665 let data = if variant.ty.is_some() {
14666 if args.is_empty() {
14667 return Err(PerlError::runtime(
14668 format!(
14669 "enum variant `{}::{}` requires data",
14670 def.name, variant_name
14671 ),
14672 line,
14673 )
14674 .into());
14675 }
14676 if args.len() == 1 {
14677 args.into_iter().next().unwrap()
14678 } else {
14679 PerlValue::array(args)
14680 }
14681 } else {
14682 if !args.is_empty() {
14683 return Err(PerlError::runtime(
14684 format!(
14685 "enum variant `{}::{}` does not take data",
14686 def.name, variant_name
14687 ),
14688 line,
14689 )
14690 .into());
14691 }
14692 PerlValue::UNDEF
14693 };
14694 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
14695 Ok(PerlValue::enum_inst(Arc::new(inst)))
14696 }
14697
14698 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
14700 matches!(name, "STDIN" | "STDOUT" | "STDERR")
14701 || self.input_handles.contains_key(name)
14702 || self.output_handles.contains_key(name)
14703 || self.io_file_slots.contains_key(name)
14704 || self.pipe_children.contains_key(name)
14705 }
14706
14707 pub(crate) fn io_handle_method(
14709 &mut self,
14710 name: &str,
14711 method: &str,
14712 args: &[PerlValue],
14713 line: usize,
14714 ) -> PerlResult<PerlValue> {
14715 match method {
14716 "print" => self.io_handle_print(name, args, false, line),
14717 "say" => self.io_handle_print(name, args, true, line),
14718 "printf" => self.io_handle_printf(name, args, line),
14719 "getline" | "readline" => {
14720 if !args.is_empty() {
14721 return Err(PerlError::runtime(
14722 format!("{}: too many arguments", method),
14723 line,
14724 ));
14725 }
14726 self.readline_builtin_execute(Some(name))
14727 }
14728 "close" => {
14729 if !args.is_empty() {
14730 return Err(PerlError::runtime("close: too many arguments", line));
14731 }
14732 self.close_builtin_execute(name.to_string())
14733 }
14734 "eof" => {
14735 if !args.is_empty() {
14736 return Err(PerlError::runtime("eof: too many arguments", line));
14737 }
14738 let at_eof = !self.has_input_handle(name);
14739 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
14740 }
14741 "getc" => {
14742 if !args.is_empty() {
14743 return Err(PerlError::runtime("getc: too many arguments", line));
14744 }
14745 match crate::builtins::try_builtin(
14746 self,
14747 "getc",
14748 &[PerlValue::string(name.to_string())],
14749 line,
14750 ) {
14751 Some(r) => r,
14752 None => Err(PerlError::runtime("getc: not available", line)),
14753 }
14754 }
14755 "binmode" => match crate::builtins::try_builtin(
14756 self,
14757 "binmode",
14758 &[PerlValue::string(name.to_string())],
14759 line,
14760 ) {
14761 Some(r) => r,
14762 None => Err(PerlError::runtime("binmode: not available", line)),
14763 },
14764 "fileno" => match crate::builtins::try_builtin(
14765 self,
14766 "fileno",
14767 &[PerlValue::string(name.to_string())],
14768 line,
14769 ) {
14770 Some(r) => r,
14771 None => Err(PerlError::runtime("fileno: not available", line)),
14772 },
14773 "flush" => {
14774 if !args.is_empty() {
14775 return Err(PerlError::runtime("flush: too many arguments", line));
14776 }
14777 self.io_handle_flush(name, line)
14778 }
14779 _ => Err(PerlError::runtime(
14780 format!("Unknown method for filehandle: {}", method),
14781 line,
14782 )),
14783 }
14784 }
14785
14786 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> PerlResult<PerlValue> {
14787 match handle_name {
14788 "STDOUT" => {
14789 let _ = IoWrite::flush(&mut io::stdout());
14790 }
14791 "STDERR" => {
14792 let _ = IoWrite::flush(&mut io::stderr());
14793 }
14794 name => {
14795 if let Some(writer) = self.output_handles.get_mut(name) {
14796 let _ = IoWrite::flush(&mut *writer);
14797 } else {
14798 return Err(PerlError::runtime(
14799 format!("flush on unopened filehandle {}", name),
14800 line,
14801 ));
14802 }
14803 }
14804 }
14805 Ok(PerlValue::integer(1))
14806 }
14807
14808 fn io_handle_print(
14809 &mut self,
14810 handle_name: &str,
14811 args: &[PerlValue],
14812 newline: bool,
14813 line: usize,
14814 ) -> PerlResult<PerlValue> {
14815 if newline && (self.feature_bits & FEAT_SAY) == 0 {
14816 return Err(PerlError::runtime(
14817 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
14818 line,
14819 ));
14820 }
14821 let mut output = String::new();
14822 if args.is_empty() {
14823 output.push_str(&self.scope.get_scalar("_").to_string());
14825 } else {
14826 for (i, val) in args.iter().enumerate() {
14827 if i > 0 && !self.ofs.is_empty() {
14828 output.push_str(&self.ofs);
14829 }
14830 output.push_str(&val.to_string());
14831 }
14832 }
14833 if newline {
14834 output.push('\n');
14835 }
14836 output.push_str(&self.ors);
14837
14838 self.write_formatted_print(handle_name, &output, line)?;
14839 Ok(PerlValue::integer(1))
14840 }
14841
14842 pub(crate) fn write_formatted_print(
14845 &mut self,
14846 handle_name: &str,
14847 output: &str,
14848 line: usize,
14849 ) -> PerlResult<()> {
14850 match handle_name {
14851 "STDOUT" => {
14852 if !self.suppress_stdout {
14853 print!("{}", output);
14854 if self.output_autoflush {
14855 let _ = io::stdout().flush();
14856 }
14857 }
14858 }
14859 "STDERR" => {
14860 eprint!("{}", output);
14861 let _ = io::stderr().flush();
14862 }
14863 name => {
14864 if let Some(writer) = self.output_handles.get_mut(name) {
14865 let _ = writer.write_all(output.as_bytes());
14866 if self.output_autoflush {
14867 let _ = writer.flush();
14868 }
14869 } else {
14870 return Err(PerlError::runtime(
14871 format!("print on unopened filehandle {}", name),
14872 line,
14873 ));
14874 }
14875 }
14876 }
14877 Ok(())
14878 }
14879
14880 fn io_handle_printf(
14881 &mut self,
14882 handle_name: &str,
14883 args: &[PerlValue],
14884 line: usize,
14885 ) -> PerlResult<PerlValue> {
14886 let (fmt, rest): (String, &[PerlValue]) = if args.is_empty() {
14887 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
14888 Ok(s) => s,
14889 Err(FlowOrError::Error(e)) => return Err(e),
14890 Err(FlowOrError::Flow(_)) => {
14891 return Err(PerlError::runtime(
14892 "printf: unexpected control flow in sprintf",
14893 line,
14894 ));
14895 }
14896 };
14897 (s, &[])
14898 } else {
14899 (args[0].to_string(), &args[1..])
14900 };
14901 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
14902 Ok(s) => s,
14903 Err(FlowOrError::Error(e)) => return Err(e),
14904 Err(FlowOrError::Flow(_)) => {
14905 return Err(PerlError::runtime(
14906 "printf: unexpected control flow in sprintf",
14907 line,
14908 ));
14909 }
14910 };
14911 match handle_name {
14912 "STDOUT" => {
14913 if !self.suppress_stdout {
14914 print!("{}", output);
14915 if self.output_autoflush {
14916 let _ = IoWrite::flush(&mut io::stdout());
14917 }
14918 }
14919 }
14920 "STDERR" => {
14921 eprint!("{}", output);
14922 let _ = IoWrite::flush(&mut io::stderr());
14923 }
14924 name => {
14925 if let Some(writer) = self.output_handles.get_mut(name) {
14926 let _ = writer.write_all(output.as_bytes());
14927 if self.output_autoflush {
14928 let _ = writer.flush();
14929 }
14930 } else {
14931 return Err(PerlError::runtime(
14932 format!("printf on unopened filehandle {}", name),
14933 line,
14934 ));
14935 }
14936 }
14937 }
14938 Ok(PerlValue::integer(1))
14939 }
14940
14941 pub(crate) fn try_native_method(
14943 &mut self,
14944 receiver: &PerlValue,
14945 method: &str,
14946 args: &[PerlValue],
14947 line: usize,
14948 ) -> Option<PerlResult<PerlValue>> {
14949 if let Some(name) = receiver.as_io_handle_name() {
14950 return Some(self.io_handle_method(&name, method, args, line));
14951 }
14952 if let Some(ref s) = receiver.as_str() {
14953 if self.is_bound_handle(s) {
14954 return Some(self.io_handle_method(s, method, args, line));
14955 }
14956 }
14957 if let Some(c) = receiver.as_sqlite_conn() {
14958 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
14959 }
14960 if let Some(s) = receiver.as_struct_inst() {
14961 if let Some(idx) = s.def.field_index(method) {
14963 match args.len() {
14964 0 => {
14965 return Some(Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF)));
14966 }
14967 1 => {
14968 let field = &s.def.fields[idx];
14969 let new_val = args[0].clone();
14970 if let Err(msg) = field.ty.check_value(&new_val) {
14971 return Some(Err(PerlError::type_error(
14972 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
14973 line,
14974 )));
14975 }
14976 s.set_field(idx, new_val.clone());
14977 return Some(Ok(new_val));
14978 }
14979 _ => {
14980 return Some(Err(PerlError::runtime(
14981 format!(
14982 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
14983 method,
14984 args.len()
14985 ),
14986 line,
14987 )));
14988 }
14989 }
14990 }
14991 match method {
14993 "with" => {
14994 let mut new_values = s.get_values();
14996 let mut i = 0;
14997 while i + 1 < args.len() {
14998 let k = args[i].to_string();
14999 let v = args[i + 1].clone();
15000 if let Some(idx) = s.def.field_index(&k) {
15001 let field = &s.def.fields[idx];
15002 if let Err(msg) = field.ty.check_value(&v) {
15003 return Some(Err(PerlError::type_error(
15004 format!(
15005 "struct {} field `{}`: {}",
15006 s.def.name, field.name, msg
15007 ),
15008 line,
15009 )));
15010 }
15011 new_values[idx] = v;
15012 } else {
15013 return Some(Err(PerlError::runtime(
15014 format!("struct {}: unknown field `{}`", s.def.name, k),
15015 line,
15016 )));
15017 }
15018 i += 2;
15019 }
15020 return Some(Ok(PerlValue::struct_inst(Arc::new(
15021 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
15022 ))));
15023 }
15024 "to_hash" => {
15025 if !args.is_empty() {
15027 return Some(Err(PerlError::runtime(
15028 "struct to_hash takes no arguments",
15029 line,
15030 )));
15031 }
15032 let mut map = IndexMap::new();
15033 let values = s.get_values();
15034 for (i, field) in s.def.fields.iter().enumerate() {
15035 map.insert(field.name.clone(), values[i].clone());
15036 }
15037 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
15038 }
15039 "fields" => {
15040 if !args.is_empty() {
15042 return Some(Err(PerlError::runtime(
15043 "struct fields takes no arguments",
15044 line,
15045 )));
15046 }
15047 let names: Vec<PerlValue> = s
15048 .def
15049 .fields
15050 .iter()
15051 .map(|f| PerlValue::string(f.name.clone()))
15052 .collect();
15053 return Some(Ok(PerlValue::array(names)));
15054 }
15055 "clone" => {
15056 if !args.is_empty() {
15058 return Some(Err(PerlError::runtime(
15059 "struct clone takes no arguments",
15060 line,
15061 )));
15062 }
15063 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
15064 return Some(Ok(PerlValue::struct_inst(Arc::new(
15065 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
15066 ))));
15067 }
15068 _ => {}
15069 }
15070 if let Some(m) = s.def.method(method) {
15072 let body = m.body.clone();
15073 let params = m.params.clone();
15074 let mut call_args = vec![receiver.clone()];
15076 call_args.extend(args.iter().cloned());
15077 return Some(
15078 match self.call_struct_method(&body, ¶ms, call_args, line) {
15079 Ok(v) => Ok(v),
15080 Err(FlowOrError::Error(e)) => Err(e),
15081 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15082 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
15083 "unexpected control flow in struct method",
15084 line,
15085 )),
15086 },
15087 );
15088 }
15089 return None;
15090 }
15091 if let Some(c) = receiver.as_class_inst() {
15093 let all_fields_full = self.collect_class_fields_full(&c.def);
15095 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
15096 .iter()
15097 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
15098 .collect();
15099
15100 if let Some(idx) = all_fields_full
15102 .iter()
15103 .position(|(name, _, _, _, _)| name == method)
15104 {
15105 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
15106
15107 match vis {
15109 crate::ast::Visibility::Private => {
15110 let caller_class = self
15112 .scope
15113 .get_scalar("self")
15114 .as_class_inst()
15115 .map(|ci| ci.def.name.clone());
15116 if caller_class.as_deref() != Some(owner_class.as_str()) {
15117 return Some(Err(PerlError::runtime(
15118 format!("field `{}` of class {} is private", method, owner_class),
15119 line,
15120 )));
15121 }
15122 }
15123 crate::ast::Visibility::Protected => {
15124 let caller_class = self
15126 .scope
15127 .get_scalar("self")
15128 .as_class_inst()
15129 .map(|ci| ci.def.name.clone());
15130 let allowed = caller_class.as_deref().is_some_and(|caller| {
15131 caller == owner_class || self.class_inherits_from(caller, owner_class)
15132 });
15133 if !allowed {
15134 return Some(Err(PerlError::runtime(
15135 format!("field `{}` of class {} is protected", method, owner_class),
15136 line,
15137 )));
15138 }
15139 }
15140 crate::ast::Visibility::Public => {}
15141 }
15142
15143 match args.len() {
15144 0 => {
15145 return Some(Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF)));
15146 }
15147 1 => {
15148 let new_val = args[0].clone();
15149 if let Err(msg) = ty.check_value(&new_val) {
15150 return Some(Err(PerlError::type_error(
15151 format!("class {} field `{}`: {}", c.def.name, method, msg),
15152 line,
15153 )));
15154 }
15155 c.set_field(idx, new_val.clone());
15156 return Some(Ok(new_val));
15157 }
15158 _ => {
15159 return Some(Err(PerlError::runtime(
15160 format!(
15161 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
15162 method,
15163 args.len()
15164 ),
15165 line,
15166 )));
15167 }
15168 }
15169 }
15170 match method {
15172 "with" => {
15173 let mut new_values = c.get_values();
15174 let mut i = 0;
15175 while i + 1 < args.len() {
15176 let k = args[i].to_string();
15177 let v = args[i + 1].clone();
15178 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
15179 let (_, _, ref ty) = all_fields[idx];
15180 if let Err(msg) = ty.check_value(&v) {
15181 return Some(Err(PerlError::type_error(
15182 format!("class {} field `{}`: {}", c.def.name, k, msg),
15183 line,
15184 )));
15185 }
15186 new_values[idx] = v;
15187 } else {
15188 return Some(Err(PerlError::runtime(
15189 format!("class {}: unknown field `{}`", c.def.name, k),
15190 line,
15191 )));
15192 }
15193 i += 2;
15194 }
15195 return Some(Ok(PerlValue::class_inst(Arc::new(
15196 crate::value::ClassInstance::new_with_isa(
15197 Arc::clone(&c.def),
15198 new_values,
15199 c.isa_chain.clone(),
15200 ),
15201 ))));
15202 }
15203 "to_hash" => {
15204 if !args.is_empty() {
15205 return Some(Err(PerlError::runtime(
15206 "class to_hash takes no arguments",
15207 line,
15208 )));
15209 }
15210 let mut map = IndexMap::new();
15211 let values = c.get_values();
15212 for (i, (name, _, _)) in all_fields.iter().enumerate() {
15213 if let Some(v) = values.get(i) {
15214 map.insert(name.clone(), v.clone());
15215 }
15216 }
15217 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
15218 }
15219 "fields" => {
15220 if !args.is_empty() {
15221 return Some(Err(PerlError::runtime(
15222 "class fields takes no arguments",
15223 line,
15224 )));
15225 }
15226 let names: Vec<PerlValue> = all_fields
15227 .iter()
15228 .map(|(name, _, _)| PerlValue::string(name.clone()))
15229 .collect();
15230 return Some(Ok(PerlValue::array(names)));
15231 }
15232 "clone" => {
15233 if !args.is_empty() {
15234 return Some(Err(PerlError::runtime(
15235 "class clone takes no arguments",
15236 line,
15237 )));
15238 }
15239 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
15240 return Some(Ok(PerlValue::class_inst(Arc::new(
15241 crate::value::ClassInstance::new_with_isa(
15242 Arc::clone(&c.def),
15243 new_values,
15244 c.isa_chain.clone(),
15245 ),
15246 ))));
15247 }
15248 "isa" => {
15249 if args.len() != 1 {
15250 return Some(Err(PerlError::runtime("isa requires one argument", line)));
15251 }
15252 let class_name = args[0].to_string();
15253 let is_a = c.isa(&class_name);
15254 return Some(Ok(if is_a {
15255 PerlValue::integer(1)
15256 } else {
15257 PerlValue::string(String::new())
15258 }));
15259 }
15260 "does" => {
15261 if args.len() != 1 {
15262 return Some(Err(PerlError::runtime("does requires one argument", line)));
15263 }
15264 let trait_name = args[0].to_string();
15265 let implements = c.def.implements.contains(&trait_name);
15266 return Some(Ok(if implements {
15267 PerlValue::integer(1)
15268 } else {
15269 PerlValue::string(String::new())
15270 }));
15271 }
15272 "methods" => {
15273 if !args.is_empty() {
15274 return Some(Err(PerlError::runtime("methods takes no arguments", line)));
15275 }
15276 let mut names = Vec::new();
15277 self.collect_class_method_names(&c.def, &mut names);
15278 let values: Vec<PerlValue> = names.into_iter().map(PerlValue::string).collect();
15279 return Some(Ok(PerlValue::array(values)));
15280 }
15281 "superclass" => {
15282 if !args.is_empty() {
15283 return Some(Err(PerlError::runtime(
15284 "superclass takes no arguments",
15285 line,
15286 )));
15287 }
15288 let parents: Vec<PerlValue> = c
15289 .def
15290 .extends
15291 .iter()
15292 .map(|s| PerlValue::string(s.clone()))
15293 .collect();
15294 return Some(Ok(PerlValue::array(parents)));
15295 }
15296 "destroy" => {
15297 let destroy_chain = self.collect_destroy_chain(&c.def);
15299 for (body, params) in &destroy_chain {
15300 let call_args = vec![receiver.clone()];
15301 match self.call_class_method(body, params, call_args, line) {
15302 Ok(_) => {}
15303 Err(FlowOrError::Flow(Flow::Return(_))) => {}
15304 Err(FlowOrError::Error(e)) => return Some(Err(e)),
15305 Err(_) => {}
15306 }
15307 }
15308 return Some(Ok(PerlValue::UNDEF));
15309 }
15310 _ => {}
15311 }
15312 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
15314 match m.visibility {
15316 crate::ast::Visibility::Private => {
15317 let caller_class = self
15318 .scope
15319 .get_scalar("self")
15320 .as_class_inst()
15321 .map(|ci| ci.def.name.clone());
15322 if caller_class.as_deref() != Some(owner_class.as_str()) {
15323 return Some(Err(PerlError::runtime(
15324 format!("method `{}` of class {} is private", method, owner_class),
15325 line,
15326 )));
15327 }
15328 }
15329 crate::ast::Visibility::Protected => {
15330 let caller_class = self
15331 .scope
15332 .get_scalar("self")
15333 .as_class_inst()
15334 .map(|ci| ci.def.name.clone());
15335 let allowed = caller_class.as_deref().is_some_and(|caller| {
15336 caller == owner_class.as_str()
15337 || self.class_inherits_from(caller, owner_class)
15338 });
15339 if !allowed {
15340 return Some(Err(PerlError::runtime(
15341 format!(
15342 "method `{}` of class {} is protected",
15343 method, owner_class
15344 ),
15345 line,
15346 )));
15347 }
15348 }
15349 crate::ast::Visibility::Public => {}
15350 }
15351 if let Some(ref body) = m.body {
15352 let params = m.params.clone();
15353 let mut call_args = vec![receiver.clone()];
15354 call_args.extend(args.iter().cloned());
15355 return Some(
15356 match self.call_class_method(body, ¶ms, call_args, line) {
15357 Ok(v) => Ok(v),
15358 Err(FlowOrError::Error(e)) => Err(e),
15359 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15360 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
15361 "unexpected control flow in class method",
15362 line,
15363 )),
15364 },
15365 );
15366 }
15367 }
15368 return None;
15369 }
15370 if let Some(d) = receiver.as_dataframe() {
15371 return Some(self.dataframe_method(d, method, args, line));
15372 }
15373 if let Some(s) = crate::value::set_payload(receiver) {
15374 return Some(self.set_method(s, method, args, line));
15375 }
15376 if let Some(d) = receiver.as_deque() {
15377 return Some(self.deque_method(d, method, args, line));
15378 }
15379 if let Some(h) = receiver.as_heap_pq() {
15380 return Some(self.heap_method(h, method, args, line));
15381 }
15382 if let Some(p) = receiver.as_pipeline() {
15383 return Some(self.pipeline_method(p, method, args, line));
15384 }
15385 if let Some(c) = receiver.as_capture() {
15386 return Some(self.capture_method(c, method, args, line));
15387 }
15388 if let Some(p) = receiver.as_ppool() {
15389 return Some(self.ppool_method(p, method, args, line));
15390 }
15391 if let Some(b) = receiver.as_barrier() {
15392 return Some(self.barrier_method(b, method, args, line));
15393 }
15394 if let Some(g) = receiver.as_generator() {
15395 if method == "next" {
15396 if !args.is_empty() {
15397 return Some(Err(PerlError::runtime(
15398 "generator->next takes no arguments",
15399 line,
15400 )));
15401 }
15402 return Some(self.generator_next(&g));
15403 }
15404 return None;
15405 }
15406 if let Some(arc) = receiver.as_atomic_arc() {
15407 let inner = arc.lock().clone();
15408 if let Some(d) = inner.as_deque() {
15409 return Some(self.deque_method(d, method, args, line));
15410 }
15411 if let Some(h) = inner.as_heap_pq() {
15412 return Some(self.heap_method(h, method, args, line));
15413 }
15414 }
15415 None
15416 }
15417
15418 fn dataframe_method(
15420 &mut self,
15421 d: Arc<Mutex<PerlDataFrame>>,
15422 method: &str,
15423 args: &[PerlValue],
15424 line: usize,
15425 ) -> PerlResult<PerlValue> {
15426 match method {
15427 "nrow" | "nrows" => {
15428 if !args.is_empty() {
15429 return Err(PerlError::runtime(
15430 format!("dataframe {} takes no arguments", method),
15431 line,
15432 ));
15433 }
15434 Ok(PerlValue::integer(d.lock().nrows() as i64))
15435 }
15436 "ncol" | "ncols" => {
15437 if !args.is_empty() {
15438 return Err(PerlError::runtime(
15439 format!("dataframe {} takes no arguments", method),
15440 line,
15441 ));
15442 }
15443 Ok(PerlValue::integer(d.lock().ncols() as i64))
15444 }
15445 "filter" => {
15446 if args.len() != 1 {
15447 return Err(PerlError::runtime(
15448 "dataframe filter expects 1 argument (sub)",
15449 line,
15450 ));
15451 }
15452 let Some(sub) = args[0].as_code_ref() else {
15453 return Err(PerlError::runtime(
15454 "dataframe filter expects a code reference",
15455 line,
15456 ));
15457 };
15458 let df_guard = d.lock();
15459 let n = df_guard.nrows();
15460 let mut keep = vec![false; n];
15461 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
15462 let row = df_guard.row_hashref(r);
15463 self.scope_push_hook();
15464 self.scope.set_topic(row);
15465 if let Some(ref env) = sub.closure_env {
15466 self.scope.restore_capture(env);
15467 }
15468 let pass = match self.exec_block_no_scope(&sub.body) {
15469 Ok(v) => v.is_true(),
15470 Err(_) => false,
15471 };
15472 self.scope_pop_hook();
15473 *row_keep = pass;
15474 }
15475 let columns = df_guard.columns.clone();
15476 let cols: Vec<Vec<PerlValue>> = (0..df_guard.ncols())
15477 .map(|i| {
15478 let mut out = Vec::new();
15479 for (r, pass_row) in keep.iter().enumerate().take(n) {
15480 if *pass_row {
15481 out.push(df_guard.cols[i][r].clone());
15482 }
15483 }
15484 out
15485 })
15486 .collect();
15487 let group_by = df_guard.group_by.clone();
15488 drop(df_guard);
15489 let new_df = PerlDataFrame {
15490 columns,
15491 cols,
15492 group_by,
15493 };
15494 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15495 }
15496 "group_by" => {
15497 if args.len() != 1 {
15498 return Err(PerlError::runtime(
15499 "dataframe group_by expects 1 column name",
15500 line,
15501 ));
15502 }
15503 let key = args[0].to_string();
15504 let inner = d.lock();
15505 if inner.col_index(&key).is_none() {
15506 return Err(PerlError::runtime(
15507 format!("dataframe group_by: unknown column \"{}\"", key),
15508 line,
15509 ));
15510 }
15511 let new_df = PerlDataFrame {
15512 columns: inner.columns.clone(),
15513 cols: inner.cols.clone(),
15514 group_by: Some(key),
15515 };
15516 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15517 }
15518 "sum" => {
15519 if args.len() != 1 {
15520 return Err(PerlError::runtime(
15521 "dataframe sum expects 1 column name",
15522 line,
15523 ));
15524 }
15525 let col_name = args[0].to_string();
15526 let inner = d.lock();
15527 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
15528 PerlError::runtime(
15529 format!("dataframe sum: unknown column \"{}\"", col_name),
15530 line,
15531 )
15532 })?;
15533 match &inner.group_by {
15534 Some(gcol) => {
15535 let gi = inner.col_index(gcol).ok_or_else(|| {
15536 PerlError::runtime(
15537 format!("dataframe sum: unknown group column \"{}\"", gcol),
15538 line,
15539 )
15540 })?;
15541 let mut acc: IndexMap<String, f64> = IndexMap::new();
15542 for r in 0..inner.nrows() {
15543 let k = inner.cols[gi][r].to_string();
15544 let v = inner.cols[val_idx][r].to_number();
15545 *acc.entry(k).or_insert(0.0) += v;
15546 }
15547 let keys: Vec<String> = acc.keys().cloned().collect();
15548 let sums: Vec<f64> = acc.values().copied().collect();
15549 let cols = vec![
15550 keys.into_iter().map(PerlValue::string).collect(),
15551 sums.into_iter().map(PerlValue::float).collect(),
15552 ];
15553 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
15554 let out = PerlDataFrame {
15555 columns,
15556 cols,
15557 group_by: None,
15558 };
15559 Ok(PerlValue::dataframe(Arc::new(Mutex::new(out))))
15560 }
15561 None => {
15562 let total: f64 = (0..inner.nrows())
15563 .map(|r| inner.cols[val_idx][r].to_number())
15564 .sum();
15565 Ok(PerlValue::float(total))
15566 }
15567 }
15568 }
15569 _ => Err(PerlError::runtime(
15570 format!("Unknown method for dataframe: {}", method),
15571 line,
15572 )),
15573 }
15574 }
15575
15576 fn set_method(
15578 &self,
15579 s: Arc<crate::value::PerlSet>,
15580 method: &str,
15581 args: &[PerlValue],
15582 line: usize,
15583 ) -> PerlResult<PerlValue> {
15584 match method {
15585 "has" | "contains" | "member" => {
15586 if args.len() != 1 {
15587 return Err(PerlError::runtime(
15588 "set->has expects one argument (element)",
15589 line,
15590 ));
15591 }
15592 let k = crate::value::set_member_key(&args[0]);
15593 Ok(PerlValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
15594 }
15595 "size" | "len" | "count" => {
15596 if !args.is_empty() {
15597 return Err(PerlError::runtime("set->size takes no arguments", line));
15598 }
15599 Ok(PerlValue::integer(s.len() as i64))
15600 }
15601 "values" | "list" | "elements" => {
15602 if !args.is_empty() {
15603 return Err(PerlError::runtime("set->values takes no arguments", line));
15604 }
15605 Ok(PerlValue::array(s.values().cloned().collect()))
15606 }
15607 _ => Err(PerlError::runtime(
15608 format!("Unknown method for set: {}", method),
15609 line,
15610 )),
15611 }
15612 }
15613
15614 fn deque_method(
15615 &mut self,
15616 d: Arc<Mutex<VecDeque<PerlValue>>>,
15617 method: &str,
15618 args: &[PerlValue],
15619 line: usize,
15620 ) -> PerlResult<PerlValue> {
15621 match method {
15622 "push_back" => {
15623 if args.len() != 1 {
15624 return Err(PerlError::runtime("push_back expects 1 argument", line));
15625 }
15626 d.lock().push_back(args[0].clone());
15627 Ok(PerlValue::integer(d.lock().len() as i64))
15628 }
15629 "push_front" => {
15630 if args.len() != 1 {
15631 return Err(PerlError::runtime("push_front expects 1 argument", line));
15632 }
15633 d.lock().push_front(args[0].clone());
15634 Ok(PerlValue::integer(d.lock().len() as i64))
15635 }
15636 "pop_back" => Ok(d.lock().pop_back().unwrap_or(PerlValue::UNDEF)),
15637 "pop_front" => Ok(d.lock().pop_front().unwrap_or(PerlValue::UNDEF)),
15638 "size" | "len" => Ok(PerlValue::integer(d.lock().len() as i64)),
15639 _ => Err(PerlError::runtime(
15640 format!("Unknown method for deque: {}", method),
15641 line,
15642 )),
15643 }
15644 }
15645
15646 fn heap_method(
15647 &mut self,
15648 h: Arc<Mutex<PerlHeap>>,
15649 method: &str,
15650 args: &[PerlValue],
15651 line: usize,
15652 ) -> PerlResult<PerlValue> {
15653 match method {
15654 "push" => {
15655 if args.len() != 1 {
15656 return Err(PerlError::runtime("heap push expects 1 argument", line));
15657 }
15658 let mut g = h.lock();
15659 let n = g.items.len();
15660 g.items.push(args[0].clone());
15661 let cmp = g.cmp.clone();
15662 drop(g);
15663 let mut g = h.lock();
15664 self.heap_sift_up(&mut g.items, &cmp, n);
15665 Ok(PerlValue::integer(g.items.len() as i64))
15666 }
15667 "pop" => {
15668 let mut g = h.lock();
15669 if g.items.is_empty() {
15670 return Ok(PerlValue::UNDEF);
15671 }
15672 let cmp = g.cmp.clone();
15673 let n = g.items.len();
15674 g.items.swap(0, n - 1);
15675 let v = g.items.pop().unwrap();
15676 if !g.items.is_empty() {
15677 self.heap_sift_down(&mut g.items, &cmp, 0);
15678 }
15679 Ok(v)
15680 }
15681 "peek" => Ok(h.lock().items.first().cloned().unwrap_or(PerlValue::UNDEF)),
15682 _ => Err(PerlError::runtime(
15683 format!("Unknown method for heap: {}", method),
15684 line,
15685 )),
15686 }
15687 }
15688
15689 fn ppool_method(
15690 &mut self,
15691 pool: PerlPpool,
15692 method: &str,
15693 args: &[PerlValue],
15694 line: usize,
15695 ) -> PerlResult<PerlValue> {
15696 match method {
15697 "submit" => pool.submit(self, args, line),
15698 "collect" => {
15699 if !args.is_empty() {
15700 return Err(PerlError::runtime("collect() takes no arguments", line));
15701 }
15702 pool.collect(line)
15703 }
15704 _ => Err(PerlError::runtime(
15705 format!("Unknown method for ppool: {}", method),
15706 line,
15707 )),
15708 }
15709 }
15710
15711 fn barrier_method(
15712 &self,
15713 barrier: PerlBarrier,
15714 method: &str,
15715 args: &[PerlValue],
15716 line: usize,
15717 ) -> PerlResult<PerlValue> {
15718 match method {
15719 "wait" => {
15720 if !args.is_empty() {
15721 return Err(PerlError::runtime("wait() takes no arguments", line));
15722 }
15723 let _ = barrier.0.wait();
15724 Ok(PerlValue::integer(1))
15725 }
15726 _ => Err(PerlError::runtime(
15727 format!("Unknown method for barrier: {}", method),
15728 line,
15729 )),
15730 }
15731 }
15732
15733 fn capture_method(
15734 &self,
15735 c: Arc<CaptureResult>,
15736 method: &str,
15737 args: &[PerlValue],
15738 line: usize,
15739 ) -> PerlResult<PerlValue> {
15740 if !args.is_empty() {
15741 return Err(PerlError::runtime(
15742 format!("capture: {} takes no arguments", method),
15743 line,
15744 ));
15745 }
15746 match method {
15747 "stdout" => Ok(PerlValue::string(c.stdout.clone())),
15748 "stderr" => Ok(PerlValue::string(c.stderr.clone())),
15749 "exitcode" => Ok(PerlValue::integer(c.exitcode)),
15750 "failed" => Ok(PerlValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
15751 _ => Err(PerlError::runtime(
15752 format!("Unknown method for capture: {}", method),
15753 line,
15754 )),
15755 }
15756 }
15757
15758 pub(crate) fn builtin_par_pipeline_stream(
15759 &mut self,
15760 args: &[PerlValue],
15761 _line: usize,
15762 ) -> PerlResult<PerlValue> {
15763 let mut items = Vec::new();
15764 for v in args {
15765 if let Some(a) = v.as_array_vec() {
15766 items.extend(a);
15767 } else {
15768 items.push(v.clone());
15769 }
15770 }
15771 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15772 source: items,
15773 ops: Vec::new(),
15774 has_scalar_terminal: false,
15775 par_stream: true,
15776 streaming: false,
15777 streaming_workers: 0,
15778 streaming_buffer: 256,
15779 }))))
15780 }
15781
15782 pub(crate) fn builtin_par_pipeline_stream_new(
15785 &mut self,
15786 args: &[PerlValue],
15787 _line: usize,
15788 ) -> PerlResult<PerlValue> {
15789 let mut items = Vec::new();
15790 let mut workers: usize = 0;
15791 let mut buffer: usize = 256;
15792 let mut i = 0;
15794 while i < args.len() {
15795 let s = args[i].to_string();
15796 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
15797 let val = args[i + 1].to_int().max(1) as usize;
15798 if s == "workers" {
15799 workers = val;
15800 } else {
15801 buffer = val;
15802 }
15803 i += 2;
15804 } else if let Some(a) = args[i].as_array_vec() {
15805 items.extend(a);
15806 i += 1;
15807 } else {
15808 items.push(args[i].clone());
15809 i += 1;
15810 }
15811 }
15812 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15813 source: items,
15814 ops: Vec::new(),
15815 has_scalar_terminal: false,
15816 par_stream: false,
15817 streaming: true,
15818 streaming_workers: workers,
15819 streaming_buffer: buffer,
15820 }))))
15821 }
15822
15823 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<PerlSub> {
15825 let line = 1usize;
15826 let body = vec![Statement {
15827 label: None,
15828 kind: StmtKind::Expression(Expr {
15829 kind: ExprKind::BinOp {
15830 left: Box::new(Expr {
15831 kind: ExprKind::ScalarVar("_".into()),
15832 line,
15833 }),
15834 op: BinOp::Mul,
15835 right: Box::new(Expr {
15836 kind: ExprKind::Integer(k),
15837 line,
15838 }),
15839 },
15840 line,
15841 }),
15842 line,
15843 }];
15844 Arc::new(PerlSub {
15845 name: "__pipeline_int_mul__".into(),
15846 params: vec![],
15847 body,
15848 closure_env: None,
15849 prototype: None,
15850 fib_like: None,
15851 })
15852 }
15853
15854 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<PerlSub> {
15855 let captured = self.scope.capture();
15856 Arc::new(PerlSub {
15857 name: "__ANON__".into(),
15858 params: vec![],
15859 body: block.clone(),
15860 closure_env: Some(captured),
15861 prototype: None,
15862 fib_like: None,
15863 })
15864 }
15865
15866 pub(crate) fn builtin_collect_execute(
15867 &mut self,
15868 args: &[PerlValue],
15869 line: usize,
15870 ) -> PerlResult<PerlValue> {
15871 if args.is_empty() {
15872 return Err(PerlError::runtime(
15873 "collect() expects at least one argument",
15874 line,
15875 ));
15876 }
15877 if args.len() == 1 {
15880 if let Some(p) = args[0].as_pipeline() {
15881 return self.pipeline_collect(&p, line);
15882 }
15883 return Ok(PerlValue::array(args[0].to_list()));
15884 }
15885 Ok(PerlValue::array(args.to_vec()))
15886 }
15887
15888 pub(crate) fn pipeline_push(
15889 &self,
15890 p: &Arc<Mutex<PipelineInner>>,
15891 op: PipelineOp,
15892 line: usize,
15893 ) -> PerlResult<()> {
15894 let mut g = p.lock();
15895 if g.has_scalar_terminal {
15896 return Err(PerlError::runtime(
15897 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
15898 line,
15899 ));
15900 }
15901 if matches!(
15902 &op,
15903 PipelineOp::PReduce { .. }
15904 | PipelineOp::PReduceInit { .. }
15905 | PipelineOp::PMapReduce { .. }
15906 ) {
15907 g.has_scalar_terminal = true;
15908 }
15909 g.ops.push(op);
15910 Ok(())
15911 }
15912
15913 fn pipeline_parse_sub_progress(
15914 args: &[PerlValue],
15915 line: usize,
15916 name: &str,
15917 ) -> PerlResult<(Arc<PerlSub>, bool)> {
15918 if args.is_empty() {
15919 return Err(PerlError::runtime(
15920 format!("pipeline {}: expects at least 1 argument (code ref)", name),
15921 line,
15922 ));
15923 }
15924 let Some(sub) = args[0].as_code_ref() else {
15925 return Err(PerlError::runtime(
15926 format!("pipeline {}: first argument must be a code reference", name),
15927 line,
15928 ));
15929 };
15930 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
15931 if args.len() > 2 {
15932 return Err(PerlError::runtime(
15933 format!(
15934 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
15935 name
15936 ),
15937 line,
15938 ));
15939 }
15940 Ok((sub, progress))
15941 }
15942
15943 pub(crate) fn pipeline_method(
15944 &mut self,
15945 p: Arc<Mutex<PipelineInner>>,
15946 method: &str,
15947 args: &[PerlValue],
15948 line: usize,
15949 ) -> PerlResult<PerlValue> {
15950 match method {
15951 "filter" | "f" | "grep" => {
15952 if args.len() != 1 {
15953 return Err(PerlError::runtime(
15954 "pipeline filter/grep expects 1 argument (sub)",
15955 line,
15956 ));
15957 }
15958 let Some(sub) = args[0].as_code_ref() else {
15959 return Err(PerlError::runtime(
15960 "pipeline filter/grep expects a code reference",
15961 line,
15962 ));
15963 };
15964 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
15965 Ok(PerlValue::pipeline(Arc::clone(&p)))
15966 }
15967 "map" => {
15968 if args.len() != 1 {
15969 return Err(PerlError::runtime(
15970 "pipeline map expects 1 argument (sub)",
15971 line,
15972 ));
15973 }
15974 let Some(sub) = args[0].as_code_ref() else {
15975 return Err(PerlError::runtime(
15976 "pipeline map expects a code reference",
15977 line,
15978 ));
15979 };
15980 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
15981 Ok(PerlValue::pipeline(Arc::clone(&p)))
15982 }
15983 "tap" | "peek" => {
15984 if args.len() != 1 {
15985 return Err(PerlError::runtime(
15986 "pipeline tap/peek expects 1 argument (sub)",
15987 line,
15988 ));
15989 }
15990 let Some(sub) = args[0].as_code_ref() else {
15991 return Err(PerlError::runtime(
15992 "pipeline tap/peek expects a code reference",
15993 line,
15994 ));
15995 };
15996 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
15997 Ok(PerlValue::pipeline(Arc::clone(&p)))
15998 }
15999 "take" => {
16000 if args.len() != 1 {
16001 return Err(PerlError::runtime("pipeline take expects 1 argument", line));
16002 }
16003 let n = args[0].to_int();
16004 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
16005 Ok(PerlValue::pipeline(Arc::clone(&p)))
16006 }
16007 "pmap" => {
16008 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
16009 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
16010 Ok(PerlValue::pipeline(Arc::clone(&p)))
16011 }
16012 "pgrep" => {
16013 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
16014 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
16015 Ok(PerlValue::pipeline(Arc::clone(&p)))
16016 }
16017 "pfor" => {
16018 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
16019 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
16020 Ok(PerlValue::pipeline(Arc::clone(&p)))
16021 }
16022 "pmap_chunked" => {
16023 if args.len() < 2 {
16024 return Err(PerlError::runtime(
16025 "pipeline pmap_chunked expects chunk size and a code reference",
16026 line,
16027 ));
16028 }
16029 let chunk = args[0].to_int().max(1);
16030 let Some(sub) = args[1].as_code_ref() else {
16031 return Err(PerlError::runtime(
16032 "pipeline pmap_chunked: second argument must be a code reference",
16033 line,
16034 ));
16035 };
16036 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
16037 if args.len() > 3 {
16038 return Err(PerlError::runtime(
16039 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
16040 line,
16041 ));
16042 }
16043 self.pipeline_push(
16044 &p,
16045 PipelineOp::PMapChunked {
16046 chunk,
16047 sub,
16048 progress,
16049 },
16050 line,
16051 )?;
16052 Ok(PerlValue::pipeline(Arc::clone(&p)))
16053 }
16054 "psort" => {
16055 let (cmp, progress) = match args.len() {
16056 0 => (None, false),
16057 1 => {
16058 if let Some(s) = args[0].as_code_ref() {
16059 (Some(s), false)
16060 } else {
16061 (None, args[0].is_true())
16062 }
16063 }
16064 2 => {
16065 let Some(s) = args[0].as_code_ref() else {
16066 return Err(PerlError::runtime(
16067 "pipeline psort: with two arguments, the first must be a comparator sub",
16068 line,
16069 ));
16070 };
16071 (Some(s), args[1].is_true())
16072 }
16073 _ => {
16074 return Err(PerlError::runtime(
16075 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
16076 line,
16077 ));
16078 }
16079 };
16080 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
16081 Ok(PerlValue::pipeline(Arc::clone(&p)))
16082 }
16083 "pcache" => {
16084 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
16085 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
16086 Ok(PerlValue::pipeline(Arc::clone(&p)))
16087 }
16088 "preduce" => {
16089 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
16090 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
16091 Ok(PerlValue::pipeline(Arc::clone(&p)))
16092 }
16093 "preduce_init" => {
16094 if args.len() < 2 {
16095 return Err(PerlError::runtime(
16096 "pipeline preduce_init expects init value and a code reference",
16097 line,
16098 ));
16099 }
16100 let init = args[0].clone();
16101 let Some(sub) = args[1].as_code_ref() else {
16102 return Err(PerlError::runtime(
16103 "pipeline preduce_init: second argument must be a code reference",
16104 line,
16105 ));
16106 };
16107 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
16108 if args.len() > 3 {
16109 return Err(PerlError::runtime(
16110 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
16111 line,
16112 ));
16113 }
16114 self.pipeline_push(
16115 &p,
16116 PipelineOp::PReduceInit {
16117 init,
16118 sub,
16119 progress,
16120 },
16121 line,
16122 )?;
16123 Ok(PerlValue::pipeline(Arc::clone(&p)))
16124 }
16125 "pmap_reduce" => {
16126 if args.len() < 2 {
16127 return Err(PerlError::runtime(
16128 "pipeline pmap_reduce expects map sub and reduce sub",
16129 line,
16130 ));
16131 }
16132 let Some(map) = args[0].as_code_ref() else {
16133 return Err(PerlError::runtime(
16134 "pipeline pmap_reduce: first argument must be a code reference (map)",
16135 line,
16136 ));
16137 };
16138 let Some(reduce) = args[1].as_code_ref() else {
16139 return Err(PerlError::runtime(
16140 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
16141 line,
16142 ));
16143 };
16144 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
16145 if args.len() > 3 {
16146 return Err(PerlError::runtime(
16147 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
16148 line,
16149 ));
16150 }
16151 self.pipeline_push(
16152 &p,
16153 PipelineOp::PMapReduce {
16154 map,
16155 reduce,
16156 progress,
16157 },
16158 line,
16159 )?;
16160 Ok(PerlValue::pipeline(Arc::clone(&p)))
16161 }
16162 "collect" => {
16163 if !args.is_empty() {
16164 return Err(PerlError::runtime(
16165 "pipeline collect takes no arguments",
16166 line,
16167 ));
16168 }
16169 self.pipeline_collect(&p, line)
16170 }
16171 _ => {
16172 if let Some(sub) = self.resolve_sub_by_name(method) {
16175 if !args.is_empty() {
16176 return Err(PerlError::runtime(
16177 format!(
16178 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
16179 method
16180 ),
16181 line,
16182 ));
16183 }
16184 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
16185 Ok(PerlValue::pipeline(Arc::clone(&p)))
16186 } else {
16187 Err(PerlError::runtime(
16188 format!("Unknown method for pipeline: {}", method),
16189 line,
16190 ))
16191 }
16192 }
16193 }
16194 }
16195
16196 fn pipeline_parallel_map(
16197 &mut self,
16198 items: Vec<PerlValue>,
16199 sub: &Arc<PerlSub>,
16200 progress: bool,
16201 ) -> Vec<PerlValue> {
16202 let subs = self.subs.clone();
16203 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16204 let pmap_progress = PmapProgress::new(progress, items.len());
16205 let results: Vec<PerlValue> = items
16206 .into_par_iter()
16207 .map(|item| {
16208 let mut local_interp = Interpreter::new();
16209 local_interp.subs = subs.clone();
16210 local_interp.scope.restore_capture(&scope_capture);
16211 local_interp
16212 .scope
16213 .restore_atomics(&atomic_arrays, &atomic_hashes);
16214 local_interp.enable_parallel_guard();
16215 local_interp.scope.set_topic(item);
16216 local_interp.scope_push_hook();
16217 let val = match local_interp.exec_block_no_scope(&sub.body) {
16218 Ok(val) => val,
16219 Err(_) => PerlValue::UNDEF,
16220 };
16221 local_interp.scope_pop_hook();
16222 pmap_progress.tick();
16223 val
16224 })
16225 .collect();
16226 pmap_progress.finish();
16227 results
16228 }
16229
16230 fn pipeline_par_stream_filter(
16232 &mut self,
16233 items: Vec<PerlValue>,
16234 sub: &Arc<PerlSub>,
16235 ) -> Vec<PerlValue> {
16236 if items.is_empty() {
16237 return items;
16238 }
16239 let subs = self.subs.clone();
16240 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16241 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
16242 let mut kept: Vec<(usize, PerlValue)> = indexed
16243 .into_par_iter()
16244 .filter_map(|(i, item)| {
16245 let mut local_interp = Interpreter::new();
16246 local_interp.subs = subs.clone();
16247 local_interp.scope.restore_capture(&scope_capture);
16248 local_interp
16249 .scope
16250 .restore_atomics(&atomic_arrays, &atomic_hashes);
16251 local_interp.enable_parallel_guard();
16252 local_interp.scope.set_topic(item.clone());
16253 local_interp.scope_push_hook();
16254 let keep = match local_interp.exec_block_no_scope(&sub.body) {
16255 Ok(val) => val.is_true(),
16256 Err(_) => false,
16257 };
16258 local_interp.scope_pop_hook();
16259 if keep {
16260 Some((i, item))
16261 } else {
16262 None
16263 }
16264 })
16265 .collect();
16266 kept.sort_by_key(|(i, _)| *i);
16267 kept.into_iter().map(|(_, x)| x).collect()
16268 }
16269
16270 fn pipeline_par_stream_map(
16272 &mut self,
16273 items: Vec<PerlValue>,
16274 sub: &Arc<PerlSub>,
16275 ) -> Vec<PerlValue> {
16276 if items.is_empty() {
16277 return items;
16278 }
16279 let subs = self.subs.clone();
16280 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16281 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
16282 let mut mapped: Vec<(usize, PerlValue)> = indexed
16283 .into_par_iter()
16284 .map(|(i, item)| {
16285 let mut local_interp = Interpreter::new();
16286 local_interp.subs = subs.clone();
16287 local_interp.scope.restore_capture(&scope_capture);
16288 local_interp
16289 .scope
16290 .restore_atomics(&atomic_arrays, &atomic_hashes);
16291 local_interp.enable_parallel_guard();
16292 local_interp.scope.set_topic(item);
16293 local_interp.scope_push_hook();
16294 let val = match local_interp.exec_block_no_scope(&sub.body) {
16295 Ok(val) => val,
16296 Err(_) => PerlValue::UNDEF,
16297 };
16298 local_interp.scope_pop_hook();
16299 (i, val)
16300 })
16301 .collect();
16302 mapped.sort_by_key(|(i, _)| *i);
16303 mapped.into_iter().map(|(_, x)| x).collect()
16304 }
16305
16306 fn pipeline_collect(
16307 &mut self,
16308 p: &Arc<Mutex<PipelineInner>>,
16309 line: usize,
16310 ) -> PerlResult<PerlValue> {
16311 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
16312 let g = p.lock();
16313 (
16314 g.source.clone(),
16315 g.ops.clone(),
16316 g.par_stream,
16317 g.streaming,
16318 g.streaming_workers,
16319 g.streaming_buffer,
16320 )
16321 };
16322 if streaming {
16323 return self.pipeline_collect_streaming(
16324 v,
16325 &ops,
16326 streaming_workers,
16327 streaming_buffer,
16328 line,
16329 );
16330 }
16331 for op in ops {
16332 match op {
16333 PipelineOp::Filter(sub) => {
16334 if par_stream {
16335 v = self.pipeline_par_stream_filter(v, &sub);
16336 } else {
16337 let mut out = Vec::new();
16338 for item in v {
16339 self.scope_push_hook();
16340 self.scope.set_topic(item.clone());
16341 if let Some(ref env) = sub.closure_env {
16342 self.scope.restore_capture(env);
16343 }
16344 let keep = match self.exec_block_no_scope(&sub.body) {
16345 Ok(val) => val.is_true(),
16346 Err(_) => false,
16347 };
16348 self.scope_pop_hook();
16349 if keep {
16350 out.push(item);
16351 }
16352 }
16353 v = out;
16354 }
16355 }
16356 PipelineOp::Map(sub) => {
16357 if par_stream {
16358 v = self.pipeline_par_stream_map(v, &sub);
16359 } else {
16360 let mut out = Vec::new();
16361 for item in v {
16362 self.scope_push_hook();
16363 self.scope.set_topic(item);
16364 if let Some(ref env) = sub.closure_env {
16365 self.scope.restore_capture(env);
16366 }
16367 let mapped = match self.exec_block_no_scope(&sub.body) {
16368 Ok(val) => val,
16369 Err(_) => PerlValue::UNDEF,
16370 };
16371 self.scope_pop_hook();
16372 out.push(mapped);
16373 }
16374 v = out;
16375 }
16376 }
16377 PipelineOp::Tap(sub) => {
16378 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
16379 Ok(_) => {}
16380 Err(FlowOrError::Error(e)) => return Err(e),
16381 Err(FlowOrError::Flow(_)) => {
16382 return Err(PerlError::runtime(
16383 "tap: unsupported control flow in block",
16384 line,
16385 ));
16386 }
16387 }
16388 }
16389 PipelineOp::Take(n) => {
16390 let n = n.max(0) as usize;
16391 if v.len() > n {
16392 v.truncate(n);
16393 }
16394 }
16395 PipelineOp::PMap { sub, progress } => {
16396 v = self.pipeline_parallel_map(v, &sub, progress);
16397 }
16398 PipelineOp::PGrep { sub, progress } => {
16399 let subs = self.subs.clone();
16400 let (scope_capture, atomic_arrays, atomic_hashes) =
16401 self.scope.capture_with_atomics();
16402 let pmap_progress = PmapProgress::new(progress, v.len());
16403 v = v
16404 .into_par_iter()
16405 .filter_map(|item| {
16406 let mut local_interp = Interpreter::new();
16407 local_interp.subs = subs.clone();
16408 local_interp.scope.restore_capture(&scope_capture);
16409 local_interp
16410 .scope
16411 .restore_atomics(&atomic_arrays, &atomic_hashes);
16412 local_interp.enable_parallel_guard();
16413 local_interp.scope.set_topic(item.clone());
16414 local_interp.scope_push_hook();
16415 let keep = match local_interp.exec_block_no_scope(&sub.body) {
16416 Ok(val) => val.is_true(),
16417 Err(_) => false,
16418 };
16419 local_interp.scope_pop_hook();
16420 pmap_progress.tick();
16421 if keep {
16422 Some(item)
16423 } else {
16424 None
16425 }
16426 })
16427 .collect();
16428 pmap_progress.finish();
16429 }
16430 PipelineOp::PFor { sub, progress } => {
16431 let subs = self.subs.clone();
16432 let (scope_capture, atomic_arrays, atomic_hashes) =
16433 self.scope.capture_with_atomics();
16434 let pmap_progress = PmapProgress::new(progress, v.len());
16435 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
16436 v.clone().into_par_iter().for_each(|item| {
16437 if first_err.lock().is_some() {
16438 return;
16439 }
16440 let mut local_interp = Interpreter::new();
16441 local_interp.subs = subs.clone();
16442 local_interp.scope.restore_capture(&scope_capture);
16443 local_interp
16444 .scope
16445 .restore_atomics(&atomic_arrays, &atomic_hashes);
16446 local_interp.enable_parallel_guard();
16447 local_interp.scope.set_topic(item);
16448 local_interp.scope_push_hook();
16449 match local_interp.exec_block_no_scope(&sub.body) {
16450 Ok(_) => {}
16451 Err(e) => {
16452 let stryke = match e {
16453 FlowOrError::Error(stryke) => stryke,
16454 FlowOrError::Flow(_) => PerlError::runtime(
16455 "return/last/next/redo not supported inside pipeline pfor block",
16456 line,
16457 ),
16458 };
16459 let mut g = first_err.lock();
16460 if g.is_none() {
16461 *g = Some(stryke);
16462 }
16463 }
16464 }
16465 local_interp.scope_pop_hook();
16466 pmap_progress.tick();
16467 });
16468 pmap_progress.finish();
16469 let pfor_err = first_err.lock().take();
16470 if let Some(e) = pfor_err {
16471 return Err(e);
16472 }
16473 }
16474 PipelineOp::PMapChunked {
16475 chunk,
16476 sub,
16477 progress,
16478 } => {
16479 let chunk_n = chunk.max(1) as usize;
16480 let subs = self.subs.clone();
16481 let (scope_capture, atomic_arrays, atomic_hashes) =
16482 self.scope.capture_with_atomics();
16483 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = v
16484 .chunks(chunk_n)
16485 .enumerate()
16486 .map(|(i, c)| (i, c.to_vec()))
16487 .collect();
16488 let n_chunks = indexed_chunks.len();
16489 let pmap_progress = PmapProgress::new(progress, n_chunks);
16490 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
16491 .into_par_iter()
16492 .map(|(chunk_idx, chunk)| {
16493 let mut local_interp = Interpreter::new();
16494 local_interp.subs = subs.clone();
16495 local_interp.scope.restore_capture(&scope_capture);
16496 local_interp
16497 .scope
16498 .restore_atomics(&atomic_arrays, &atomic_hashes);
16499 local_interp.enable_parallel_guard();
16500 let mut out = Vec::with_capacity(chunk.len());
16501 for item in chunk {
16502 local_interp.scope.set_topic(item);
16503 local_interp.scope_push_hook();
16504 match local_interp.exec_block_no_scope(&sub.body) {
16505 Ok(val) => {
16506 local_interp.scope_pop_hook();
16507 out.push(val);
16508 }
16509 Err(_) => {
16510 local_interp.scope_pop_hook();
16511 out.push(PerlValue::UNDEF);
16512 }
16513 }
16514 }
16515 pmap_progress.tick();
16516 (chunk_idx, out)
16517 })
16518 .collect();
16519 pmap_progress.finish();
16520 chunk_results.sort_by_key(|(i, _)| *i);
16521 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
16522 }
16523 PipelineOp::PSort { cmp, progress } => {
16524 let pmap_progress = PmapProgress::new(progress, 2);
16525 pmap_progress.tick();
16526 match cmp {
16527 Some(cmp_block) => {
16528 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
16529 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
16530 } else {
16531 let subs = self.subs.clone();
16532 let scope_capture = self.scope.capture();
16533 v.par_sort_by(|a, b| {
16534 let mut local_interp = Interpreter::new();
16535 local_interp.subs = subs.clone();
16536 local_interp.scope.restore_capture(&scope_capture);
16537 local_interp.enable_parallel_guard();
16538 let _ = local_interp.scope.set_scalar("a", a.clone());
16539 let _ = local_interp.scope.set_scalar("b", b.clone());
16540 let _ = local_interp.scope.set_scalar("_0", a.clone());
16541 let _ = local_interp.scope.set_scalar("_1", b.clone());
16542 local_interp.scope_push_hook();
16543 let ord =
16544 match local_interp.exec_block_no_scope(&cmp_block.body) {
16545 Ok(v) => {
16546 let n = v.to_int();
16547 if n < 0 {
16548 std::cmp::Ordering::Less
16549 } else if n > 0 {
16550 std::cmp::Ordering::Greater
16551 } else {
16552 std::cmp::Ordering::Equal
16553 }
16554 }
16555 Err(_) => std::cmp::Ordering::Equal,
16556 };
16557 local_interp.scope_pop_hook();
16558 ord
16559 });
16560 }
16561 }
16562 None => {
16563 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
16564 }
16565 }
16566 pmap_progress.tick();
16567 pmap_progress.finish();
16568 }
16569 PipelineOp::PCache { sub, progress } => {
16570 let subs = self.subs.clone();
16571 let scope_capture = self.scope.capture();
16572 let cache = &*crate::pcache::GLOBAL_PCACHE;
16573 let pmap_progress = PmapProgress::new(progress, v.len());
16574 v = v
16575 .into_par_iter()
16576 .map(|item| {
16577 let k = crate::pcache::cache_key(&item);
16578 if let Some(cached) = cache.get(&k) {
16579 pmap_progress.tick();
16580 return cached.clone();
16581 }
16582 let mut local_interp = Interpreter::new();
16583 local_interp.subs = subs.clone();
16584 local_interp.scope.restore_capture(&scope_capture);
16585 local_interp.enable_parallel_guard();
16586 local_interp.scope.set_topic(item.clone());
16587 local_interp.scope_push_hook();
16588 let val = match local_interp.exec_block_no_scope(&sub.body) {
16589 Ok(v) => v,
16590 Err(_) => PerlValue::UNDEF,
16591 };
16592 local_interp.scope_pop_hook();
16593 cache.insert(k, val.clone());
16594 pmap_progress.tick();
16595 val
16596 })
16597 .collect();
16598 pmap_progress.finish();
16599 }
16600 PipelineOp::PReduce { sub, progress } => {
16601 if v.is_empty() {
16602 return Ok(PerlValue::UNDEF);
16603 }
16604 if v.len() == 1 {
16605 return Ok(v.into_iter().next().unwrap());
16606 }
16607 let block = sub.body.clone();
16608 let subs = self.subs.clone();
16609 let scope_capture = self.scope.capture();
16610 let pmap_progress = PmapProgress::new(progress, v.len());
16611 let result = v
16612 .into_par_iter()
16613 .map(|x| {
16614 pmap_progress.tick();
16615 x
16616 })
16617 .reduce_with(|a, b| {
16618 let mut local_interp = Interpreter::new();
16619 local_interp.subs = subs.clone();
16620 local_interp.scope.restore_capture(&scope_capture);
16621 local_interp.enable_parallel_guard();
16622 let _ = local_interp.scope.set_scalar("a", a.clone());
16623 let _ = local_interp.scope.set_scalar("b", b.clone());
16624 let _ = local_interp.scope.set_scalar("_0", a);
16625 let _ = local_interp.scope.set_scalar("_1", b);
16626 match local_interp.exec_block(&block) {
16627 Ok(val) => val,
16628 Err(_) => PerlValue::UNDEF,
16629 }
16630 });
16631 pmap_progress.finish();
16632 return Ok(result.unwrap_or(PerlValue::UNDEF));
16633 }
16634 PipelineOp::PReduceInit {
16635 init,
16636 sub,
16637 progress,
16638 } => {
16639 if v.is_empty() {
16640 return Ok(init);
16641 }
16642 let block = sub.body.clone();
16643 let subs = self.subs.clone();
16644 let scope_capture = self.scope.capture();
16645 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
16646 if v.len() == 1 {
16647 return Ok(fold_preduce_init_step(
16648 &subs,
16649 cap,
16650 &block,
16651 preduce_init_fold_identity(&init),
16652 v.into_iter().next().unwrap(),
16653 ));
16654 }
16655 let pmap_progress = PmapProgress::new(progress, v.len());
16656 let result = v
16657 .into_par_iter()
16658 .fold(
16659 || preduce_init_fold_identity(&init),
16660 |acc, item| {
16661 pmap_progress.tick();
16662 fold_preduce_init_step(&subs, cap, &block, acc, item)
16663 },
16664 )
16665 .reduce(
16666 || preduce_init_fold_identity(&init),
16667 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
16668 );
16669 pmap_progress.finish();
16670 return Ok(result);
16671 }
16672 PipelineOp::PMapReduce {
16673 map,
16674 reduce,
16675 progress,
16676 } => {
16677 if v.is_empty() {
16678 return Ok(PerlValue::UNDEF);
16679 }
16680 let map_block = map.body.clone();
16681 let reduce_block = reduce.body.clone();
16682 let subs = self.subs.clone();
16683 let scope_capture = self.scope.capture();
16684 if v.len() == 1 {
16685 let mut local_interp = Interpreter::new();
16686 local_interp.subs = subs.clone();
16687 local_interp.scope.restore_capture(&scope_capture);
16688 local_interp.scope.set_topic(v[0].clone());
16689 return match local_interp.exec_block_no_scope(&map_block) {
16690 Ok(val) => Ok(val),
16691 Err(_) => Ok(PerlValue::UNDEF),
16692 };
16693 }
16694 let pmap_progress = PmapProgress::new(progress, v.len());
16695 let result = v
16696 .into_par_iter()
16697 .map(|item| {
16698 let mut local_interp = Interpreter::new();
16699 local_interp.subs = subs.clone();
16700 local_interp.scope.restore_capture(&scope_capture);
16701 local_interp.scope.set_topic(item);
16702 let val = match local_interp.exec_block_no_scope(&map_block) {
16703 Ok(val) => val,
16704 Err(_) => PerlValue::UNDEF,
16705 };
16706 pmap_progress.tick();
16707 val
16708 })
16709 .reduce_with(|a, b| {
16710 let mut local_interp = Interpreter::new();
16711 local_interp.subs = subs.clone();
16712 local_interp.scope.restore_capture(&scope_capture);
16713 let _ = local_interp.scope.set_scalar("a", a.clone());
16714 let _ = local_interp.scope.set_scalar("b", b.clone());
16715 let _ = local_interp.scope.set_scalar("_0", a);
16716 let _ = local_interp.scope.set_scalar("_1", b);
16717 match local_interp.exec_block_no_scope(&reduce_block) {
16718 Ok(val) => val,
16719 Err(_) => PerlValue::UNDEF,
16720 }
16721 });
16722 pmap_progress.finish();
16723 return Ok(result.unwrap_or(PerlValue::UNDEF));
16724 }
16725 }
16726 }
16727 Ok(PerlValue::array(v))
16728 }
16729
16730 fn pipeline_collect_streaming(
16733 &mut self,
16734 source: Vec<PerlValue>,
16735 ops: &[PipelineOp],
16736 workers_per_stage: usize,
16737 buffer: usize,
16738 line: usize,
16739 ) -> PerlResult<PerlValue> {
16740 use crossbeam::channel::{bounded, Receiver, Sender};
16741
16742 for op in ops {
16744 match op {
16745 PipelineOp::PSort { .. }
16746 | PipelineOp::PReduce { .. }
16747 | PipelineOp::PReduceInit { .. }
16748 | PipelineOp::PMapReduce { .. }
16749 | PipelineOp::PMapChunked { .. } => {
16750 return Err(PerlError::runtime(
16751 format!(
16752 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
16753 std::mem::discriminant(op)
16754 ),
16755 line,
16756 ));
16757 }
16758 _ => {}
16759 }
16760 }
16761
16762 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
16765 if streamable_ops.is_empty() {
16766 return Ok(PerlValue::array(source));
16767 }
16768
16769 let n_stages = streamable_ops.len();
16770 let wn = if workers_per_stage > 0 {
16771 workers_per_stage
16772 } else {
16773 self.parallel_thread_count()
16774 };
16775 let subs = self.subs.clone();
16776 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16777
16778 let mut channels: Vec<(Sender<PerlValue>, Receiver<PerlValue>)> =
16783 (0..=n_stages).map(|_| bounded(buffer)).collect();
16784
16785 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
16786 let take_done: Arc<std::sync::atomic::AtomicBool> =
16787 Arc::new(std::sync::atomic::AtomicBool::new(false));
16788
16789 let source_tx = channels[0].0.clone();
16792 let result_rx = channels[n_stages].1.clone();
16793 let results: Arc<Mutex<Vec<PerlValue>>> = Arc::new(Mutex::new(Vec::new()));
16794
16795 std::thread::scope(|scope| {
16796 let result_rx_c = result_rx.clone();
16799 let results_c = Arc::clone(&results);
16800 scope.spawn(move || {
16801 while let Ok(item) = result_rx_c.recv() {
16802 results_c.lock().push(item);
16803 }
16804 });
16805
16806 let err_s = Arc::clone(&err);
16808 let take_done_s = Arc::clone(&take_done);
16809 scope.spawn(move || {
16810 for item in source {
16811 if err_s.lock().is_some()
16812 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
16813 {
16814 break;
16815 }
16816 if source_tx.send(item).is_err() {
16817 break;
16818 }
16819 }
16820 });
16821
16822 for (stage_idx, op) in streamable_ops.iter().enumerate() {
16824 let rx = channels[stage_idx].1.clone();
16825 let tx = channels[stage_idx + 1].0.clone();
16826
16827 for _ in 0..wn {
16828 let rx = rx.clone();
16829 let tx = tx.clone();
16830 let subs = subs.clone();
16831 let capture = capture.clone();
16832 let atomic_arrays = atomic_arrays.clone();
16833 let atomic_hashes = atomic_hashes.clone();
16834 let err_w = Arc::clone(&err);
16835 let take_done_w = Arc::clone(&take_done);
16836
16837 match *op {
16838 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
16839 let sub = Arc::clone(sub);
16840 scope.spawn(move || {
16841 while let Ok(item) = rx.recv() {
16842 if err_w.lock().is_some() {
16843 break;
16844 }
16845 let mut interp = Interpreter::new();
16846 interp.subs = subs.clone();
16847 interp.scope.restore_capture(&capture);
16848 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16849 interp.enable_parallel_guard();
16850 interp.scope.set_topic(item.clone());
16851 interp.scope_push_hook();
16852 let keep = match interp.exec_block_no_scope(&sub.body) {
16853 Ok(val) => val.is_true(),
16854 Err(_) => false,
16855 };
16856 interp.scope_pop_hook();
16857 if keep && tx.send(item).is_err() {
16858 break;
16859 }
16860 }
16861 });
16862 }
16863 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
16864 let sub = Arc::clone(sub);
16865 scope.spawn(move || {
16866 while let Ok(item) = rx.recv() {
16867 if err_w.lock().is_some() {
16868 break;
16869 }
16870 let mut interp = Interpreter::new();
16871 interp.subs = subs.clone();
16872 interp.scope.restore_capture(&capture);
16873 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16874 interp.enable_parallel_guard();
16875 interp.scope.set_topic(item);
16876 interp.scope_push_hook();
16877 let mapped = match interp.exec_block_no_scope(&sub.body) {
16878 Ok(val) => val,
16879 Err(_) => PerlValue::UNDEF,
16880 };
16881 interp.scope_pop_hook();
16882 if tx.send(mapped).is_err() {
16883 break;
16884 }
16885 }
16886 });
16887 }
16888 PipelineOp::Take(n) => {
16889 let limit = (*n).max(0) as usize;
16890 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
16891 let count_w = Arc::clone(&count);
16892 scope.spawn(move || {
16893 while let Ok(item) = rx.recv() {
16894 let prev =
16895 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
16896 if prev >= limit {
16897 take_done_w
16898 .store(true, std::sync::atomic::Ordering::Relaxed);
16899 break;
16900 }
16901 if tx.send(item).is_err() {
16902 break;
16903 }
16904 }
16905 });
16906 break;
16908 }
16909 PipelineOp::PFor { ref sub, .. } => {
16910 let sub = Arc::clone(sub);
16911 scope.spawn(move || {
16912 while let Ok(item) = rx.recv() {
16913 if err_w.lock().is_some() {
16914 break;
16915 }
16916 let mut interp = Interpreter::new();
16917 interp.subs = subs.clone();
16918 interp.scope.restore_capture(&capture);
16919 interp
16920 .scope
16921 .restore_atomics(&atomic_arrays, &atomic_hashes);
16922 interp.enable_parallel_guard();
16923 interp.scope.set_topic(item.clone());
16924 interp.scope_push_hook();
16925 match interp.exec_block_no_scope(&sub.body) {
16926 Ok(_) => {}
16927 Err(e) => {
16928 let msg = match e {
16929 FlowOrError::Error(stryke) => stryke.to_string(),
16930 FlowOrError::Flow(_) => {
16931 "unexpected control flow in par_pipeline_stream pfor".into()
16932 }
16933 };
16934 let mut g = err_w.lock();
16935 if g.is_none() {
16936 *g = Some(msg);
16937 }
16938 interp.scope_pop_hook();
16939 break;
16940 }
16941 }
16942 interp.scope_pop_hook();
16943 if tx.send(item).is_err() {
16944 break;
16945 }
16946 }
16947 });
16948 }
16949 PipelineOp::Tap(ref sub) => {
16950 let sub = Arc::clone(sub);
16951 scope.spawn(move || {
16952 while let Ok(item) = rx.recv() {
16953 if err_w.lock().is_some() {
16954 break;
16955 }
16956 let mut interp = Interpreter::new();
16957 interp.subs = subs.clone();
16958 interp.scope.restore_capture(&capture);
16959 interp
16960 .scope
16961 .restore_atomics(&atomic_arrays, &atomic_hashes);
16962 interp.enable_parallel_guard();
16963 match interp.call_sub(
16964 &sub,
16965 vec![item.clone()],
16966 WantarrayCtx::Void,
16967 line,
16968 )
16969 {
16970 Ok(_) => {}
16971 Err(e) => {
16972 let msg = match e {
16973 FlowOrError::Error(stryke) => stryke.to_string(),
16974 FlowOrError::Flow(_) => {
16975 "unexpected control flow in par_pipeline_stream tap"
16976 .into()
16977 }
16978 };
16979 let mut g = err_w.lock();
16980 if g.is_none() {
16981 *g = Some(msg);
16982 }
16983 break;
16984 }
16985 }
16986 if tx.send(item).is_err() {
16987 break;
16988 }
16989 }
16990 });
16991 }
16992 PipelineOp::PCache { ref sub, .. } => {
16993 let sub = Arc::clone(sub);
16994 scope.spawn(move || {
16995 while let Ok(item) = rx.recv() {
16996 if err_w.lock().is_some() {
16997 break;
16998 }
16999 let k = crate::pcache::cache_key(&item);
17000 let val = if let Some(cached) =
17001 crate::pcache::GLOBAL_PCACHE.get(&k)
17002 {
17003 cached.clone()
17004 } else {
17005 let mut interp = Interpreter::new();
17006 interp.subs = subs.clone();
17007 interp.scope.restore_capture(&capture);
17008 interp
17009 .scope
17010 .restore_atomics(&atomic_arrays, &atomic_hashes);
17011 interp.enable_parallel_guard();
17012 interp.scope.set_topic(item);
17013 interp.scope_push_hook();
17014 let v = match interp.exec_block_no_scope(&sub.body) {
17015 Ok(v) => v,
17016 Err(_) => PerlValue::UNDEF,
17017 };
17018 interp.scope_pop_hook();
17019 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
17020 v
17021 };
17022 if tx.send(val).is_err() {
17023 break;
17024 }
17025 }
17026 });
17027 }
17028 _ => unreachable!(),
17030 }
17031 }
17032 }
17033
17034 channels.clear();
17038 drop(result_rx);
17039 });
17040
17041 if let Some(msg) = err.lock().take() {
17042 return Err(PerlError::runtime(msg, line));
17043 }
17044
17045 let results = std::mem::take(&mut *results.lock());
17046 Ok(PerlValue::array(results))
17047 }
17048
17049 fn heap_compare(&mut self, cmp: &Arc<PerlSub>, a: &PerlValue, b: &PerlValue) -> Ordering {
17050 self.scope_push_hook();
17051 if let Some(ref env) = cmp.closure_env {
17052 self.scope.restore_capture(env);
17053 }
17054 let _ = self.scope.set_scalar("a", a.clone());
17055 let _ = self.scope.set_scalar("b", b.clone());
17056 let _ = self.scope.set_scalar("_0", a.clone());
17057 let _ = self.scope.set_scalar("_1", b.clone());
17058 let ord = match self.exec_block_no_scope(&cmp.body) {
17059 Ok(v) => {
17060 let n = v.to_int();
17061 if n < 0 {
17062 Ordering::Less
17063 } else if n > 0 {
17064 Ordering::Greater
17065 } else {
17066 Ordering::Equal
17067 }
17068 }
17069 Err(_) => Ordering::Equal,
17070 };
17071 self.scope_pop_hook();
17072 ord
17073 }
17074
17075 fn heap_sift_up(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
17076 while i > 0 {
17077 let p = (i - 1) / 2;
17078 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
17079 break;
17080 }
17081 items.swap(i, p);
17082 i = p;
17083 }
17084 }
17085
17086 fn heap_sift_down(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
17087 let n = items.len();
17088 loop {
17089 let mut sm = i;
17090 let l = 2 * i + 1;
17091 let r = 2 * i + 2;
17092 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
17093 sm = l;
17094 }
17095 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
17096 sm = r;
17097 }
17098 if sm == i {
17099 break;
17100 }
17101 items.swap(i, sm);
17102 i = sm;
17103 }
17104 }
17105
17106 fn hash_for_signature_destruct(
17107 &mut self,
17108 v: &PerlValue,
17109 line: usize,
17110 ) -> PerlResult<IndexMap<String, PerlValue>> {
17111 let Some(m) = self.match_subject_as_hash(v) else {
17112 return Err(PerlError::runtime(
17113 format!(
17114 "sub signature hash destruct: expected HASH or HASH reference, got {}",
17115 v.ref_type()
17116 ),
17117 line,
17118 ));
17119 };
17120 Ok(m)
17121 }
17122
17123 pub(crate) fn apply_sub_signature(
17125 &mut self,
17126 sub: &PerlSub,
17127 argv: &[PerlValue],
17128 line: usize,
17129 ) -> PerlResult<()> {
17130 if sub.params.is_empty() {
17131 return Ok(());
17132 }
17133 let mut i = 0usize;
17134 for p in &sub.params {
17135 match p {
17136 SubSigParam::Scalar(name, ty, default) => {
17137 let val = if i < argv.len() {
17138 argv[i].clone()
17139 } else if let Some(default_expr) = default {
17140 match self.eval_expr(default_expr) {
17141 Ok(v) => v,
17142 Err(FlowOrError::Error(e)) => return Err(e),
17143 Err(FlowOrError::Flow(_)) => {
17144 return Err(PerlError::runtime(
17145 "unexpected control flow in parameter default",
17146 line,
17147 ))
17148 }
17149 }
17150 } else {
17151 PerlValue::UNDEF
17152 };
17153 i += 1;
17154 if let Some(t) = ty {
17155 if let Err(e) = t.check_value(&val) {
17156 return Err(PerlError::runtime(
17157 format!("sub parameter ${}: {}", name, e),
17158 line,
17159 ));
17160 }
17161 }
17162 let n = self.english_scalar_name(name);
17163 self.scope.declare_scalar(n, val);
17164 }
17165 SubSigParam::Array(name, default) => {
17166 let rest: Vec<PerlValue> = if i < argv.len() {
17167 let r = argv[i..].to_vec();
17168 i = argv.len();
17169 r
17170 } else if let Some(default_expr) = default {
17171 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17172 Ok(v) => v,
17173 Err(FlowOrError::Error(e)) => return Err(e),
17174 Err(FlowOrError::Flow(_)) => {
17175 return Err(PerlError::runtime(
17176 "unexpected control flow in parameter default",
17177 line,
17178 ))
17179 }
17180 };
17181 val.to_list()
17182 } else {
17183 vec![]
17184 };
17185 let aname = self.stash_array_name_for_package(name);
17186 self.scope.declare_array(&aname, rest);
17187 }
17188 SubSigParam::Hash(name, default) => {
17189 let rest: Vec<PerlValue> = if i < argv.len() {
17190 let r = argv[i..].to_vec();
17191 i = argv.len();
17192 r
17193 } else if let Some(default_expr) = default {
17194 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17195 Ok(v) => v,
17196 Err(FlowOrError::Error(e)) => return Err(e),
17197 Err(FlowOrError::Flow(_)) => {
17198 return Err(PerlError::runtime(
17199 "unexpected control flow in parameter default",
17200 line,
17201 ))
17202 }
17203 };
17204 val.to_list()
17205 } else {
17206 vec![]
17207 };
17208 let mut map = IndexMap::new();
17209 let mut j = 0;
17210 while j + 1 < rest.len() {
17211 map.insert(rest[j].to_string(), rest[j + 1].clone());
17212 j += 2;
17213 }
17214 self.scope.declare_hash(name, map);
17215 }
17216 SubSigParam::ArrayDestruct(elems) => {
17217 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17218 i += 1;
17219 let Some(arr) = self.match_subject_as_array(&arg) else {
17220 return Err(PerlError::runtime(
17221 format!(
17222 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
17223 arg.ref_type()
17224 ),
17225 line,
17226 ));
17227 };
17228 let binds = self
17229 .match_array_pattern_elems(&arr, elems, line)
17230 .map_err(|e| match e {
17231 FlowOrError::Error(stryke) => stryke,
17232 FlowOrError::Flow(_) => PerlError::runtime(
17233 "unexpected flow in sub signature array destruct",
17234 line,
17235 ),
17236 })?;
17237 let Some(binds) = binds else {
17238 return Err(PerlError::runtime(
17239 "sub signature array destruct: length or element mismatch",
17240 line,
17241 ));
17242 };
17243 for b in binds {
17244 match b {
17245 PatternBinding::Scalar(name, v) => {
17246 let n = self.english_scalar_name(&name);
17247 self.scope.declare_scalar(n, v);
17248 }
17249 PatternBinding::Array(name, elems) => {
17250 self.scope.declare_array(&name, elems);
17251 }
17252 }
17253 }
17254 }
17255 SubSigParam::HashDestruct(pairs) => {
17256 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17257 i += 1;
17258 let map = self.hash_for_signature_destruct(&arg, line)?;
17259 for (key, varname) in pairs {
17260 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
17261 let n = self.english_scalar_name(varname);
17262 self.scope.declare_scalar(n, v);
17263 }
17264 }
17265 }
17266 }
17267 Ok(())
17268 }
17269
17270 pub(crate) fn try_hof_dispatch(
17274 &mut self,
17275 sub: &PerlSub,
17276 args: &[PerlValue],
17277 want: WantarrayCtx,
17278 line: usize,
17279 ) -> Option<ExecResult> {
17280 let env = sub.closure_env.as_ref()?;
17281 fn env_get<'a>(env: &'a [(String, PerlValue)], key: &str) -> Option<&'a PerlValue> {
17282 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
17283 }
17284
17285 match sub.name.as_str() {
17286 "__comp__" => {
17288 let fns = env_get(env, "__comp_fns__")?.to_list();
17289 let mut val = args.first().cloned().unwrap_or(PerlValue::UNDEF);
17290 for f in fns.iter().rev() {
17291 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
17292 Ok(v) => val = v,
17293 Err(e) => return Some(Err(e)),
17294 }
17295 }
17296 Some(Ok(val))
17297 }
17298 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
17300 "__juxt__" => {
17302 let fns = env_get(env, "__juxt_fns__")?.to_list();
17303 let mut results = Vec::with_capacity(fns.len());
17304 for f in &fns {
17305 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
17306 Ok(v) => results.push(v),
17307 Err(e) => return Some(Err(e)),
17308 }
17309 }
17310 Some(Ok(PerlValue::array(results)))
17311 }
17312 "__partial__" => {
17314 let fn_val = env_get(env, "__partial_fn__")?.clone();
17315 let bound = env_get(env, "__partial_args__")?.to_list();
17316 let mut all_args = bound;
17317 all_args.extend_from_slice(args);
17318 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
17319 }
17320 "__complement__" => {
17322 let fn_val = env_get(env, "__complement_fn__")?.clone();
17323 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17324 Ok(v) => Some(Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }))),
17325 Err(e) => Some(Err(e)),
17326 }
17327 }
17328 "__fnil__" => {
17330 let fn_val = env_get(env, "__fnil_fn__")?.clone();
17331 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
17332 let mut patched = args.to_vec();
17333 for (i, d) in defaults.iter().enumerate() {
17334 if i < patched.len() {
17335 if patched[i].is_undef() {
17336 patched[i] = d.clone();
17337 }
17338 } else {
17339 patched.push(d.clone());
17340 }
17341 }
17342 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
17343 }
17344 "__memoize__" => {
17346 let fn_val = env_get(env, "__memoize_fn__")?.clone();
17347 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
17348 let key = args
17349 .iter()
17350 .map(|a| a.to_string())
17351 .collect::<Vec<_>>()
17352 .join("\x00");
17353 if let Some(href) = cache_ref.as_hash_ref() {
17354 if let Some(cached) = href.read().get(&key) {
17355 return Some(Ok(cached.clone()));
17356 }
17357 }
17358 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17359 Ok(v) => {
17360 if let Some(href) = cache_ref.as_hash_ref() {
17361 href.write().insert(key, v.clone());
17362 }
17363 Some(Ok(v))
17364 }
17365 Err(e) => Some(Err(e)),
17366 }
17367 }
17368 "__curry__" => {
17370 let fn_val = env_get(env, "__curry_fn__")?.clone();
17371 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
17372 let bound = env_get(env, "__curry_bound__")?.to_list();
17373 let mut all = bound;
17374 all.extend_from_slice(args);
17375 if all.len() >= arity {
17376 Some(self.dispatch_indirect_call(fn_val, all, want, line))
17377 } else {
17378 let curry_sub = PerlSub {
17379 name: "__curry__".to_string(),
17380 params: vec![],
17381 body: vec![],
17382 closure_env: Some(vec![
17383 ("__curry_fn__".to_string(), fn_val),
17384 (
17385 "__curry_arity__".to_string(),
17386 PerlValue::integer(arity as i64),
17387 ),
17388 ("__curry_bound__".to_string(), PerlValue::array(all)),
17389 ]),
17390 prototype: None,
17391 fib_like: None,
17392 };
17393 Some(Ok(PerlValue::code_ref(Arc::new(curry_sub))))
17394 }
17395 }
17396 "__once__" => {
17398 let cache_ref = env_get(env, "__once_cache__")?.clone();
17399 if let Some(href) = cache_ref.as_hash_ref() {
17400 let r = href.read();
17401 if r.contains_key("done") {
17402 return Some(Ok(r.get("val").cloned().unwrap_or(PerlValue::UNDEF)));
17403 }
17404 }
17405 let fn_val = env_get(env, "__once_fn__")?.clone();
17406 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17407 Ok(v) => {
17408 if let Some(href) = cache_ref.as_hash_ref() {
17409 let mut w = href.write();
17410 w.insert("done".to_string(), PerlValue::integer(1));
17411 w.insert("val".to_string(), v.clone());
17412 }
17413 Some(Ok(v))
17414 }
17415 Err(e) => Some(Err(e)),
17416 }
17417 }
17418 _ => None,
17419 }
17420 }
17421
17422 pub(crate) fn call_sub(
17423 &mut self,
17424 sub: &PerlSub,
17425 args: Vec<PerlValue>,
17426 want: WantarrayCtx,
17427 _line: usize,
17428 ) -> ExecResult {
17429 self.current_sub_stack.push(Arc::new(sub.clone()));
17431
17432 self.scope_push_hook();
17435 self.scope.declare_array("_", args.clone());
17436 if let Some(ref env) = sub.closure_env {
17437 self.scope.restore_capture(env);
17438 }
17439 self.scope.set_closure_args(&args);
17443 let argv = self.scope.take_sub_underscore().unwrap_or_default();
17445 self.apply_sub_signature(sub, &argv, _line)?;
17446 let saved = self.wantarray_kind;
17447 self.wantarray_kind = want;
17448 if let Some(r) = crate::list_util::native_dispatch(self, sub, &argv, want) {
17449 self.wantarray_kind = saved;
17450 self.scope_pop_hook();
17451 self.current_sub_stack.pop();
17452 return match r {
17453 Ok(v) => Ok(v),
17454 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17455 Err(e) => Err(e),
17456 };
17457 }
17458 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
17459 self.wantarray_kind = saved;
17460 self.scope_pop_hook();
17461 self.current_sub_stack.pop();
17462 return match r {
17463 Ok(v) => Ok(v),
17464 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17465 Err(e) => Err(e),
17466 };
17467 }
17468 if let Some(pat) = sub.fib_like.as_ref() {
17469 if argv.len() == 1 {
17470 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
17471 let t0 = self.profiler.is_some().then(std::time::Instant::now);
17472 if let Some(p) = &mut self.profiler {
17473 p.enter_sub(&sub.name);
17474 }
17475 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
17476 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
17477 p.exit_sub(t0.elapsed());
17478 }
17479 self.wantarray_kind = saved;
17480 self.scope_pop_hook();
17481 self.current_sub_stack.pop();
17482 return Ok(PerlValue::integer(n));
17483 }
17484 }
17485 }
17486 self.scope.declare_array("_", argv.clone());
17487 let t0 = self.profiler.is_some().then(std::time::Instant::now);
17490 if let Some(p) = &mut self.profiler {
17491 p.enter_sub(&sub.name);
17492 }
17493 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
17497 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
17498 p.exit_sub(t0.elapsed());
17499 }
17500 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
17502 Some(self.scope.get_array("_"))
17503 } else {
17504 None
17505 };
17506 self.wantarray_kind = saved;
17507 self.scope_pop_hook();
17508 self.current_sub_stack.pop();
17509 match result {
17510 Ok(v) => Ok(v),
17511 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17512 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
17513 let goto_args = goto_args.unwrap_or_default();
17515 let fqn = if target_name.contains("::") {
17516 target_name.clone()
17517 } else {
17518 format!("{}::{}", self.current_package(), target_name)
17519 };
17520 if let Some(target_sub) = self
17521 .subs
17522 .get(&fqn)
17523 .cloned()
17524 .or_else(|| self.subs.get(&target_name).cloned())
17525 {
17526 self.call_sub(&target_sub, goto_args, want, _line)
17527 } else {
17528 Err(
17529 PerlError::runtime(format!("Undefined subroutine &{}", target_name), _line)
17530 .into(),
17531 )
17532 }
17533 }
17534 Err(FlowOrError::Flow(Flow::Yield(_))) => {
17535 Err(PerlError::runtime("yield is only valid inside gen { }", 0).into())
17536 }
17537 Err(e) => Err(e),
17538 }
17539 }
17540
17541 fn call_struct_method(
17543 &mut self,
17544 body: &Block,
17545 params: &[SubSigParam],
17546 args: Vec<PerlValue>,
17547 line: usize,
17548 ) -> ExecResult {
17549 self.scope_push_hook();
17550 self.scope.declare_array("_", args.clone());
17551 if let Some(self_val) = args.first() {
17553 self.scope.declare_scalar("self", self_val.clone());
17554 }
17555 self.scope.set_closure_args(&args);
17557 let user_args: Vec<PerlValue> = args.iter().skip(1).cloned().collect();
17559 self.apply_params_to_argv(params, &user_args, line)?;
17560 let result = self.exec_block_no_scope(body);
17561 self.scope_pop_hook();
17562 match result {
17563 Ok(v) => Ok(v),
17564 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17565 Err(e) => Err(e),
17566 }
17567 }
17568
17569 pub(crate) fn call_class_method(
17571 &mut self,
17572 body: &Block,
17573 params: &[SubSigParam],
17574 args: Vec<PerlValue>,
17575 line: usize,
17576 ) -> ExecResult {
17577 self.call_class_method_inner(body, params, args, line, false)
17578 }
17579
17580 pub(crate) fn call_static_class_method(
17582 &mut self,
17583 body: &Block,
17584 params: &[SubSigParam],
17585 args: Vec<PerlValue>,
17586 line: usize,
17587 ) -> ExecResult {
17588 self.call_class_method_inner(body, params, args, line, true)
17589 }
17590
17591 fn call_class_method_inner(
17592 &mut self,
17593 body: &Block,
17594 params: &[SubSigParam],
17595 args: Vec<PerlValue>,
17596 line: usize,
17597 is_static: bool,
17598 ) -> ExecResult {
17599 self.scope_push_hook();
17600 self.scope.declare_array("_", args.clone());
17601 if !is_static {
17602 if let Some(self_val) = args.first() {
17604 self.scope.declare_scalar("self", self_val.clone());
17605 }
17606 }
17607 self.scope.set_closure_args(&args);
17609 let user_args: Vec<PerlValue> = if is_static {
17611 args.clone()
17612 } else {
17613 args.iter().skip(1).cloned().collect()
17614 };
17615 self.apply_params_to_argv(params, &user_args, line)?;
17616 let result = self.exec_block_no_scope(body);
17617 self.scope_pop_hook();
17618 match result {
17619 Ok(v) => Ok(v),
17620 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17621 Err(e) => Err(e),
17622 }
17623 }
17624
17625 fn apply_params_to_argv(
17627 &mut self,
17628 params: &[SubSigParam],
17629 argv: &[PerlValue],
17630 line: usize,
17631 ) -> PerlResult<()> {
17632 let mut i = 0;
17633 for param in params {
17634 match param {
17635 SubSigParam::Scalar(name, ty_opt, default) => {
17636 let v = if i < argv.len() {
17637 argv[i].clone()
17638 } else if let Some(default_expr) = default {
17639 match self.eval_expr(default_expr) {
17640 Ok(v) => v,
17641 Err(FlowOrError::Error(e)) => return Err(e),
17642 Err(FlowOrError::Flow(_)) => {
17643 return Err(PerlError::runtime(
17644 "unexpected control flow in parameter default",
17645 line,
17646 ))
17647 }
17648 }
17649 } else {
17650 PerlValue::UNDEF
17651 };
17652 i += 1;
17653 if let Some(ty) = ty_opt {
17654 ty.check_value(&v).map_err(|msg| {
17655 PerlError::type_error(
17656 format!("method parameter ${}: {}", name, msg),
17657 line,
17658 )
17659 })?;
17660 }
17661 let n = self.english_scalar_name(name);
17662 self.scope.declare_scalar(n, v);
17663 }
17664 SubSigParam::Array(name, default) => {
17665 let rest: Vec<PerlValue> = if i < argv.len() {
17666 let r = argv[i..].to_vec();
17667 i = argv.len();
17668 r
17669 } else if let Some(default_expr) = default {
17670 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17671 Ok(v) => v,
17672 Err(FlowOrError::Error(e)) => return Err(e),
17673 Err(FlowOrError::Flow(_)) => {
17674 return Err(PerlError::runtime(
17675 "unexpected control flow in parameter default",
17676 line,
17677 ))
17678 }
17679 };
17680 val.to_list()
17681 } else {
17682 vec![]
17683 };
17684 let aname = self.stash_array_name_for_package(name);
17685 self.scope.declare_array(&aname, rest);
17686 }
17687 SubSigParam::Hash(name, default) => {
17688 let rest: Vec<PerlValue> = if i < argv.len() {
17689 let r = argv[i..].to_vec();
17690 i = argv.len();
17691 r
17692 } else if let Some(default_expr) = default {
17693 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17694 Ok(v) => v,
17695 Err(FlowOrError::Error(e)) => return Err(e),
17696 Err(FlowOrError::Flow(_)) => {
17697 return Err(PerlError::runtime(
17698 "unexpected control flow in parameter default",
17699 line,
17700 ))
17701 }
17702 };
17703 val.to_list()
17704 } else {
17705 vec![]
17706 };
17707 let mut map = IndexMap::new();
17708 let mut j = 0;
17709 while j + 1 < rest.len() {
17710 map.insert(rest[j].to_string(), rest[j + 1].clone());
17711 j += 2;
17712 }
17713 self.scope.declare_hash(name, map);
17714 }
17715 SubSigParam::ArrayDestruct(elems) => {
17716 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17717 i += 1;
17718 let Some(arr) = self.match_subject_as_array(&arg) else {
17719 return Err(PerlError::runtime(
17720 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
17721 line,
17722 ));
17723 };
17724 let binds = self
17725 .match_array_pattern_elems(&arr, elems, line)
17726 .map_err(|e| match e {
17727 FlowOrError::Error(stryke) => stryke,
17728 FlowOrError::Flow(_) => {
17729 PerlError::runtime("unexpected flow in method array destruct", line)
17730 }
17731 })?;
17732 let Some(binds) = binds else {
17733 return Err(PerlError::runtime(
17734 format!(
17735 "method parameter: array destructure failed at position {}",
17736 i
17737 ),
17738 line,
17739 ));
17740 };
17741 for b in binds {
17742 match b {
17743 PatternBinding::Scalar(name, v) => {
17744 let n = self.english_scalar_name(&name);
17745 self.scope.declare_scalar(n, v);
17746 }
17747 PatternBinding::Array(name, elems) => {
17748 self.scope.declare_array(&name, elems);
17749 }
17750 }
17751 }
17752 }
17753 SubSigParam::HashDestruct(pairs) => {
17754 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17755 i += 1;
17756 let map = self.hash_for_signature_destruct(&arg, line)?;
17757 for (key, varname) in pairs {
17758 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
17759 let n = self.english_scalar_name(varname);
17760 self.scope.declare_scalar(n, v);
17761 }
17762 }
17763 }
17764 }
17765 Ok(())
17766 }
17767
17768 fn builtin_new(&mut self, class: &str, args: Vec<PerlValue>, line: usize) -> ExecResult {
17769 if class == "Set" {
17770 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
17771 }
17772 if let Some(def) = self.struct_defs.get(class).cloned() {
17773 let mut provided = Vec::new();
17774 let mut i = 1;
17775 while i + 1 < args.len() {
17776 let k = args[i].to_string();
17777 let v = args[i + 1].clone();
17778 provided.push((k, v));
17779 i += 2;
17780 }
17781 let mut defaults = Vec::with_capacity(def.fields.len());
17782 for field in &def.fields {
17783 if let Some(ref expr) = field.default {
17784 let val = self.eval_expr(expr)?;
17785 defaults.push(Some(val));
17786 } else {
17787 defaults.push(None);
17788 }
17789 }
17790 return Ok(crate::native_data::struct_new_with_defaults(
17791 &def, &provided, &defaults, line,
17792 )?);
17793 }
17794 let mut map = IndexMap::new();
17796 let mut i = 1; while i + 1 < args.len() {
17798 let k = args[i].to_string();
17799 let v = args[i + 1].clone();
17800 map.insert(k, v);
17801 i += 2;
17802 }
17803 Ok(PerlValue::blessed(Arc::new(
17804 crate::value::BlessedRef::new_blessed(class.to_string(), PerlValue::hash(map)),
17805 )))
17806 }
17807
17808 fn exec_print(
17809 &mut self,
17810 handle: Option<&str>,
17811 args: &[Expr],
17812 newline: bool,
17813 line: usize,
17814 ) -> ExecResult {
17815 if newline && (self.feature_bits & FEAT_SAY) == 0 {
17816 return Err(PerlError::runtime(
17817 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
17818 line,
17819 )
17820 .into());
17821 }
17822 let mut output = String::new();
17823 if args.is_empty() {
17824 let topic = self.scope.get_scalar("_").clone();
17826 let s = self.stringify_value(topic, line)?;
17827 output.push_str(&s);
17828 } else {
17829 for (i, a) in args.iter().enumerate() {
17832 if i > 0 {
17833 output.push_str(&self.ofs);
17834 }
17835 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17836 for item in val.to_list() {
17837 let s = self.stringify_value(item, line)?;
17838 output.push_str(&s);
17839 }
17840 }
17841 }
17842 if newline {
17843 output.push('\n');
17844 }
17845 output.push_str(&self.ors);
17846
17847 let handle_name =
17848 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17849 self.write_formatted_print(handle_name.as_str(), &output, line)?;
17850 Ok(PerlValue::integer(1))
17851 }
17852
17853 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
17854 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
17855 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
17857 (s, &[])
17858 } else {
17859 (self.eval_expr(&args[0])?.to_string(), &args[1..])
17860 };
17861 let mut arg_vals = Vec::new();
17865 for a in rest {
17866 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17867 if let Some(items) = v.as_array_vec() {
17868 arg_vals.extend(items);
17869 } else {
17870 arg_vals.push(v);
17871 }
17872 }
17873 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
17874 let handle_name =
17875 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17876 match handle_name.as_str() {
17877 "STDOUT" => {
17878 if !self.suppress_stdout {
17879 print!("{}", output);
17880 if self.output_autoflush {
17881 let _ = io::stdout().flush();
17882 }
17883 }
17884 }
17885 "STDERR" => {
17886 eprint!("{}", output);
17887 let _ = io::stderr().flush();
17888 }
17889 name => {
17890 if let Some(writer) = self.output_handles.get_mut(name) {
17891 let _ = writer.write_all(output.as_bytes());
17892 if self.output_autoflush {
17893 let _ = writer.flush();
17894 }
17895 }
17896 }
17897 }
17898 Ok(PerlValue::integer(1))
17899 }
17900
17901 pub(crate) fn eval_substr_expr(
17903 &mut self,
17904 string: &Expr,
17905 offset: &Expr,
17906 length: Option<&Expr>,
17907 replacement: Option<&Expr>,
17908 _line: usize,
17909 ) -> Result<PerlValue, FlowOrError> {
17910 let s = self.eval_expr(string)?.to_string();
17911 let off = self.eval_expr(offset)?.to_int();
17912 let start = if off < 0 {
17913 (s.len() as i64 + off).max(0) as usize
17914 } else {
17915 off as usize
17916 };
17917 let len = if let Some(l) = length {
17918 let len_val = self.eval_expr(l)?.to_int();
17919 if len_val < 0 {
17920 let remaining = s.len().saturating_sub(start) as i64;
17922 (remaining + len_val).max(0) as usize
17923 } else {
17924 len_val as usize
17925 }
17926 } else {
17927 s.len().saturating_sub(start)
17928 };
17929 let end = start.saturating_add(len).min(s.len());
17930 let result = s.get(start..end).unwrap_or("").to_string();
17931 if let Some(rep) = replacement {
17932 let rep_s = self.eval_expr(rep)?.to_string();
17933 let mut new_s = String::new();
17934 new_s.push_str(&s[..start]);
17935 new_s.push_str(&rep_s);
17936 new_s.push_str(&s[end..]);
17937 self.assign_value(string, PerlValue::string(new_s))?;
17938 }
17939 Ok(PerlValue::string(result))
17940 }
17941
17942 pub(crate) fn eval_push_expr(
17943 &mut self,
17944 array: &Expr,
17945 values: &[Expr],
17946 line: usize,
17947 ) -> Result<PerlValue, FlowOrError> {
17948 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17949 for v in values {
17950 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17951 self.push_array_deref_value(aref.clone(), val, line)?;
17952 }
17953 let len = self.array_deref_len(aref, line)?;
17954 return Ok(PerlValue::integer(len));
17955 }
17956 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17957 if self.scope.is_array_frozen(&arr_name) {
17958 return Err(PerlError::runtime(
17959 format!("Modification of a frozen value: @{}", arr_name),
17960 line,
17961 )
17962 .into());
17963 }
17964 for v in values {
17965 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17966 if let Some(items) = val.as_array_vec() {
17967 for item in items {
17968 self.scope
17969 .push_to_array(&arr_name, item)
17970 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17971 }
17972 } else {
17973 self.scope
17974 .push_to_array(&arr_name, val)
17975 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17976 }
17977 }
17978 let len = self.scope.array_len(&arr_name);
17979 Ok(PerlValue::integer(len as i64))
17980 }
17981
17982 pub(crate) fn eval_pop_expr(
17983 &mut self,
17984 array: &Expr,
17985 line: usize,
17986 ) -> Result<PerlValue, FlowOrError> {
17987 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17988 return self.pop_array_deref(aref, line);
17989 }
17990 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17991 self.scope
17992 .pop_from_array(&arr_name)
17993 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17994 }
17995
17996 pub(crate) fn eval_shift_expr(
17997 &mut self,
17998 array: &Expr,
17999 line: usize,
18000 ) -> Result<PerlValue, FlowOrError> {
18001 if let Some(aref) = self.try_eval_array_deref_container(array)? {
18002 return self.shift_array_deref(aref, line);
18003 }
18004 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
18005 self.scope
18006 .shift_from_array(&arr_name)
18007 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18008 }
18009
18010 pub(crate) fn eval_unshift_expr(
18011 &mut self,
18012 array: &Expr,
18013 values: &[Expr],
18014 line: usize,
18015 ) -> Result<PerlValue, FlowOrError> {
18016 if let Some(aref) = self.try_eval_array_deref_container(array)? {
18017 let mut vals = Vec::new();
18018 for v in values {
18019 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
18020 if let Some(items) = val.as_array_vec() {
18021 vals.extend(items);
18022 } else {
18023 vals.push(val);
18024 }
18025 }
18026 let len = self.unshift_array_deref_multi(aref, vals, line)?;
18027 return Ok(PerlValue::integer(len));
18028 }
18029 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
18030 let mut vals = Vec::new();
18031 for v in values {
18032 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
18033 if let Some(items) = val.as_array_vec() {
18034 vals.extend(items);
18035 } else {
18036 vals.push(val);
18037 }
18038 }
18039 let arr = self
18040 .scope
18041 .get_array_mut(&arr_name)
18042 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18043 for (i, v) in vals.into_iter().enumerate() {
18044 arr.insert(i, v);
18045 }
18046 let len = arr.len();
18047 Ok(PerlValue::integer(len as i64))
18048 }
18049
18050 pub(crate) fn push_array_deref_value(
18052 &mut self,
18053 arr_ref: PerlValue,
18054 val: PerlValue,
18055 line: usize,
18056 ) -> Result<(), FlowOrError> {
18057 if let Some(r) = arr_ref.as_array_ref() {
18058 let mut w = r.write();
18059 if let Some(items) = val.as_array_vec() {
18060 w.extend(items.iter().cloned());
18061 } else {
18062 w.push(val);
18063 }
18064 return Ok(());
18065 }
18066 if let Some(name) = arr_ref.as_array_binding_name() {
18067 if let Some(items) = val.as_array_vec() {
18068 for item in items {
18069 self.scope
18070 .push_to_array(&name, item)
18071 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18072 }
18073 } else {
18074 self.scope
18075 .push_to_array(&name, val)
18076 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18077 }
18078 return Ok(());
18079 }
18080 if let Some(s) = arr_ref.as_str() {
18081 if self.strict_refs {
18082 return Err(PerlError::runtime(
18083 format!(
18084 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18085 s
18086 ),
18087 line,
18088 )
18089 .into());
18090 }
18091 let name = s.to_string();
18092 if let Some(items) = val.as_array_vec() {
18093 for item in items {
18094 self.scope
18095 .push_to_array(&name, item)
18096 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18097 }
18098 } else {
18099 self.scope
18100 .push_to_array(&name, val)
18101 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18102 }
18103 return Ok(());
18104 }
18105 Err(PerlError::runtime("push argument is not an ARRAY reference", line).into())
18106 }
18107
18108 pub(crate) fn array_deref_len(
18109 &self,
18110 arr_ref: PerlValue,
18111 line: usize,
18112 ) -> Result<i64, FlowOrError> {
18113 if let Some(r) = arr_ref.as_array_ref() {
18114 return Ok(r.read().len() as i64);
18115 }
18116 if let Some(name) = arr_ref.as_array_binding_name() {
18117 return Ok(self.scope.array_len(&name) as i64);
18118 }
18119 if let Some(s) = arr_ref.as_str() {
18120 if self.strict_refs {
18121 return Err(PerlError::runtime(
18122 format!(
18123 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18124 s
18125 ),
18126 line,
18127 )
18128 .into());
18129 }
18130 return Ok(self.scope.array_len(&s) as i64);
18131 }
18132 Err(PerlError::runtime("argument is not an ARRAY reference", line).into())
18133 }
18134
18135 pub(crate) fn pop_array_deref(
18136 &mut self,
18137 arr_ref: PerlValue,
18138 line: usize,
18139 ) -> Result<PerlValue, FlowOrError> {
18140 if let Some(r) = arr_ref.as_array_ref() {
18141 let mut w = r.write();
18142 return Ok(w.pop().unwrap_or(PerlValue::UNDEF));
18143 }
18144 if let Some(name) = arr_ref.as_array_binding_name() {
18145 return self
18146 .scope
18147 .pop_from_array(&name)
18148 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18149 }
18150 if let Some(s) = arr_ref.as_str() {
18151 if self.strict_refs {
18152 return Err(PerlError::runtime(
18153 format!(
18154 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18155 s
18156 ),
18157 line,
18158 )
18159 .into());
18160 }
18161 return self
18162 .scope
18163 .pop_from_array(&s)
18164 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18165 }
18166 Err(PerlError::runtime("pop argument is not an ARRAY reference", line).into())
18167 }
18168
18169 pub(crate) fn shift_array_deref(
18170 &mut self,
18171 arr_ref: PerlValue,
18172 line: usize,
18173 ) -> Result<PerlValue, FlowOrError> {
18174 if let Some(r) = arr_ref.as_array_ref() {
18175 let mut w = r.write();
18176 return Ok(if w.is_empty() {
18177 PerlValue::UNDEF
18178 } else {
18179 w.remove(0)
18180 });
18181 }
18182 if let Some(name) = arr_ref.as_array_binding_name() {
18183 return self
18184 .scope
18185 .shift_from_array(&name)
18186 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18187 }
18188 if let Some(s) = arr_ref.as_str() {
18189 if self.strict_refs {
18190 return Err(PerlError::runtime(
18191 format!(
18192 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18193 s
18194 ),
18195 line,
18196 )
18197 .into());
18198 }
18199 return self
18200 .scope
18201 .shift_from_array(&s)
18202 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18203 }
18204 Err(PerlError::runtime("shift argument is not an ARRAY reference", line).into())
18205 }
18206
18207 pub(crate) fn unshift_array_deref_multi(
18208 &mut self,
18209 arr_ref: PerlValue,
18210 vals: Vec<PerlValue>,
18211 line: usize,
18212 ) -> Result<i64, FlowOrError> {
18213 let mut flat: Vec<PerlValue> = Vec::new();
18214 for v in vals {
18215 if let Some(items) = v.as_array_vec() {
18216 flat.extend(items);
18217 } else {
18218 flat.push(v);
18219 }
18220 }
18221 if let Some(r) = arr_ref.as_array_ref() {
18222 let mut w = r.write();
18223 for (i, v) in flat.into_iter().enumerate() {
18224 w.insert(i, v);
18225 }
18226 return Ok(w.len() as i64);
18227 }
18228 if let Some(name) = arr_ref.as_array_binding_name() {
18229 let arr = self
18230 .scope
18231 .get_array_mut(&name)
18232 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18233 for (i, v) in flat.into_iter().enumerate() {
18234 arr.insert(i, v);
18235 }
18236 return Ok(arr.len() as i64);
18237 }
18238 if let Some(s) = arr_ref.as_str() {
18239 if self.strict_refs {
18240 return Err(PerlError::runtime(
18241 format!(
18242 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18243 s
18244 ),
18245 line,
18246 )
18247 .into());
18248 }
18249 let name = s.to_string();
18250 let arr = self
18251 .scope
18252 .get_array_mut(&name)
18253 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18254 for (i, v) in flat.into_iter().enumerate() {
18255 arr.insert(i, v);
18256 }
18257 return Ok(arr.len() as i64);
18258 }
18259 Err(PerlError::runtime("unshift argument is not an ARRAY reference", line).into())
18260 }
18261
18262 pub(crate) fn splice_array_deref(
18265 &mut self,
18266 aref: PerlValue,
18267 offset_val: PerlValue,
18268 length_val: PerlValue,
18269 rep_vals: Vec<PerlValue>,
18270 line: usize,
18271 ) -> Result<PerlValue, FlowOrError> {
18272 let ctx = self.wantarray_kind;
18273 if let Some(r) = aref.as_array_ref() {
18274 let arr_len = r.read().len();
18275 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18276 let mut w = r.write();
18277 let removed: Vec<PerlValue> = w.drain(off..end).collect();
18278 for (i, v) in rep_vals.into_iter().enumerate() {
18279 w.insert(off + i, v);
18280 }
18281 return Ok(match ctx {
18282 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18283 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18284 });
18285 }
18286 if let Some(name) = aref.as_array_binding_name() {
18287 let arr_len = self.scope.array_len(&name);
18288 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18289 let arr = self
18290 .scope
18291 .get_array_mut(&name)
18292 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18293 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
18294 for (i, v) in rep_vals.into_iter().enumerate() {
18295 arr.insert(off + i, v);
18296 }
18297 return Ok(match ctx {
18298 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18299 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18300 });
18301 }
18302 if let Some(s) = aref.as_str() {
18303 if self.strict_refs {
18304 return Err(PerlError::runtime(
18305 format!(
18306 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18307 s
18308 ),
18309 line,
18310 )
18311 .into());
18312 }
18313 let arr_len = self.scope.array_len(&s);
18314 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18315 let arr = self
18316 .scope
18317 .get_array_mut(&s)
18318 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18319 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
18320 for (i, v) in rep_vals.into_iter().enumerate() {
18321 arr.insert(off + i, v);
18322 }
18323 return Ok(match ctx {
18324 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18325 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18326 });
18327 }
18328 Err(PerlError::runtime("splice argument is not an ARRAY reference", line).into())
18329 }
18330
18331 pub(crate) fn eval_splice_expr(
18332 &mut self,
18333 array: &Expr,
18334 offset: Option<&Expr>,
18335 length: Option<&Expr>,
18336 replacement: &[Expr],
18337 ctx: WantarrayCtx,
18338 line: usize,
18339 ) -> Result<PerlValue, FlowOrError> {
18340 if let Some(aref) = self.try_eval_array_deref_container(array)? {
18341 let offset_val = if let Some(o) = offset {
18342 self.eval_expr(o)?
18343 } else {
18344 PerlValue::integer(0)
18345 };
18346 let length_val = if let Some(l) = length {
18347 self.eval_expr(l)?
18348 } else {
18349 PerlValue::UNDEF
18350 };
18351 let mut rep_vals = Vec::new();
18352 for r in replacement {
18353 rep_vals.push(self.eval_expr(r)?);
18354 }
18355 let saved = self.wantarray_kind;
18356 self.wantarray_kind = ctx;
18357 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
18358 self.wantarray_kind = saved;
18359 return out;
18360 }
18361 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
18362 let arr_len = self.scope.array_len(&arr_name);
18363 let offset_val = if let Some(o) = offset {
18364 self.eval_expr(o)?
18365 } else {
18366 PerlValue::integer(0)
18367 };
18368 let length_val = if let Some(l) = length {
18369 self.eval_expr(l)?
18370 } else {
18371 PerlValue::UNDEF
18372 };
18373 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18374 let mut rep_vals = Vec::new();
18375 for r in replacement {
18376 rep_vals.push(self.eval_expr(r)?);
18377 }
18378 let arr = self
18379 .scope
18380 .get_array_mut(&arr_name)
18381 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18382 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
18383 for (i, v) in rep_vals.into_iter().enumerate() {
18384 arr.insert(off + i, v);
18385 }
18386 Ok(match ctx {
18387 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18388 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18389 })
18390 }
18391
18392 pub(crate) fn keys_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
18394 if let Some(h) = val.as_hash_map() {
18395 Ok(PerlValue::array(
18396 h.keys().map(|k| PerlValue::string(k.clone())).collect(),
18397 ))
18398 } else if let Some(r) = val.as_hash_ref() {
18399 Ok(PerlValue::array(
18400 r.read()
18401 .keys()
18402 .map(|k| PerlValue::string(k.clone()))
18403 .collect(),
18404 ))
18405 } else {
18406 Err(PerlError::runtime("keys requires hash", line).into())
18407 }
18408 }
18409
18410 pub(crate) fn eval_keys_expr(
18411 &mut self,
18412 expr: &Expr,
18413 line: usize,
18414 ) -> Result<PerlValue, FlowOrError> {
18415 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
18418 Self::keys_from_value(val, line)
18419 }
18420
18421 pub(crate) fn values_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
18423 if let Some(h) = val.as_hash_map() {
18424 Ok(PerlValue::array(h.values().cloned().collect()))
18425 } else if let Some(r) = val.as_hash_ref() {
18426 Ok(PerlValue::array(r.read().values().cloned().collect()))
18427 } else {
18428 Err(PerlError::runtime("values requires hash", line).into())
18429 }
18430 }
18431
18432 pub(crate) fn eval_values_expr(
18433 &mut self,
18434 expr: &Expr,
18435 line: usize,
18436 ) -> Result<PerlValue, FlowOrError> {
18437 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
18438 Self::values_from_value(val, line)
18439 }
18440
18441 pub(crate) fn eval_delete_operand(
18442 &mut self,
18443 expr: &Expr,
18444 line: usize,
18445 ) -> Result<PerlValue, FlowOrError> {
18446 match &expr.kind {
18447 ExprKind::HashElement { hash, key } => {
18448 let k = self.eval_expr(key)?.to_string();
18449 self.touch_env_hash(hash);
18450 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
18451 let class = obj
18452 .as_blessed_ref()
18453 .map(|b| b.class.clone())
18454 .unwrap_or_default();
18455 let full = format!("{}::DELETE", class);
18456 if let Some(sub) = self.subs.get(&full).cloned() {
18457 return self.call_sub(
18458 &sub,
18459 vec![obj, PerlValue::string(k)],
18460 WantarrayCtx::Scalar,
18461 line,
18462 );
18463 }
18464 }
18465 self.scope
18466 .delete_hash_element(hash, &k)
18467 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18468 }
18469 ExprKind::ArrayElement { array, index } => {
18470 self.check_strict_array_var(array, line)?;
18471 let idx = self.eval_expr(index)?.to_int();
18472 let aname = self.stash_array_name_for_package(array);
18473 self.scope
18474 .delete_array_element(&aname, idx)
18475 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18476 }
18477 ExprKind::ArrowDeref {
18478 expr: inner,
18479 index,
18480 kind: DerefKind::Hash,
18481 } => {
18482 let k = self.eval_expr(index)?.to_string();
18483 let container = self.eval_expr(inner)?;
18484 self.delete_arrow_hash_element(container, &k, line)
18485 .map_err(Into::into)
18486 }
18487 ExprKind::ArrowDeref {
18488 expr: inner,
18489 index,
18490 kind: DerefKind::Array,
18491 } => {
18492 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
18493 return Err(PerlError::runtime(
18494 "delete on array element needs scalar subscript",
18495 line,
18496 )
18497 .into());
18498 }
18499 let container = self.eval_expr(inner)?;
18500 let idx = self.eval_expr(index)?.to_int();
18501 self.delete_arrow_array_element(container, idx, line)
18502 .map_err(Into::into)
18503 }
18504 _ => Err(PerlError::runtime("delete requires hash or array element", line).into()),
18505 }
18506 }
18507
18508 pub(crate) fn eval_exists_operand(
18509 &mut self,
18510 expr: &Expr,
18511 line: usize,
18512 ) -> Result<PerlValue, FlowOrError> {
18513 match &expr.kind {
18514 ExprKind::HashElement { hash, key } => {
18515 let k = self.eval_expr(key)?.to_string();
18516 self.touch_env_hash(hash);
18517 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
18518 let class = obj
18519 .as_blessed_ref()
18520 .map(|b| b.class.clone())
18521 .unwrap_or_default();
18522 let full = format!("{}::EXISTS", class);
18523 if let Some(sub) = self.subs.get(&full).cloned() {
18524 return self.call_sub(
18525 &sub,
18526 vec![obj, PerlValue::string(k)],
18527 WantarrayCtx::Scalar,
18528 line,
18529 );
18530 }
18531 }
18532 Ok(PerlValue::integer(
18533 if self.scope.exists_hash_element(hash, &k) {
18534 1
18535 } else {
18536 0
18537 },
18538 ))
18539 }
18540 ExprKind::ArrayElement { array, index } => {
18541 self.check_strict_array_var(array, line)?;
18542 let idx = self.eval_expr(index)?.to_int();
18543 let aname = self.stash_array_name_for_package(array);
18544 Ok(PerlValue::integer(
18545 if self.scope.exists_array_element(&aname, idx) {
18546 1
18547 } else {
18548 0
18549 },
18550 ))
18551 }
18552 ExprKind::ArrowDeref {
18553 expr: inner,
18554 index,
18555 kind: DerefKind::Hash,
18556 } => {
18557 let k = self.eval_expr(index)?.to_string();
18558 let container = self.eval_expr(inner)?;
18559 let yes = self.exists_arrow_hash_element(container, &k, line)?;
18560 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
18561 }
18562 ExprKind::ArrowDeref {
18563 expr: inner,
18564 index,
18565 kind: DerefKind::Array,
18566 } => {
18567 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
18568 return Err(PerlError::runtime(
18569 "exists on array element needs scalar subscript",
18570 line,
18571 )
18572 .into());
18573 }
18574 let container = self.eval_expr(inner)?;
18575 let idx = self.eval_expr(index)?.to_int();
18576 let yes = self.exists_arrow_array_element(container, idx, line)?;
18577 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
18578 }
18579 _ => Err(PerlError::runtime("exists requires hash or array element", line).into()),
18580 }
18581 }
18582
18583 pub(crate) fn eval_pmap_remote(
18591 &mut self,
18592 cluster_pv: PerlValue,
18593 list_pv: PerlValue,
18594 show_progress: bool,
18595 block: &Block,
18596 flat_outputs: bool,
18597 line: usize,
18598 ) -> Result<PerlValue, FlowOrError> {
18599 let Some(cluster) = cluster_pv.as_remote_cluster() else {
18600 return Err(PerlError::runtime("pmap_on: expected cluster(...) value", line).into());
18601 };
18602 let items = list_pv.to_list();
18603 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18604 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
18605 return Err(PerlError::runtime(
18606 "pmap_on: mysync/atomic capture is not supported for remote workers",
18607 line,
18608 )
18609 .into());
18610 }
18611 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
18612 .map_err(|e| PerlError::runtime(e, line))?;
18613 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
18614 let block_src = crate::fmt::format_block(block);
18615 let item_jsons =
18616 crate::cluster::perl_items_to_json(&items).map_err(|e| PerlError::runtime(e, line))?;
18617
18618 let pmap_progress = PmapProgress::new(show_progress, items.len());
18621 let result_values =
18622 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
18623 .map_err(|e| PerlError::runtime(format!("pmap_on remote: {e}"), line))?;
18624 for _ in 0..result_values.len() {
18625 pmap_progress.tick();
18626 }
18627 pmap_progress.finish();
18628
18629 if flat_outputs {
18630 let flattened: Vec<PerlValue> = result_values
18631 .into_iter()
18632 .flat_map(|v| v.map_flatten_outputs(true))
18633 .collect();
18634 Ok(PerlValue::array(flattened))
18635 } else {
18636 Ok(PerlValue::array(result_values))
18637 }
18638 }
18639
18640 pub(crate) fn eval_par_lines_expr(
18642 &mut self,
18643 path: &Expr,
18644 callback: &Expr,
18645 progress: Option<&Expr>,
18646 line: usize,
18647 ) -> Result<PerlValue, FlowOrError> {
18648 let show_progress = progress
18649 .map(|p| self.eval_expr(p))
18650 .transpose()?
18651 .map(|v| v.is_true())
18652 .unwrap_or(false);
18653 let path_s = self.eval_expr(path)?.to_string();
18654 let cb_val = self.eval_expr(callback)?;
18655 let sub = if let Some(s) = cb_val.as_code_ref() {
18656 s
18657 } else {
18658 return Err(PerlError::runtime(
18659 "par_lines: second argument must be a code reference",
18660 line,
18661 )
18662 .into());
18663 };
18664 let subs = self.subs.clone();
18665 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18666 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
18667 FlowOrError::Error(PerlError::runtime(format!("par_lines: {}", e), line))
18668 })?;
18669 let mmap = unsafe {
18670 memmap2::Mmap::map(&file).map_err(|e| {
18671 FlowOrError::Error(PerlError::runtime(format!("par_lines: mmap: {}", e), line))
18672 })?
18673 };
18674 let data: &[u8] = &mmap;
18675 if data.is_empty() {
18676 return Ok(PerlValue::UNDEF);
18677 }
18678 let line_total = crate::par_lines::line_count_bytes(data);
18679 let pmap_progress = PmapProgress::new(show_progress, line_total);
18680 if self.num_threads == 0 {
18681 self.num_threads = rayon::current_num_threads();
18682 }
18683 let num_chunks = self.num_threads.saturating_mul(8).max(1);
18684 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
18685 chunks.into_par_iter().try_for_each(|(start, end)| {
18686 let slice = &data[start..end];
18687 let mut s = 0usize;
18688 while s < slice.len() {
18689 let e = slice[s..]
18690 .iter()
18691 .position(|&b| b == b'\n')
18692 .map(|p| s + p)
18693 .unwrap_or(slice.len());
18694 let line_bytes = &slice[s..e];
18695 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
18696 let mut local_interp = Interpreter::new();
18697 local_interp.subs = subs.clone();
18698 local_interp.scope.restore_capture(&scope_capture);
18699 local_interp
18700 .scope
18701 .restore_atomics(&atomic_arrays, &atomic_hashes);
18702 local_interp.enable_parallel_guard();
18703 local_interp.scope.set_topic(PerlValue::string(line_str));
18704 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
18705 Ok(_) => {}
18706 Err(e) => return Err(e),
18707 }
18708 pmap_progress.tick();
18709 if e >= slice.len() {
18710 break;
18711 }
18712 s = e + 1;
18713 }
18714 Ok(())
18715 })?;
18716 pmap_progress.finish();
18717 Ok(PerlValue::UNDEF)
18718 }
18719
18720 pub(crate) fn eval_par_walk_expr(
18722 &mut self,
18723 path: &Expr,
18724 callback: &Expr,
18725 progress: Option<&Expr>,
18726 line: usize,
18727 ) -> Result<PerlValue, FlowOrError> {
18728 let show_progress = progress
18729 .map(|p| self.eval_expr(p))
18730 .transpose()?
18731 .map(|v| v.is_true())
18732 .unwrap_or(false);
18733 let path_val = self.eval_expr(path)?;
18734 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
18735 arr.into_iter()
18736 .map(|v| PathBuf::from(v.to_string()))
18737 .collect()
18738 } else {
18739 vec![PathBuf::from(path_val.to_string())]
18740 };
18741 let cb_val = self.eval_expr(callback)?;
18742 let sub = if let Some(s) = cb_val.as_code_ref() {
18743 s
18744 } else {
18745 return Err(PerlError::runtime(
18746 "par_walk: second argument must be a code reference",
18747 line,
18748 )
18749 .into());
18750 };
18751 let subs = self.subs.clone();
18752 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18753
18754 if show_progress {
18755 let paths = crate::par_walk::collect_paths(&roots);
18756 let pmap_progress = PmapProgress::new(true, paths.len());
18757 paths.into_par_iter().try_for_each(|p| {
18758 let s = p.to_string_lossy().into_owned();
18759 let mut local_interp = Interpreter::new();
18760 local_interp.subs = subs.clone();
18761 local_interp.scope.restore_capture(&scope_capture);
18762 local_interp
18763 .scope
18764 .restore_atomics(&atomic_arrays, &atomic_hashes);
18765 local_interp.enable_parallel_guard();
18766 local_interp.scope.set_topic(PerlValue::string(s));
18767 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
18768 Ok(_) => {}
18769 Err(e) => return Err(e),
18770 }
18771 pmap_progress.tick();
18772 Ok(())
18773 })?;
18774 pmap_progress.finish();
18775 } else {
18776 for r in &roots {
18777 par_walk_recursive(
18778 r.as_path(),
18779 &sub,
18780 &subs,
18781 &scope_capture,
18782 &atomic_arrays,
18783 &atomic_hashes,
18784 line,
18785 )?;
18786 }
18787 }
18788 Ok(PerlValue::UNDEF)
18789 }
18790
18791 pub(crate) fn builtin_par_sed(
18793 &mut self,
18794 args: &[PerlValue],
18795 line: usize,
18796 has_progress: bool,
18797 ) -> PerlResult<PerlValue> {
18798 let show_progress = if has_progress {
18799 args.last().map(|v| v.is_true()).unwrap_or(false)
18800 } else {
18801 false
18802 };
18803 let slice = if has_progress {
18804 &args[..args.len().saturating_sub(1)]
18805 } else {
18806 args
18807 };
18808 if slice.len() < 3 {
18809 return Err(PerlError::runtime(
18810 "par_sed: need pattern, replacement, and at least one file path",
18811 line,
18812 ));
18813 }
18814 let pat_val = &slice[0];
18815 let repl = slice[1].to_string();
18816 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
18817
18818 let re = if let Some(rx) = pat_val.as_regex() {
18819 rx
18820 } else {
18821 let pattern = pat_val.to_string();
18822 match self.compile_regex(&pattern, "g", line) {
18823 Ok(r) => r,
18824 Err(FlowOrError::Error(e)) => return Err(e),
18825 Err(FlowOrError::Flow(f)) => {
18826 return Err(PerlError::runtime(format!("par_sed: {:?}", f), line))
18827 }
18828 }
18829 };
18830
18831 let pmap = PmapProgress::new(show_progress, files.len());
18832 let touched = AtomicUsize::new(0);
18833 files.par_iter().try_for_each(|path| {
18834 let content = read_file_text_perl_compat(path)
18835 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18836 let new_s = re.replace_all(&content, &repl);
18837 if new_s != content {
18838 std::fs::write(path, new_s.as_bytes())
18839 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18840 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
18841 }
18842 pmap.tick();
18843 Ok(())
18844 })?;
18845 pmap.finish();
18846 Ok(PerlValue::integer(
18847 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
18848 ))
18849 }
18850
18851 pub(crate) fn eval_pwatch_expr(
18853 &mut self,
18854 path: &Expr,
18855 callback: &Expr,
18856 line: usize,
18857 ) -> Result<PerlValue, FlowOrError> {
18858 let pattern_s = self.eval_expr(path)?.to_string();
18859 let cb_val = self.eval_expr(callback)?;
18860 let sub = if let Some(s) = cb_val.as_code_ref() {
18861 s
18862 } else {
18863 return Err(PerlError::runtime(
18864 "pwatch: second argument must be a code reference",
18865 line,
18866 )
18867 .into());
18868 };
18869 let subs = self.subs.clone();
18870 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18871 crate::pwatch::run_pwatch(
18872 &pattern_s,
18873 sub,
18874 subs,
18875 scope_capture,
18876 atomic_arrays,
18877 atomic_hashes,
18878 line,
18879 )
18880 .map_err(FlowOrError::Error)
18881 }
18882
18883 fn interpolate_replacement_string(&self, replacement: &str) -> String {
18885 let mut out = String::with_capacity(replacement.len());
18886 let chars: Vec<char> = replacement.chars().collect();
18887 let mut i = 0;
18888 while i < chars.len() {
18889 if chars[i] == '\\' && i + 1 < chars.len() {
18890 out.push(chars[i]);
18891 out.push(chars[i + 1]);
18892 i += 2;
18893 continue;
18894 }
18895 if chars[i] == '$' && i + 1 < chars.len() {
18896 let start = i;
18897 i += 1;
18898 if chars[i].is_ascii_digit() {
18899 out.push('$');
18900 while i < chars.len() && chars[i].is_ascii_digit() {
18901 out.push(chars[i]);
18902 i += 1;
18903 }
18904 continue;
18905 }
18906 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
18907 out.push('$');
18908 out.push(chars[i]);
18909 i += 1;
18910 continue;
18911 }
18912 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
18913 out.push('$');
18914 continue;
18915 }
18916 let mut name = String::new();
18917 if chars[i] == '{' {
18918 i += 1;
18919 while i < chars.len() && chars[i] != '}' {
18920 name.push(chars[i]);
18921 i += 1;
18922 }
18923 if i < chars.len() {
18924 i += 1;
18925 }
18926 } else {
18927 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18928 name.push(chars[i]);
18929 i += 1;
18930 }
18931 }
18932 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
18933 let val = self.scope.get_scalar(&name);
18934 out.push_str(&val.to_string());
18935 } else if !name.is_empty() {
18936 out.push_str(&replacement[start..i]);
18937 } else {
18938 out.push('$');
18939 }
18940 continue;
18941 }
18942 out.push(chars[i]);
18943 i += 1;
18944 }
18945 out
18946 }
18947
18948 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
18950 let mut out = String::with_capacity(pattern.len());
18951 let chars: Vec<char> = pattern.chars().collect();
18952 let mut i = 0;
18953 while i < chars.len() {
18954 if chars[i] == '\\' && i + 1 < chars.len() {
18955 out.push(chars[i]);
18957 out.push(chars[i + 1]);
18958 i += 2;
18959 continue;
18960 }
18961 if chars[i] == '$' && i + 1 < chars.len() {
18962 i += 1;
18963 if i >= chars.len()
18965 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
18966 {
18967 out.push('$');
18968 continue;
18969 }
18970 let mut name = String::new();
18971 if chars[i] == '{' {
18972 i += 1;
18973 while i < chars.len() && chars[i] != '}' {
18974 name.push(chars[i]);
18975 i += 1;
18976 }
18977 if i < chars.len() {
18978 i += 1;
18979 } } else {
18981 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18982 name.push(chars[i]);
18983 i += 1;
18984 }
18985 }
18986 if !name.is_empty() {
18987 let val = self.scope.get_scalar(&name);
18988 out.push_str(&val.to_string());
18989 } else {
18990 out.push('$');
18991 }
18992 continue;
18993 }
18994 out.push(chars[i]);
18995 i += 1;
18996 }
18997 out
18998 }
18999
19000 pub(crate) fn compile_regex(
19001 &mut self,
19002 pattern: &str,
19003 flags: &str,
19004 line: usize,
19005 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
19006 let pattern = if pattern.contains('$') || pattern.contains('@') {
19008 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
19009 } else {
19010 std::borrow::Cow::Borrowed(pattern)
19011 };
19012 let pattern = pattern.as_ref();
19013 let multiline = self.multiline_match;
19016 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
19017 if lp == pattern && lf == flags && *lm == multiline {
19018 return Ok(lr.clone());
19019 }
19020 }
19021 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
19023 if let Some(cached) = self.regex_cache.get(&key) {
19024 self.regex_last = Some((
19025 pattern.to_string(),
19026 flags.to_string(),
19027 multiline,
19028 cached.clone(),
19029 ));
19030 return Ok(cached.clone());
19031 }
19032 let expanded = expand_perl_regex_quotemeta(pattern);
19033 let expanded = expand_perl_regex_octal_escapes(&expanded);
19034 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
19035 let mut re_str = String::new();
19036 if flags.contains('i') {
19037 re_str.push_str("(?i)");
19038 }
19039 if flags.contains('s') {
19040 re_str.push_str("(?s)");
19041 }
19042 if flags.contains('m') {
19043 re_str.push_str("(?m)");
19044 }
19045 if flags.contains('x') {
19046 re_str.push_str("(?x)");
19047 }
19048 if multiline {
19050 re_str.push_str("(?s)");
19051 }
19052 re_str.push_str(&expanded);
19053 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
19054 FlowOrError::Error(PerlError::runtime(
19055 format!("Invalid regex /{}/: {}", pattern, e),
19056 line,
19057 ))
19058 })?;
19059 let arc = re;
19060 self.regex_last = Some((
19061 pattern.to_string(),
19062 flags.to_string(),
19063 multiline,
19064 arc.clone(),
19065 ));
19066 self.regex_cache.insert(key, arc.clone());
19067 Ok(arc)
19068 }
19069
19070 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
19072 if self.last_readline_handle.is_empty() {
19073 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
19074 }
19075 let n = *self
19076 .handle_line_numbers
19077 .get(&self.last_readline_handle)
19078 .unwrap_or(&0);
19079 if n <= 0 {
19080 return None;
19081 }
19082 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
19083 {
19084 return Some(("<>".to_string(), n));
19085 }
19086 if self.last_readline_handle == "STDIN" {
19087 return Some((self.last_stdin_die_bracket.clone(), n));
19088 }
19089 Some((format!("<{}>", self.last_readline_handle), n))
19090 }
19091
19092 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
19094 let mut s = format!(" at {} line {}", self.file, source_line);
19095 if let Some((bracket, n)) = self.die_warn_io_annotation() {
19096 s.push_str(&format!(", {} line {}.", bracket, n));
19097 } else {
19098 s.push('.');
19099 }
19100 s
19101 }
19102
19103 pub fn process_line(
19108 &mut self,
19109 line_str: &str,
19110 program: &Program,
19111 is_last_input_line: bool,
19112 ) -> PerlResult<Option<String>> {
19113 self.line_mode_eof_pending = is_last_input_line;
19114 let result: PerlResult<Option<String>> = (|| {
19115 self.line_number += 1;
19116 self.scope
19117 .set_topic(PerlValue::string(line_str.to_string()));
19118
19119 if self.auto_split {
19120 let sep = self.field_separator.as_deref().unwrap_or(" ");
19121 let re = regex::Regex::new(sep).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
19122 let fields: Vec<PerlValue> = re
19123 .split(line_str)
19124 .map(|s| PerlValue::string(s.to_string()))
19125 .collect();
19126 self.scope.set_array("F", fields)?;
19127 }
19128
19129 for stmt in &program.statements {
19130 match &stmt.kind {
19131 StmtKind::SubDecl { .. }
19132 | StmtKind::Begin(_)
19133 | StmtKind::UnitCheck(_)
19134 | StmtKind::Check(_)
19135 | StmtKind::Init(_)
19136 | StmtKind::End(_) => continue,
19137 _ => match self.exec_statement(stmt) {
19138 Ok(_) => {}
19139 Err(FlowOrError::Error(e)) => return Err(e),
19140 Err(FlowOrError::Flow(_)) => {}
19141 },
19142 }
19143 }
19144
19145 let mut out = self.scope.get_scalar("_").to_string();
19147 out.push_str(&self.ors);
19148 Ok(Some(out))
19149 })();
19150 self.line_mode_eof_pending = false;
19151 result
19152 }
19153}
19154
19155fn par_walk_invoke_entry(
19156 path: &Path,
19157 sub: &Arc<PerlSub>,
19158 subs: &HashMap<String, Arc<PerlSub>>,
19159 scope_capture: &[(String, PerlValue)],
19160 atomic_arrays: &[(String, crate::scope::AtomicArray)],
19161 atomic_hashes: &[(String, crate::scope::AtomicHash)],
19162 line: usize,
19163) -> Result<(), FlowOrError> {
19164 let s = path.to_string_lossy().into_owned();
19165 let mut local_interp = Interpreter::new();
19166 local_interp.subs = subs.clone();
19167 local_interp.scope.restore_capture(scope_capture);
19168 local_interp
19169 .scope
19170 .restore_atomics(atomic_arrays, atomic_hashes);
19171 local_interp.enable_parallel_guard();
19172 local_interp.scope.set_topic(PerlValue::string(s));
19173 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
19174 Ok(())
19175}
19176
19177fn par_walk_recursive(
19178 path: &Path,
19179 sub: &Arc<PerlSub>,
19180 subs: &HashMap<String, Arc<PerlSub>>,
19181 scope_capture: &[(String, PerlValue)],
19182 atomic_arrays: &[(String, crate::scope::AtomicArray)],
19183 atomic_hashes: &[(String, crate::scope::AtomicHash)],
19184 line: usize,
19185) -> Result<(), FlowOrError> {
19186 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
19187 return par_walk_invoke_entry(
19188 path,
19189 sub,
19190 subs,
19191 scope_capture,
19192 atomic_arrays,
19193 atomic_hashes,
19194 line,
19195 );
19196 }
19197 if !path.is_dir() {
19198 return Ok(());
19199 }
19200 par_walk_invoke_entry(
19201 path,
19202 sub,
19203 subs,
19204 scope_capture,
19205 atomic_arrays,
19206 atomic_hashes,
19207 line,
19208 )?;
19209 let read = match std::fs::read_dir(path) {
19210 Ok(r) => r,
19211 Err(_) => return Ok(()),
19212 };
19213 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
19214 entries.par_iter().try_for_each(|e| {
19215 par_walk_recursive(
19216 &e.path(),
19217 sub,
19218 subs,
19219 scope_capture,
19220 atomic_arrays,
19221 atomic_hashes,
19222 line,
19223 )
19224 })?;
19225 Ok(())
19226}
19227
19228pub(crate) fn perl_sprintf_format_with<F>(
19230 fmt: &str,
19231 args: &[PerlValue],
19232 mut string_for_s: F,
19233) -> Result<String, FlowOrError>
19234where
19235 F: FnMut(&PerlValue) -> Result<String, FlowOrError>,
19236{
19237 let mut result = String::new();
19238 let mut arg_idx = 0;
19239 let chars: Vec<char> = fmt.chars().collect();
19240 let mut i = 0;
19241
19242 while i < chars.len() {
19243 if chars[i] == '%' {
19244 i += 1;
19245 if i >= chars.len() {
19246 break;
19247 }
19248 if chars[i] == '%' {
19249 result.push('%');
19250 i += 1;
19251 continue;
19252 }
19253
19254 let mut flags = String::new();
19256 while i < chars.len() && "-+ #0".contains(chars[i]) {
19257 flags.push(chars[i]);
19258 i += 1;
19259 }
19260 let mut width = String::new();
19261 while i < chars.len() && chars[i].is_ascii_digit() {
19262 width.push(chars[i]);
19263 i += 1;
19264 }
19265 let mut precision = String::new();
19266 if i < chars.len() && chars[i] == '.' {
19267 i += 1;
19268 while i < chars.len() && chars[i].is_ascii_digit() {
19269 precision.push(chars[i]);
19270 i += 1;
19271 }
19272 }
19273 if i >= chars.len() {
19274 break;
19275 }
19276 let spec = chars[i];
19277 i += 1;
19278
19279 let arg = args.get(arg_idx).cloned().unwrap_or(PerlValue::UNDEF);
19280 arg_idx += 1;
19281
19282 let w: usize = width.parse().unwrap_or(0);
19283 let p: usize = precision.parse().unwrap_or(6);
19284
19285 let zero_pad = flags.contains('0') && !flags.contains('-');
19286 let left_align = flags.contains('-');
19287 let formatted = match spec {
19288 'd' | 'i' => {
19289 if zero_pad {
19290 format!("{:0width$}", arg.to_int(), width = w)
19291 } else if left_align {
19292 format!("{:<width$}", arg.to_int(), width = w)
19293 } else {
19294 format!("{:width$}", arg.to_int(), width = w)
19295 }
19296 }
19297 'u' => {
19298 if zero_pad {
19299 format!("{:0width$}", arg.to_int() as u64, width = w)
19300 } else {
19301 format!("{:width$}", arg.to_int() as u64, width = w)
19302 }
19303 }
19304 'f' => format!("{:width$.prec$}", arg.to_number(), width = w, prec = p),
19305 'e' => format!("{:width$.prec$e}", arg.to_number(), width = w, prec = p),
19306 'g' => {
19307 let n = arg.to_number();
19308 if n.abs() >= 1e-4 && n.abs() < 1e15 {
19309 format!("{:width$.prec$}", n, width = w, prec = p)
19310 } else {
19311 format!("{:width$.prec$e}", n, width = w, prec = p)
19312 }
19313 }
19314 's' => {
19315 let s = string_for_s(&arg)?;
19316 if !precision.is_empty() {
19317 let truncated: String = s.chars().take(p).collect();
19318 if flags.contains('-') {
19319 format!("{:<width$}", truncated, width = w)
19320 } else {
19321 format!("{:>width$}", truncated, width = w)
19322 }
19323 } else if flags.contains('-') {
19324 format!("{:<width$}", s, width = w)
19325 } else {
19326 format!("{:>width$}", s, width = w)
19327 }
19328 }
19329 'x' => {
19330 let v = arg.to_int();
19331 if zero_pad && w > 0 {
19332 format!("{:0width$x}", v, width = w)
19333 } else if left_align {
19334 format!("{:<width$x}", v, width = w)
19335 } else if w > 0 {
19336 format!("{:width$x}", v, width = w)
19337 } else {
19338 format!("{:x}", v)
19339 }
19340 }
19341 'X' => {
19342 let v = arg.to_int();
19343 if zero_pad && w > 0 {
19344 format!("{:0width$X}", v, width = w)
19345 } else if left_align {
19346 format!("{:<width$X}", v, width = w)
19347 } else if w > 0 {
19348 format!("{:width$X}", v, width = w)
19349 } else {
19350 format!("{:X}", v)
19351 }
19352 }
19353 'o' => {
19354 let v = arg.to_int();
19355 if zero_pad && w > 0 {
19356 format!("{:0width$o}", v, width = w)
19357 } else if left_align {
19358 format!("{:<width$o}", v, width = w)
19359 } else if w > 0 {
19360 format!("{:width$o}", v, width = w)
19361 } else {
19362 format!("{:o}", v)
19363 }
19364 }
19365 'b' => {
19366 let v = arg.to_int();
19367 if zero_pad && w > 0 {
19368 format!("{:0width$b}", v, width = w)
19369 } else if left_align {
19370 format!("{:<width$b}", v, width = w)
19371 } else if w > 0 {
19372 format!("{:width$b}", v, width = w)
19373 } else {
19374 format!("{:b}", v)
19375 }
19376 }
19377 'c' => char::from_u32(arg.to_int() as u32)
19378 .map(|c| c.to_string())
19379 .unwrap_or_default(),
19380 _ => arg.to_string(),
19381 };
19382
19383 result.push_str(&formatted);
19384 } else {
19385 result.push(chars[i]);
19386 i += 1;
19387 }
19388 }
19389 Ok(result)
19390}
19391
19392#[cfg(test)]
19393mod regex_expand_tests {
19394 use super::Interpreter;
19395
19396 #[test]
19397 fn compile_regex_quotemeta_qe_matches_literal() {
19398 let mut i = Interpreter::new();
19399 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
19400 assert!(re.is_match("a.c"));
19401 assert!(!re.is_match("abc"));
19402 }
19403
19404 #[test]
19407 fn compile_regex_char_class_leading_close_bracket_is_literal() {
19408 let mut i = Interpreter::new();
19409 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
19410 assert!(re.is_match("$"));
19411 assert!(re.is_match("]"));
19412 assert!(!re.is_match("x"));
19413 }
19414}
19415
19416#[cfg(test)]
19417mod special_scalar_name_tests {
19418 use super::Interpreter;
19419
19420 #[test]
19421 fn special_scalar_name_for_get_matches_magic_globals() {
19422 assert!(Interpreter::is_special_scalar_name_for_get("0"));
19423 assert!(Interpreter::is_special_scalar_name_for_get("!"));
19424 assert!(Interpreter::is_special_scalar_name_for_get("^W"));
19425 assert!(Interpreter::is_special_scalar_name_for_get("^O"));
19426 assert!(Interpreter::is_special_scalar_name_for_get("^MATCH"));
19427 assert!(Interpreter::is_special_scalar_name_for_get("<"));
19428 assert!(Interpreter::is_special_scalar_name_for_get("?"));
19429 assert!(Interpreter::is_special_scalar_name_for_get("|"));
19430 assert!(Interpreter::is_special_scalar_name_for_get("^UNICODE"));
19431 assert!(Interpreter::is_special_scalar_name_for_get("\""));
19432 assert!(!Interpreter::is_special_scalar_name_for_get("foo"));
19433 assert!(!Interpreter::is_special_scalar_name_for_get("plainvar"));
19434 }
19435
19436 #[test]
19437 fn special_scalar_name_for_set_matches_set_special_var_arms() {
19438 assert!(Interpreter::is_special_scalar_name_for_set("0"));
19439 assert!(Interpreter::is_special_scalar_name_for_set("^D"));
19440 assert!(Interpreter::is_special_scalar_name_for_set("^H"));
19441 assert!(Interpreter::is_special_scalar_name_for_set("^WARNING_BITS"));
19442 assert!(Interpreter::is_special_scalar_name_for_set("ARGV"));
19443 assert!(Interpreter::is_special_scalar_name_for_set("|"));
19444 assert!(Interpreter::is_special_scalar_name_for_set("?"));
19445 assert!(Interpreter::is_special_scalar_name_for_set("^UNICODE"));
19446 assert!(Interpreter::is_special_scalar_name_for_set("."));
19447 assert!(!Interpreter::is_special_scalar_name_for_set("foo"));
19448 assert!(!Interpreter::is_special_scalar_name_for_set("__PACKAGE__"));
19449 }
19450
19451 #[test]
19452 fn caret_and_id_specials_roundtrip_get() {
19453 let i = Interpreter::new();
19454 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
19455 assert_eq!(
19456 i.get_special_var("^V").to_string(),
19457 format!("v{}", env!("CARGO_PKG_VERSION"))
19458 );
19459 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
19460 assert!(i.get_special_var("^T").to_int() >= 0);
19461 #[cfg(unix)]
19462 {
19463 assert!(i.get_special_var("<").to_int() >= 0);
19464 }
19465 }
19466
19467 #[test]
19468 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
19469 let mut i = Interpreter::new();
19470 i.last_readline_handle.clear();
19471 i.line_number = 3;
19472 i.prepare_flip_flop_vm_slots(1);
19473 assert_eq!(
19474 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
19475 1
19476 );
19477 assert!(i.flip_flop_active[0]);
19478 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
19479 assert_eq!(
19481 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
19482 1
19483 );
19484 assert!(i.flip_flop_active[0]);
19485 }
19486
19487 #[test]
19488 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
19489 let mut i = Interpreter::new();
19490 i.last_readline_handle.clear();
19491 i.line_number = 2;
19492 i.prepare_flip_flop_vm_slots(1);
19493 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
19494 assert!(i.flip_flop_active[0]);
19495 i.line_number = 3;
19496 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
19497 assert!(!i.flip_flop_active[0]);
19498 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
19499 }
19500}