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 = VMHelper::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 local_interp.scope.set_sort_pair(a, b);
120 match local_interp.exec_block(block) {
121 Ok(val) => val,
122 Err(_) => PerlValue::UNDEF,
123 }
124}
125
126pub(crate) fn preduce_init_fold_identity(init: &PerlValue) -> PerlValue {
129 if let Some(m) = init.as_hash_map() {
130 return PerlValue::hash(m.clone());
131 }
132 if let Some(r) = init.as_hash_ref() {
133 return PerlValue::hash_ref(Arc::new(RwLock::new(r.read().clone())));
134 }
135 init.clone()
136}
137
138pub(crate) fn fold_preduce_init_step(
139 subs: &HashMap<String, Arc<PerlSub>>,
140 scope_capture: &[(String, PerlValue)],
141 block: &Block,
142 acc: PerlValue,
143 item: PerlValue,
144) -> PerlValue {
145 let mut local_interp = VMHelper::new();
146 local_interp.subs = subs.clone();
147 local_interp.scope.restore_capture(scope_capture);
148 local_interp.enable_parallel_guard();
149 local_interp
150 .scope
151 .declare_array("_", vec![acc.clone(), item.clone()]);
152 local_interp.scope.set_sort_pair(acc, item);
153 match local_interp.exec_block(block) {
154 Ok(val) => val,
155 Err(_) => PerlValue::UNDEF,
156 }
157}
158
159pub const FEAT_SAY: u64 = 1 << 0;
161pub const FEAT_STATE: u64 = 1 << 1;
163pub const FEAT_SWITCH: u64 = 1 << 2;
165pub const FEAT_UNICODE_STRINGS: u64 = 1 << 3;
167
168#[derive(Debug)]
170pub(crate) enum Flow {
171 Return(PerlValue),
172 Last(Option<String>),
173 Next(Option<String>),
174 Redo(Option<String>),
175 Yield(PerlValue),
176 GotoSub(String),
178}
179
180pub(crate) type ExecResult = Result<PerlValue, FlowOrError>;
181
182#[derive(Debug)]
183pub(crate) enum FlowOrError {
184 Flow(Flow),
185 Error(PerlError),
186}
187
188impl From<PerlError> for FlowOrError {
189 fn from(e: PerlError) -> Self {
190 FlowOrError::Error(e)
191 }
192}
193
194impl From<Flow> for FlowOrError {
195 fn from(f: Flow) -> Self {
196 FlowOrError::Flow(f)
197 }
198}
199
200enum PatternBinding {
202 Scalar(String, PerlValue),
203 Array(String, Vec<PerlValue>),
204}
205
206pub fn perl_bracket_version() -> f64 {
209 const PERL_EMUL_MINOR: u32 = 38;
210 const PERL_EMUL_PATCH: u32 = 0;
211 5.0 + (PERL_EMUL_MINOR as f64) / 1000.0 + (PERL_EMUL_PATCH as f64) / 1_000_000.0
212}
213
214#[inline]
216fn fast_rng_seed() -> u64 {
217 let local: u8 = 0;
218 let addr = &local as *const u8 as u64;
219 (std::process::id() as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ addr
220}
221
222fn cached_executable_path() -> String {
224 static CACHED: OnceLock<String> = OnceLock::new();
225 CACHED
226 .get_or_init(|| {
227 std::env::current_exe()
228 .map(|p| p.to_string_lossy().into_owned())
229 .unwrap_or_else(|_| "stryke".to_string())
230 })
231 .clone()
232}
233
234fn build_term_hash() -> IndexMap<String, PerlValue> {
235 let mut m = IndexMap::new();
236 m.insert(
237 "TERM".into(),
238 PerlValue::string(std::env::var("TERM").unwrap_or_default()),
239 );
240 m.insert(
241 "COLORTERM".into(),
242 PerlValue::string(std::env::var("COLORTERM").unwrap_or_default()),
243 );
244
245 let (rows, cols) = term_size();
246 m.insert("rows".into(), PerlValue::integer(rows));
247 m.insert("cols".into(), PerlValue::integer(cols));
248
249 #[cfg(unix)]
250 let is_tty = unsafe { libc::isatty(1) != 0 };
251 #[cfg(not(unix))]
252 let is_tty = false;
253 m.insert(
254 "is_tty".into(),
255 PerlValue::integer(if is_tty { 1 } else { 0 }),
256 );
257
258 m
259}
260
261fn term_size() -> (i64, i64) {
262 #[cfg(unix)]
263 {
264 unsafe {
265 let mut ws: libc::winsize = std::mem::zeroed();
266 if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 {
267 return (ws.ws_row as i64, ws.ws_col as i64);
268 }
269 }
270 }
271 let rows = std::env::var("LINES")
272 .ok()
273 .and_then(|s| s.parse().ok())
274 .unwrap_or(24);
275 let cols = std::env::var("COLUMNS")
276 .ok()
277 .and_then(|s| s.parse().ok())
278 .unwrap_or(80);
279 (rows, cols)
280}
281
282#[cfg(unix)]
283fn build_uname_hash() -> IndexMap<String, PerlValue> {
284 fn uts_field(slice: &[libc::c_char]) -> String {
285 let n = slice.iter().take_while(|&&c| c != 0).count();
286 let bytes: Vec<u8> = slice[..n].iter().map(|&c| c as u8).collect();
287 String::from_utf8_lossy(&bytes).into_owned()
288 }
289 let mut m = IndexMap::new();
290 let mut uts: libc::utsname = unsafe { std::mem::zeroed() };
291 if unsafe { libc::uname(&mut uts) } == 0 {
292 m.insert(
293 "sysname".into(),
294 PerlValue::string(uts_field(uts.sysname.as_slice())),
295 );
296 m.insert(
297 "nodename".into(),
298 PerlValue::string(uts_field(uts.nodename.as_slice())),
299 );
300 m.insert(
301 "release".into(),
302 PerlValue::string(uts_field(uts.release.as_slice())),
303 );
304 m.insert(
305 "version".into(),
306 PerlValue::string(uts_field(uts.version.as_slice())),
307 );
308 m.insert(
309 "machine".into(),
310 PerlValue::string(uts_field(uts.machine.as_slice())),
311 );
312 }
313 m
314}
315
316#[cfg(unix)]
317fn build_limits_hash() -> IndexMap<String, PerlValue> {
318 use libc::{getrlimit, rlimit, RLIM_INFINITY};
319 #[cfg(target_os = "linux")]
320 type RlimitResource = libc::__rlimit_resource_t;
321 #[cfg(not(target_os = "linux"))]
322 type RlimitResource = libc::c_int;
323 fn get_limit(resource: RlimitResource) -> (i64, i64) {
324 let mut rlim = rlimit {
325 rlim_cur: 0,
326 rlim_max: 0,
327 };
328 if unsafe { getrlimit(resource, &mut rlim) } == 0 {
329 let cur = if rlim.rlim_cur == RLIM_INFINITY {
330 -1
331 } else {
332 rlim.rlim_cur as i64
333 };
334 let max = if rlim.rlim_max == RLIM_INFINITY {
335 -1
336 } else {
337 rlim.rlim_max as i64
338 };
339 (cur, max)
340 } else {
341 (-1, -1)
342 }
343 }
344 let mut m = IndexMap::new();
345 let (cur, max) = get_limit(libc::RLIMIT_NOFILE);
346 m.insert("nofile".into(), PerlValue::integer(cur));
347 m.insert("nofile_max".into(), PerlValue::integer(max));
348 let (cur, max) = get_limit(libc::RLIMIT_STACK);
349 m.insert("stack".into(), PerlValue::integer(cur));
350 m.insert("stack_max".into(), PerlValue::integer(max));
351 let (cur, max) = get_limit(libc::RLIMIT_AS);
352 m.insert("as".into(), PerlValue::integer(cur));
353 m.insert("as_max".into(), PerlValue::integer(max));
354 let (cur, max) = get_limit(libc::RLIMIT_DATA);
355 m.insert("data".into(), PerlValue::integer(cur));
356 m.insert("data_max".into(), PerlValue::integer(max));
357 let (cur, max) = get_limit(libc::RLIMIT_FSIZE);
358 m.insert("fsize".into(), PerlValue::integer(cur));
359 m.insert("fsize_max".into(), PerlValue::integer(max));
360 let (cur, max) = get_limit(libc::RLIMIT_CORE);
361 m.insert("core".into(), PerlValue::integer(cur));
362 m.insert("core_max".into(), PerlValue::integer(max));
363 let (cur, max) = get_limit(libc::RLIMIT_CPU);
364 m.insert("cpu".into(), PerlValue::integer(cur));
365 m.insert("cpu_max".into(), PerlValue::integer(max));
366 let (cur, max) = get_limit(libc::RLIMIT_NPROC);
367 m.insert("nproc".into(), PerlValue::integer(cur));
368 m.insert("nproc_max".into(), PerlValue::integer(max));
369 #[cfg(target_os = "linux")]
370 {
371 let (cur, max) = get_limit(libc::RLIMIT_MEMLOCK);
372 m.insert("memlock".into(), PerlValue::integer(cur));
373 m.insert("memlock_max".into(), PerlValue::integer(max));
374 }
375 m
376}
377
378#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
380pub(crate) enum WantarrayCtx {
381 #[default]
382 Scalar,
383 List,
384 Void,
385}
386
387impl WantarrayCtx {
388 #[inline]
389 pub(crate) fn from_byte(b: u8) -> Self {
390 match b {
391 1 => Self::List,
392 2 => Self::Void,
393 _ => Self::Scalar,
394 }
395 }
396
397 #[inline]
398 pub(crate) fn as_byte(self) -> u8 {
399 match self {
400 Self::Scalar => 0,
401 Self::List => 1,
402 Self::Void => 2,
403 }
404 }
405}
406
407#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
409pub(crate) enum LogLevelFilter {
410 Trace,
411 Debug,
412 Info,
413 Warn,
414 Error,
415}
416
417impl LogLevelFilter {
418 pub(crate) fn parse(s: &str) -> Option<Self> {
419 match s.trim().to_ascii_lowercase().as_str() {
420 "trace" => Some(Self::Trace),
421 "debug" => Some(Self::Debug),
422 "info" => Some(Self::Info),
423 "warn" | "warning" => Some(Self::Warn),
424 "error" => Some(Self::Error),
425 _ => None,
426 }
427 }
428
429 pub(crate) fn as_str(self) -> &'static str {
430 match self {
431 Self::Trace => "trace",
432 Self::Debug => "debug",
433 Self::Info => "info",
434 Self::Warn => "warn",
435 Self::Error => "error",
436 }
437 }
438}
439
440fn arrow_deref_array_assign_rhs_list_ctx(index: &Expr) -> bool {
442 match &index.kind {
443 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => true,
444 ExprKind::QW(ws) => ws.len() > 1,
445 ExprKind::List(el) => {
446 if el.len() > 1 {
447 true
448 } else if el.len() == 1 {
449 arrow_deref_array_assign_rhs_list_ctx(&el[0])
450 } else {
451 false
452 }
453 }
454 _ => false,
455 }
456}
457
458pub(crate) fn assign_rhs_wantarray(target: &Expr) -> WantarrayCtx {
461 match &target.kind {
462 ExprKind::ArrayVar(_) | ExprKind::HashVar(_) => WantarrayCtx::List,
463 ExprKind::ScalarVar(_) | ExprKind::ArrayElement { .. } | ExprKind::HashElement { .. } => {
464 WantarrayCtx::Scalar
465 }
466 ExprKind::Deref { kind, .. } => match kind {
467 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
468 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
469 },
470 ExprKind::ArrowDeref {
471 index,
472 kind: DerefKind::Array,
473 ..
474 } => {
475 if arrow_deref_array_assign_rhs_list_ctx(index) {
476 WantarrayCtx::List
477 } else {
478 WantarrayCtx::Scalar
479 }
480 }
481 ExprKind::ArrowDeref {
482 kind: DerefKind::Hash,
483 ..
484 }
485 | ExprKind::ArrowDeref {
486 kind: DerefKind::Call,
487 ..
488 } => WantarrayCtx::Scalar,
489 ExprKind::HashSliceDeref { .. }
490 | ExprKind::HashSlice { .. }
491 | ExprKind::HashKvSlice { .. } => 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 VMHelper {
571 pub scope: Scope,
572 pub(crate) subs: HashMap<String, Arc<PerlSub>>,
573 pub(crate) intercepts: Vec<crate::aop::Intercept>,
575 pub(crate) next_intercept_id: u32,
577 pub(crate) intercept_ctx_stack: Vec<crate::aop::InterceptCtx>,
579 pub(crate) intercept_active_names: Vec<String>,
583 pub(crate) file: String,
584 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
586 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
587 pub ofs: String,
589 pub ors: String,
591 pub irs: Option<String>,
594 pub errno: String,
596 pub errno_code: i32,
598 pub eval_error: String,
600 pub eval_error_code: i32,
602 pub eval_error_value: Option<PerlValue>,
604 pub argv: Vec<String>,
606 pub env: IndexMap<String, PerlValue>,
608 pub env_materialized: bool,
610 pub program_name: String,
612 pub line_number: i64,
614 pub last_readline_handle: String,
616 pub(crate) last_stdin_die_bracket: String,
618 pub handle_line_numbers: HashMap<String, i64>,
620 pub(crate) flip_flop_active: Vec<bool>,
624 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
627 pub(crate) flip_flop_sequence: Vec<i64>,
631 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
635 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
637 pub sigint_pending_caret: Cell<bool>,
639 pub auto_split: bool,
641 pub field_separator: Option<String>,
643 begin_blocks: Vec<Block>,
645 unit_check_blocks: Vec<Block>,
647 check_blocks: Vec<Block>,
649 init_blocks: Vec<Block>,
651 end_blocks: Vec<Block>,
653 pub warnings: bool,
655 pub output_autoflush: bool,
657 pub default_print_handle: String,
659 pub suppress_stdout: bool,
661 pub test_pass_count: std::sync::atomic::AtomicUsize,
668 pub test_fail_count: std::sync::atomic::AtomicUsize,
669 pub test_skip_count: std::sync::atomic::AtomicUsize,
670 pub test_run_failed: std::sync::atomic::AtomicBool,
676 pub child_exit_status: i64,
678 pub last_match: String,
680 pub prematch: String,
682 pub postmatch: String,
684 pub last_paren_match: String,
686 pub list_separator: String,
688 pub script_start_time: i64,
690 pub compile_hints: i64,
692 pub warning_bits: i64,
694 pub global_phase: String,
696 pub subscript_sep: String,
698 pub inplace_edit: String,
701 pub debug_flags: i64,
703 pub perl_debug_flags: i64,
705 pub eval_nesting: u32,
707 pub argv_current_file: String,
709 pub(crate) diamond_next_idx: usize,
711 pub(crate) diamond_reader: Option<BufReader<File>>,
713 pub strict_refs: bool,
715 pub strict_subs: bool,
716 pub strict_vars: bool,
717 pub utf8_pragma: bool,
719 pub open_pragma_utf8: bool,
721 pub feature_bits: u64,
723 pub num_threads: usize,
725 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
727 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
730 regex_match_memo: Option<RegexMatchMemo>,
739 regex_capture_scope_fresh: bool,
743 pub(crate) regex_pos: HashMap<String, Option<usize>>,
745 pub(crate) state_vars: HashMap<String, PerlValue>,
747 pub(crate) state_bindings_stack: Vec<Vec<(String, String)>>,
749 pub(crate) rand_rng: StdRng,
751 pub(crate) dir_handles: HashMap<String, DirHandleState>,
753 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
755 pub(crate) pipe_children: HashMap<String, Child>,
757 pub(crate) socket_handles: HashMap<String, PerlSocket>,
759 pub(crate) wantarray_kind: WantarrayCtx,
761 pub struct_defs: HashMap<String, Arc<StructDef>>,
763 pub enum_defs: HashMap<String, Arc<EnumDef>>,
765 pub class_defs: HashMap<String, Arc<ClassDef>>,
767 pub trait_defs: HashMap<String, Arc<TraitDef>>,
769 pub profiler: Option<Profiler>,
772 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
774 pub(crate) virtual_modules: HashMap<String, String>,
776 pub(crate) tied_hashes: HashMap<String, PerlValue>,
778 pub(crate) tied_scalars: HashMap<String, PerlValue>,
780 pub(crate) tied_arrays: HashMap<String, PerlValue>,
782 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
784 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
786 pub(crate) special_caret_scalars: HashMap<String, PerlValue>,
788 pub format_page_number: i64,
790 pub format_lines_per_page: i64,
792 pub format_lines_left: i64,
794 pub format_line_break_chars: String,
796 pub format_top_name: String,
798 pub accumulator_format: String,
800 pub max_system_fd: i64,
802 pub emergency_memory: String,
804 pub last_subpattern_name: String,
806 pub inc_hook_index: i64,
808 pub multiline_match: bool,
810 pub executable_path: String,
812 pub formfeed_string: String,
814 pub(crate) glob_handle_alias: HashMap<String, String>,
816 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
818 pub(crate) special_var_restore_frames: Vec<Vec<(String, PerlValue)>>,
823 pub(crate) reflection_hashes_ready: bool,
827 pub(crate) english_enabled: bool,
828 pub(crate) english_no_match_vars: bool,
830 pub(crate) english_match_vars_ever_enabled: bool,
834 english_lexical_scalars: Vec<HashSet<String>>,
836 our_lexical_scalars: Vec<HashSet<String>>,
838 pub vm_jit_enabled: bool,
841 pub disasm_bytecode: bool,
843 pub cached_chunk: Option<crate::bytecode::Chunk>,
847 pub cache_script_path: Option<std::path::PathBuf>,
849 pub(crate) in_generator: bool,
851 pub line_mode_skip_main: bool,
853 pub line_mode_chunk: Option<crate::bytecode::Chunk>,
856 pub(crate) line_mode_eof_pending: bool,
860 pub line_mode_stdin_pending: VecDeque<String>,
863 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
865 pub(crate) log_level_override: Option<LogLevelFilter>,
867 pub(crate) current_sub_stack: Vec<Arc<PerlSub>>,
870 pub debugger: Option<crate::debugger::Debugger>,
872 pub(crate) debug_call_stack: Vec<(String, usize)>,
874}
875
876#[derive(Debug, Clone, Default)]
878pub struct ReplCompletionSnapshot {
879 pub subs: Vec<String>,
880 pub blessed_scalars: HashMap<String, String>,
881 pub isa_for_class: HashMap<String, Vec<String>>,
882}
883
884impl ReplCompletionSnapshot {
885 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
887 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
888 let mro = linearize_c3(class, &parents, 0);
889 let mut names = HashSet::new();
890 for pkg in &mro {
891 if pkg == "UNIVERSAL" {
892 continue;
893 }
894 let prefix = format!("{}::", pkg);
895 for k in &self.subs {
896 if k.starts_with(&prefix) {
897 let rest = &k[prefix.len()..];
898 if !rest.contains("::") {
899 names.insert(rest.to_string());
900 }
901 }
902 }
903 }
904 for k in &self.subs {
905 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
906 if !rest.contains("::") {
907 names.insert(rest.to_string());
908 }
909 }
910 }
911 let mut v: Vec<String> = names.into_iter().collect();
912 v.sort();
913 v
914 }
915}
916
917fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
918 let left = left.trim_end();
919 if left.is_empty() {
920 return None;
921 }
922 if let Some(i) = left.rfind('$') {
923 let name = left[i + 1..].trim();
924 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
925 return state.blessed_scalars.get(name).cloned();
926 }
927 }
928 let tok = left.split_whitespace().last()?;
929 if tok.contains("::") {
930 return Some(tok.to_string());
931 }
932 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
933 return Some(tok.to_string());
934 }
935 None
936}
937
938pub fn repl_arrow_method_completions(
940 state: &ReplCompletionSnapshot,
941 line: &str,
942 pos: usize,
943) -> Option<(usize, Vec<String>)> {
944 let pos = pos.min(line.len());
945 let before = &line[..pos];
946 let arrow_idx = before.rfind("->")?;
947 let after_arrow = &before[arrow_idx + 2..];
948 let rest = after_arrow.trim_start();
949 let ws_len = after_arrow.len() - rest.len();
950 let method_start = arrow_idx + 2 + ws_len;
951 let method_prefix = &line[method_start..pos];
952 if !method_prefix
953 .chars()
954 .all(|c| c.is_alphanumeric() || c == '_')
955 {
956 return None;
957 }
958 let left = line[..arrow_idx].trim_end();
959 let class = repl_resolve_class_for_arrow(state, left)?;
960 let mut methods = state.methods_for_class(&class);
961 methods.retain(|m| m.starts_with(method_prefix));
962 Some((method_start, methods))
963}
964
965#[derive(Debug, Clone, Default)]
967pub(crate) struct ModuleExportLists {
968 pub export: Vec<String>,
970 pub export_ok: Vec<String>,
972}
973
974fn piped_shell_command(cmd: &str) -> Command {
976 if cfg!(windows) {
977 let mut c = Command::new("cmd");
978 c.arg("/C").arg(cmd);
979 c
980 } else {
981 let mut c = Command::new("sh");
982 c.arg("-c").arg(cmd);
983 c
984 }
985}
986
987fn expand_perl_regex_octal_escapes(pat: &str) -> String {
994 let mut out = String::with_capacity(pat.len());
995 let mut it = pat.chars().peekable();
996 while let Some(c) = it.next() {
997 if c == '\\' {
998 if let Some(&'0') = it.peek() {
999 let mut oct = String::new();
1001 while oct.len() < 3 {
1002 if let Some(&d) = it.peek() {
1003 if ('0'..='7').contains(&d) {
1004 oct.push(d);
1005 it.next();
1006 } else {
1007 break;
1008 }
1009 } else {
1010 break;
1011 }
1012 }
1013 if let Ok(val) = u8::from_str_radix(&oct, 8) {
1014 out.push_str(&format!("\\x{:02x}", val));
1015 } else {
1016 out.push('\\');
1017 out.push_str(&oct);
1018 }
1019 continue;
1020 }
1021 }
1022 out.push(c);
1023 }
1024 out
1025}
1026
1027fn expand_perl_regex_quotemeta(pat: &str) -> String {
1028 let mut out = String::with_capacity(pat.len().saturating_mul(2));
1029 let mut it = pat.chars().peekable();
1030 let mut in_q = false;
1031 while let Some(c) = it.next() {
1032 if in_q {
1033 if c == '\\' && it.peek() == Some(&'E') {
1034 it.next();
1035 in_q = false;
1036 continue;
1037 }
1038 out.push_str(&perl_quotemeta(&c.to_string()));
1039 continue;
1040 }
1041 if c == '\\' && it.peek() == Some(&'Q') {
1042 it.next();
1043 in_q = true;
1044 continue;
1045 }
1046 out.push(c);
1047 }
1048 out
1049}
1050
1051pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
1057 let mut out = String::with_capacity(replacement.len() + 8);
1058 let mut it = replacement.chars().peekable();
1059 while let Some(c) = it.next() {
1060 if c == '\\' {
1061 match it.peek() {
1062 Some(&d) if d.is_ascii_digit() => {
1063 it.next();
1064 out.push_str("${");
1065 out.push(d);
1066 while let Some(&d2) = it.peek() {
1067 if !d2.is_ascii_digit() {
1068 break;
1069 }
1070 it.next();
1071 out.push(d2);
1072 }
1073 out.push('}');
1074 }
1075 Some(&'\\') => {
1076 it.next();
1077 out.push('\\');
1078 }
1079 _ => out.push('\\'),
1080 }
1081 } else if c == '$' {
1082 match it.peek() {
1083 Some(&d) if d.is_ascii_digit() => {
1084 it.next();
1085 out.push_str("${");
1086 out.push(d);
1087 while let Some(&d2) = it.peek() {
1088 if !d2.is_ascii_digit() {
1089 break;
1090 }
1091 it.next();
1092 out.push(d2);
1093 }
1094 out.push('}');
1095 }
1096 Some(&'{') => {
1097 out.push('$');
1099 }
1100 _ => out.push('$'),
1101 }
1102 } else {
1103 out.push(c);
1104 }
1105 }
1106 out
1107}
1108
1109fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
1112 debug_assert_eq!(chars.get(i), Some(&'['));
1113 out.push('[');
1114 i += 1;
1115 if i < chars.len() && chars[i] == '^' {
1116 out.push('^');
1117 i += 1;
1118 }
1119 if i >= chars.len() {
1120 return i;
1121 }
1122 if chars[i] == ']' {
1126 if i + 1 < chars.len() && chars[i + 1] == ']' {
1127 out.push(']');
1129 i += 1;
1130 } else {
1131 let mut scan = i + 1;
1132 let mut found_closing = false;
1133 while scan < chars.len() {
1134 if chars[scan] == '\\' && scan + 1 < chars.len() {
1135 scan += 2;
1136 continue;
1137 }
1138 if chars[scan] == ']' {
1139 found_closing = true;
1140 break;
1141 }
1142 scan += 1;
1143 }
1144 if found_closing {
1145 out.push(']');
1146 i += 1;
1147 } else {
1148 out.push(']');
1149 return i + 1;
1150 }
1151 }
1152 }
1153 while i < chars.len() && chars[i] != ']' {
1154 if chars[i] == '\\' && i + 1 < chars.len() {
1155 out.push(chars[i]);
1156 out.push(chars[i + 1]);
1157 i += 2;
1158 continue;
1159 }
1160 out.push(chars[i]);
1161 i += 1;
1162 }
1163 if i < chars.len() {
1164 out.push(']');
1165 i += 1;
1166 }
1167 i
1168}
1169
1170fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1175 if multiline_flag {
1176 return pat.to_string();
1177 }
1178 let chars: Vec<char> = pat.chars().collect();
1179 let mut out = String::with_capacity(pat.len().saturating_add(16));
1180 let mut i = 0usize;
1181 while i < chars.len() {
1182 let c = chars[i];
1183 if c == '\\' && i + 1 < chars.len() {
1184 out.push(c);
1185 out.push(chars[i + 1]);
1186 i += 2;
1187 continue;
1188 }
1189 if c == '[' {
1190 i = copy_regex_char_class(&chars, i, &mut out);
1191 continue;
1192 }
1193 if c == '$' {
1194 if let Some(&next) = chars.get(i + 1) {
1195 if next.is_ascii_digit() {
1196 out.push(c);
1197 i += 1;
1198 continue;
1199 }
1200 if next == '{' {
1201 out.push(c);
1202 i += 1;
1203 continue;
1204 }
1205 if next.is_ascii_alphanumeric() || next == '_' {
1206 out.push(c);
1207 i += 1;
1208 continue;
1209 }
1210 }
1211 out.push_str("(?=\\n?\\z)");
1212 i += 1;
1213 continue;
1214 }
1215 out.push(c);
1216 i += 1;
1217 }
1218 out
1219}
1220
1221#[derive(Debug, Clone)]
1223pub(crate) struct DirHandleState {
1224 pub entries: Vec<String>,
1225 pub pos: usize,
1226}
1227
1228pub(crate) fn perl_osname() -> String {
1230 match std::env::consts::OS {
1231 "linux" => "linux".to_string(),
1232 "macos" => "darwin".to_string(),
1233 "windows" => "MSWin32".to_string(),
1234 other => other.to_string(),
1235 }
1236}
1237
1238fn perl_version_v_string() -> String {
1239 format!("v{}", env!("CARGO_PKG_VERSION"))
1240}
1241
1242fn extended_os_error_string() -> String {
1243 std::io::Error::last_os_error().to_string()
1244}
1245
1246#[cfg(unix)]
1247fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1248 unsafe {
1249 (
1250 libc::getuid() as i64,
1251 libc::geteuid() as i64,
1252 libc::getgid() as i64,
1253 libc::getegid() as i64,
1254 )
1255 }
1256}
1257
1258#[cfg(not(unix))]
1259fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1260 (0, 0, 0, 0)
1261}
1262
1263fn unix_id_for_special(name: &str) -> i64 {
1264 let (r, e, _, _) = unix_real_effective_ids();
1265 match name {
1266 "<" => r,
1267 ">" => e,
1268 _ => 0,
1269 }
1270}
1271
1272#[cfg(unix)]
1273fn unix_group_list_string(primary: libc::gid_t) -> String {
1274 let mut buf = vec![0 as libc::gid_t; 256];
1275 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1276 if n <= 0 {
1277 return format!("{}", primary);
1278 }
1279 let mut parts = vec![format!("{}", primary)];
1280 for g in buf.iter().take(n as usize) {
1281 parts.push(format!("{}", g));
1282 }
1283 parts.join(" ")
1284}
1285
1286#[cfg(unix)]
1288fn unix_group_list_for_special(name: &str) -> String {
1289 let (_, _, gid, egid) = unix_real_effective_ids();
1290 match name {
1291 "(" => unix_group_list_string(gid as libc::gid_t),
1292 ")" => unix_group_list_string(egid as libc::gid_t),
1293 _ => String::new(),
1294 }
1295}
1296
1297#[cfg(not(unix))]
1298fn unix_group_list_for_special(_name: &str) -> String {
1299 String::new()
1300}
1301
1302#[cfg(unix)]
1305fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1306 use libc::{getpwuid_r, getuid};
1307 use std::ffi::CStr;
1308 use std::os::unix::ffi::OsStringExt;
1309 let uid = unsafe { getuid() };
1310 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1311 let mut result: *mut libc::passwd = std::ptr::null_mut();
1312 let mut buf = vec![0u8; 16_384];
1313 let rc = unsafe {
1314 getpwuid_r(
1315 uid,
1316 &mut pw,
1317 buf.as_mut_ptr().cast::<libc::c_char>(),
1318 buf.len(),
1319 &mut result,
1320 )
1321 };
1322 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1323 return None;
1324 }
1325 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1326 if bytes.is_empty() {
1327 return None;
1328 }
1329 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1330}
1331
1332#[cfg(unix)]
1334fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1335 use libc::getpwnam_r;
1336 use std::ffi::{CStr, CString};
1337 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1338 let bytes = login.as_bytes();
1339 if bytes.is_empty() || bytes.contains(&0) {
1340 return None;
1341 }
1342 let cname = CString::new(bytes).ok()?;
1343 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1344 let mut result: *mut libc::passwd = std::ptr::null_mut();
1345 let mut buf = vec![0u8; 16_384];
1346 let rc = unsafe {
1347 getpwnam_r(
1348 cname.as_ptr(),
1349 &mut pw,
1350 buf.as_mut_ptr().cast::<libc::c_char>(),
1351 buf.len(),
1352 &mut result,
1353 )
1354 };
1355 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1356 return None;
1357 }
1358 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1359 if dir_bytes.is_empty() {
1360 return None;
1361 }
1362 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1363}
1364
1365impl Default for VMHelper {
1366 fn default() -> Self {
1367 Self::new()
1368 }
1369}
1370
1371#[derive(Clone, Copy)]
1373pub(crate) enum CaptureAllMode {
1374 Empty,
1376 Append,
1378 Skip,
1380}
1381
1382impl VMHelper {
1383 pub fn new() -> Self {
1384 let mut scope = Scope::new();
1385 scope.declare_array("INC", vec![PerlValue::string(".".to_string())]);
1386 scope.declare_hash("INC", IndexMap::new());
1387 scope.declare_array("ARGV", vec![]);
1388 scope.declare_array("_", vec![]);
1389
1390 let path_vec: Vec<PerlValue> = std::env::var("PATH")
1392 .unwrap_or_default()
1393 .split(if cfg!(windows) { ';' } else { ':' })
1394 .filter(|s| !s.is_empty())
1395 .map(|p| PerlValue::string(p.to_string()))
1396 .collect();
1397 scope.declare_array_frozen("path", path_vec.clone(), true);
1398 scope.declare_array_frozen("p", path_vec, true);
1399
1400 let fpath_vec: Vec<PerlValue> = std::env::var("FPATH")
1402 .unwrap_or_default()
1403 .split(':')
1404 .filter(|s| !s.is_empty())
1405 .map(|p| PerlValue::string(p.to_string()))
1406 .collect();
1407 scope.declare_array_frozen("fpath", fpath_vec.clone(), true);
1408 scope.declare_array_frozen("f", fpath_vec, true);
1409 scope.declare_hash("ENV", IndexMap::new());
1410 scope.declare_hash("SIG", IndexMap::new());
1411
1412 let term_map = build_term_hash();
1414 scope.declare_hash_global_frozen("term", term_map);
1415
1416 #[cfg(unix)]
1418 {
1419 let uname_map = build_uname_hash();
1420 scope.declare_hash_global_frozen("uname", uname_map);
1421 }
1422 #[cfg(not(unix))]
1423 {
1424 scope.declare_hash_global_frozen("uname", IndexMap::new());
1425 }
1426
1427 #[cfg(unix)]
1429 {
1430 let limits_map = build_limits_hash();
1431 scope.declare_hash_global_frozen("limits", limits_map);
1432 }
1433 #[cfg(not(unix))]
1434 {
1435 scope.declare_hash_global_frozen("limits", IndexMap::new());
1436 }
1437
1438 scope.declare_scalar(
1457 "stryke::VERSION",
1458 PerlValue::string(env!("CARGO_PKG_VERSION").to_string()),
1459 );
1460 scope.declare_array("-", vec![]);
1461 scope.declare_array("+", vec![]);
1462 scope.declare_array("^CAPTURE", vec![]);
1463 scope.declare_array("^CAPTURE_ALL", vec![]);
1464 scope.declare_hash("^HOOK", IndexMap::new());
1465 scope.declare_scalar("~", PerlValue::string("STDOUT".to_string()));
1466
1467 let script_start_time = std::time::SystemTime::now()
1468 .duration_since(std::time::UNIX_EPOCH)
1469 .map(|d| d.as_secs() as i64)
1470 .unwrap_or(0);
1471
1472 let executable_path = cached_executable_path();
1473
1474 let mut special_caret_scalars: HashMap<String, PerlValue> = HashMap::new();
1475 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1476 special_caret_scalars.insert(format!("^{}", name), PerlValue::UNDEF);
1477 }
1478
1479 let mut s = Self {
1480 scope,
1481 subs: HashMap::new(),
1482 intercepts: Vec::new(),
1483 next_intercept_id: 1,
1484 intercept_ctx_stack: Vec::new(),
1485 intercept_active_names: Vec::new(),
1486 struct_defs: HashMap::new(),
1487 enum_defs: HashMap::new(),
1488 class_defs: HashMap::new(),
1489 trait_defs: HashMap::new(),
1490 file: "-e".to_string(),
1491 output_handles: HashMap::new(),
1492 input_handles: HashMap::new(),
1493 ofs: String::new(),
1494 ors: String::new(),
1495 irs: Some("\n".to_string()),
1496 errno: String::new(),
1497 errno_code: 0,
1498 eval_error: String::new(),
1499 eval_error_code: 0,
1500 eval_error_value: None,
1501 argv: Vec::new(),
1502 env: IndexMap::new(),
1503 env_materialized: false,
1504 program_name: "stryke".to_string(),
1505 line_number: 0,
1506 last_readline_handle: String::new(),
1507 last_stdin_die_bracket: "<STDIN>".to_string(),
1508 handle_line_numbers: HashMap::new(),
1509 flip_flop_active: Vec::new(),
1510 flip_flop_exclusive_left_line: Vec::new(),
1511 flip_flop_sequence: Vec::new(),
1512 flip_flop_last_dot: Vec::new(),
1513 flip_flop_tree: HashMap::new(),
1514 sigint_pending_caret: Cell::new(false),
1515 auto_split: false,
1516 field_separator: None,
1517 begin_blocks: Vec::new(),
1518 unit_check_blocks: Vec::new(),
1519 check_blocks: Vec::new(),
1520 init_blocks: Vec::new(),
1521 end_blocks: Vec::new(),
1522 warnings: false,
1523 output_autoflush: false,
1524 default_print_handle: "STDOUT".to_string(),
1525 suppress_stdout: false,
1526 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1527 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1528 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1529 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1530 child_exit_status: 0,
1531 last_match: String::new(),
1532 prematch: String::new(),
1533 postmatch: String::new(),
1534 last_paren_match: String::new(),
1535 list_separator: " ".to_string(),
1536 script_start_time,
1537 compile_hints: 0,
1538 warning_bits: 0,
1539 global_phase: "RUN".to_string(),
1540 subscript_sep: "\x1c".to_string(),
1541 inplace_edit: String::new(),
1542 debug_flags: 0,
1543 perl_debug_flags: 0,
1544 eval_nesting: 0,
1545 argv_current_file: String::new(),
1546 diamond_next_idx: 0,
1547 diamond_reader: None,
1548 strict_refs: false,
1549 strict_subs: false,
1550 strict_vars: false,
1551 utf8_pragma: false,
1552 open_pragma_utf8: false,
1553 feature_bits: FEAT_SAY,
1555 num_threads: 0, regex_cache: HashMap::new(),
1557 regex_last: None,
1558 regex_match_memo: None,
1559 regex_capture_scope_fresh: false,
1560 regex_pos: HashMap::new(),
1561 state_vars: HashMap::new(),
1562 state_bindings_stack: Vec::new(),
1563 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1564 dir_handles: HashMap::new(),
1565 io_file_slots: HashMap::new(),
1566 pipe_children: HashMap::new(),
1567 socket_handles: HashMap::new(),
1568 wantarray_kind: WantarrayCtx::Scalar,
1569 profiler: None,
1570 module_export_lists: HashMap::new(),
1571 virtual_modules: HashMap::new(),
1572 tied_hashes: HashMap::new(),
1573 tied_scalars: HashMap::new(),
1574 tied_arrays: HashMap::new(),
1575 overload_table: HashMap::new(),
1576 format_templates: HashMap::new(),
1577 special_caret_scalars,
1578 format_page_number: 0,
1579 format_lines_per_page: 60,
1580 format_lines_left: 0,
1581 format_line_break_chars: "\n".to_string(),
1582 format_top_name: String::new(),
1583 accumulator_format: String::new(),
1584 max_system_fd: 2,
1585 emergency_memory: String::new(),
1586 last_subpattern_name: String::new(),
1587 inc_hook_index: 0,
1588 multiline_match: false,
1589 executable_path,
1590 formfeed_string: "\x0c".to_string(),
1591 glob_handle_alias: HashMap::new(),
1592 glob_restore_frames: vec![Vec::new()],
1593 special_var_restore_frames: vec![Vec::new()],
1594 reflection_hashes_ready: false,
1595 english_enabled: false,
1596 english_no_match_vars: false,
1597 english_match_vars_ever_enabled: false,
1598 english_lexical_scalars: vec![HashSet::new()],
1599 our_lexical_scalars: vec![HashSet::new()],
1600 vm_jit_enabled: !matches!(
1601 std::env::var("STRYKE_NO_JIT"),
1602 Ok(v)
1603 if v == "1"
1604 || v.eq_ignore_ascii_case("true")
1605 || v.eq_ignore_ascii_case("yes")
1606 ),
1607 disasm_bytecode: false,
1608 cached_chunk: None,
1609 cache_script_path: None,
1610 in_generator: false,
1611 line_mode_skip_main: false,
1612 line_mode_chunk: None,
1613 line_mode_eof_pending: false,
1614 line_mode_stdin_pending: VecDeque::new(),
1615 rate_limit_slots: Vec::new(),
1616 log_level_override: None,
1617 current_sub_stack: Vec::new(),
1618 debugger: None,
1619 debug_call_stack: Vec::new(),
1620 };
1621 s.install_overload_pragma_stubs();
1622 s
1623 }
1624
1625 pub(crate) fn ensure_reflection_hashes(&mut self) {
1629 if self.reflection_hashes_ready {
1630 return;
1631 }
1632 self.reflection_hashes_ready = true;
1633 self.refresh_package_stashes();
1636 if crate::compat_mode() {
1640 return;
1641 }
1642 let builtins_map = crate::builtins::builtins_hash_map();
1643 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1644 let extensions_map = crate::builtins::extensions_hash_map();
1645 let aliases_map = crate::builtins::aliases_hash_map();
1646 let descriptions_map = crate::builtins::descriptions_hash_map();
1647 let categories_map = crate::builtins::categories_hash_map();
1648 let primaries_map = crate::builtins::primaries_hash_map();
1649 let keywords_map = crate::builtins::keywords_hash_map();
1650 let all_map = crate::builtins::all_hash_map();
1651 self.scope
1652 .declare_hash_global_frozen("stryke::builtins", builtins_map.clone());
1653 self.scope
1654 .declare_hash_global_frozen("stryke::perl_compats", perl_compats_map.clone());
1655 self.scope
1656 .declare_hash_global_frozen("stryke::extensions", extensions_map.clone());
1657 self.scope
1658 .declare_hash_global_frozen("stryke::aliases", aliases_map.clone());
1659 self.scope
1660 .declare_hash_global_frozen("stryke::descriptions", descriptions_map.clone());
1661 self.scope
1662 .declare_hash_global_frozen("stryke::categories", categories_map.clone());
1663 self.scope
1664 .declare_hash_global_frozen("stryke::primaries", primaries_map.clone());
1665 self.scope
1666 .declare_hash_global_frozen("stryke::keywords", keywords_map.clone());
1667 self.scope
1668 .declare_hash_global_frozen("stryke::all", all_map.clone());
1669 for (name, val) in [
1672 ("b", builtins_map),
1673 ("pc", perl_compats_map),
1674 ("e", extensions_map),
1675 ("a", aliases_map),
1676 ("d", descriptions_map),
1677 ("c", categories_map),
1678 ("p", primaries_map),
1679 ("k", keywords_map),
1680 ("all", all_map),
1681 ] {
1682 if !self.scope.any_frame_has_hash(name) {
1683 self.scope.declare_hash_global_frozen(name, val);
1684 }
1685 }
1686 if !self.scope.has_lexical_hash("parameters") {
1689 self.refresh_parameters_hash();
1690 }
1691 }
1692
1693 pub fn refresh_parameters_hash(&mut self) {
1702 let pairs = self.scope.parameters_pairs();
1703 let mut h: indexmap::IndexMap<String, PerlValue> =
1704 indexmap::IndexMap::with_capacity(pairs.len());
1705 for (name, kind) in pairs {
1706 h.insert(name, PerlValue::string(kind.to_string()));
1707 }
1708 self.scope.declare_hash_global_frozen("parameters", h);
1711 }
1712
1713 pub fn refresh_package_stashes(&mut self) {
1723 use indexmap::IndexMap;
1724
1725 let mut by_pkg: std::collections::HashMap<String, IndexMap<String, PerlValue>> =
1726 std::collections::HashMap::new();
1727
1728 let record =
1729 |pkg: &str,
1730 sym: &str,
1731 kind: &str,
1732 map: &mut std::collections::HashMap<String, IndexMap<String, PerlValue>>| {
1733 map.entry(pkg.to_string())
1734 .or_default()
1735 .insert(sym.to_string(), PerlValue::string(kind.to_string()));
1736 };
1737
1738 for key in self.subs.keys() {
1740 if let Some(idx) = key.rfind("::") {
1741 let (pkg, rest) = key.split_at(idx);
1742 let sym = &rest[2..];
1743 if pkg.is_empty() || sym.is_empty() {
1744 continue;
1745 }
1746 record(pkg, sym, "sub", &mut by_pkg);
1747 } else {
1748 record("main", key, "sub", &mut by_pkg);
1750 }
1751 }
1752
1753 for frame in self.scope.frames_for_introspection() {
1755 let (scalars, arrays, hashes) = frame;
1756 for name in scalars {
1757 if let Some(idx) = name.rfind("::") {
1758 let (pkg, rest) = name.split_at(idx);
1759 let sym = &rest[2..];
1760 if !pkg.is_empty() && !sym.is_empty() {
1761 record(pkg, sym, "scalar", &mut by_pkg);
1762 }
1763 }
1764 }
1765 for name in arrays {
1766 if let Some(idx) = name.rfind("::") {
1767 let (pkg, rest) = name.split_at(idx);
1768 let sym = &rest[2..];
1769 if !pkg.is_empty() && !sym.is_empty() {
1770 record(pkg, sym, "array", &mut by_pkg);
1771 }
1772 }
1773 }
1774 for name in hashes {
1775 if let Some(idx) = name.rfind("::") {
1776 let (pkg, rest) = name.split_at(idx);
1777 let sym = &rest[2..];
1778 if !pkg.is_empty() && !sym.is_empty() {
1779 record(pkg, sym, "hash", &mut by_pkg);
1780 }
1781 }
1782 }
1783 }
1784
1785 for (pkg, mut entries) in by_pkg {
1789 entries.sort_keys();
1790 let key = format!("{}::", pkg);
1791 self.scope.declare_hash_global_frozen(&key, entries);
1792 }
1793 }
1794
1795 fn install_overload_pragma_stubs(&mut self) {
1799 let empty: Block = vec![];
1800 for key in ["overload::import", "overload::unimport"] {
1801 let name = key.to_string();
1802 self.subs.insert(
1803 name.clone(),
1804 Arc::new(PerlSub {
1805 name,
1806 params: vec![],
1807 body: empty.clone(),
1808 prototype: None,
1809 closure_env: None,
1810 fib_like: None,
1811 }),
1812 );
1813 }
1814 }
1815
1816 pub fn line_mode_worker_clone(&self) -> VMHelper {
1819 VMHelper {
1820 scope: self.scope.clone(),
1821 subs: self.subs.clone(),
1822 intercepts: self.intercepts.clone(),
1823 next_intercept_id: self.next_intercept_id,
1824 intercept_ctx_stack: self.intercept_ctx_stack.clone(),
1825 intercept_active_names: self.intercept_active_names.clone(),
1826 struct_defs: self.struct_defs.clone(),
1827 enum_defs: self.enum_defs.clone(),
1828 class_defs: self.class_defs.clone(),
1829 trait_defs: self.trait_defs.clone(),
1830 file: self.file.clone(),
1831 output_handles: HashMap::new(),
1832 input_handles: HashMap::new(),
1833 ofs: self.ofs.clone(),
1834 ors: self.ors.clone(),
1835 irs: self.irs.clone(),
1836 errno: self.errno.clone(),
1837 errno_code: self.errno_code,
1838 eval_error: self.eval_error.clone(),
1839 eval_error_code: self.eval_error_code,
1840 eval_error_value: self.eval_error_value.clone(),
1841 argv: self.argv.clone(),
1842 env: self.env.clone(),
1843 env_materialized: self.env_materialized,
1844 program_name: self.program_name.clone(),
1845 line_number: 0,
1846 last_readline_handle: String::new(),
1847 last_stdin_die_bracket: "<STDIN>".to_string(),
1848 handle_line_numbers: HashMap::new(),
1849 flip_flop_active: Vec::new(),
1850 flip_flop_exclusive_left_line: Vec::new(),
1851 flip_flop_sequence: Vec::new(),
1852 flip_flop_last_dot: Vec::new(),
1853 flip_flop_tree: HashMap::new(),
1854 sigint_pending_caret: Cell::new(false),
1855 auto_split: self.auto_split,
1856 field_separator: self.field_separator.clone(),
1857 begin_blocks: self.begin_blocks.clone(),
1858 unit_check_blocks: self.unit_check_blocks.clone(),
1859 check_blocks: self.check_blocks.clone(),
1860 init_blocks: self.init_blocks.clone(),
1861 end_blocks: self.end_blocks.clone(),
1862 warnings: self.warnings,
1863 output_autoflush: self.output_autoflush,
1864 default_print_handle: self.default_print_handle.clone(),
1865 suppress_stdout: self.suppress_stdout,
1866 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1870 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1871 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1872 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1873 child_exit_status: self.child_exit_status,
1874 last_match: self.last_match.clone(),
1875 prematch: self.prematch.clone(),
1876 postmatch: self.postmatch.clone(),
1877 last_paren_match: self.last_paren_match.clone(),
1878 list_separator: self.list_separator.clone(),
1879 script_start_time: self.script_start_time,
1880 compile_hints: self.compile_hints,
1881 warning_bits: self.warning_bits,
1882 global_phase: self.global_phase.clone(),
1883 subscript_sep: self.subscript_sep.clone(),
1884 inplace_edit: self.inplace_edit.clone(),
1885 debug_flags: self.debug_flags,
1886 perl_debug_flags: self.perl_debug_flags,
1887 eval_nesting: self.eval_nesting,
1888 argv_current_file: String::new(),
1889 diamond_next_idx: 0,
1890 diamond_reader: None,
1891 strict_refs: self.strict_refs,
1892 strict_subs: self.strict_subs,
1893 strict_vars: self.strict_vars,
1894 utf8_pragma: self.utf8_pragma,
1895 open_pragma_utf8: self.open_pragma_utf8,
1896 feature_bits: self.feature_bits,
1897 num_threads: 0,
1898 regex_cache: self.regex_cache.clone(),
1899 regex_last: self.regex_last.clone(),
1900 regex_match_memo: self.regex_match_memo.clone(),
1901 regex_capture_scope_fresh: false,
1902 regex_pos: self.regex_pos.clone(),
1903 state_vars: self.state_vars.clone(),
1904 state_bindings_stack: Vec::new(),
1905 rand_rng: self.rand_rng.clone(),
1906 dir_handles: HashMap::new(),
1907 io_file_slots: HashMap::new(),
1908 pipe_children: HashMap::new(),
1909 socket_handles: HashMap::new(),
1910 wantarray_kind: self.wantarray_kind,
1911 profiler: None,
1912 module_export_lists: self.module_export_lists.clone(),
1913 virtual_modules: self.virtual_modules.clone(),
1914 tied_hashes: self.tied_hashes.clone(),
1915 tied_scalars: self.tied_scalars.clone(),
1916 tied_arrays: self.tied_arrays.clone(),
1917 overload_table: self.overload_table.clone(),
1918 format_templates: self.format_templates.clone(),
1919 special_caret_scalars: self.special_caret_scalars.clone(),
1920 format_page_number: self.format_page_number,
1921 format_lines_per_page: self.format_lines_per_page,
1922 format_lines_left: self.format_lines_left,
1923 format_line_break_chars: self.format_line_break_chars.clone(),
1924 format_top_name: self.format_top_name.clone(),
1925 accumulator_format: self.accumulator_format.clone(),
1926 max_system_fd: self.max_system_fd,
1927 emergency_memory: self.emergency_memory.clone(),
1928 last_subpattern_name: self.last_subpattern_name.clone(),
1929 inc_hook_index: self.inc_hook_index,
1930 multiline_match: self.multiline_match,
1931 executable_path: self.executable_path.clone(),
1932 formfeed_string: self.formfeed_string.clone(),
1933 glob_handle_alias: self.glob_handle_alias.clone(),
1934 glob_restore_frames: self.glob_restore_frames.clone(),
1935 special_var_restore_frames: self.special_var_restore_frames.clone(),
1936 reflection_hashes_ready: self.reflection_hashes_ready,
1937 english_enabled: self.english_enabled,
1938 english_no_match_vars: self.english_no_match_vars,
1939 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1940 english_lexical_scalars: self.english_lexical_scalars.clone(),
1941 our_lexical_scalars: self.our_lexical_scalars.clone(),
1942 vm_jit_enabled: self.vm_jit_enabled,
1943 disasm_bytecode: self.disasm_bytecode,
1944 cached_chunk: None,
1946 cache_script_path: None,
1947 in_generator: false,
1948 line_mode_skip_main: false,
1949 line_mode_chunk: self.line_mode_chunk.clone(),
1950 line_mode_eof_pending: false,
1951 line_mode_stdin_pending: VecDeque::new(),
1952 rate_limit_slots: Vec::new(),
1953 log_level_override: self.log_level_override,
1954 current_sub_stack: Vec::new(),
1955 debugger: None,
1956 debug_call_stack: Vec::new(),
1957 }
1958 }
1959
1960 pub(crate) fn parallel_thread_count(&mut self) -> usize {
1962 if self.num_threads == 0 {
1963 self.num_threads = rayon::current_num_threads();
1964 }
1965 self.num_threads
1966 }
1967
1968 pub(crate) fn eval_par_list_call(
1970 &mut self,
1971 name: &str,
1972 args: &[PerlValue],
1973 ctx: WantarrayCtx,
1974 line: usize,
1975 ) -> PerlResult<PerlValue> {
1976 match name {
1977 "puniq" => {
1978 let (list_src, show_prog) = match args.len() {
1979 0 => return Err(PerlError::runtime("puniq: expected LIST", line)),
1980 1 => (&args[0], false),
1981 2 => (&args[0], args[1].is_true()),
1982 _ => {
1983 return Err(PerlError::runtime(
1984 "puniq: expected LIST [, progress => EXPR]",
1985 line,
1986 ));
1987 }
1988 };
1989 let list = list_src.to_list();
1990 let n_threads = self.parallel_thread_count();
1991 let pmap_progress = PmapProgress::new(show_prog, list.len());
1992 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
1993 pmap_progress.finish();
1994 if ctx == WantarrayCtx::List {
1995 Ok(PerlValue::array(out))
1996 } else {
1997 Ok(PerlValue::integer(out.len() as i64))
1998 }
1999 }
2000 "pfirst" => {
2001 let (code_val, list_src, show_prog) = match args.len() {
2002 2 => (&args[0], &args[1], false),
2003 3 => (&args[0], &args[1], args[2].is_true()),
2004 _ => {
2005 return Err(PerlError::runtime(
2006 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
2007 line,
2008 ));
2009 }
2010 };
2011 let Some(sub) = code_val.as_code_ref() else {
2012 return Err(PerlError::runtime(
2013 "pfirst: first argument must be a code reference",
2014 line,
2015 ));
2016 };
2017 let sub = sub.clone();
2018 let list = list_src.to_list();
2019 if list.is_empty() {
2020 return Ok(PerlValue::UNDEF);
2021 }
2022 let pmap_progress = PmapProgress::new(show_prog, list.len());
2023 let subs = self.subs.clone();
2024 let (scope_capture, atomic_arrays, atomic_hashes) =
2025 self.scope.capture_with_atomics();
2026 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
2027 let mut local_interp = VMHelper::new();
2028 local_interp.subs = subs.clone();
2029 local_interp.scope.restore_capture(&scope_capture);
2030 local_interp
2031 .scope
2032 .restore_atomics(&atomic_arrays, &atomic_hashes);
2033 local_interp.enable_parallel_guard();
2034 local_interp.scope.set_topic(item);
2035 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2036 Ok(v) => v.is_true(),
2037 Err(_) => false,
2038 }
2039 });
2040 pmap_progress.finish();
2041 Ok(out.unwrap_or(PerlValue::UNDEF))
2042 }
2043 "pany" => {
2044 let (code_val, list_src, show_prog) = match args.len() {
2045 2 => (&args[0], &args[1], false),
2046 3 => (&args[0], &args[1], args[2].is_true()),
2047 _ => {
2048 return Err(PerlError::runtime(
2049 "pany: expected BLOCK, LIST [, progress => EXPR]",
2050 line,
2051 ));
2052 }
2053 };
2054 let Some(sub) = code_val.as_code_ref() else {
2055 return Err(PerlError::runtime(
2056 "pany: first argument must be a code reference",
2057 line,
2058 ));
2059 };
2060 let sub = sub.clone();
2061 let list = list_src.to_list();
2062 let pmap_progress = PmapProgress::new(show_prog, list.len());
2063 let subs = self.subs.clone();
2064 let (scope_capture, atomic_arrays, atomic_hashes) =
2065 self.scope.capture_with_atomics();
2066 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
2067 let mut local_interp = VMHelper::new();
2068 local_interp.subs = subs.clone();
2069 local_interp.scope.restore_capture(&scope_capture);
2070 local_interp
2071 .scope
2072 .restore_atomics(&atomic_arrays, &atomic_hashes);
2073 local_interp.enable_parallel_guard();
2074 local_interp.scope.set_topic(item);
2075 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2076 Ok(v) => v.is_true(),
2077 Err(_) => false,
2078 }
2079 });
2080 pmap_progress.finish();
2081 Ok(PerlValue::integer(if b { 1 } else { 0 }))
2082 }
2083 _ => Err(PerlError::runtime(
2084 format!("internal: unknown par_list builtin {name}"),
2085 line,
2086 )),
2087 }
2088 }
2089
2090 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
2091 #[cfg(unix)]
2092 if let Some(sig) = s.signal() {
2093 return sig as i64 & 0x7f;
2094 }
2095 let code = s.code().unwrap_or(0) as i64;
2096 code << 8
2097 }
2098
2099 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
2100 self.child_exit_status = self.encode_exit_status(s);
2101 }
2102
2103 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
2105 let s = e.to_string();
2108 let stripped = s
2109 .rfind(" (os error ")
2110 .map(|i| s[..i].to_string())
2111 .unwrap_or(s);
2112 self.errno = stripped;
2113 self.errno_code = e.raw_os_error().unwrap_or(0);
2114 }
2115
2116 pub(crate) fn ssh_builtin_execute(&mut self, args: &[PerlValue]) -> PerlResult<PerlValue> {
2127 use std::process::Command;
2128 let mut cmd = Command::new("ssh");
2129 #[cfg(unix)]
2130 {
2131 use libc::geteuid;
2132 let home_for_ssh = if unsafe { geteuid() } == 0 {
2133 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
2134 } else {
2135 None
2136 };
2137 if let Some(h) = home_for_ssh {
2138 cmd.env("HOME", h);
2139 } else if std::env::var_os("HOME").is_none() {
2140 if let Some(h) = pw_home_dir_for_current_uid() {
2141 cmd.env("HOME", h);
2142 }
2143 }
2144 }
2145 for a in args {
2146 cmd.arg(a.to_string());
2147 }
2148 match cmd.status() {
2149 Ok(s) => {
2150 self.record_child_exit_status(s);
2151 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
2152 }
2153 Err(e) => {
2154 self.apply_io_error_to_errno(&e);
2155 Ok(PerlValue::integer(-1))
2156 }
2157 }
2158 }
2159
2160 pub(crate) fn set_eval_error(&mut self, msg: String) {
2162 self.eval_error = msg;
2163 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2164 self.eval_error_value = None;
2165 }
2166
2167 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &PerlError) {
2168 self.eval_error = e.to_string();
2169 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2170 self.eval_error_value = e.die_value.clone();
2171 }
2172
2173 pub(crate) fn clear_eval_error(&mut self) {
2174 self.eval_error = String::new();
2175 self.eval_error_code = 0;
2176 self.eval_error_value = None;
2177 }
2178
2179 fn bump_line_for_handle(&mut self, handle_key: &str) {
2181 self.last_readline_handle = handle_key.to_string();
2182 *self
2183 .handle_line_numbers
2184 .entry(handle_key.to_string())
2185 .or_insert(0) += 1;
2186 }
2187
2188 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
2190 if name.starts_with('^') {
2191 return name.to_string();
2192 }
2193 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
2194 let pkg = self.current_package();
2195 if !pkg.is_empty() && pkg != "main" {
2196 return format!("{}::{}", pkg, name);
2197 }
2198 }
2199 name.to_string()
2200 }
2201
2202 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
2204 if name.contains("::") {
2205 return name.to_string();
2206 }
2207 let pkg = self.current_package();
2208 if pkg.is_empty() || pkg == "main" {
2209 format!("main::{}", name)
2210 } else {
2211 format!("{}::{}", pkg, name)
2212 }
2213 }
2214
2215 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
2217 if name.contains("::") {
2218 return name.to_string();
2219 }
2220 for (lex, our) in self
2221 .english_lexical_scalars
2222 .iter()
2223 .zip(self.our_lexical_scalars.iter())
2224 .rev()
2225 {
2226 if lex.contains(name) {
2227 if our.contains(name) {
2228 return self.stash_scalar_name_for_package(name);
2229 }
2230 return name.to_string();
2231 }
2232 }
2233 name.to_string()
2234 }
2235
2236 pub(crate) fn tie_execute(
2238 &mut self,
2239 target_kind: u8,
2240 target_name: &str,
2241 class_and_args: Vec<PerlValue>,
2242 line: usize,
2243 ) -> PerlResult<PerlValue> {
2244 let mut it = class_and_args.into_iter();
2245 let class = it.next().unwrap_or(PerlValue::UNDEF);
2246 let pkg = class.to_string();
2247 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
2248 let tie_ctor = match target_kind {
2249 0 => "TIESCALAR",
2250 1 => "TIEARRAY",
2251 2 => "TIEHASH",
2252 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
2253 };
2254 let tie_fn = format!("{}::{}", pkg, tie_ctor);
2255 let sub = self
2256 .subs
2257 .get(&tie_fn)
2258 .cloned()
2259 .ok_or_else(|| PerlError::runtime(format!("tie: cannot find &{}", tie_fn), line))?;
2260 let mut call_args = vec![PerlValue::string(pkg.clone())];
2261 call_args.extend(it);
2262 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
2263 Ok(v) => v,
2264 Err(FlowOrError::Flow(_)) => PerlValue::UNDEF,
2265 Err(FlowOrError::Error(e)) => return Err(e),
2266 };
2267 match target_kind {
2268 0 => {
2269 self.tied_scalars.insert(target_name.to_string(), obj);
2270 }
2271 1 => {
2272 let key = self.stash_array_name_for_package(target_name);
2273 self.tied_arrays.insert(key, obj);
2274 }
2275 2 => {
2276 self.tied_hashes.insert(target_name.to_string(), obj);
2277 }
2278 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
2279 }
2280 Ok(PerlValue::UNDEF)
2281 }
2282
2283 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
2285 let key = format!("{}::ISA", class);
2286 self.scope
2287 .get_array(&key)
2288 .into_iter()
2289 .map(|v| v.to_string())
2290 .collect()
2291 }
2292
2293 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
2294 let p = |c: &str| self.parents_of_class(c);
2295 linearize_c3(class, &p, 0)
2296 }
2297
2298 pub(crate) fn resolve_method_full_name(
2300 &self,
2301 invocant_class: &str,
2302 method: &str,
2303 super_mode: bool,
2304 ) -> Option<String> {
2305 let mro = self.mro_linearize(invocant_class);
2306 let start = if super_mode {
2310 mro.iter()
2311 .position(|p| p == invocant_class)
2312 .map(|i| i + 1)
2313 .unwrap_or(1)
2316 } else {
2317 0
2318 };
2319 for pkg in mro.iter().skip(start) {
2320 if pkg == "UNIVERSAL" {
2321 continue;
2322 }
2323 let fq = format!("{}::{}", pkg, method);
2324 if self.subs.contains_key(&fq) {
2325 return Some(fq);
2326 }
2327 }
2328 mro.iter()
2329 .skip(start)
2330 .find(|p| *p != "UNIVERSAL")
2331 .map(|pkg| format!("{}::{}", pkg, method))
2332 }
2333
2334 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
2335 if let Some(alias) = self.glob_handle_alias.get(name) {
2336 return alias.clone();
2337 }
2338 if let Some(var_name) = name.strip_prefix('$') {
2341 let val = self.scope.get_scalar(var_name);
2342 let s = val.to_string();
2343 if !s.is_empty() {
2344 return self.resolve_io_handle_name(&s);
2345 }
2346 }
2347 name.to_string()
2348 }
2349
2350 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2352 if name.contains("::") {
2353 name.to_string()
2354 } else {
2355 self.qualify_sub_key(name)
2356 }
2357 }
2358
2359 pub(crate) fn copy_typeglob_slots(
2361 &mut self,
2362 lhs: &str,
2363 rhs: &str,
2364 line: usize,
2365 ) -> PerlResult<()> {
2366 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2367 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2368 match self.subs.get(&rhs_sub).cloned() {
2369 Some(s) => {
2370 self.subs.insert(lhs_sub, s);
2371 }
2372 None => {
2373 self.subs.remove(&lhs_sub);
2374 }
2375 }
2376 let sv = self.scope.get_scalar(rhs);
2377 self.scope
2378 .set_scalar(lhs, sv.clone())
2379 .map_err(|e| e.at_line(line))?;
2380 let lhs_an = self.stash_array_name_for_package(lhs);
2381 let rhs_an = self.stash_array_name_for_package(rhs);
2382 let av = self.scope.get_array(&rhs_an);
2383 self.scope
2384 .set_array(&lhs_an, av.clone())
2385 .map_err(|e| e.at_line(line))?;
2386 let hv = self.scope.get_hash(rhs);
2387 self.scope
2388 .set_hash(lhs, hv.clone())
2389 .map_err(|e| e.at_line(line))?;
2390 match self.glob_handle_alias.get(rhs).cloned() {
2391 Some(t) => {
2392 self.glob_handle_alias.insert(lhs.to_string(), t);
2393 }
2394 None => {
2395 self.glob_handle_alias.remove(lhs);
2396 }
2397 }
2398 Ok(())
2399 }
2400
2401 pub(crate) fn install_format_decl(
2403 &mut self,
2404 basename: &str,
2405 lines: &[String],
2406 line: usize,
2407 ) -> PerlResult<()> {
2408 let pkg = self.current_package();
2409 let key = format!("{}::{}", pkg, basename);
2410 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2411 self.format_templates.insert(key, Arc::new(tmpl));
2412 Ok(())
2413 }
2414
2415 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2421 let pkg = self.current_package();
2422 for (_, v) in pairs {
2423 if v.starts_with("__overload_anon_") {
2424 let pkg_key = format!("{}::{}", pkg, v);
2431 if !self.subs.contains_key(&pkg_key) {
2432 let src = if let Some(s) = self.subs.get(v) {
2433 Some(s.clone())
2434 } else {
2435 self.subs.get(&format!("main::{}", v)).cloned()
2436 };
2437 if let Some(sub) = src {
2438 self.subs.insert(pkg_key, sub);
2439 }
2440 }
2441 }
2442 }
2443 let ent = self.overload_table.entry(pkg).or_default();
2444 for (k, v) in pairs {
2445 ent.insert(k.clone(), v.clone());
2446 }
2447 }
2448
2449 pub(crate) fn local_declare_typeglob(
2452 &mut self,
2453 lhs: &str,
2454 rhs: Option<&str>,
2455 line: usize,
2456 ) -> PerlResult<()> {
2457 let old = self.glob_handle_alias.remove(lhs);
2458 let Some(frame) = self.glob_restore_frames.last_mut() else {
2459 return Err(PerlError::runtime(
2460 "internal: no glob restore frame for local *GLOB",
2461 line,
2462 ));
2463 };
2464 frame.push((lhs.to_string(), old));
2465 if let Some(r) = rhs {
2466 self.glob_handle_alias
2467 .insert(lhs.to_string(), r.to_string());
2468 }
2469 Ok(())
2470 }
2471
2472 pub(crate) fn scope_push_hook(&mut self) {
2473 self.scope.push_frame();
2474 self.glob_restore_frames.push(Vec::new());
2475 self.special_var_restore_frames.push(Vec::new());
2476 self.english_lexical_scalars.push(HashSet::new());
2477 self.our_lexical_scalars.push(HashSet::new());
2478 self.state_bindings_stack.push(Vec::new());
2479 }
2480
2481 #[inline]
2482 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2483 if let Some(s) = self.english_lexical_scalars.last_mut() {
2484 s.insert(name.to_string());
2485 }
2486 }
2487
2488 #[inline]
2491 pub(crate) fn english_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2492 self.english_lexical_scalars.clone()
2493 }
2494
2495 #[inline]
2498 pub(crate) fn our_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2499 self.our_lexical_scalars.clone()
2500 }
2501
2502 #[inline]
2504 pub(crate) fn set_english_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2505 self.english_lexical_scalars = v;
2506 }
2507
2508 #[inline]
2510 pub(crate) fn set_our_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2511 self.our_lexical_scalars = v;
2512 }
2513
2514 #[inline]
2515 fn note_our_scalar(&mut self, bare_name: &str) {
2516 if let Some(s) = self.our_lexical_scalars.last_mut() {
2517 s.insert(bare_name.to_string());
2518 }
2519 }
2520
2521 #[inline]
2525 pub(crate) fn english_note_lexical_scalar_pub(&mut self, name: &str) {
2526 self.english_note_lexical_scalar(name);
2527 }
2528
2529 #[inline]
2531 pub(crate) fn note_our_scalar_pub(&mut self, bare_name: &str) {
2532 self.note_our_scalar(bare_name);
2533 }
2534
2535 pub(crate) fn scope_pop_hook(&mut self) {
2536 if !self.scope.can_pop_frame() {
2537 return;
2538 }
2539 let defers = self.scope.take_defers();
2543 for coderef in defers {
2544 if let Some(sub) = coderef.as_code_ref() {
2545 let saved_wa = self.wantarray_kind;
2549 self.wantarray_kind = WantarrayCtx::Void;
2550 let _ = self.exec_block_no_scope(&sub.body);
2551 self.wantarray_kind = saved_wa;
2552 }
2553 }
2554 if let Some(bindings) = self.state_bindings_stack.pop() {
2556 for (var_name, state_key) in &bindings {
2557 let val = self.scope.get_scalar(var_name).clone();
2558 self.state_vars.insert(state_key.clone(), val);
2559 }
2560 }
2561 if let Some(entries) = self.special_var_restore_frames.pop() {
2564 for (name, old) in entries.into_iter().rev() {
2565 let _ = self.set_special_var(&name, &old);
2566 }
2567 }
2568 if let Some(entries) = self.glob_restore_frames.pop() {
2569 for (name, old) in entries.into_iter().rev() {
2570 match old {
2571 Some(s) => {
2572 self.glob_handle_alias.insert(name, s);
2573 }
2574 None => {
2575 self.glob_handle_alias.remove(&name);
2576 }
2577 }
2578 }
2579 }
2580 self.scope.pop_frame();
2581 let _ = self.english_lexical_scalars.pop();
2582 let _ = self.our_lexical_scalars.pop();
2583 }
2584
2585 #[inline]
2588 pub(crate) fn enable_parallel_guard(&mut self) {
2589 self.scope.set_parallel_guard(true);
2590 }
2591
2592 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2594 self.begin_blocks.clear();
2595 self.unit_check_blocks.clear();
2596 self.check_blocks.clear();
2597 self.init_blocks.clear();
2598 self.end_blocks.clear();
2599 }
2600
2601 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2607 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2608 self.scope_pop_hook();
2609 }
2610 }
2611
2612 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> PerlResult<()> {
2619 self.touch_env_hash("SIG");
2620 let v = self.scope.get_hash_element("SIG", sig);
2621 if v.is_undef() {
2622 return Self::default_sig_action(sig);
2623 }
2624 if let Some(s) = v.as_str() {
2625 if s == "IGNORE" {
2626 return Ok(());
2627 }
2628 if s == "DEFAULT" {
2629 return Self::default_sig_action(sig);
2630 }
2631 }
2632 if let Some(sub) = v.as_code_ref() {
2633 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2634 Ok(_) => Ok(()),
2635 Err(FlowOrError::Flow(_)) => Ok(()),
2636 Err(FlowOrError::Error(e)) => Err(e),
2637 }
2638 } else {
2639 Self::default_sig_action(sig)
2640 }
2641 }
2642
2643 pub(crate) fn fire_pseudosig_warn(&mut self, msg: &str, line: usize) -> PerlResult<()> {
2647 self.touch_env_hash("SIG");
2648 let slot = self.scope.get_hash_element("SIG", "__WARN__");
2649 if let Some(sub) = slot.as_code_ref() {
2650 let prev = slot;
2651 let _ = self
2652 .scope
2653 .set_hash_element("SIG", "__WARN__", PerlValue::UNDEF);
2654 let arg = PerlValue::string(msg.to_string());
2655 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2656 let _ = self.scope.set_hash_element("SIG", "__WARN__", prev);
2657 return match r {
2658 Ok(_) => Ok(()),
2659 Err(FlowOrError::Flow(_)) => Ok(()),
2660 Err(FlowOrError::Error(e)) => Err(e),
2661 };
2662 }
2663 eprint!("{}", msg);
2664 Ok(())
2665 }
2666
2667 pub(crate) fn fire_pseudosig_die(&mut self, msg: &str, line: usize) -> PerlResult<()> {
2673 self.touch_env_hash("SIG");
2674 let slot = self.scope.get_hash_element("SIG", "__DIE__");
2675 if let Some(sub) = slot.as_code_ref() {
2676 let prev = slot;
2677 let _ = self
2678 .scope
2679 .set_hash_element("SIG", "__DIE__", PerlValue::UNDEF);
2680 let arg = PerlValue::string(msg.to_string());
2681 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2682 let _ = self.scope.set_hash_element("SIG", "__DIE__", prev);
2683 return match r {
2684 Ok(_) => Ok(()),
2685 Err(FlowOrError::Flow(_)) => Ok(()),
2686 Err(FlowOrError::Error(e)) => Err(e),
2687 };
2688 }
2689 Ok(())
2690 }
2691
2692 #[inline]
2694 fn default_sig_action(sig: &str) -> PerlResult<()> {
2695 match sig {
2696 "INT" => std::process::exit(130),
2698 "TERM" => std::process::exit(143),
2699 "ALRM" => std::process::exit(142),
2700 "CHLD" => Ok(()),
2702 _ => Ok(()),
2703 }
2704 }
2705
2706 pub fn materialize_env_if_needed(&mut self) {
2709 if self.env_materialized {
2710 return;
2711 }
2712 self.env = std::env::vars()
2713 .map(|(k, v)| (k, PerlValue::string(v)))
2714 .collect();
2715 self.scope
2716 .set_hash("ENV", self.env.clone())
2717 .expect("set %ENV");
2718 self.env_materialized = true;
2719 }
2720
2721 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2723 self.materialize_env_if_needed();
2724 if let Some(x) = self.log_level_override {
2725 return x;
2726 }
2727 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2728 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2729 }
2730
2731 pub(crate) fn no_color_effective(&mut self) -> bool {
2733 self.materialize_env_if_needed();
2734 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2735 if v.is_undef() {
2736 return false;
2737 }
2738 !v.to_string().is_empty()
2739 }
2740
2741 #[inline]
2742 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2743 let hash_name: &str = crate::scope::strip_main_prefix(hash_name).unwrap_or(hash_name);
2750 if hash_name == "ENV" {
2751 self.materialize_env_if_needed();
2752 } else if hash_name == "parameters"
2753 && !crate::compat_mode()
2754 && !self.scope.has_lexical_hash("parameters")
2755 {
2756 self.ensure_reflection_hashes();
2762 self.refresh_parameters_hash();
2763 } else if hash_name.ends_with("::") && hash_name.len() > 2 {
2764 self.refresh_package_stashes();
2769 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2770 match hash_name {
2771 "b"
2772 | "pc"
2773 | "e"
2774 | "a"
2775 | "d"
2776 | "c"
2777 | "p"
2778 | "k"
2779 | "all"
2780 | "stryke::builtins"
2781 | "stryke::perl_compats"
2782 | "stryke::extensions"
2783 | "stryke::aliases"
2784 | "stryke::descriptions"
2785 | "stryke::categories"
2786 | "stryke::primaries"
2787 | "stryke::keywords"
2788 | "stryke::all" => {
2789 self.ensure_reflection_hashes();
2790 }
2791 _ => {}
2792 }
2793 }
2794 }
2795
2796 pub(crate) fn exists_arrow_hash_element(
2798 &self,
2799 container: PerlValue,
2800 key: &str,
2801 line: usize,
2802 ) -> PerlResult<bool> {
2803 if let Some(r) = container.as_hash_ref() {
2804 return Ok(r.read().contains_key(key));
2805 }
2806 if let Some(b) = container.as_blessed_ref() {
2807 let data = b.data.read();
2808 if let Some(r) = data.as_hash_ref() {
2809 return Ok(r.read().contains_key(key));
2810 }
2811 if let Some(hm) = data.as_hash_map() {
2812 return Ok(hm.contains_key(key));
2813 }
2814 return Err(PerlError::runtime(
2815 "exists argument is not a HASH reference",
2816 line,
2817 ));
2818 }
2819 let _ = line;
2823 Ok(false)
2824 }
2825
2826 pub(crate) fn delete_arrow_hash_element(
2828 &self,
2829 container: PerlValue,
2830 key: &str,
2831 line: usize,
2832 ) -> PerlResult<PerlValue> {
2833 if let Some(r) = container.as_hash_ref() {
2834 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2835 }
2836 if let Some(b) = container.as_blessed_ref() {
2837 let mut data = b.data.write();
2838 if let Some(r) = data.as_hash_ref() {
2839 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2840 }
2841 if let Some(mut map) = data.as_hash_map() {
2842 let v = map.shift_remove(key).unwrap_or(PerlValue::UNDEF);
2843 *data = PerlValue::hash(map);
2844 return Ok(v);
2845 }
2846 return Err(PerlError::runtime(
2847 "delete argument is not a HASH reference",
2848 line,
2849 ));
2850 }
2851 Err(PerlError::runtime(
2852 "delete argument is not a HASH reference",
2853 line,
2854 ))
2855 }
2856
2857 pub(crate) fn exists_arrow_array_element(
2859 &self,
2860 container: PerlValue,
2861 idx: i64,
2862 line: usize,
2863 ) -> PerlResult<bool> {
2864 if let Some(a) = container.as_array_ref() {
2865 let arr = a.read();
2866 let i = if idx < 0 {
2867 (arr.len() as i64 + idx) as usize
2868 } else {
2869 idx as usize
2870 };
2871 return Ok(i < arr.len());
2872 }
2873 let _ = line;
2876 Ok(false)
2877 }
2878
2879 pub(crate) fn delete_arrow_array_element(
2881 &self,
2882 container: PerlValue,
2883 idx: i64,
2884 line: usize,
2885 ) -> PerlResult<PerlValue> {
2886 if let Some(a) = container.as_array_ref() {
2887 let mut arr = a.write();
2888 let i = if idx < 0 {
2889 (arr.len() as i64 + idx) as usize
2890 } else {
2891 idx as usize
2892 };
2893 if i >= arr.len() {
2894 return Ok(PerlValue::UNDEF);
2895 }
2896 let old = arr.get(i).cloned().unwrap_or(PerlValue::UNDEF);
2897 arr[i] = PerlValue::UNDEF;
2898 return Ok(old);
2899 }
2900 Err(PerlError::runtime(
2901 "delete argument is not an ARRAY reference",
2902 line,
2903 ))
2904 }
2905
2906 pub(crate) fn inc_directories(&self) -> Vec<String> {
2908 let mut v: Vec<String> = self
2909 .scope
2910 .get_array("INC")
2911 .into_iter()
2912 .map(|x| x.to_string())
2913 .filter(|s| !s.is_empty())
2914 .collect();
2915 if v.is_empty() {
2916 v.push(".".to_string());
2917 }
2918 v
2919 }
2920
2921 #[inline]
2922 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
2923 matches!(
2924 name,
2925 "_" | "0"
2926 | "!"
2927 | "@"
2928 | "/"
2929 | "\\"
2930 | ","
2931 | "."
2932 | "__PACKAGE__"
2933 | "$$"
2934 | "|"
2935 | "?"
2936 | "\""
2937 | "&"
2938 | "`"
2939 | "'"
2940 | "+"
2941 | "<"
2942 | ">"
2943 | "("
2944 | ")"
2945 | "]"
2946 | ";"
2947 | "ARGV"
2948 | "%"
2949 | "="
2950 | "-"
2951 | ":"
2952 | "*"
2953 | "INC"
2954 | "a"
2958 | "b"
2959 ) || name.chars().all(|c| c.is_ascii_digit())
2960 || name.starts_with('^')
2961 || (name.starts_with('#') && name.len() > 1)
2962 || (name.starts_with('_')
2969 && name.len() > 1
2970 && name[1..].chars().all(|c| c.is_ascii_digit()))
2971 }
2972
2973 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2974 if !self.strict_vars
2975 || Self::strict_scalar_exempt(name)
2976 || name.contains("::")
2977 || self.scope.scalar_binding_exists(name)
2978 {
2979 return Ok(());
2980 }
2981 Err(PerlError::runtime(
2982 format!(
2983 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
2984 name, name
2985 ),
2986 line,
2987 )
2988 .into())
2989 }
2990
2991 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2992 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
2993 return Ok(());
2994 }
2995 Err(PerlError::runtime(
2996 format!(
2997 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
2998 name, name
2999 ),
3000 line,
3001 )
3002 .into())
3003 }
3004
3005 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3006 if !self.strict_vars
3008 || name.contains("::")
3009 || self.scope.hash_binding_exists(name)
3010 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
3011 {
3012 return Ok(());
3013 }
3014 Err(PerlError::runtime(
3015 format!(
3016 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
3017 name, name
3018 ),
3019 line,
3020 )
3021 .into())
3022 }
3023
3024 fn looks_like_version_only(spec: &str) -> bool {
3025 let t = spec.trim();
3026 !t.is_empty()
3027 && !t.contains('/')
3028 && !t.contains('\\')
3029 && !t.contains("::")
3030 && t.chars()
3031 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
3032 && t.chars().any(|c| c.is_ascii_digit())
3033 }
3034
3035 fn module_spec_to_relpath(spec: &str) -> String {
3036 let t = spec.trim();
3037 if t.contains("::") {
3038 format!("{}.pm", t.replace("::", "/"))
3039 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
3040 t.replace('\\', "/")
3041 } else {
3042 format!("{}.pm", t)
3043 }
3044 }
3045
3046 fn try_resolve_via_lockfile(relpath: &str) -> Option<std::path::PathBuf> {
3053 let cwd = std::env::current_dir().ok()?;
3054 let project_root = crate::pkg::commands::find_project_root(&cwd)?;
3055
3056 let stem = relpath
3059 .strip_suffix(".pm")
3060 .or_else(|| relpath.strip_suffix(".pl"))
3061 .or_else(|| relpath.strip_suffix(".stk"))
3062 .unwrap_or(relpath);
3063 let logical = stem.replace('/', "::");
3064
3065 crate::pkg::commands::resolve_module(&project_root, &logical).unwrap_or_default()
3066 }
3067
3068 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
3073 if name.contains("::") {
3074 return name.to_string();
3075 }
3076 let pkg = self.current_package();
3077 if pkg.is_empty() || pkg == "main" {
3078 name.to_string()
3079 } else {
3080 format!("{}::{}", pkg, name)
3081 }
3082 }
3083
3084 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
3086 let mut msg = format!("Undefined subroutine &{}", name);
3087 if self.strict_subs {
3088 msg.push_str(
3089 " (strict subs: declare the sub or use a fully qualified name before calling)",
3090 );
3091 }
3092 msg
3093 }
3094
3095 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
3097 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
3098 if self.strict_subs {
3099 msg.push_str(
3100 " (strict subs: declare the sub or use a fully qualified name before calling)",
3101 );
3102 }
3103 msg
3104 }
3105
3106 fn import_alias_key(&self, short: &str) -> String {
3108 self.qualify_sub_key(short)
3109 }
3110
3111 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
3113 if imports.len() == 1 {
3114 match &imports[0].kind {
3115 ExprKind::QW(ws) => return ws.is_empty(),
3116 ExprKind::List(xs) => return xs.is_empty(),
3118 _ => {}
3119 }
3120 }
3121 false
3122 }
3123
3124 fn apply_module_import(
3126 &mut self,
3127 module: &str,
3128 imports: &[Expr],
3129 line: usize,
3130 ) -> PerlResult<()> {
3131 if imports.is_empty() {
3132 return self.import_all_from_module(module, line);
3133 }
3134 if Self::is_explicit_empty_import_list(imports) {
3135 return Ok(());
3136 }
3137 let names = Self::pragma_import_strings(imports, line)?;
3138 if names.is_empty() {
3139 return Ok(());
3140 }
3141 for name in names {
3142 self.import_one_symbol(module, &name, line)?;
3143 }
3144 Ok(())
3145 }
3146
3147 fn import_all_from_module(&mut self, module: &str, line: usize) -> PerlResult<()> {
3148 if let Some(lists) = self.module_export_lists.get(module) {
3149 let export: Vec<String> = lists.export.clone();
3150 for short in export {
3151 self.import_named_sub(module, &short, line)?;
3152 }
3153 return Ok(());
3154 }
3155 let prefix = format!("{}::", module);
3157 let keys: Vec<String> = self
3158 .subs
3159 .keys()
3160 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
3161 .cloned()
3162 .collect();
3163 for k in keys {
3164 let short = k[prefix.len()..].to_string();
3165 if let Some(sub) = self.subs.get(&k).cloned() {
3166 let alias = self.import_alias_key(&short);
3167 self.subs.insert(alias, sub);
3168 }
3169 }
3170 Ok(())
3171 }
3172
3173 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> PerlResult<()> {
3175 let qual = format!("{}::{}", module, short);
3176 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
3177 PerlError::runtime(
3178 format!(
3179 "`{}` is not defined in module `{}` (expected `{}`)",
3180 short, module, qual
3181 ),
3182 line,
3183 )
3184 })?;
3185 let alias = self.import_alias_key(short);
3186 self.subs.insert(alias, sub);
3187 Ok(())
3188 }
3189
3190 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> PerlResult<()> {
3191 if let Some(lists) = self.module_export_lists.get(module) {
3192 let allowed: HashSet<&str> = lists
3193 .export
3194 .iter()
3195 .map(|s| s.as_str())
3196 .chain(lists.export_ok.iter().map(|s| s.as_str()))
3197 .collect();
3198 if !allowed.contains(export) {
3199 return Err(PerlError::runtime(
3200 format!(
3201 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
3202 export, module
3203 ),
3204 line,
3205 ));
3206 }
3207 }
3208 self.import_named_sub(module, export, line)
3209 }
3210
3211 fn record_exporter_our_array_name(&mut self, name: &str, items: &[PerlValue]) {
3213 if name != "EXPORT" && name != "EXPORT_OK" {
3214 return;
3215 }
3216 let pkg = self.current_package();
3217 if pkg.is_empty() || pkg == "main" {
3218 return;
3219 }
3220 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
3221 let ent = self.module_export_lists.entry(pkg).or_default();
3222 if name == "EXPORT" {
3223 ent.export = names;
3224 } else {
3225 ent.export_ok = names;
3226 }
3227 }
3228
3229 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
3233 let key = self.qualify_sub_key(name);
3234 let Some(sub) = self.subs.get(&key).cloned() else {
3235 return;
3236 };
3237 let captured = self.scope.capture();
3238 let closure_env = if captured.is_empty() {
3239 None
3240 } else {
3241 Some(captured)
3242 };
3243 let mut new_sub = (*sub).clone();
3244 new_sub.closure_env = closure_env;
3245 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
3246 self.subs.insert(key, Arc::new(new_sub));
3247 }
3248
3249 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<PerlSub>> {
3250 if let Some(s) = self.subs.get(name) {
3251 return Some(s.clone());
3252 }
3253 if !name.contains("::") {
3254 let pkg = self.current_package();
3256 if !pkg.is_empty() && pkg != "main" {
3257 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
3258 q.push_str(&pkg);
3259 q.push_str("::");
3260 q.push_str(name);
3261 return self.subs.get(&q).cloned();
3262 }
3263 return None;
3264 }
3265 if let Some(rest) = name.strip_prefix("main::") {
3270 if !rest.contains("::") {
3271 return self.subs.get(rest).cloned();
3272 }
3273 }
3274 None
3275 }
3276
3277 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
3280 if let Some(first) = imports.first() {
3281 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
3282 return &imports[1..];
3283 }
3284 }
3285 imports
3286 }
3287
3288 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> PerlResult<Vec<String>> {
3290 let mut out = Vec::new();
3291 for e in imports {
3292 match &e.kind {
3293 ExprKind::String(s) => out.push(s.clone()),
3294 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
3295 ExprKind::Integer(n) => out.push(n.to_string()),
3296 ExprKind::InterpolatedString(parts) => {
3299 let mut s = String::new();
3300 for p in parts {
3301 match p {
3302 StringPart::Literal(l) => s.push_str(l),
3303 StringPart::ScalarVar(v) => {
3304 s.push('$');
3305 s.push_str(v);
3306 }
3307 StringPart::ArrayVar(v) => {
3308 s.push('@');
3309 s.push_str(v);
3310 }
3311 _ => {
3312 return Err(PerlError::runtime(
3313 "pragma import must be a compile-time string, qw(), or integer",
3314 e.line.max(default_line),
3315 ));
3316 }
3317 }
3318 }
3319 out.push(s);
3320 }
3321 _ => {
3322 return Err(PerlError::runtime(
3323 "pragma import must be a compile-time string, qw(), or integer",
3324 e.line.max(default_line),
3325 ));
3326 }
3327 }
3328 }
3329 Ok(out)
3330 }
3331
3332 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3333 if imports.is_empty() {
3334 self.strict_refs = true;
3335 self.strict_subs = true;
3336 self.strict_vars = true;
3337 return Ok(());
3338 }
3339 let names = Self::pragma_import_strings(imports, line)?;
3340 for name in names {
3341 match name.as_str() {
3342 "refs" => self.strict_refs = true,
3343 "subs" => self.strict_subs = true,
3344 "vars" => self.strict_vars = true,
3345 _ => {
3346 return Err(PerlError::runtime(
3347 format!("Unknown strict mode `{}`", name),
3348 line,
3349 ));
3350 }
3351 }
3352 }
3353 Ok(())
3354 }
3355
3356 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3357 if imports.is_empty() {
3358 self.strict_refs = false;
3359 self.strict_subs = false;
3360 self.strict_vars = false;
3361 return Ok(());
3362 }
3363 let names = Self::pragma_import_strings(imports, line)?;
3364 for name in names {
3365 match name.as_str() {
3366 "refs" => self.strict_refs = false,
3367 "subs" => self.strict_subs = false,
3368 "vars" => self.strict_vars = false,
3369 _ => {
3370 return Err(PerlError::runtime(
3371 format!("Unknown strict mode `{}`", name),
3372 line,
3373 ));
3374 }
3375 }
3376 }
3377 Ok(())
3378 }
3379
3380 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3381 let items = Self::pragma_import_strings(imports, line)?;
3382 if items.is_empty() {
3383 return Err(PerlError::runtime(
3384 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
3385 line,
3386 ));
3387 }
3388 for item in items {
3389 let s = item.trim();
3390 if let Some(rest) = s.strip_prefix(':') {
3391 self.apply_feature_bundle(rest, line)?;
3392 } else {
3393 self.apply_feature_name(s, true, line)?;
3394 }
3395 }
3396 Ok(())
3397 }
3398
3399 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3400 if imports.is_empty() {
3401 self.feature_bits = 0;
3402 return Ok(());
3403 }
3404 let items = Self::pragma_import_strings(imports, line)?;
3405 for item in items {
3406 let s = item.trim();
3407 if let Some(rest) = s.strip_prefix(':') {
3408 self.clear_feature_bundle(rest);
3409 } else {
3410 self.apply_feature_name(s, false, line)?;
3411 }
3412 }
3413 Ok(())
3414 }
3415
3416 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> PerlResult<()> {
3417 let key = v.trim();
3418 match key {
3419 "5.10" | "5.010" | "5.10.0" => {
3420 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3421 }
3422 "5.12" | "5.012" | "5.12.0" => {
3423 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3424 }
3425 _ => {
3426 return Err(PerlError::runtime(
3427 format!("unsupported feature bundle :{}", key),
3428 line,
3429 ));
3430 }
3431 }
3432 Ok(())
3433 }
3434
3435 fn clear_feature_bundle(&mut self, v: &str) {
3436 let key = v.trim();
3437 if matches!(
3438 key,
3439 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
3440 ) {
3441 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
3442 }
3443 }
3444
3445 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> PerlResult<()> {
3446 let bit = match name {
3447 "say" => FEAT_SAY,
3448 "state" => FEAT_STATE,
3449 "switch" => FEAT_SWITCH,
3450 "unicode_strings" => FEAT_UNICODE_STRINGS,
3451 "postderef"
3455 | "postderef_qq"
3456 | "evalbytes"
3457 | "current_sub"
3458 | "fc"
3459 | "lexical_subs"
3460 | "signatures"
3461 | "refaliasing"
3462 | "bitwise"
3463 | "isa"
3464 | "indirect"
3465 | "multidimensional"
3466 | "bareword_filehandles"
3467 | "try"
3468 | "defer"
3469 | "extra_paired_delimiters"
3470 | "module_true"
3471 | "class"
3472 | "array_base" => return Ok(()),
3473 _ => {
3474 return Err(PerlError::runtime(
3475 format!("unknown feature `{}`", name),
3476 line,
3477 ));
3478 }
3479 };
3480 if enable {
3481 self.feature_bits |= bit;
3482 } else {
3483 self.feature_bits &= !bit;
3484 }
3485 Ok(())
3486 }
3487
3488 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> PerlResult<PerlValue> {
3490 let t = spec.trim();
3491 if t.is_empty() {
3492 return Err(PerlError::runtime("require: empty argument", line));
3493 }
3494 match t {
3495 "strict" => {
3496 self.apply_use_strict(&[], line)?;
3497 return Ok(PerlValue::integer(1));
3498 }
3499 "utf8" => {
3500 self.utf8_pragma = true;
3501 return Ok(PerlValue::integer(1));
3502 }
3503 "feature" | "v5" => {
3504 return Ok(PerlValue::integer(1));
3505 }
3506 "warnings" => {
3507 self.warnings = true;
3508 return Ok(PerlValue::integer(1));
3509 }
3510 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
3511 return Ok(PerlValue::integer(1));
3512 }
3513 _ => {}
3514 }
3515 let p = Path::new(t);
3516 if p.is_absolute() {
3517 return self.require_absolute_path(p, line);
3518 }
3519 if t.starts_with("./") || t.starts_with("../") {
3520 return self.require_relative_path(p, line);
3521 }
3522 if Self::looks_like_version_only(t) {
3523 return Ok(PerlValue::integer(1));
3524 }
3525 let relpath = Self::module_spec_to_relpath(t);
3526 self.require_from_inc(&relpath, line)
3527 }
3528
3529 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> PerlResult<()> {
3531 let v = self.scope.get_hash_element("^HOOK", key);
3532 if v.is_undef() {
3533 return Ok(());
3534 }
3535 let Some(sub) = v.as_code_ref() else {
3536 return Ok(());
3537 };
3538 let r = self.call_sub(
3539 sub.as_ref(),
3540 vec![PerlValue::string(path.to_string())],
3541 WantarrayCtx::Scalar,
3542 line,
3543 );
3544 match r {
3545 Ok(_) => Ok(()),
3546 Err(FlowOrError::Error(e)) => Err(e),
3547 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3548 Err(FlowOrError::Flow(other)) => Err(PerlError::runtime(
3549 format!(
3550 "require hook {:?} returned unexpected control flow: {:?}",
3551 key, other
3552 ),
3553 line,
3554 )),
3555 }
3556 }
3557
3558 fn require_absolute_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3559 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3560 let key = canon.to_string_lossy().into_owned();
3561 if self.scope.exists_hash_element("INC", &key) {
3562 return Ok(PerlValue::integer(1));
3563 }
3564 self.invoke_require_hook("require__before", &key, line)?;
3565 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3566 PerlError::runtime(
3567 format!("Can't open {} for reading: {}", canon.display(), e),
3568 line,
3569 )
3570 })?;
3571 let code = crate::data_section::strip_perl_end_marker(&code);
3572 self.scope
3573 .set_hash_element("INC", &key, PerlValue::string(key.clone()))?;
3574 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3575 let r = crate::parse_and_run_module_in_file(code, self, &key);
3576 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3577 r?;
3578 self.invoke_require_hook("require__after", &key, line)?;
3579 Ok(PerlValue::integer(1))
3580 }
3581
3582 fn require_relative_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3583 if !path.exists() {
3584 return Err(PerlError::runtime(
3585 format!(
3586 "Can't locate {} (relative path does not exist)",
3587 path.display()
3588 ),
3589 line,
3590 ));
3591 }
3592 self.require_absolute_path(path, line)
3593 }
3594
3595 fn require_from_inc(&mut self, relpath: &str, line: usize) -> PerlResult<PerlValue> {
3596 if self.scope.exists_hash_element("INC", relpath) {
3597 return Ok(PerlValue::integer(1));
3598 }
3599 self.invoke_require_hook("require__before", relpath, line)?;
3600
3601 if let Some(found) = Self::try_resolve_via_lockfile(relpath) {
3607 let code = read_file_text_perl_compat(&found).map_err(|e| {
3608 PerlError::runtime(
3609 format!("Can't open {} for reading: {}", found.display(), e),
3610 line,
3611 )
3612 })?;
3613 let code = crate::data_section::strip_perl_end_marker(&code);
3614 let abs = found.canonicalize().unwrap_or(found);
3615 let abs_s = abs.to_string_lossy().into_owned();
3616 self.scope
3617 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3618 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3619 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3620 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3621 r?;
3622 self.invoke_require_hook("require__after", relpath, line)?;
3623 return Ok(PerlValue::integer(1));
3624 }
3625
3626 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3628 let code = crate::data_section::strip_perl_end_marker(&code);
3629 self.scope.set_hash_element(
3630 "INC",
3631 relpath,
3632 PerlValue::string(format!("(virtual)/{}", relpath)),
3633 )?;
3634 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3635 let r = crate::parse_and_run_module_in_file(code, self, relpath);
3636 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3637 r?;
3638 self.invoke_require_hook("require__after", relpath, line)?;
3639 return Ok(PerlValue::integer(1));
3640 }
3641
3642 for dir in self.inc_directories() {
3643 let full = Path::new(&dir).join(relpath);
3644 if full.is_file() {
3645 let code = read_file_text_perl_compat(&full).map_err(|e| {
3646 PerlError::runtime(
3647 format!("Can't open {} for reading: {}", full.display(), e),
3648 line,
3649 )
3650 })?;
3651 let code = crate::data_section::strip_perl_end_marker(&code);
3652 let abs = full.canonicalize().unwrap_or(full);
3653 let abs_s = abs.to_string_lossy().into_owned();
3654 self.scope
3655 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3656 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3657 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3658 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3659 r?;
3660 self.invoke_require_hook("require__after", relpath, line)?;
3661 return Ok(PerlValue::integer(1));
3662 }
3663 }
3664 Err(PerlError::runtime(
3665 format!(
3666 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3667 relpath
3668 ),
3669 line,
3670 ))
3671 }
3672
3673 pub fn register_virtual_module(&mut self, path: String, source: String) {
3675 self.virtual_modules.insert(path, source);
3676 }
3677
3678 pub(crate) fn exec_use_stmt(
3680 &mut self,
3681 module: &str,
3682 imports: &[Expr],
3683 line: usize,
3684 ) -> PerlResult<()> {
3685 match module {
3686 "strict" => self.apply_use_strict(imports, line),
3687 "utf8" => {
3688 if !imports.is_empty() {
3689 return Err(PerlError::runtime("use utf8 takes no arguments", line));
3690 }
3691 self.utf8_pragma = true;
3692 Ok(())
3693 }
3694 "feature" => self.apply_use_feature(imports, line),
3695 "v5" => Ok(()),
3696 "warnings" => {
3697 self.warnings = true;
3698 Ok(())
3699 }
3700 "English" => {
3701 self.english_enabled = true;
3702 let args = Self::pragma_import_strings(imports, line)?;
3703 let no_match = args.iter().any(|a| a == "-no_match_vars");
3704 if !no_match {
3708 self.english_match_vars_ever_enabled = true;
3709 }
3710 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3711 Ok(())
3712 }
3713 "Env" => self.apply_use_env(imports, line),
3714 "open" => self.apply_use_open(imports, line),
3715 "constant" => self.apply_use_constant(imports, line),
3716 "bigint" | "bignum" | "bigrat" => {
3717 crate::set_bigint_pragma(true);
3724 Ok(())
3725 }
3726 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3727 _ => {
3728 self.require_execute(module, line)?;
3729 let imports = Self::imports_after_leading_use_version(imports);
3730 self.apply_module_import(module, imports, line)?;
3731 Ok(())
3732 }
3733 }
3734 }
3735
3736 pub(crate) fn exec_no_stmt(
3738 &mut self,
3739 module: &str,
3740 imports: &[Expr],
3741 line: usize,
3742 ) -> PerlResult<()> {
3743 match module {
3744 "strict" => self.apply_no_strict(imports, line),
3745 "utf8" => {
3746 if !imports.is_empty() {
3747 return Err(PerlError::runtime("no utf8 takes no arguments", line));
3748 }
3749 self.utf8_pragma = false;
3750 Ok(())
3751 }
3752 "feature" => self.apply_no_feature(imports, line),
3753 "v5" => Ok(()),
3754 "warnings" => {
3755 self.warnings = false;
3756 Ok(())
3757 }
3758 "English" => {
3759 self.english_enabled = false;
3760 if !self.english_match_vars_ever_enabled {
3763 self.english_no_match_vars = false;
3764 }
3765 Ok(())
3766 }
3767 "open" => {
3768 self.open_pragma_utf8 = false;
3769 Ok(())
3770 }
3771 "bigint" | "bignum" | "bigrat" => {
3772 crate::set_bigint_pragma(false);
3773 Ok(())
3774 }
3775 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3776 _ => Ok(()),
3777 }
3778 }
3779
3780 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3782 let names = Self::pragma_import_strings(imports, line)?;
3783 for n in names {
3784 let key = n.trim_start_matches('@');
3785 if key.eq_ignore_ascii_case("PATH") {
3786 let path_env = std::env::var("PATH").unwrap_or_default();
3787 let path_vec: Vec<PerlValue> = std::env::split_paths(&path_env)
3788 .map(|p| PerlValue::string(p.to_string_lossy().into_owned()))
3789 .collect();
3790 let aname = self.stash_array_name_for_package("PATH");
3791 self.scope.declare_array(&aname, path_vec);
3792 }
3793 }
3794 Ok(())
3795 }
3796
3797 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3799 let items = Self::pragma_import_strings(imports, line)?;
3800 for item in items {
3801 let s = item.trim();
3802 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3803 self.open_pragma_utf8 = true;
3804 continue;
3805 }
3806 if let Some(rest) = s.strip_prefix(":encoding(") {
3807 if let Some(inner) = rest.strip_suffix(')') {
3808 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3809 self.open_pragma_utf8 = true;
3810 }
3811 }
3812 }
3813 }
3814 Ok(())
3815 }
3816
3817 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3819 if imports.is_empty() {
3820 return Ok(());
3821 }
3822 if imports.len() == 1 {
3824 match &imports[0].kind {
3825 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
3826 _ => {}
3827 }
3828 }
3829 for imp in imports {
3830 match &imp.kind {
3831 ExprKind::List(items) => {
3832 if items.len() % 2 != 0 {
3833 return Err(PerlError::runtime(
3834 format!(
3835 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
3836 items.len()
3837 ),
3838 line,
3839 ));
3840 }
3841 let mut i = 0;
3842 while i < items.len() {
3843 let name = match &items[i].kind {
3844 ExprKind::String(s) => s.clone(),
3845 _ => {
3846 return Err(PerlError::runtime(
3847 "use constant: constant name must be a string literal",
3848 line,
3849 ));
3850 }
3851 };
3852 let val = match self.eval_expr(&items[i + 1]) {
3853 Ok(v) => v,
3854 Err(FlowOrError::Error(e)) => return Err(e),
3855 Err(FlowOrError::Flow(_)) => {
3856 return Err(PerlError::runtime(
3857 "use constant: unexpected control flow in initializer",
3858 line,
3859 ));
3860 }
3861 };
3862 self.install_constant_sub(&name, &val, line)?;
3863 i += 2;
3864 }
3865 }
3866 _ => {
3867 return Err(PerlError::runtime(
3868 "use constant: expected list of NAME => VALUE pairs",
3869 line,
3870 ));
3871 }
3872 }
3873 }
3874 Ok(())
3875 }
3876
3877 fn install_constant_sub(&mut self, name: &str, val: &PerlValue, line: usize) -> PerlResult<()> {
3878 let key = self.qualify_sub_key(name);
3879 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
3880 let body = vec![Statement {
3881 label: None,
3882 kind: StmtKind::Return(Some(ret_expr)),
3883 line,
3884 }];
3885 self.subs.insert(
3886 key.clone(),
3887 Arc::new(PerlSub {
3888 name: key,
3889 params: vec![],
3890 body,
3891 prototype: None,
3892 closure_env: None,
3893 fib_like: None,
3894 }),
3895 );
3896 Ok(())
3897 }
3898
3899 fn perl_value_to_const_literal_expr(&self, v: &PerlValue, line: usize) -> PerlResult<Expr> {
3901 if v.is_undef() {
3902 return Ok(Expr {
3903 kind: ExprKind::Undef,
3904 line,
3905 });
3906 }
3907 if let Some(n) = v.as_integer() {
3908 return Ok(Expr {
3909 kind: ExprKind::Integer(n),
3910 line,
3911 });
3912 }
3913 if let Some(f) = v.as_float() {
3914 return Ok(Expr {
3915 kind: ExprKind::Float(f),
3916 line,
3917 });
3918 }
3919 if let Some(s) = v.as_str() {
3920 return Ok(Expr {
3921 kind: ExprKind::String(s),
3922 line,
3923 });
3924 }
3925 if let Some(arr) = v.as_array_vec() {
3926 let mut elems = Vec::with_capacity(arr.len());
3927 for e in &arr {
3928 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3929 }
3930 return Ok(Expr {
3931 kind: ExprKind::ArrayRef(elems),
3932 line,
3933 });
3934 }
3935 if let Some(h) = v.as_hash_map() {
3936 let mut pairs = Vec::with_capacity(h.len());
3937 for (k, vv) in h.iter() {
3938 pairs.push((
3939 Expr {
3940 kind: ExprKind::String(k.clone()),
3941 line,
3942 },
3943 self.perl_value_to_const_literal_expr(vv, line)?,
3944 ));
3945 }
3946 return Ok(Expr {
3947 kind: ExprKind::HashRef(pairs),
3948 line,
3949 });
3950 }
3951 if let Some(aref) = v.as_array_ref() {
3952 let arr = aref.read();
3953 let mut elems = Vec::with_capacity(arr.len());
3954 for e in arr.iter() {
3955 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3956 }
3957 return Ok(Expr {
3958 kind: ExprKind::ArrayRef(elems),
3959 line,
3960 });
3961 }
3962 if let Some(href) = v.as_hash_ref() {
3963 let h = href.read();
3964 let mut pairs = Vec::with_capacity(h.len());
3965 for (k, vv) in h.iter() {
3966 pairs.push((
3967 Expr {
3968 kind: ExprKind::String(k.clone()),
3969 line,
3970 },
3971 self.perl_value_to_const_literal_expr(vv, line)?,
3972 ));
3973 }
3974 return Ok(Expr {
3975 kind: ExprKind::HashRef(pairs),
3976 line,
3977 });
3978 }
3979 Err(PerlError::runtime(
3980 format!("use constant: unsupported value type ({v:?})"),
3981 line,
3982 ))
3983 }
3984
3985 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> PerlResult<()> {
3987 self.utf8_pragma = false;
3993 for stmt in &program.statements {
3994 match &stmt.kind {
3995 StmtKind::Package { name } => {
3996 let _ = self
3997 .scope
3998 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
3999 }
4000 StmtKind::SubDecl {
4001 name,
4002 params,
4003 body,
4004 prototype,
4005 } => {
4006 let key = self.qualify_sub_key(name);
4007 let mut sub = PerlSub {
4008 name: name.clone(),
4009 params: params.clone(),
4010 body: body.clone(),
4011 closure_env: None,
4012 prototype: prototype.clone(),
4013 fib_like: None,
4014 };
4015 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
4016 self.subs.insert(key, Arc::new(sub));
4017 }
4018 StmtKind::UsePerlVersion { .. } => {}
4019 StmtKind::Use { module, imports } => {
4020 self.exec_use_stmt(module, imports, stmt.line)?;
4021 }
4022 StmtKind::UseOverload { pairs } => {
4023 self.install_use_overload_pairs(pairs);
4024 }
4025 StmtKind::FormatDecl { name, lines } => {
4026 self.install_format_decl(name, lines, stmt.line)?;
4027 }
4028 StmtKind::No { module, imports } => {
4029 self.exec_no_stmt(module, imports, stmt.line)?;
4030 }
4031 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
4032 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
4033 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
4034 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
4035 StmtKind::End(block) => self.end_blocks.push(block.clone()),
4036 _ => {}
4037 }
4038 }
4039 Ok(())
4040 }
4041
4042 pub fn install_data_handle(&mut self, data: Vec<u8>) {
4044 self.input_handles.insert(
4045 "DATA".to_string(),
4046 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
4047 );
4048 }
4049
4050 pub(crate) fn open_builtin_execute(
4056 &mut self,
4057 handle_name: String,
4058 mode_s: String,
4059 file_opt: Option<String>,
4060 line: usize,
4061 ) -> PerlResult<PerlValue> {
4062 let (actual_mode, path) = if let Some(f) = file_opt {
4067 (mode_s, f)
4068 } else {
4069 let trimmed = mode_s.trim();
4070 if let Some(rest) = trimmed.strip_prefix('|') {
4071 ("|-".to_string(), rest.trim_start().to_string())
4072 } else if trimmed.ends_with('|') {
4073 let mut cmd = trimmed.to_string();
4074 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
4076 } else if let Some(rest) = trimmed.strip_prefix(">>") {
4077 (">>".to_string(), rest.trim().to_string())
4078 } else if let Some(rest) = trimmed.strip_prefix('>') {
4079 (">".to_string(), rest.trim().to_string())
4080 } else if let Some(rest) = trimmed.strip_prefix('<') {
4081 ("<".to_string(), rest.trim().to_string())
4082 } else {
4083 ("<".to_string(), trimmed.to_string())
4084 }
4085 };
4086 let handle_return = handle_name.clone();
4087 match actual_mode.as_str() {
4088 "-|" => {
4089 let mut cmd = piped_shell_command(&path);
4090 cmd.stdout(Stdio::piped());
4091 let mut child = cmd.spawn().map_err(|e| {
4092 self.apply_io_error_to_errno(&e);
4093 PerlError::runtime(format!("Can't open pipe from command: {}", e), line)
4094 })?;
4095 let stdout = child
4096 .stdout
4097 .take()
4098 .ok_or_else(|| PerlError::runtime("pipe: child has no stdout", line))?;
4099 self.input_handles
4100 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
4101 self.pipe_children.insert(handle_name, child);
4102 }
4103 "|-" => {
4104 let mut cmd = piped_shell_command(&path);
4105 cmd.stdin(Stdio::piped());
4106 let mut child = cmd.spawn().map_err(|e| {
4107 self.apply_io_error_to_errno(&e);
4108 PerlError::runtime(format!("Can't open pipe to command: {}", e), line)
4109 })?;
4110 let stdin = child
4111 .stdin
4112 .take()
4113 .ok_or_else(|| PerlError::runtime("pipe: child has no stdin", line))?;
4114 self.output_handles
4115 .insert(handle_name.clone(), Box::new(stdin));
4116 self.pipe_children.insert(handle_name, child);
4117 }
4118 "<" => {
4119 let file = match std::fs::File::open(&path) {
4120 Ok(f) => f,
4121 Err(e) => {
4122 self.apply_io_error_to_errno(&e);
4123 return Ok(PerlValue::integer(0));
4124 }
4125 };
4126 let shared = Arc::new(Mutex::new(file));
4127 self.io_file_slots
4128 .insert(handle_name.clone(), Arc::clone(&shared));
4129 self.input_handles.insert(
4130 handle_name.clone(),
4131 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
4132 );
4133 }
4134 ">" => {
4135 let file = match std::fs::File::create(&path) {
4136 Ok(f) => f,
4137 Err(e) => {
4138 self.apply_io_error_to_errno(&e);
4139 return Ok(PerlValue::integer(0));
4140 }
4141 };
4142 let shared = Arc::new(Mutex::new(file));
4143 self.io_file_slots
4144 .insert(handle_name.clone(), Arc::clone(&shared));
4145 self.output_handles.insert(
4146 handle_name.clone(),
4147 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4148 );
4149 }
4150 ">>" => {
4151 let file = match std::fs::OpenOptions::new()
4152 .append(true)
4153 .create(true)
4154 .open(&path)
4155 {
4156 Ok(f) => f,
4157 Err(e) => {
4158 self.apply_io_error_to_errno(&e);
4159 return Ok(PerlValue::integer(0));
4160 }
4161 };
4162 let shared = Arc::new(Mutex::new(file));
4163 self.io_file_slots
4164 .insert(handle_name.clone(), Arc::clone(&shared));
4165 self.output_handles.insert(
4166 handle_name.clone(),
4167 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4168 );
4169 }
4170 _ => {
4171 return Err(PerlError::runtime(
4172 format!("Unknown open mode '{}'", actual_mode),
4173 line,
4174 ));
4175 }
4176 }
4177 Ok(PerlValue::io_handle(handle_return))
4178 }
4179
4180 pub(crate) fn eval_chunk_by_builtin(
4184 &mut self,
4185 key_spec: &Expr,
4186 list_expr: &Expr,
4187 ctx: WantarrayCtx,
4188 line: usize,
4189 ) -> ExecResult {
4190 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
4191 let chunks = match &key_spec.kind {
4192 ExprKind::CodeRef { .. } => {
4193 let cr = self.eval_expr(key_spec)?;
4194 let Some(sub) = cr.as_code_ref() else {
4195 return Err(PerlError::runtime(
4196 "group_by/chunk_by: first argument must be { BLOCK }",
4197 line,
4198 )
4199 .into());
4200 };
4201 let sub = sub.clone();
4202 let mut chunks: Vec<PerlValue> = Vec::new();
4203 let mut run: Vec<PerlValue> = Vec::new();
4204 let mut prev_key: Option<PerlValue> = None;
4205 for item in list {
4206 self.scope.set_topic(item.clone());
4207 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
4208 Ok(k) => k,
4209 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
4210 Err(FlowOrError::Flow(Flow::Return(v))) => v,
4211 Err(_) => PerlValue::UNDEF,
4212 };
4213 match &prev_key {
4214 None => {
4215 run.push(item);
4216 prev_key = Some(key);
4217 }
4218 Some(pk) => {
4219 if key.str_eq(pk) {
4220 run.push(item);
4221 } else {
4222 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
4223 std::mem::take(&mut run),
4224 ))));
4225 run.push(item);
4226 prev_key = Some(key);
4227 }
4228 }
4229 }
4230 }
4231 if !run.is_empty() {
4232 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
4233 }
4234 chunks
4235 }
4236 _ => {
4237 let mut chunks: Vec<PerlValue> = Vec::new();
4238 let mut run: Vec<PerlValue> = Vec::new();
4239 let mut prev_key: Option<PerlValue> = None;
4240 for item in list {
4241 self.scope.set_topic(item.clone());
4242 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
4243 match &prev_key {
4244 None => {
4245 run.push(item);
4246 prev_key = Some(key);
4247 }
4248 Some(pk) => {
4249 if key.str_eq(pk) {
4250 run.push(item);
4251 } else {
4252 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
4253 std::mem::take(&mut run),
4254 ))));
4255 run.push(item);
4256 prev_key = Some(key);
4257 }
4258 }
4259 }
4260 }
4261 if !run.is_empty() {
4262 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
4263 }
4264 chunks
4265 }
4266 };
4267 Ok(match ctx {
4268 WantarrayCtx::List => PerlValue::array(chunks),
4269 WantarrayCtx::Scalar => PerlValue::integer(chunks.len() as i64),
4270 WantarrayCtx::Void => PerlValue::UNDEF,
4271 })
4272 }
4273
4274 pub(crate) fn list_higher_order_block_builtin(
4276 &mut self,
4277 name: &str,
4278 args: &[PerlValue],
4279 line: usize,
4280 ) -> PerlResult<PerlValue> {
4281 match self.list_higher_order_block_builtin_exec(name, args, line) {
4282 Ok(v) => Ok(v),
4283 Err(FlowOrError::Error(e)) => Err(e),
4284 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
4285 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
4286 format!("{name}: unsupported control flow in block"),
4287 line,
4288 )),
4289 }
4290 }
4291
4292 fn list_higher_order_block_builtin_exec(
4293 &mut self,
4294 name: &str,
4295 args: &[PerlValue],
4296 line: usize,
4297 ) -> ExecResult {
4298 if args.is_empty() {
4299 return Err(
4300 PerlError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
4301 );
4302 }
4303 let Some(sub) = args[0].as_code_ref() else {
4304 return Err(PerlError::runtime(
4305 format!("{name}: first argument must be {{ BLOCK }}"),
4306 line,
4307 )
4308 .into());
4309 };
4310 let sub = sub.clone();
4311 let items: Vec<PerlValue> = args[1..].to_vec();
4312 if matches!(name, "tap" | "peek") && items.len() == 1 {
4313 if let Some(p) = items[0].as_pipeline() {
4314 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
4315 return Ok(PerlValue::pipeline(Arc::clone(&p)));
4316 }
4317 let v = &items[0];
4318 if v.is_iterator() || v.as_array_vec().is_some() {
4319 let source = crate::map_stream::into_pull_iter(v.clone());
4320 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
4321 return Ok(PerlValue::iterator(Arc::new(
4322 crate::map_stream::TapIterator::new(
4323 source,
4324 sub,
4325 self.subs.clone(),
4326 capture,
4327 atomic_arrays,
4328 atomic_hashes,
4329 ),
4330 )));
4331 }
4332 }
4333 let wa = self.wantarray_kind;
4338 match name {
4339 "take_while" => {
4340 let mut out = Vec::new();
4341 for item in items {
4342 self.scope.set_topic(item.clone());
4347 let pred =
4348 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4349 if !pred.is_true() {
4350 break;
4351 }
4352 out.push(item);
4353 }
4354 Ok(match wa {
4355 WantarrayCtx::List => PerlValue::array(out),
4356 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4357 WantarrayCtx::Void => PerlValue::UNDEF,
4358 })
4359 }
4360 "drop_while" | "skip_while" => {
4361 let mut i = 0usize;
4362 while i < items.len() {
4363 let it = items[i].clone();
4364 self.scope.set_topic(it.clone());
4365 let pred = self.call_sub(&sub, vec![it], WantarrayCtx::Scalar, line)?;
4366 if !pred.is_true() {
4367 break;
4368 }
4369 i += 1;
4370 }
4371 let rest = items[i..].to_vec();
4372 Ok(match wa {
4373 WantarrayCtx::List => PerlValue::array(rest),
4374 WantarrayCtx::Scalar => PerlValue::integer(rest.len() as i64),
4375 WantarrayCtx::Void => PerlValue::UNDEF,
4376 })
4377 }
4378 "reject" | "grepv" => {
4379 let mut out = Vec::new();
4380 for item in items {
4381 self.scope.set_topic(item.clone());
4382 let pred =
4383 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4384 if !pred.is_true() {
4385 out.push(item);
4386 }
4387 }
4388 Ok(match wa {
4389 WantarrayCtx::List => PerlValue::array(out),
4390 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4391 WantarrayCtx::Void => PerlValue::UNDEF,
4392 })
4393 }
4394 "tap" | "peek" => {
4395 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
4396 Ok(match wa {
4397 WantarrayCtx::List => PerlValue::array(items),
4398 WantarrayCtx::Scalar => PerlValue::integer(items.len() as i64),
4399 WantarrayCtx::Void => PerlValue::UNDEF,
4400 })
4401 }
4402 "partition" => {
4403 let mut yes = Vec::new();
4404 let mut no = Vec::new();
4405 for item in items {
4406 self.scope.set_topic(item.clone());
4407 let pred =
4408 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4409 if pred.is_true() {
4410 yes.push(item);
4411 } else {
4412 no.push(item);
4413 }
4414 }
4415 let yes_ref = PerlValue::array_ref(Arc::new(RwLock::new(yes)));
4416 let no_ref = PerlValue::array_ref(Arc::new(RwLock::new(no)));
4417 Ok(match wa {
4418 WantarrayCtx::List => PerlValue::array(vec![yes_ref, no_ref]),
4419 WantarrayCtx::Scalar => PerlValue::integer(2),
4420 WantarrayCtx::Void => PerlValue::UNDEF,
4421 })
4422 }
4423 "min_by" => {
4424 let mut best: Option<(PerlValue, PerlValue)> = None;
4425 for item in items {
4426 self.scope.set_topic(item.clone());
4427 let key =
4428 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4429 best = Some(match best {
4430 None => (item, key),
4431 Some((bv, bk)) => {
4432 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
4433 (item, key)
4434 } else {
4435 (bv, bk)
4436 }
4437 }
4438 });
4439 }
4440 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
4441 }
4442 "max_by" => {
4443 let mut best: Option<(PerlValue, PerlValue)> = None;
4444 for item in items {
4445 self.scope.set_topic(item.clone());
4446 let key =
4447 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4448 best = Some(match best {
4449 None => (item, key),
4450 Some((bv, bk)) => {
4451 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
4452 (item, key)
4453 } else {
4454 (bv, bk)
4455 }
4456 }
4457 });
4458 }
4459 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
4460 }
4461 "zip_with" => {
4462 let flat: Vec<PerlValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
4465 let refs: Vec<Vec<PerlValue>> = flat
4466 .iter()
4467 .map(|el| {
4468 if let Some(ar) = el.as_array_ref() {
4469 ar.read().clone()
4470 } else if let Some(name) = el.as_array_binding_name() {
4471 self.scope.get_array(&name)
4472 } else {
4473 vec![el.clone()]
4474 }
4475 })
4476 .collect();
4477 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
4478 let mut out = Vec::with_capacity(max_len);
4479 for i in 0..max_len {
4480 let pair: Vec<PerlValue> = refs
4481 .iter()
4482 .map(|l| l.get(i).cloned().unwrap_or(PerlValue::UNDEF))
4483 .collect();
4484 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
4485 out.push(result);
4486 }
4487 Ok(match wa {
4488 WantarrayCtx::List => PerlValue::array(out),
4489 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4490 WantarrayCtx::Void => PerlValue::UNDEF,
4491 })
4492 }
4493 "count_by" => {
4494 let mut counts = indexmap::IndexMap::new();
4495 for item in items {
4496 self.scope.set_topic(item.clone());
4497 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4498 let k = key.to_string();
4499 let entry = counts.entry(k).or_insert(PerlValue::integer(0));
4500 *entry = PerlValue::integer(entry.to_int() + 1);
4501 }
4502 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(counts))))
4503 }
4504 _ => Err(PerlError::runtime(
4505 format!("internal: unknown list block builtin `{name}`"),
4506 line,
4507 )
4508 .into()),
4509 }
4510 }
4511
4512 pub(crate) fn builtin_rmdir_execute(
4514 &mut self,
4515 args: &[PerlValue],
4516 _line: usize,
4517 ) -> PerlResult<PerlValue> {
4518 let mut count = 0i64;
4519 for a in args {
4520 let p = a.to_string();
4521 if p.is_empty() {
4522 continue;
4523 }
4524 if std::fs::remove_dir(&p).is_ok() {
4525 count += 1;
4526 }
4527 }
4528 Ok(PerlValue::integer(count))
4529 }
4530
4531 pub(crate) fn builtin_touch_execute(
4533 &mut self,
4534 args: &[PerlValue],
4535 _line: usize,
4536 ) -> PerlResult<PerlValue> {
4537 let paths: Vec<String> = args.iter().map(|v| v.to_string()).collect();
4538 Ok(PerlValue::integer(crate::perl_fs::touch_paths(&paths)))
4539 }
4540
4541 pub(crate) fn builtin_utime_execute(
4543 &mut self,
4544 args: &[PerlValue],
4545 line: usize,
4546 ) -> PerlResult<PerlValue> {
4547 if args.len() < 3 {
4548 return Err(PerlError::runtime(
4549 "utime requires at least three arguments (atime, mtime, files...)",
4550 line,
4551 ));
4552 }
4553 let at = args[0].to_int();
4554 let mt = args[1].to_int();
4555 let paths: Vec<String> = args.iter().skip(2).map(|v| v.to_string()).collect();
4556 let n = crate::perl_fs::utime_paths(at, mt, &paths);
4557 #[cfg(not(unix))]
4558 if !paths.is_empty() && n == 0 {
4559 return Err(PerlError::runtime(
4560 "utime is not supported on this platform",
4561 line,
4562 ));
4563 }
4564 Ok(PerlValue::integer(n))
4565 }
4566
4567 pub(crate) fn builtin_umask_execute(
4569 &mut self,
4570 args: &[PerlValue],
4571 line: usize,
4572 ) -> PerlResult<PerlValue> {
4573 #[cfg(unix)]
4574 {
4575 let _ = line;
4576 if args.is_empty() {
4577 let cur = unsafe { libc::umask(0) };
4578 unsafe { libc::umask(cur) };
4579 return Ok(PerlValue::integer(cur as i64));
4580 }
4581 let new_m = args[0].to_int() as libc::mode_t;
4582 let old = unsafe { libc::umask(new_m) };
4583 Ok(PerlValue::integer(old as i64))
4584 }
4585 #[cfg(not(unix))]
4586 {
4587 let _ = args;
4588 Err(PerlError::runtime(
4589 "umask is not supported on this platform",
4590 line,
4591 ))
4592 }
4593 }
4594
4595 pub(crate) fn builtin_getcwd_execute(
4597 &mut self,
4598 args: &[PerlValue],
4599 line: usize,
4600 ) -> PerlResult<PerlValue> {
4601 if !args.is_empty() {
4602 return Err(PerlError::runtime("getcwd takes no arguments", line));
4603 }
4604 match std::env::current_dir() {
4605 Ok(p) => Ok(PerlValue::string(p.to_string_lossy().into_owned())),
4606 Err(e) => {
4607 self.apply_io_error_to_errno(&e);
4608 Ok(PerlValue::UNDEF)
4609 }
4610 }
4611 }
4612
4613 pub(crate) fn builtin_realpath_execute(
4615 &mut self,
4616 args: &[PerlValue],
4617 line: usize,
4618 ) -> PerlResult<PerlValue> {
4619 let path = args
4620 .first()
4621 .ok_or_else(|| PerlError::runtime("realpath: need path", line))?
4622 .to_string();
4623 if path.is_empty() {
4624 return Err(PerlError::runtime("realpath: need path", line));
4625 }
4626 match crate::perl_fs::realpath_resolved(&path) {
4627 Ok(s) => Ok(PerlValue::string(s)),
4628 Err(e) => {
4629 self.apply_io_error_to_errno(&e);
4630 Ok(PerlValue::UNDEF)
4631 }
4632 }
4633 }
4634
4635 pub(crate) fn builtin_pipe_execute(
4637 &mut self,
4638 args: &[PerlValue],
4639 line: usize,
4640 ) -> PerlResult<PerlValue> {
4641 if args.len() != 2 {
4642 return Err(PerlError::runtime(
4643 "pipe requires exactly two arguments",
4644 line,
4645 ));
4646 }
4647 #[cfg(unix)]
4648 {
4649 use std::fs::File;
4650 use std::os::unix::io::FromRawFd;
4651
4652 let read_name = args[0].to_string();
4653 let write_name = args[1].to_string();
4654 if read_name.is_empty() || write_name.is_empty() {
4655 return Err(PerlError::runtime("pipe: invalid handle name", line));
4656 }
4657 let mut fds = [0i32; 2];
4658 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
4659 let e = std::io::Error::last_os_error();
4660 self.apply_io_error_to_errno(&e);
4661 return Ok(PerlValue::integer(0));
4662 }
4663 let read_file = unsafe { File::from_raw_fd(fds[0]) };
4664 let write_file = unsafe { File::from_raw_fd(fds[1]) };
4665
4666 let read_shared = Arc::new(Mutex::new(read_file));
4667 let write_shared = Arc::new(Mutex::new(write_file));
4668
4669 self.close_builtin_execute(read_name.clone()).ok();
4670 self.close_builtin_execute(write_name.clone()).ok();
4671
4672 self.io_file_slots
4673 .insert(read_name.clone(), Arc::clone(&read_shared));
4674 self.input_handles.insert(
4675 read_name,
4676 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
4677 );
4678
4679 self.io_file_slots
4680 .insert(write_name.clone(), Arc::clone(&write_shared));
4681 self.output_handles
4682 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
4683
4684 Ok(PerlValue::integer(1))
4685 }
4686 #[cfg(not(unix))]
4687 {
4688 let _ = args;
4689 Err(PerlError::runtime(
4690 "pipe is not supported on this platform",
4691 line,
4692 ))
4693 }
4694 }
4695
4696 pub(crate) fn close_builtin_execute(&mut self, name: String) -> PerlResult<PerlValue> {
4697 self.output_handles.remove(&name);
4698 self.input_handles.remove(&name);
4699 self.io_file_slots.remove(&name);
4700 if let Some(mut child) = self.pipe_children.remove(&name) {
4701 if let Ok(st) = child.wait() {
4702 self.record_child_exit_status(st);
4703 }
4704 }
4705 Ok(PerlValue::integer(1))
4706 }
4707
4708 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
4709 self.input_handles.contains_key(name)
4710 }
4711
4712 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
4716 self.line_mode_eof_pending
4717 }
4718
4719 pub(crate) fn eof_builtin_execute(
4723 &self,
4724 args: &[PerlValue],
4725 line: usize,
4726 ) -> PerlResult<PerlValue> {
4727 match args.len() {
4728 0 => Ok(PerlValue::integer(if self.eof_without_arg_is_true() {
4729 1
4730 } else {
4731 0
4732 })),
4733 1 => {
4734 let name = args[0].to_string();
4735 let at_eof = !self.has_input_handle(&name);
4736 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
4737 }
4738 _ => Err(PerlError::runtime("eof: too many arguments", line)),
4739 }
4740 }
4741
4742 pub(crate) fn study_return_value(s: &str) -> PerlValue {
4745 if s.is_empty() {
4746 PerlValue::string(String::new())
4747 } else {
4748 PerlValue::integer(1)
4749 }
4750 }
4751
4752 pub(crate) fn readline_builtin_execute(
4753 &mut self,
4754 handle: Option<&str>,
4755 ) -> PerlResult<PerlValue> {
4756 if handle.is_none() {
4758 let argv = self.scope.get_array("ARGV");
4759 if !argv.is_empty() {
4760 loop {
4761 if self.diamond_reader.is_none() {
4762 while self.diamond_next_idx < argv.len() {
4763 let path = argv[self.diamond_next_idx].to_string();
4764 self.diamond_next_idx += 1;
4765 match File::open(&path) {
4766 Ok(f) => {
4767 self.argv_current_file = path;
4768 self.diamond_reader = Some(BufReader::new(f));
4769 break;
4770 }
4771 Err(e) => {
4772 self.apply_io_error_to_errno(&e);
4773 }
4774 }
4775 }
4776 if self.diamond_reader.is_none() {
4777 return Ok(PerlValue::UNDEF);
4778 }
4779 }
4780 let mut line_str = String::new();
4781 let read_result: Result<usize, io::Error> =
4782 if let Some(reader) = self.diamond_reader.as_mut() {
4783 if self.open_pragma_utf8 {
4784 let mut buf = Vec::new();
4785 reader.read_until(b'\n', &mut buf).inspect(|n| {
4786 if *n > 0 {
4787 line_str = String::from_utf8_lossy(&buf).into_owned();
4788 }
4789 })
4790 } else {
4791 let mut buf = Vec::new();
4792 match reader.read_until(b'\n', &mut buf) {
4793 Ok(n) => {
4794 if n > 0 {
4795 line_str =
4796 crate::perl_decode::decode_utf8_or_latin1_read_until(
4797 &buf,
4798 );
4799 }
4800 Ok(n)
4801 }
4802 Err(e) => Err(e),
4803 }
4804 }
4805 } else {
4806 unreachable!()
4807 };
4808 match read_result {
4809 Ok(0) => {
4810 self.diamond_reader = None;
4811 continue;
4812 }
4813 Ok(_) => {
4814 self.bump_line_for_handle(&self.argv_current_file.clone());
4815 return Ok(PerlValue::string(line_str));
4816 }
4817 Err(e) => {
4818 self.apply_io_error_to_errno(&e);
4819 self.diamond_reader = None;
4820 continue;
4821 }
4822 }
4823 }
4824 } else {
4825 self.argv_current_file.clear();
4826 }
4827 }
4828
4829 let handle_name = handle.unwrap_or("STDIN");
4830 let mut line_str = String::new();
4831 if handle_name == "STDIN" {
4832 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
4833 self.last_stdin_die_bracket = if handle.is_none() {
4834 "<>".to_string()
4835 } else {
4836 "<STDIN>".to_string()
4837 };
4838 self.bump_line_for_handle("STDIN");
4839 return Ok(PerlValue::string(queued));
4840 }
4841 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4842 let mut buf = Vec::new();
4843 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
4844 if *n > 0 {
4845 line_str = String::from_utf8_lossy(&buf).into_owned();
4846 }
4847 })
4848 } else {
4849 let mut buf = Vec::new();
4850 let mut lock = io::stdin().lock();
4851 match lock.read_until(b'\n', &mut buf) {
4852 Ok(n) => {
4853 if n > 0 {
4854 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4855 }
4856 Ok(n)
4857 }
4858 Err(e) => Err(e),
4859 }
4860 };
4861 match r {
4862 Ok(0) => Ok(PerlValue::UNDEF),
4863 Ok(_) => {
4864 self.last_stdin_die_bracket = if handle.is_none() {
4865 "<>".to_string()
4866 } else {
4867 "<STDIN>".to_string()
4868 };
4869 self.bump_line_for_handle("STDIN");
4870 Ok(PerlValue::string(line_str))
4871 }
4872 Err(e) => {
4873 self.apply_io_error_to_errno(&e);
4874 Ok(PerlValue::UNDEF)
4875 }
4876 }
4877 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
4878 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4879 let mut buf = Vec::new();
4880 reader.read_until(b'\n', &mut buf).inspect(|n| {
4881 if *n > 0 {
4882 line_str = String::from_utf8_lossy(&buf).into_owned();
4883 }
4884 })
4885 } else {
4886 let mut buf = Vec::new();
4887 match reader.read_until(b'\n', &mut buf) {
4888 Ok(n) => {
4889 if n > 0 {
4890 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4891 }
4892 Ok(n)
4893 }
4894 Err(e) => Err(e),
4895 }
4896 };
4897 match r {
4898 Ok(0) => Ok(PerlValue::UNDEF),
4899 Ok(_) => {
4900 self.bump_line_for_handle(handle_name);
4901 Ok(PerlValue::string(line_str))
4902 }
4903 Err(e) => {
4904 self.apply_io_error_to_errno(&e);
4905 Ok(PerlValue::UNDEF)
4906 }
4907 }
4908 } else {
4909 Ok(PerlValue::UNDEF)
4910 }
4911 }
4912
4913 pub(crate) fn readline_builtin_execute_list(
4915 &mut self,
4916 handle: Option<&str>,
4917 ) -> PerlResult<PerlValue> {
4918 let mut lines = Vec::new();
4919 loop {
4920 let v = self.readline_builtin_execute(handle)?;
4921 if v.is_undef() {
4922 break;
4923 }
4924 lines.push(v);
4925 }
4926 Ok(PerlValue::array(lines))
4927 }
4928
4929 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> PerlValue {
4930 match std::fs::read_dir(path) {
4931 Ok(rd) => {
4932 let entries: Vec<String> = rd
4933 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
4934 .collect();
4935 self.dir_handles
4936 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
4937 PerlValue::integer(1)
4938 }
4939 Err(e) => {
4940 self.apply_io_error_to_errno(&e);
4941 PerlValue::integer(0)
4942 }
4943 }
4944 }
4945
4946 pub(crate) fn readdir_handle(&mut self, handle: &str) -> PerlValue {
4947 if let Some(dh) = self.dir_handles.get_mut(handle) {
4948 if dh.pos < dh.entries.len() {
4949 let s = dh.entries[dh.pos].clone();
4950 dh.pos += 1;
4951 PerlValue::string(s)
4952 } else {
4953 PerlValue::UNDEF
4954 }
4955 } else {
4956 PerlValue::UNDEF
4957 }
4958 }
4959
4960 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> PerlValue {
4962 if let Some(dh) = self.dir_handles.get_mut(handle) {
4963 let rest: Vec<PerlValue> = dh.entries[dh.pos..]
4964 .iter()
4965 .cloned()
4966 .map(PerlValue::string)
4967 .collect();
4968 dh.pos = dh.entries.len();
4969 PerlValue::array(rest)
4970 } else {
4971 PerlValue::array(Vec::new())
4972 }
4973 }
4974
4975 pub(crate) fn closedir_handle(&mut self, handle: &str) -> PerlValue {
4976 PerlValue::integer(if self.dir_handles.remove(handle).is_some() {
4977 1
4978 } else {
4979 0
4980 })
4981 }
4982
4983 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> PerlValue {
4984 if let Some(dh) = self.dir_handles.get_mut(handle) {
4985 dh.pos = 0;
4986 PerlValue::integer(1)
4987 } else {
4988 PerlValue::integer(0)
4989 }
4990 }
4991
4992 pub(crate) fn telldir_handle(&mut self, handle: &str) -> PerlValue {
4993 self.dir_handles
4994 .get(handle)
4995 .map(|dh| PerlValue::integer(dh.pos as i64))
4996 .unwrap_or(PerlValue::UNDEF)
4997 }
4998
4999 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> PerlValue {
5000 if let Some(dh) = self.dir_handles.get_mut(handle) {
5001 dh.pos = pos.min(dh.entries.len());
5002 PerlValue::integer(1)
5003 } else {
5004 PerlValue::integer(0)
5005 }
5006 }
5007
5008 #[inline]
5013 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
5014 crate::special_vars::is_regex_match_scalar_name(name)
5015 }
5016
5017 #[inline]
5021 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
5022 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
5023 self.regex_capture_scope_fresh = false;
5024 }
5025 }
5026
5027 pub(crate) fn apply_regex_captures(
5028 &mut self,
5029 haystack: &str,
5030 offset: usize,
5031 re: &PerlCompiledRegex,
5032 caps: &PerlCaptures<'_>,
5033 capture_all: CaptureAllMode,
5034 ) -> Result<(), FlowOrError> {
5035 let m0 = caps.get(0).expect("regex capture 0");
5036 let s0 = offset + m0.start;
5037 let e0 = offset + m0.end;
5038 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
5039 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
5040 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
5041 let mut last_paren = String::new();
5042 for i in 1..caps.len() {
5043 if let Some(m) = caps.get(i) {
5044 last_paren = m.text.to_string();
5045 }
5046 }
5047 self.last_paren_match = last_paren;
5048 self.last_subpattern_name = String::new();
5049 for n in re.capture_names().flatten() {
5050 if caps.name(n).is_some() {
5051 self.last_subpattern_name = n.to_string();
5052 }
5053 }
5054 self.scope
5055 .set_scalar("&", PerlValue::string(self.last_match.clone()))?;
5056 self.scope
5057 .set_scalar("`", PerlValue::string(self.prematch.clone()))?;
5058 self.scope
5059 .set_scalar("'", PerlValue::string(self.postmatch.clone()))?;
5060 self.scope
5061 .set_scalar("+", PerlValue::string(self.last_paren_match.clone()))?;
5062 for i in 1..caps.len() {
5063 if let Some(m) = caps.get(i) {
5064 self.scope
5065 .set_scalar(&i.to_string(), PerlValue::string(m.text.to_string()))?;
5066 }
5067 }
5068 let mut start_arr = vec![PerlValue::integer(s0 as i64)];
5069 let mut end_arr = vec![PerlValue::integer(e0 as i64)];
5070 for i in 1..caps.len() {
5071 if let Some(m) = caps.get(i) {
5072 start_arr.push(PerlValue::integer((offset + m.start) as i64));
5073 end_arr.push(PerlValue::integer((offset + m.end) as i64));
5074 } else {
5075 start_arr.push(PerlValue::integer(-1));
5076 end_arr.push(PerlValue::integer(-1));
5077 }
5078 }
5079 self.scope.set_array("-", start_arr)?;
5080 self.scope.set_array("+", end_arr)?;
5081 let mut named = IndexMap::new();
5082 for name in re.capture_names().flatten() {
5083 if let Some(m) = caps.name(name) {
5084 named.insert(name.to_string(), PerlValue::string(m.text.to_string()));
5085 }
5086 }
5087 self.scope.set_hash("+", named.clone())?;
5088 let mut named_minus = IndexMap::new();
5090 for (name, val) in &named {
5091 named_minus.insert(
5092 name.clone(),
5093 PerlValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
5094 );
5095 }
5096 self.scope.set_hash("-", named_minus)?;
5097 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
5098 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
5099 match capture_all {
5100 CaptureAllMode::Empty => {
5101 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5102 }
5103 CaptureAllMode::Append => {
5104 let mut rows = self.scope.get_array("^CAPTURE_ALL");
5105 rows.push(PerlValue::array(cap_flat));
5106 self.scope.set_array("^CAPTURE_ALL", rows)?;
5107 }
5108 CaptureAllMode::Skip => {}
5109 }
5110 Ok(())
5111 }
5112
5113 pub(crate) fn clear_flip_flop_state(&mut self) {
5114 self.flip_flop_active.clear();
5115 self.flip_flop_exclusive_left_line.clear();
5116 self.flip_flop_sequence.clear();
5117 self.flip_flop_last_dot.clear();
5118 self.flip_flop_tree.clear();
5119 }
5120
5121 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
5122 self.flip_flop_active.resize(slots as usize, false);
5123 self.flip_flop_active.fill(false);
5124 self.flip_flop_exclusive_left_line
5125 .resize(slots as usize, None);
5126 self.flip_flop_exclusive_left_line.fill(None);
5127 self.flip_flop_sequence.resize(slots as usize, 0);
5128 self.flip_flop_sequence.fill(0);
5129 self.flip_flop_last_dot.resize(slots as usize, None);
5130 self.flip_flop_last_dot.fill(None);
5131 }
5132
5133 #[inline]
5137 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
5138 if self.last_readline_handle.is_empty() {
5139 self.line_number
5140 } else {
5141 *self
5142 .handle_line_numbers
5143 .get(&self.last_readline_handle)
5144 .unwrap_or(&0)
5145 }
5146 }
5147
5148 pub(crate) fn scalar_flip_flop_eval(
5157 &mut self,
5158 left: i64,
5159 right: i64,
5160 slot: usize,
5161 exclusive: bool,
5162 ) -> PerlResult<PerlValue> {
5163 if self.flip_flop_active.len() <= slot {
5164 self.flip_flop_active.resize(slot + 1, false);
5165 }
5166 if self.flip_flop_exclusive_left_line.len() <= slot {
5167 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5168 }
5169 if self.flip_flop_sequence.len() <= slot {
5170 self.flip_flop_sequence.resize(slot + 1, 0);
5171 }
5172 if self.flip_flop_last_dot.len() <= slot {
5173 self.flip_flop_last_dot.resize(slot + 1, None);
5174 }
5175 let dot = self.scalar_flipflop_dot_line();
5176 let active = &mut self.flip_flop_active[slot];
5177 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5178 let seq = &mut self.flip_flop_sequence[slot];
5179 let last_dot = &mut self.flip_flop_last_dot[slot];
5180 if !*active {
5181 if dot == left {
5182 *active = true;
5183 *seq = 1;
5184 *last_dot = Some(dot);
5185 if exclusive {
5186 *excl_left = Some(dot);
5187 } else {
5188 *excl_left = None;
5189 if dot == right {
5190 *active = false;
5191 return Ok(PerlValue::string(format!("{}E0", *seq)));
5192 }
5193 }
5194 return Ok(PerlValue::string(seq.to_string()));
5195 }
5196 *last_dot = Some(dot);
5197 return Ok(PerlValue::string(String::new()));
5198 }
5199 if *last_dot != Some(dot) {
5202 *seq += 1;
5203 *last_dot = Some(dot);
5204 }
5205 let cur_seq = *seq;
5206 if let Some(ll) = *excl_left {
5207 if dot == right && dot > ll {
5208 *active = false;
5209 *excl_left = None;
5210 *seq = 0;
5211 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
5212 }
5213 } else if dot == right {
5214 *active = false;
5215 *seq = 0;
5216 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
5217 }
5218 Ok(PerlValue::string(cur_seq.to_string()))
5219 }
5220
5221 fn regex_flip_flop_transition(
5222 active: &mut bool,
5223 excl_left: &mut Option<i64>,
5224 exclusive: bool,
5225 dot: i64,
5226 left_m: bool,
5227 right_m: bool,
5228 ) -> i64 {
5229 if !*active {
5230 if left_m {
5231 *active = true;
5232 if exclusive {
5233 *excl_left = Some(dot);
5234 } else {
5235 *excl_left = None;
5236 if right_m {
5237 *active = false;
5238 }
5239 }
5240 return 1;
5241 }
5242 return 0;
5243 }
5244 if let Some(ll) = *excl_left {
5245 if right_m && dot > ll {
5246 *active = false;
5247 *excl_left = None;
5248 }
5249 } else if right_m {
5250 *active = false;
5251 }
5252 1
5253 }
5254
5255 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
5260 &mut self,
5261 left_pat: &str,
5262 left_flags: &str,
5263 right_pat: &str,
5264 right_flags: &str,
5265 slot: usize,
5266 exclusive: bool,
5267 line: usize,
5268 ) -> PerlResult<PerlValue> {
5269 let dot = self.scalar_flipflop_dot_line();
5270 let subject = self.scope.get_scalar("_").to_string();
5271 let left_re = self
5272 .compile_regex(left_pat, left_flags, line)
5273 .map_err(|e| match e {
5274 FlowOrError::Error(err) => err,
5275 FlowOrError::Flow(_) => {
5276 PerlError::runtime("unexpected flow in regex flip-flop", line)
5277 }
5278 })?;
5279 let right_re = self
5280 .compile_regex(right_pat, right_flags, line)
5281 .map_err(|e| match e {
5282 FlowOrError::Error(err) => err,
5283 FlowOrError::Flow(_) => {
5284 PerlError::runtime("unexpected flow in regex flip-flop", line)
5285 }
5286 })?;
5287 let left_m = left_re.is_match(&subject);
5288 let right_m = right_re.is_match(&subject);
5289 if self.flip_flop_active.len() <= slot {
5290 self.flip_flop_active.resize(slot + 1, false);
5291 }
5292 if self.flip_flop_exclusive_left_line.len() <= slot {
5293 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5294 }
5295 let active = &mut self.flip_flop_active[slot];
5296 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5297 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5298 active, excl_left, exclusive, dot, left_m, right_m,
5299 )))
5300 }
5301
5302 pub(crate) fn regex_flip_flop_eval_dynamic_right(
5304 &mut self,
5305 left_pat: &str,
5306 left_flags: &str,
5307 slot: usize,
5308 exclusive: bool,
5309 line: usize,
5310 right_m: bool,
5311 ) -> PerlResult<PerlValue> {
5312 let dot = self.scalar_flipflop_dot_line();
5313 let subject = self.scope.get_scalar("_").to_string();
5314 let left_re = self
5315 .compile_regex(left_pat, left_flags, line)
5316 .map_err(|e| match e {
5317 FlowOrError::Error(err) => err,
5318 FlowOrError::Flow(_) => {
5319 PerlError::runtime("unexpected flow in regex flip-flop", line)
5320 }
5321 })?;
5322 let left_m = left_re.is_match(&subject);
5323 if self.flip_flop_active.len() <= slot {
5324 self.flip_flop_active.resize(slot + 1, false);
5325 }
5326 if self.flip_flop_exclusive_left_line.len() <= slot {
5327 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5328 }
5329 let active = &mut self.flip_flop_active[slot];
5330 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5331 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5332 active, excl_left, exclusive, dot, left_m, right_m,
5333 )))
5334 }
5335
5336 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
5338 &mut self,
5339 left_pat: &str,
5340 left_flags: &str,
5341 slot: usize,
5342 exclusive: bool,
5343 line: usize,
5344 rhs_line: i64,
5345 ) -> PerlResult<PerlValue> {
5346 let dot = self.scalar_flipflop_dot_line();
5347 let subject = self.scope.get_scalar("_").to_string();
5348 let left_re = self
5349 .compile_regex(left_pat, left_flags, line)
5350 .map_err(|e| match e {
5351 FlowOrError::Error(err) => err,
5352 FlowOrError::Flow(_) => {
5353 PerlError::runtime("unexpected flow in regex flip-flop", line)
5354 }
5355 })?;
5356 let left_m = left_re.is_match(&subject);
5357 let right_m = dot == rhs_line;
5358 if self.flip_flop_active.len() <= slot {
5359 self.flip_flop_active.resize(slot + 1, false);
5360 }
5361 if self.flip_flop_exclusive_left_line.len() <= slot {
5362 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5363 }
5364 let active = &mut self.flip_flop_active[slot];
5365 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5366 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5367 active, excl_left, exclusive, dot, left_m, right_m,
5368 )))
5369 }
5370
5371 pub(crate) fn regex_eof_flip_flop_eval(
5376 &mut self,
5377 left_pat: &str,
5378 left_flags: &str,
5379 slot: usize,
5380 exclusive: bool,
5381 line: usize,
5382 ) -> PerlResult<PerlValue> {
5383 let dot = self.scalar_flipflop_dot_line();
5384 let subject = self.scope.get_scalar("_").to_string();
5385 let left_re = self
5386 .compile_regex(left_pat, left_flags, line)
5387 .map_err(|e| match e {
5388 FlowOrError::Error(err) => err,
5389 FlowOrError::Flow(_) => {
5390 PerlError::runtime("unexpected flow in regex/eof flip-flop", line)
5391 }
5392 })?;
5393 let left_m = left_re.is_match(&subject);
5394 let right_m = self.eof_without_arg_is_true();
5395 if self.flip_flop_active.len() <= slot {
5396 self.flip_flop_active.resize(slot + 1, false);
5397 }
5398 if self.flip_flop_exclusive_left_line.len() <= slot {
5399 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5400 }
5401 let active = &mut self.flip_flop_active[slot];
5402 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5403 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5404 active, excl_left, exclusive, dot, left_m, right_m,
5405 )))
5406 }
5407
5408 pub(crate) fn builtin_read_into(
5412 &mut self,
5413 fh_val: PerlValue,
5414 var_name: &str,
5415 length: usize,
5416 line: usize,
5417 ) -> ExecResult {
5418 use std::io::Read;
5419 let fh = fh_val
5420 .as_io_handle_name()
5421 .unwrap_or_else(|| fh_val.to_string());
5422 let mut buf = vec![0u8; length];
5423 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
5424 slot.lock().read(&mut buf).unwrap_or(0)
5425 } else if fh == "STDIN" {
5426 std::io::stdin().read(&mut buf).unwrap_or(0)
5427 } else {
5428 return Err(PerlError::runtime(format!("read: unopened handle {}", fh), line).into());
5429 };
5430 buf.truncate(n);
5431 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
5432 let _ = self.scope.set_scalar(var_name, PerlValue::string(read_str));
5433 Ok(PerlValue::integer(n as i64))
5434 }
5435
5436 pub(crate) fn chomp_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
5437 match &target.kind {
5443 ExprKind::ArrayVar(name) => {
5444 let arr = self.scope.get_array(name);
5445 let mut total = 0i64;
5446 let mut new_arr = Vec::with_capacity(arr.len());
5447 for v in arr {
5448 let mut s = v.to_string();
5449 if s.ends_with('\n') {
5450 s.pop();
5451 total += 1;
5452 }
5453 new_arr.push(PerlValue::string(s));
5454 }
5455 self.scope
5456 .set_array(name, new_arr)
5457 .map_err(FlowOrError::Error)?;
5458 return Ok(PerlValue::integer(total));
5459 }
5460 ExprKind::HashVar(name) => {
5461 let h = self.scope.get_hash(name);
5462 let mut total = 0i64;
5463 let mut new_h: indexmap::IndexMap<String, PerlValue> =
5464 indexmap::IndexMap::with_capacity(h.len());
5465 for (k, v) in h {
5466 let mut s = v.to_string();
5467 if s.ends_with('\n') {
5468 s.pop();
5469 total += 1;
5470 }
5471 new_h.insert(k, PerlValue::string(s));
5472 }
5473 self.scope
5474 .set_hash(name, new_h)
5475 .map_err(FlowOrError::Error)?;
5476 return Ok(PerlValue::integer(total));
5477 }
5478 _ => {}
5479 }
5480 let mut s = val.to_string();
5481 let removed = if s.ends_with('\n') {
5482 s.pop();
5483 1i64
5484 } else {
5485 0i64
5486 };
5487 self.assign_value(target, PerlValue::string(s))?;
5488 Ok(PerlValue::integer(removed))
5489 }
5490
5491 pub(crate) fn chop_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
5493 match &target.kind {
5499 ExprKind::ArrayVar(name) => {
5500 let arr = self.scope.get_array(name);
5501 let mut last = PerlValue::UNDEF;
5502 let mut new_arr = Vec::with_capacity(arr.len());
5503 for v in arr {
5504 let mut s = v.to_string();
5505 if let Some(c) = s.pop() {
5506 last = PerlValue::string(c.to_string());
5507 }
5508 new_arr.push(PerlValue::string(s));
5509 }
5510 self.scope
5511 .set_array(name, new_arr)
5512 .map_err(FlowOrError::Error)?;
5513 return Ok(last);
5514 }
5515 ExprKind::HashVar(name) => {
5516 let h = self.scope.get_hash(name);
5517 let mut last = PerlValue::UNDEF;
5518 let mut new_h: indexmap::IndexMap<String, PerlValue> =
5519 indexmap::IndexMap::with_capacity(h.len());
5520 for (k, v) in h {
5521 let mut s = v.to_string();
5522 if let Some(c) = s.pop() {
5523 last = PerlValue::string(c.to_string());
5524 }
5525 new_h.insert(k, PerlValue::string(s));
5526 }
5527 self.scope
5528 .set_hash(name, new_h)
5529 .map_err(FlowOrError::Error)?;
5530 return Ok(last);
5531 }
5532 _ => {}
5533 }
5534 let mut s = val.to_string();
5535 let chopped = s
5536 .pop()
5537 .map(|c| PerlValue::string(c.to_string()))
5538 .unwrap_or(PerlValue::UNDEF);
5539 self.assign_value(target, PerlValue::string(s))?;
5540 Ok(chopped)
5541 }
5542
5543 pub(crate) fn regex_match_execute(
5545 &mut self,
5546 s: String,
5547 pattern: &str,
5548 flags: &str,
5549 scalar_g: bool,
5550 pos_key: &str,
5551 line: usize,
5552 ) -> ExecResult {
5553 if !flags.contains('g') && !scalar_g {
5561 let memo_hit = {
5562 if let Some(ref mem) = self.regex_match_memo {
5563 mem.pattern == pattern
5564 && mem.flags == flags
5565 && mem.multiline == self.multiline_match
5566 && mem.haystack == s
5567 } else {
5568 false
5569 }
5570 };
5571 if memo_hit {
5572 if self.regex_capture_scope_fresh {
5573 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
5574 }
5575 let (memo_s, memo_result) = {
5578 let mem = self.regex_match_memo.as_ref().expect("memo");
5579 (mem.haystack.clone(), mem.result.clone())
5580 };
5581 let re = self.compile_regex(pattern, flags, line)?;
5582 if let Some(caps) = re.captures(&memo_s) {
5583 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
5584 }
5585 self.regex_capture_scope_fresh = true;
5586 return Ok(memo_result);
5587 }
5588 }
5589 let re = self.compile_regex(pattern, flags, line)?;
5590 if flags.contains('g') && scalar_g {
5591 let key = pos_key.to_string();
5592 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
5593 if start == 0 {
5594 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5595 }
5596 if start > s.len() {
5597 self.regex_pos.insert(key, None);
5598 return Ok(PerlValue::integer(0));
5599 }
5600 let sub = s.get(start..).unwrap_or("");
5601 if let Some(caps) = re.captures(sub) {
5602 let overall = caps.get(0).expect("capture 0");
5603 let abs_end = start + overall.end;
5604 self.regex_pos.insert(key, Some(abs_end));
5605 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
5606 Ok(PerlValue::integer(1))
5607 } else {
5608 self.regex_pos.insert(key, None);
5609 Ok(PerlValue::integer(0))
5610 }
5611 } else if flags.contains('g') {
5612 let mut rows = Vec::new();
5613 let mut last_caps: Option<PerlCaptures<'_>> = None;
5614 for caps in re.captures_iter(&s) {
5615 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5616 &caps,
5617 )));
5618 last_caps = Some(caps);
5619 }
5620 self.scope.set_array("^CAPTURE_ALL", rows)?;
5621 let matches: Vec<PerlValue> = match &*re {
5622 PerlCompiledRegex::Rust(r) => r
5623 .find_iter(&s)
5624 .map(|m| PerlValue::string(m.as_str().to_string()))
5625 .collect(),
5626 PerlCompiledRegex::Fancy(r) => r
5627 .find_iter(&s)
5628 .filter_map(|m| m.ok())
5629 .map(|m| PerlValue::string(m.as_str().to_string()))
5630 .collect(),
5631 PerlCompiledRegex::Pcre2(r) => r
5632 .find_iter(s.as_bytes())
5633 .filter_map(|m| m.ok())
5634 .map(|m| {
5635 let t = s.get(m.start()..m.end()).unwrap_or("");
5636 PerlValue::string(t.to_string())
5637 })
5638 .collect(),
5639 };
5640 if matches.is_empty() {
5641 Ok(PerlValue::integer(0))
5642 } else {
5643 if let Some(caps) = last_caps {
5644 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
5645 }
5646 Ok(PerlValue::array(matches))
5647 }
5648 } else if let Some(caps) = re.captures(&s) {
5649 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
5650 let result = PerlValue::integer(1);
5651 self.regex_match_memo = Some(RegexMatchMemo {
5652 pattern: pattern.to_string(),
5653 flags: flags.to_string(),
5654 multiline: self.multiline_match,
5655 haystack: s,
5656 result: result.clone(),
5657 });
5658 self.regex_capture_scope_fresh = true;
5659 Ok(result)
5660 } else {
5661 let result = PerlValue::integer(0);
5662 self.regex_match_memo = Some(RegexMatchMemo {
5664 pattern: pattern.to_string(),
5665 flags: flags.to_string(),
5666 multiline: self.multiline_match,
5667 haystack: s,
5668 result: result.clone(),
5669 });
5670 Ok(result)
5673 }
5674 }
5675
5676 pub(crate) fn expand_env_braces_in_subst(
5680 &mut self,
5681 raw: &str,
5682 line: usize,
5683 ) -> PerlResult<String> {
5684 self.materialize_env_if_needed();
5685 let mut out = String::new();
5686 let mut rest = raw;
5687 while let Some(idx) = rest.find("$ENV{") {
5688 out.push_str(&rest[..idx]);
5689 let after = &rest[idx + 5..];
5690 let end = after
5691 .find('}')
5692 .ok_or_else(|| PerlError::runtime("Unclosed $ENV{...} in s///", line))?;
5693 let key = &after[..end];
5694 let val = self.scope.get_hash_element("ENV", key);
5695 out.push_str(&val.to_string());
5696 rest = &after[end + 1..];
5697 }
5698 out.push_str(rest);
5699 Ok(out)
5700 }
5701
5702 pub(crate) fn regex_subst_execute(
5708 &mut self,
5709 s: String,
5710 pattern: &str,
5711 replacement: &str,
5712 flags: &str,
5713 target: &Expr,
5714 line: usize,
5715 ) -> ExecResult {
5716 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
5717 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
5718 let re = self.compile_regex(&pattern, &re_flags, line)?;
5719 if flags.contains('e') {
5720 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
5721 }
5722 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
5723 let replacement = self.interpolate_replacement_string(&replacement);
5724 let replacement = normalize_replacement_backrefs(&replacement);
5725 let last_caps = if flags.contains('g') {
5726 let mut rows = Vec::new();
5727 let mut last = None;
5728 for caps in re.captures_iter(&s) {
5729 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5730 &caps,
5731 )));
5732 last = Some(caps);
5733 }
5734 self.scope.set_array("^CAPTURE_ALL", rows)?;
5735 last
5736 } else {
5737 re.captures(&s)
5738 };
5739 if let Some(caps) = last_caps {
5740 let mode = if flags.contains('g') {
5741 CaptureAllMode::Skip
5742 } else {
5743 CaptureAllMode::Empty
5744 };
5745 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
5746 }
5747 let (new_s, count) = if flags.contains('g') {
5748 let count = re.find_iter_count(&s);
5749 (re.replace_all(&s, replacement.as_str()), count)
5750 } else {
5751 let count = if re.is_match(&s) { 1 } else { 0 };
5752 (re.replace(&s, replacement.as_str()), count)
5753 };
5754 if flags.contains('r') {
5755 Ok(PerlValue::string(new_s))
5757 } else {
5758 self.assign_value(target, PerlValue::string(new_s))?;
5759 Ok(PerlValue::integer(count as i64))
5760 }
5761 }
5762
5763 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
5766 let prep_source = |raw: &str| -> String {
5767 let mut code = raw.trim().to_string();
5768 if !code.ends_with(';') {
5769 code.push(';');
5770 }
5771 code
5772 };
5773 let mut cur = prep_source(replacement);
5774 let mut last = PerlValue::UNDEF;
5775 for round in 0..e_count {
5776 last = crate::parse_and_run_string(&cur, self)?;
5777 if round + 1 < e_count {
5778 cur = prep_source(&last.to_string());
5779 }
5780 }
5781 Ok(last)
5782 }
5783
5784 fn regex_subst_execute_eval(
5785 &mut self,
5786 s: String,
5787 re: &PerlCompiledRegex,
5788 replacement: &str,
5789 flags: &str,
5790 target: &Expr,
5791 line: usize,
5792 ) -> ExecResult {
5793 let e_count = flags.chars().filter(|c| *c == 'e').count();
5794 if e_count == 0 {
5795 return Err(PerlError::runtime("s///e: internal error (no e flag)", line).into());
5796 }
5797
5798 if flags.contains('g') {
5799 let mut rows = Vec::new();
5800 let mut out = String::new();
5801 let mut last = 0usize;
5802 let mut count = 0usize;
5803 for caps in re.captures_iter(&s) {
5804 let m0 = caps.get(0).expect("regex capture 0");
5805 out.push_str(&s[last..m0.start]);
5806 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5807 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5808 out.push_str(&repl_val.to_string());
5809 last = m0.end;
5810 count += 1;
5811 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5812 &caps,
5813 )));
5814 }
5815 self.scope.set_array("^CAPTURE_ALL", rows)?;
5816 out.push_str(&s[last..]);
5817 if flags.contains('r') {
5818 return Ok(PerlValue::string(out));
5819 }
5820 self.assign_value(target, PerlValue::string(out))?;
5821 return Ok(PerlValue::integer(count as i64));
5822 }
5823 if let Some(caps) = re.captures(&s) {
5824 let m0 = caps.get(0).expect("regex capture 0");
5825 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5826 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5827 let mut out = String::new();
5828 out.push_str(&s[..m0.start]);
5829 out.push_str(&repl_val.to_string());
5830 out.push_str(&s[m0.end..]);
5831 if flags.contains('r') {
5832 return Ok(PerlValue::string(out));
5833 }
5834 self.assign_value(target, PerlValue::string(out))?;
5835 return Ok(PerlValue::integer(1));
5836 }
5837 if flags.contains('r') {
5838 return Ok(PerlValue::string(s));
5839 }
5840 self.assign_value(target, PerlValue::string(s))?;
5841 Ok(PerlValue::integer(0))
5842 }
5843
5844 pub(crate) fn regex_transliterate_execute(
5846 &mut self,
5847 s: String,
5848 from: &str,
5849 to: &str,
5850 flags: &str,
5851 target: &Expr,
5852 line: usize,
5853 ) -> ExecResult {
5854 let _ = line;
5855 let from_chars = Self::tr_expand_ranges(from);
5856 let to_chars = Self::tr_expand_ranges(to);
5857 let delete_mode = flags.contains('d');
5858 let mut count = 0i64;
5859 let new_s: String = s
5860 .chars()
5861 .filter_map(|c| {
5862 if let Some(pos) = from_chars.iter().position(|&fc| fc == c) {
5863 count += 1;
5864 if delete_mode {
5865 if pos < to_chars.len() {
5867 Some(to_chars[pos])
5868 } else {
5869 None }
5871 } else {
5872 Some(to_chars.get(pos).or(to_chars.last()).copied().unwrap_or(c))
5874 }
5875 } else {
5876 Some(c)
5877 }
5878 })
5879 .collect();
5880 if flags.contains('r') {
5881 Ok(PerlValue::string(new_s))
5883 } else {
5884 self.assign_value(target, PerlValue::string(new_s))?;
5885 Ok(PerlValue::integer(count))
5886 }
5887 }
5888
5889 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
5892 let raw: Vec<char> = spec.chars().collect();
5893 let mut out = Vec::with_capacity(raw.len());
5894 let mut i = 0;
5895 while i < raw.len() {
5896 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
5897 let start = raw[i] as u32;
5898 let end = raw[i + 2] as u32;
5899 for code in start..=end {
5900 if let Some(c) = char::from_u32(code) {
5901 out.push(c);
5902 }
5903 }
5904 i += 3;
5905 } else {
5906 out.push(raw[i]);
5907 i += 1;
5908 }
5909 }
5910 out
5911 }
5912
5913 pub(crate) fn splice_builtin_execute(
5915 &mut self,
5916 args: &[PerlValue],
5917 line: usize,
5918 ) -> PerlResult<PerlValue> {
5919 if args.is_empty() {
5920 return Err(PerlError::runtime("splice: missing array", line));
5921 }
5922 let arr_name = args[0].to_string();
5923 let arr_len = self.scope.array_len(&arr_name);
5924 let offset_val = args
5925 .get(1)
5926 .cloned()
5927 .unwrap_or_else(|| PerlValue::integer(0));
5928 let length_val = match args.get(2) {
5929 None => PerlValue::UNDEF,
5930 Some(v) => v.clone(),
5931 };
5932 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
5933 let rep_vals: Vec<PerlValue> = args.iter().skip(3).cloned().collect();
5934 let removed = self.scope.splice_in_place(&arr_name, off, end, rep_vals)?;
5935 Ok(match self.wantarray_kind {
5936 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
5937 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
5938 })
5939 }
5940
5941 pub(crate) fn unshift_builtin_execute(
5943 &mut self,
5944 args: &[PerlValue],
5945 line: usize,
5946 ) -> PerlResult<PerlValue> {
5947 if args.is_empty() {
5948 return Err(PerlError::runtime("unshift: missing array", line));
5949 }
5950 let arr_name = args[0].to_string();
5951 let mut flat_vals: Vec<PerlValue> = Vec::new();
5952 for a in args.iter().skip(1) {
5953 if let Some(items) = a.as_array_vec() {
5954 flat_vals.extend(items);
5955 } else {
5956 flat_vals.push(a.clone());
5957 }
5958 }
5959 let arr = self.scope.get_array_mut(&arr_name)?;
5960 for (i, v) in flat_vals.into_iter().enumerate() {
5961 arr.insert(i, v);
5962 }
5963 Ok(PerlValue::integer(arr.len() as i64))
5964 }
5965
5966 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
5969 if upper == 0.0 {
5970 self.rand_rng.gen_range(0.0..1.0)
5971 } else if upper > 0.0 {
5972 self.rand_rng.gen_range(0.0..upper)
5973 } else {
5974 self.rand_rng.gen_range(upper..0.0)
5975 }
5976 }
5977
5978 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
5980 let n = if let Some(s) = seed {
5981 s as i64
5982 } else {
5983 std::time::SystemTime::now()
5984 .duration_since(std::time::UNIX_EPOCH)
5985 .map(|d| d.as_secs() as i64)
5986 .unwrap_or(1)
5987 };
5988 let mag = n.unsigned_abs();
5989 self.rand_rng = StdRng::seed_from_u64(mag);
5990 n.abs()
5991 }
5992
5993 pub fn set_file(&mut self, file: &str) {
5994 self.file = file.to_string();
5995 }
5996
5997 pub fn repl_completion_names(&self) -> Vec<String> {
5999 let mut v = self.scope.repl_binding_names();
6000 v.extend(self.subs.keys().cloned());
6001 v.sort();
6002 v.dedup();
6003 v
6004 }
6005
6006 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
6008 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
6009 subs.sort();
6010 let mut classes: HashSet<String> = HashSet::new();
6011 for k in &subs {
6012 if let Some((pkg, rest)) = k.split_once("::") {
6013 if !rest.contains("::") {
6014 classes.insert(pkg.to_string());
6015 }
6016 }
6017 }
6018 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
6019 for bn in self.scope.repl_binding_names() {
6020 if let Some(r) = bn.strip_prefix('$') {
6021 let v = self.scope.get_scalar(r);
6022 if let Some(b) = v.as_blessed_ref() {
6023 blessed_scalars.insert(r.to_string(), b.class.clone());
6024 classes.insert(b.class.clone());
6025 }
6026 }
6027 }
6028 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
6029 for c in classes {
6030 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
6031 }
6032 ReplCompletionSnapshot {
6033 subs,
6034 blessed_scalars,
6035 isa_for_class,
6036 }
6037 }
6038
6039 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
6040 if n == 0 {
6041 return Err(FlowOrError::Error(PerlError::runtime(
6042 "bench: iteration count must be positive",
6043 line,
6044 )));
6045 }
6046 let mut samples = Vec::with_capacity(n);
6047 for _ in 0..n {
6048 let start = std::time::Instant::now();
6049 self.exec_block(body)?;
6050 samples.push(start.elapsed().as_secs_f64() * 1000.0);
6051 }
6052 let mut sorted = samples.clone();
6053 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6054 let min_ms = sorted[0];
6055 let mean = samples.iter().sum::<f64>() / n as f64;
6056 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
6057 .saturating_sub(1)
6058 .min(n - 1);
6059 let p99_ms = sorted[p99_idx];
6060 Ok(PerlValue::string(format!(
6061 "bench: n={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
6062 n, min_ms, mean, p99_ms
6063 )))
6064 }
6065
6066 pub fn execute(&mut self, program: &Program) -> PerlResult<PerlValue> {
6067 crate::serialize_normalize::install_class_defs(self.class_defs.clone());
6073 if self.line_mode_skip_main {
6075 crate::compile_and_run_prelude(program, self)?;
6076 return Ok(PerlValue::UNDEF);
6077 }
6078 crate::try_vm_execute(program, self)
6079 .expect("VM compilation must succeed — all execution is VM-only")
6080 }
6081
6082 pub fn run_end_blocks(&mut self) -> PerlResult<()> {
6084 self.global_phase = "END".to_string();
6085 let ends = std::mem::take(&mut self.end_blocks);
6086 for block in &ends {
6087 self.exec_block(block).map_err(|e| match e {
6088 FlowOrError::Error(e) => e,
6089 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in END", 0),
6090 })?;
6091 }
6092 Ok(())
6093 }
6094
6095 pub fn run_global_teardown(&mut self) -> PerlResult<()> {
6098 self.global_phase = "DESTRUCT".to_string();
6099 self.drain_pending_destroys(0)
6100 }
6101
6102 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> PerlResult<()> {
6104 loop {
6105 let batch = crate::pending_destroy::take_queue();
6106 if batch.is_empty() {
6107 break;
6108 }
6109 for (class, payload) in batch {
6110 let fq = format!("{}::DESTROY", class);
6111 let Some(sub) = self.subs.get(&fq).cloned() else {
6112 continue;
6113 };
6114 let inv = PerlValue::blessed(Arc::new(
6115 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
6116 ));
6117 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
6118 Ok(_) => {}
6119 Err(FlowOrError::Error(e)) => return Err(e),
6120 Err(FlowOrError::Flow(Flow::Return(_))) => {}
6121 Err(FlowOrError::Flow(other)) => {
6122 return Err(PerlError::runtime(
6123 format!("DESTROY: unexpected control flow ({other:?})"),
6124 line,
6125 ));
6126 }
6127 }
6128 }
6129 }
6130 Ok(())
6131 }
6132
6133 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
6134 self.exec_block_with_tail(block, WantarrayCtx::Void)
6135 }
6136
6137 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6140 let uses_goto = block
6141 .iter()
6142 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
6143 if uses_goto {
6144 self.scope_push_hook();
6145 let r = self.exec_block_with_goto_tail(block, tail);
6146 self.scope_pop_hook();
6147 r
6148 } else {
6149 self.scope_push_hook();
6150 let result = self.exec_block_no_scope_with_tail(block, tail);
6151 self.scope_pop_hook();
6152 result
6153 }
6154 }
6155
6156 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6157 let mut map: HashMap<String, usize> = HashMap::new();
6158 for (i, s) in block.iter().enumerate() {
6159 if let Some(l) = &s.label {
6160 map.insert(l.clone(), i);
6161 }
6162 }
6163 let mut pc = 0usize;
6164 let mut last = PerlValue::UNDEF;
6165 let last_idx = block.len().saturating_sub(1);
6166 while pc < block.len() {
6167 if let StmtKind::Goto { target } = &block[pc].kind {
6168 let line = block[pc].line;
6169 let name = self.eval_expr(target)?.to_string();
6170 pc = *map.get(&name).ok_or_else(|| {
6171 FlowOrError::Error(PerlError::runtime(
6172 format!("goto: unknown label {}", name),
6173 line,
6174 ))
6175 })?;
6176 continue;
6177 }
6178 let v = if pc == last_idx {
6179 match &block[pc].kind {
6180 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
6181 _ => self.exec_statement(&block[pc])?,
6182 }
6183 } else {
6184 self.exec_statement(&block[pc])?
6185 };
6186 last = v;
6187 pc += 1;
6188 }
6189 Ok(last)
6190 }
6191
6192 #[inline]
6195 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
6196 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
6197 }
6198
6199 pub(crate) fn exec_block_no_scope_with_tail(
6200 &mut self,
6201 block: &Block,
6202 tail: WantarrayCtx,
6203 ) -> ExecResult {
6204 if block.is_empty() {
6205 return Ok(PerlValue::UNDEF);
6206 }
6207 let last_i = block.len() - 1;
6208 for (i, stmt) in block.iter().enumerate() {
6209 if i < last_i {
6210 self.exec_statement(stmt)?;
6211 } else {
6212 return match &stmt.kind {
6213 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
6214 _ => self.exec_statement(stmt),
6215 };
6216 }
6217 }
6218 Ok(PerlValue::UNDEF)
6219 }
6220
6221 pub(crate) fn spawn_async_block(&self, block: &Block) -> PerlValue {
6223 use parking_lot::Mutex as ParkMutex;
6224
6225 let block = block.clone();
6226 let subs = self.subs.clone();
6227 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6228 let result = Arc::new(ParkMutex::new(None));
6229 let join = Arc::new(ParkMutex::new(None));
6230 let result2 = result.clone();
6231 let h = std::thread::spawn(move || {
6232 let mut interp = VMHelper::new();
6233 interp.subs = subs;
6234 interp.scope.restore_capture(&scalars);
6235 interp.scope.restore_atomics(&aar, &ahash);
6236 interp.enable_parallel_guard();
6237 let r = match interp.exec_block(&block) {
6238 Ok(v) => Ok(v),
6239 Err(FlowOrError::Error(e)) => Err(e),
6240 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6241 Err(PerlError::runtime("yield inside async/spawn block", 0))
6242 }
6243 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
6244 };
6245 *result2.lock() = Some(r);
6246 });
6247 *join.lock() = Some(h);
6248 PerlValue::async_task(Arc::new(PerlAsyncTask { result, join }))
6249 }
6250
6251 pub(crate) fn eval_timeout_block(
6253 &mut self,
6254 body: &Block,
6255 secs: f64,
6256 line: usize,
6257 ) -> ExecResult {
6258 use std::sync::mpsc::channel;
6259 use std::time::Duration;
6260
6261 let block = body.clone();
6262 let subs = self.subs.clone();
6263 let struct_defs = self.struct_defs.clone();
6264 let enum_defs = self.enum_defs.clone();
6265 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6266 self.materialize_env_if_needed();
6267 let env = self.env.clone();
6268 let argv = self.argv.clone();
6269 let inc = self.scope.get_array("INC");
6270 let (tx, rx) = channel::<PerlResult<PerlValue>>();
6271 let _handle = std::thread::spawn(move || {
6272 let mut interp = VMHelper::new();
6273 interp.subs = subs;
6274 interp.struct_defs = struct_defs;
6275 interp.enum_defs = enum_defs;
6276 interp.env = env.clone();
6277 interp.argv = argv.clone();
6278 interp.scope.declare_array(
6279 "ARGV",
6280 argv.iter().map(|s| PerlValue::string(s.clone())).collect(),
6281 );
6282 for (k, v) in env {
6283 interp
6284 .scope
6285 .set_hash_element("ENV", &k, v)
6286 .expect("set ENV in timeout thread");
6287 }
6288 interp.scope.declare_array("INC", inc);
6289 interp.scope.restore_capture(&scalars);
6290 interp.scope.restore_atomics(&aar, &ahash);
6291 interp.enable_parallel_guard();
6292 let out: PerlResult<PerlValue> = match interp.exec_block(&block) {
6293 Ok(v) => Ok(v),
6294 Err(FlowOrError::Error(e)) => Err(e),
6295 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6296 Err(PerlError::runtime("yield inside eval_timeout block", 0))
6297 }
6298 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
6299 };
6300 let _ = tx.send(out);
6301 });
6302 let dur = Duration::from_secs_f64(secs.max(0.0));
6303 match rx.recv_timeout(dur) {
6304 Ok(Ok(v)) => Ok(v),
6305 Ok(Err(e)) => Err(FlowOrError::Error(e)),
6306 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(PerlError::runtime(
6307 format!(
6308 "eval_timeout: exceeded {} second(s) (worker continues in background)",
6309 secs
6310 ),
6311 line,
6312 )
6313 .into()),
6314 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(PerlError::runtime(
6315 "eval_timeout: worker thread panicked or disconnected",
6316 line,
6317 )
6318 .into()),
6319 }
6320 }
6321
6322 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
6323 let mut last = PerlValue::UNDEF;
6324 for stmt in body {
6325 match &stmt.kind {
6326 StmtKind::When { cond, body: wb } => {
6327 if self.when_matches(cond)? {
6328 return self.exec_block_smart(wb);
6329 }
6330 }
6331 StmtKind::DefaultCase { body: db } => {
6332 return self.exec_block_smart(db);
6333 }
6334 _ => {
6335 last = self.exec_statement(stmt)?;
6336 }
6337 }
6338 }
6339 Ok(last)
6340 }
6341
6342 pub(crate) fn exec_given_with_topic_value(
6344 &mut self,
6345 topic: PerlValue,
6346 body: &Block,
6347 ) -> ExecResult {
6348 self.scope_push_hook();
6349 self.scope.declare_scalar("_", topic);
6350 self.english_note_lexical_scalar("_");
6351 let r = self.exec_given_body(body);
6352 self.scope_pop_hook();
6353 r
6354 }
6355
6356 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
6357 let t = self.eval_expr(topic)?;
6358 self.exec_given_with_topic_value(t, body)
6359 }
6360
6361 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6363 let topic = self.scope.get_scalar("_");
6364 let line = cond.line;
6365 match &cond.kind {
6366 ExprKind::Regex(pattern, flags) => {
6367 let re = self.compile_regex(pattern, flags, line)?;
6368 let s = topic.to_string();
6369 Ok(re.is_match(&s))
6370 }
6371 ExprKind::String(s) => Ok(topic.to_string() == *s),
6372 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
6373 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
6374 _ => {
6375 let c = self.eval_expr(cond)?;
6376 Ok(self.smartmatch_when(&topic, &c))
6377 }
6378 }
6379 }
6380
6381 fn smartmatch_when(&self, topic: &PerlValue, c: &PerlValue) -> bool {
6382 if let Some(re) = c.as_regex() {
6383 return re.is_match(&topic.to_string());
6384 }
6385 if let Some(arr) = c.as_array_ref() {
6392 let arr = arr.read();
6393 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
6394 }
6395 if let Some(arr) = c.as_array_vec() {
6396 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
6397 }
6398 if let Some(href) = c.as_hash_ref() {
6400 return href.read().contains_key(&topic.to_string());
6401 }
6402 if let Some(h) = c.as_hash_map() {
6403 return h.contains_key(&topic.to_string());
6404 }
6405 if let Some(sub) = c.as_code_ref() {
6407 let _ = sub;
6411 }
6412 if let (Some(a), Some(b)) = (topic.as_integer(), c.as_integer()) {
6414 return a == b;
6415 }
6416 topic.to_string() == c.to_string()
6417 }
6418
6419 pub(crate) fn eval_boolean_rvalue_condition(
6421 &mut self,
6422 cond: &Expr,
6423 ) -> Result<bool, FlowOrError> {
6424 match &cond.kind {
6425 ExprKind::Regex(pattern, flags) => {
6426 let topic = self.scope.get_scalar("_");
6427 let line = cond.line;
6428 let s = topic.to_string();
6429 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
6430 Ok(v.is_true())
6431 }
6432 ExprKind::ReadLine(_) => {
6434 let v = self.eval_expr(cond)?;
6435 self.scope.set_topic(v.clone());
6436 Ok(!v.is_undef())
6437 }
6438 _ => {
6439 let v = self.eval_expr(cond)?;
6440 Ok(v.is_true())
6441 }
6442 }
6443 }
6444
6445 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6447 self.eval_boolean_rvalue_condition(cond)
6448 }
6449
6450 pub(crate) fn eval_algebraic_match(
6451 &mut self,
6452 subject: &Expr,
6453 arms: &[MatchArm],
6454 line: usize,
6455 ) -> ExecResult {
6456 let val = self.eval_algebraic_match_subject(subject, line)?;
6457 self.eval_algebraic_match_with_subject_value(val, arms, line)
6458 }
6459
6460 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
6462 match &subject.kind {
6463 ExprKind::ArrayVar(name) => {
6464 self.check_strict_array_var(name, line)?;
6465 let aname = self.stash_array_name_for_package(name);
6466 Ok(PerlValue::array_binding_ref(aname))
6467 }
6468 ExprKind::HashVar(name) => {
6469 self.check_strict_hash_var(name, line)?;
6470 self.touch_env_hash(name);
6471 Ok(PerlValue::hash_binding_ref(name.clone()))
6472 }
6473 _ => self.eval_expr(subject),
6474 }
6475 }
6476
6477 pub(crate) fn eval_algebraic_match_with_subject_value(
6479 &mut self,
6480 val: PerlValue,
6481 arms: &[MatchArm],
6482 line: usize,
6483 ) -> ExecResult {
6484 if let Some(e) = val.as_enum_inst() {
6486 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
6487 if !has_catchall {
6488 let covered: Vec<String> = arms
6489 .iter()
6490 .filter_map(|a| {
6491 if let MatchPattern::Value(expr) = &a.pattern {
6492 if let ExprKind::FuncCall { name, .. } = &expr.kind {
6493 return name.rsplit_once("::").map(|(_, v)| v.to_string());
6494 }
6495 }
6496 None
6497 })
6498 .collect();
6499 let missing: Vec<&str> = e
6500 .def
6501 .variants
6502 .iter()
6503 .filter(|v| !covered.contains(&v.name))
6504 .map(|v| v.name.as_str())
6505 .collect();
6506 if !missing.is_empty() {
6507 return Err(PerlError::runtime(
6508 format!(
6509 "non-exhaustive match on enum `{}`: missing variant(s) {}",
6510 e.def.name,
6511 missing.join(", ")
6512 ),
6513 line,
6514 )
6515 .into());
6516 }
6517 }
6518 }
6519 for arm in arms {
6520 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
6521 let re = self.compile_regex(pattern, flags, line)?;
6522 let s = val.to_string();
6523 if let Some(caps) = re.captures(&s) {
6524 self.scope_push_hook();
6525 self.scope.declare_scalar("_", val.clone());
6526 self.english_note_lexical_scalar("_");
6527 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
6528 let guard_ok = if let Some(g) = &arm.guard {
6529 self.eval_expr(g)?.is_true()
6530 } else {
6531 true
6532 };
6533 if !guard_ok {
6534 self.scope_pop_hook();
6535 continue;
6536 }
6537 let out = self.eval_expr(&arm.body);
6538 self.scope_pop_hook();
6539 return out;
6540 }
6541 continue;
6542 }
6543 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
6544 self.scope_push_hook();
6545 self.scope.declare_scalar("_", val.clone());
6546 self.english_note_lexical_scalar("_");
6547 for b in bindings {
6548 match b {
6549 PatternBinding::Scalar(name, v) => {
6550 self.scope.declare_scalar(&name, v);
6551 self.english_note_lexical_scalar(&name);
6552 }
6553 PatternBinding::Array(name, elems) => {
6554 self.scope.declare_array(&name, elems);
6555 }
6556 }
6557 }
6558 let guard_ok = if let Some(g) = &arm.guard {
6559 self.eval_expr(g)?.is_true()
6560 } else {
6561 true
6562 };
6563 if !guard_ok {
6564 self.scope_pop_hook();
6565 continue;
6566 }
6567 let out = self.eval_expr(&arm.body);
6568 self.scope_pop_hook();
6569 return out;
6570 }
6571 }
6572 Err(PerlError::runtime(
6573 "match: no arm matched the value (add a `_` catch-all)",
6574 line,
6575 )
6576 .into())
6577 }
6578
6579 fn parse_duration_seconds(pv: &PerlValue) -> Option<f64> {
6580 let s = pv.to_string();
6581 let s = s.trim();
6582 if let Some(rest) = s.strip_suffix("ms") {
6583 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
6584 }
6585 if let Some(rest) = s.strip_suffix('s') {
6586 return rest.trim().parse::<f64>().ok();
6587 }
6588 if let Some(rest) = s.strip_suffix('m') {
6589 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
6590 }
6591 s.parse::<f64>().ok()
6592 }
6593
6594 fn eval_retry_block(
6595 &mut self,
6596 body: &Block,
6597 times: &Expr,
6598 backoff: RetryBackoff,
6599 _line: usize,
6600 ) -> ExecResult {
6601 let max = self.eval_expr(times)?.to_int().max(1) as usize;
6602 let base_ms: u64 = 10;
6603 let mut attempt = 0usize;
6604 loop {
6605 attempt += 1;
6606 match self.exec_block(body) {
6607 Ok(v) => return Ok(v),
6608 Err(FlowOrError::Error(e)) => {
6609 if attempt >= max {
6610 return Err(FlowOrError::Error(e));
6611 }
6612 let delay_ms = match backoff {
6613 RetryBackoff::None => 0,
6614 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
6615 RetryBackoff::Exponential => {
6616 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
6617 }
6618 };
6619 if delay_ms > 0 {
6620 std::thread::sleep(Duration::from_millis(delay_ms));
6621 }
6622 }
6623 Err(e) => return Err(e),
6624 }
6625 }
6626 }
6627
6628 fn eval_rate_limit_block(
6629 &mut self,
6630 slot: u32,
6631 max: &Expr,
6632 window: &Expr,
6633 body: &Block,
6634 _line: usize,
6635 ) -> ExecResult {
6636 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
6637 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
6638 .filter(|s| *s > 0.0)
6639 .unwrap_or(1.0);
6640 let window_d = Duration::from_secs_f64(window_sec);
6641 let slot = slot as usize;
6642 while self.rate_limit_slots.len() <= slot {
6643 self.rate_limit_slots.push(VecDeque::new());
6644 }
6645 {
6646 let dq = &mut self.rate_limit_slots[slot];
6647 loop {
6648 let now = Instant::now();
6649 while let Some(t0) = dq.front().copied() {
6650 if now.duration_since(t0) >= window_d {
6651 dq.pop_front();
6652 } else {
6653 break;
6654 }
6655 }
6656 if dq.len() < max_n || max_n == 0 {
6657 break;
6658 }
6659 let t0 = dq.front().copied().unwrap();
6660 let wait = window_d.saturating_sub(now.duration_since(t0));
6661 if wait.is_zero() {
6662 dq.pop_front();
6663 continue;
6664 }
6665 std::thread::sleep(wait);
6666 }
6667 dq.push_back(Instant::now());
6668 }
6669 self.exec_block(body)
6670 }
6671
6672 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
6673 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
6674 .filter(|s| *s > 0.0)
6675 .unwrap_or(1.0);
6676 loop {
6677 match self.exec_block(body) {
6678 Ok(_) => {}
6679 Err(e) => return Err(e),
6680 }
6681 std::thread::sleep(Duration::from_secs_f64(sec));
6682 }
6683 }
6684
6685 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> PerlResult<PerlValue> {
6687 let pair = |value: PerlValue, more: i64| {
6688 PerlValue::array_ref(Arc::new(RwLock::new(vec![value, PerlValue::integer(more)])))
6689 };
6690 let mut exhausted = gen.exhausted.lock();
6691 if *exhausted {
6692 return Ok(pair(PerlValue::UNDEF, 0));
6693 }
6694 let mut pc = gen.pc.lock();
6695 let mut scope_started = gen.scope_started.lock();
6696 if *pc >= gen.block.len() {
6697 if *scope_started {
6698 self.scope_pop_hook();
6699 *scope_started = false;
6700 }
6701 *exhausted = true;
6702 return Ok(pair(PerlValue::UNDEF, 0));
6703 }
6704 if !*scope_started {
6705 self.scope_push_hook();
6706 *scope_started = true;
6707 }
6708 self.in_generator = true;
6709 while *pc < gen.block.len() {
6710 let stmt = &gen.block[*pc];
6711 match self.exec_statement(stmt) {
6712 Ok(_) => {
6713 *pc += 1;
6714 }
6715 Err(FlowOrError::Flow(Flow::Yield(v))) => {
6716 *pc += 1;
6717 self.in_generator = false;
6718 if *scope_started {
6721 self.scope_pop_hook();
6722 *scope_started = false;
6723 }
6724 return Ok(pair(v, 1));
6725 }
6726 Err(e) => {
6727 self.in_generator = false;
6728 if *scope_started {
6729 self.scope_pop_hook();
6730 *scope_started = false;
6731 }
6732 return Err(match e {
6733 FlowOrError::Error(ee) => ee,
6734 FlowOrError::Flow(Flow::Yield(_)) => {
6735 unreachable!("yield handled above")
6736 }
6737 FlowOrError::Flow(flow) => PerlError::runtime(
6738 format!("unexpected control flow in generator: {:?}", flow),
6739 0,
6740 ),
6741 });
6742 }
6743 }
6744 }
6745 self.in_generator = false;
6746 if *scope_started {
6747 self.scope_pop_hook();
6748 *scope_started = false;
6749 }
6750 *exhausted = true;
6751 Ok(pair(PerlValue::UNDEF, 0))
6752 }
6753
6754 fn match_pattern_try(
6755 &mut self,
6756 subject: &PerlValue,
6757 pattern: &MatchPattern,
6758 line: usize,
6759 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6760 match pattern {
6761 MatchPattern::Any => Ok(Some(vec![])),
6762 MatchPattern::Regex { .. } => {
6763 unreachable!("regex arms are handled in eval_algebraic_match")
6764 }
6765 MatchPattern::Value(expr) => {
6766 if self.match_pattern_value_alternation(subject, expr, line)? {
6767 Ok(Some(vec![]))
6768 } else {
6769 Ok(None)
6770 }
6771 }
6772 MatchPattern::Array(elems) => {
6773 let Some(arr) = self.match_subject_as_array(subject) else {
6774 return Ok(None);
6775 };
6776 self.match_array_pattern_elems(&arr, elems, line)
6777 }
6778 MatchPattern::Hash(pairs) => {
6779 let Some(h) = self.match_subject_as_hash(subject) else {
6780 return Ok(None);
6781 };
6782 self.match_hash_pattern_pairs(&h, pairs, line)
6783 }
6784 MatchPattern::OptionSome(name) => {
6785 let Some(arr) = self.match_subject_as_array(subject) else {
6786 return Ok(None);
6787 };
6788 if arr.len() < 2 {
6789 return Ok(None);
6790 }
6791 if !arr[1].is_true() {
6792 return Ok(None);
6793 }
6794 Ok(Some(vec![PatternBinding::Scalar(
6795 name.clone(),
6796 arr[0].clone(),
6797 )]))
6798 }
6799 }
6800 }
6801
6802 fn match_pattern_value_alternation(
6805 &mut self,
6806 subject: &PerlValue,
6807 expr: &Expr,
6808 _line: usize,
6809 ) -> Result<bool, FlowOrError> {
6810 if let ExprKind::BinOp {
6811 left,
6812 op: BinOp::BitOr,
6813 right,
6814 } = &expr.kind
6815 {
6816 if self.match_pattern_value_alternation(subject, left, _line)? {
6817 return Ok(true);
6818 }
6819 return self.match_pattern_value_alternation(subject, right, _line);
6820 }
6821 let pv = self.eval_expr(expr)?;
6822 Ok(self.smartmatch_when(subject, &pv))
6823 }
6824
6825 fn match_subject_as_array(&self, v: &PerlValue) -> Option<Vec<PerlValue>> {
6827 if let Some(a) = v.as_array_vec() {
6828 return Some(a);
6829 }
6830 if let Some(r) = v.as_array_ref() {
6831 return Some(r.read().clone());
6832 }
6833 if let Some(name) = v.as_array_binding_name() {
6834 return Some(self.scope.get_array(&name));
6835 }
6836 None
6837 }
6838
6839 fn match_subject_as_hash(&mut self, v: &PerlValue) -> Option<IndexMap<String, PerlValue>> {
6840 if let Some(h) = v.as_hash_map() {
6841 return Some(h);
6842 }
6843 if let Some(r) = v.as_hash_ref() {
6844 return Some(r.read().clone());
6845 }
6846 if let Some(name) = v.as_hash_binding_name() {
6847 self.touch_env_hash(&name);
6848 return Some(self.scope.get_hash(&name));
6849 }
6850 None
6851 }
6852
6853 pub(crate) fn hash_slice_deref_values(
6856 &mut self,
6857 container: &PerlValue,
6858 key_values: &[PerlValue],
6859 line: usize,
6860 ) -> Result<PerlValue, FlowOrError> {
6861 let h = if let Some(m) = self.match_subject_as_hash(container) {
6862 m
6863 } else {
6864 return Err(PerlError::runtime(
6865 "Hash slice dereference needs a hash or hash reference value",
6866 line,
6867 )
6868 .into());
6869 };
6870 let mut result = Vec::new();
6871 for kv in key_values {
6872 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6873 vv.iter().map(|x| x.to_string()).collect()
6874 } else {
6875 vec![kv.to_string()]
6876 };
6877 for k in key_strings {
6878 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6879 }
6880 }
6881 Ok(PerlValue::array(result))
6882 }
6883
6884 pub(crate) fn assign_hash_slice_one_key(
6887 &mut self,
6888 container: PerlValue,
6889 key: &str,
6890 val: PerlValue,
6891 line: usize,
6892 ) -> Result<PerlValue, FlowOrError> {
6893 if let Some(r) = container.as_hash_ref() {
6894 r.write().insert(key.to_string(), val);
6895 return Ok(PerlValue::UNDEF);
6896 }
6897 if let Some(name) = container.as_hash_binding_name() {
6898 self.touch_env_hash(&name);
6899 self.scope
6900 .set_hash_element(&name, key, val)
6901 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6902 return Ok(PerlValue::UNDEF);
6903 }
6904 if let Some(s) = container.as_str() {
6905 self.touch_env_hash(&s);
6906 if self.strict_refs {
6907 return Err(PerlError::runtime(
6908 format!(
6909 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6910 s
6911 ),
6912 line,
6913 )
6914 .into());
6915 }
6916 self.scope
6917 .set_hash_element(&s, key, val)
6918 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6919 return Ok(PerlValue::UNDEF);
6920 }
6921 Err(PerlError::runtime(
6922 "Hash slice assignment needs a hash or hash reference value",
6923 line,
6924 )
6925 .into())
6926 }
6927
6928 pub(crate) fn assign_named_hash_slice(
6931 &mut self,
6932 hash: &str,
6933 key_values: Vec<PerlValue>,
6934 val: PerlValue,
6935 line: usize,
6936 ) -> Result<PerlValue, FlowOrError> {
6937 self.touch_env_hash(hash);
6938 let mut ks: Vec<String> = Vec::new();
6939 for kv in key_values {
6940 if let Some(vv) = kv.as_array_vec() {
6941 ks.extend(vv.iter().map(|x| x.to_string()));
6942 } else {
6943 ks.push(kv.to_string());
6944 }
6945 }
6946 if ks.is_empty() {
6947 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6948 }
6949 let items = val.to_list();
6950 for (i, k) in ks.iter().enumerate() {
6951 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6952 self.scope
6953 .set_hash_element(hash, k, v)
6954 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6955 }
6956 Ok(PerlValue::UNDEF)
6957 }
6958
6959 pub(crate) fn assign_hash_slice_deref(
6961 &mut self,
6962 container: PerlValue,
6963 key_values: Vec<PerlValue>,
6964 val: PerlValue,
6965 line: usize,
6966 ) -> Result<PerlValue, FlowOrError> {
6967 let mut ks: Vec<String> = Vec::new();
6968 for kv in key_values {
6969 if let Some(vv) = kv.as_array_vec() {
6970 ks.extend(vv.iter().map(|x| x.to_string()));
6971 } else {
6972 ks.push(kv.to_string());
6973 }
6974 }
6975 if ks.is_empty() {
6976 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6977 }
6978 let items = val.to_list();
6979 if let Some(r) = container.as_hash_ref() {
6980 let mut h = r.write();
6981 for (i, k) in ks.iter().enumerate() {
6982 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6983 h.insert(k.clone(), v);
6984 }
6985 return Ok(PerlValue::UNDEF);
6986 }
6987 if let Some(name) = container.as_hash_binding_name() {
6988 self.touch_env_hash(&name);
6989 for (i, k) in ks.iter().enumerate() {
6990 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6991 self.scope
6992 .set_hash_element(&name, k, v)
6993 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6994 }
6995 return Ok(PerlValue::UNDEF);
6996 }
6997 if let Some(s) = container.as_str() {
6998 if self.strict_refs {
6999 return Err(PerlError::runtime(
7000 format!(
7001 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7002 s
7003 ),
7004 line,
7005 )
7006 .into());
7007 }
7008 self.touch_env_hash(&s);
7009 for (i, k) in ks.iter().enumerate() {
7010 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
7011 self.scope
7012 .set_hash_element(&s, k, v)
7013 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7014 }
7015 return Ok(PerlValue::UNDEF);
7016 }
7017 Err(PerlError::runtime(
7018 "Hash slice assignment needs a hash or hash reference value",
7019 line,
7020 )
7021 .into())
7022 }
7023
7024 pub(crate) fn compound_assign_hash_slice_deref(
7027 &mut self,
7028 container: PerlValue,
7029 key_values: Vec<PerlValue>,
7030 op: BinOp,
7031 rhs: PerlValue,
7032 line: usize,
7033 ) -> Result<PerlValue, FlowOrError> {
7034 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7035 let last_old = old_list
7036 .to_list()
7037 .last()
7038 .cloned()
7039 .unwrap_or(PerlValue::UNDEF);
7040 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7041 let mut ks: Vec<String> = Vec::new();
7042 for kv in &key_values {
7043 if let Some(vv) = kv.as_array_vec() {
7044 ks.extend(vv.iter().map(|x| x.to_string()));
7045 } else {
7046 ks.push(kv.to_string());
7047 }
7048 }
7049 if ks.is_empty() {
7050 return Err(PerlError::runtime("assign to empty hash slice", line).into());
7051 }
7052 let last_key = ks.last().expect("non-empty ks");
7053 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7054 Ok(new_val)
7055 }
7056
7057 pub(crate) fn hash_slice_deref_inc_dec(
7063 &mut self,
7064 container: PerlValue,
7065 key_values: Vec<PerlValue>,
7066 kind: u8,
7067 line: usize,
7068 ) -> Result<PerlValue, FlowOrError> {
7069 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7070 let last_old = old_list
7071 .to_list()
7072 .last()
7073 .cloned()
7074 .unwrap_or(PerlValue::UNDEF);
7075 let new_val = if kind & 1 == 0 {
7076 PerlValue::integer(last_old.to_int() + 1)
7077 } else {
7078 PerlValue::integer(last_old.to_int() - 1)
7079 };
7080 let mut ks: Vec<String> = Vec::new();
7081 for kv in &key_values {
7082 if let Some(vv) = kv.as_array_vec() {
7083 ks.extend(vv.iter().map(|x| x.to_string()));
7084 } else {
7085 ks.push(kv.to_string());
7086 }
7087 }
7088 let last_key = ks.last().ok_or_else(|| {
7089 PerlError::runtime("Hash slice increment needs at least one key", line)
7090 })?;
7091 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7092 Ok(if kind < 2 { new_val } else { last_old })
7093 }
7094
7095 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[PerlValue]) -> PerlValue {
7096 self.touch_env_hash(hash);
7097 let h = self.scope.get_hash(hash);
7098 let mut result = Vec::new();
7099 for kv in key_values {
7100 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7101 vv.iter().map(|x| x.to_string()).collect()
7102 } else {
7103 vec![kv.to_string()]
7104 };
7105 for k in key_strings {
7106 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
7107 }
7108 }
7109 PerlValue::array(result)
7110 }
7111
7112 pub(crate) fn compound_assign_named_hash_slice(
7114 &mut self,
7115 hash: &str,
7116 key_values: Vec<PerlValue>,
7117 op: BinOp,
7118 rhs: PerlValue,
7119 line: usize,
7120 ) -> Result<PerlValue, FlowOrError> {
7121 let old_list = self.hash_slice_named_values(hash, &key_values);
7122 let last_old = old_list
7123 .to_list()
7124 .last()
7125 .cloned()
7126 .unwrap_or(PerlValue::UNDEF);
7127 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7128 let mut ks: Vec<String> = Vec::new();
7129 for kv in &key_values {
7130 if let Some(vv) = kv.as_array_vec() {
7131 ks.extend(vv.iter().map(|x| x.to_string()));
7132 } else {
7133 ks.push(kv.to_string());
7134 }
7135 }
7136 if ks.is_empty() {
7137 return Err(PerlError::runtime("assign to empty hash slice", line).into());
7138 }
7139 let last_key = ks.last().expect("non-empty ks");
7140 let container = PerlValue::string(hash.to_string());
7141 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7142 Ok(new_val)
7143 }
7144
7145 pub(crate) fn named_hash_slice_inc_dec(
7147 &mut self,
7148 hash: &str,
7149 key_values: Vec<PerlValue>,
7150 kind: u8,
7151 line: usize,
7152 ) -> Result<PerlValue, FlowOrError> {
7153 let old_list = self.hash_slice_named_values(hash, &key_values);
7154 let last_old = old_list
7155 .to_list()
7156 .last()
7157 .cloned()
7158 .unwrap_or(PerlValue::UNDEF);
7159 let new_val = if kind & 1 == 0 {
7160 PerlValue::integer(last_old.to_int() + 1)
7161 } else {
7162 PerlValue::integer(last_old.to_int() - 1)
7163 };
7164 let mut ks: Vec<String> = Vec::new();
7165 for kv in &key_values {
7166 if let Some(vv) = kv.as_array_vec() {
7167 ks.extend(vv.iter().map(|x| x.to_string()));
7168 } else {
7169 ks.push(kv.to_string());
7170 }
7171 }
7172 let last_key = ks.last().ok_or_else(|| {
7173 PerlError::runtime("Hash slice increment needs at least one key", line)
7174 })?;
7175 let container = PerlValue::string(hash.to_string());
7176 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7177 Ok(if kind < 2 { new_val } else { last_old })
7178 }
7179
7180 fn match_array_pattern_elems(
7181 &mut self,
7182 arr: &[PerlValue],
7183 elems: &[MatchArrayElem],
7184 line: usize,
7185 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7186 let has_rest = elems
7187 .iter()
7188 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
7189 let mut binds: Vec<PatternBinding> = Vec::new();
7190 let mut idx = 0usize;
7191 for (i, elem) in elems.iter().enumerate() {
7192 match elem {
7193 MatchArrayElem::Rest => {
7194 if i != elems.len() - 1 {
7195 return Err(PerlError::runtime(
7196 "internal: `*` must be last in array match pattern",
7197 line,
7198 )
7199 .into());
7200 }
7201 return Ok(Some(binds));
7202 }
7203 MatchArrayElem::RestBind(name) => {
7204 if i != elems.len() - 1 {
7205 return Err(PerlError::runtime(
7206 "internal: `@name` rest bind must be last in array match pattern",
7207 line,
7208 )
7209 .into());
7210 }
7211 let tail = arr[idx..].to_vec();
7212 binds.push(PatternBinding::Array(name.clone(), tail));
7213 return Ok(Some(binds));
7214 }
7215 MatchArrayElem::CaptureScalar(name) => {
7216 if idx >= arr.len() {
7217 return Ok(None);
7218 }
7219 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
7220 idx += 1;
7221 }
7222 MatchArrayElem::Expr(e) => {
7223 if idx >= arr.len() {
7224 return Ok(None);
7225 }
7226 let expected = self.eval_expr(e)?;
7227 if !self.smartmatch_when(&arr[idx], &expected) {
7228 return Ok(None);
7229 }
7230 idx += 1;
7231 }
7232 }
7233 }
7234 if !has_rest && idx != arr.len() {
7235 return Ok(None);
7236 }
7237 Ok(Some(binds))
7238 }
7239
7240 fn match_hash_pattern_pairs(
7241 &mut self,
7242 h: &IndexMap<String, PerlValue>,
7243 pairs: &[MatchHashPair],
7244 _line: usize,
7245 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7246 let mut binds = Vec::new();
7247 for pair in pairs {
7248 match pair {
7249 MatchHashPair::KeyOnly { key } => {
7250 let ks = self.eval_expr(key)?.to_string();
7251 if !h.contains_key(&ks) {
7252 return Ok(None);
7253 }
7254 }
7255 MatchHashPair::Capture { key, name } => {
7256 let ks = self.eval_expr(key)?.to_string();
7257 let Some(v) = h.get(&ks) else {
7258 return Ok(None);
7259 };
7260 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
7261 }
7262 }
7263 }
7264 Ok(Some(binds))
7265 }
7266
7267 #[inline]
7269 fn block_needs_scope(block: &Block) -> bool {
7270 block.iter().any(|s| match &s.kind {
7271 StmtKind::My(_)
7272 | StmtKind::Our(_)
7273 | StmtKind::Local(_)
7274 | StmtKind::State(_)
7275 | StmtKind::LocalExpr { .. } => true,
7276 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
7277 _ => false,
7278 })
7279 }
7280
7281 #[inline]
7283 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
7284 if Self::block_needs_scope(block) {
7285 self.exec_block(block)
7286 } else {
7287 self.exec_block_no_scope(block)
7288 }
7289 }
7290
7291 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
7292 let t0 = self.profiler.is_some().then(std::time::Instant::now);
7293 let r = self.exec_statement_inner(stmt);
7294 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
7295 prof.on_line(&self.file, stmt.line, t0.elapsed());
7296 }
7297 r
7298 }
7299
7300 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
7301 if let Err(e) = crate::perl_signal::poll(self) {
7302 return Err(FlowOrError::Error(e));
7303 }
7304 if let Err(e) = self.drain_pending_destroys(stmt.line) {
7305 return Err(FlowOrError::Error(e));
7306 }
7307 match &stmt.kind {
7308 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
7309 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
7310 StmtKind::If {
7311 condition,
7312 body,
7313 elsifs,
7314 else_block,
7315 } => {
7316 if self.eval_boolean_rvalue_condition(condition)? {
7317 return self.exec_block(body);
7318 }
7319 for (c, b) in elsifs {
7320 if self.eval_boolean_rvalue_condition(c)? {
7321 return self.exec_block(b);
7322 }
7323 }
7324 if let Some(eb) = else_block {
7325 return self.exec_block(eb);
7326 }
7327 Ok(PerlValue::UNDEF)
7328 }
7329 StmtKind::Unless {
7330 condition,
7331 body,
7332 else_block,
7333 } => {
7334 if !self.eval_boolean_rvalue_condition(condition)? {
7335 return self.exec_block(body);
7336 }
7337 if let Some(eb) = else_block {
7338 return self.exec_block(eb);
7339 }
7340 Ok(PerlValue::UNDEF)
7341 }
7342 StmtKind::While {
7343 condition,
7344 body,
7345 label,
7346 continue_block,
7347 } => {
7348 'outer: loop {
7349 if !self.eval_boolean_rvalue_condition(condition)? {
7350 break;
7351 }
7352 'inner: loop {
7353 match self.exec_block_smart(body) {
7354 Ok(_) => break 'inner,
7355 Err(FlowOrError::Flow(Flow::Last(ref l)))
7356 if l == label || l.is_none() =>
7357 {
7358 break 'outer;
7359 }
7360 Err(FlowOrError::Flow(Flow::Next(ref l)))
7361 if l == label || l.is_none() =>
7362 {
7363 if let Some(cb) = continue_block {
7364 let _ = self.exec_block_smart(cb);
7365 }
7366 continue 'outer;
7367 }
7368 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7369 if l == label || l.is_none() =>
7370 {
7371 continue 'inner;
7372 }
7373 Err(e) => return Err(e),
7374 }
7375 }
7376 if let Some(cb) = continue_block {
7377 let _ = self.exec_block_smart(cb);
7378 }
7379 }
7380 Ok(PerlValue::UNDEF)
7381 }
7382 StmtKind::Until {
7383 condition,
7384 body,
7385 label,
7386 continue_block,
7387 } => {
7388 'outer: loop {
7389 if self.eval_boolean_rvalue_condition(condition)? {
7390 break;
7391 }
7392 'inner: loop {
7393 match self.exec_block(body) {
7394 Ok(_) => break 'inner,
7395 Err(FlowOrError::Flow(Flow::Last(ref l)))
7396 if l == label || l.is_none() =>
7397 {
7398 break 'outer;
7399 }
7400 Err(FlowOrError::Flow(Flow::Next(ref l)))
7401 if l == label || l.is_none() =>
7402 {
7403 if let Some(cb) = continue_block {
7404 let _ = self.exec_block_smart(cb);
7405 }
7406 continue 'outer;
7407 }
7408 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7409 if l == label || l.is_none() =>
7410 {
7411 continue 'inner;
7412 }
7413 Err(e) => return Err(e),
7414 }
7415 }
7416 if let Some(cb) = continue_block {
7417 let _ = self.exec_block_smart(cb);
7418 }
7419 }
7420 Ok(PerlValue::UNDEF)
7421 }
7422 StmtKind::DoWhile { body, condition } => {
7423 loop {
7424 self.exec_block(body)?;
7425 if !self.eval_boolean_rvalue_condition(condition)? {
7426 break;
7427 }
7428 }
7429 Ok(PerlValue::UNDEF)
7430 }
7431 StmtKind::For {
7432 init,
7433 condition,
7434 step,
7435 body,
7436 label,
7437 continue_block,
7438 } => {
7439 self.scope_push_hook();
7440 if let Some(init) = init {
7441 self.exec_statement(init)?;
7442 }
7443 'outer: loop {
7444 if let Some(cond) = condition {
7445 if !self.eval_boolean_rvalue_condition(cond)? {
7446 break;
7447 }
7448 }
7449 'inner: loop {
7450 match self.exec_block_smart(body) {
7451 Ok(_) => break 'inner,
7452 Err(FlowOrError::Flow(Flow::Last(ref l)))
7453 if l == label || l.is_none() =>
7454 {
7455 break 'outer;
7456 }
7457 Err(FlowOrError::Flow(Flow::Next(ref l)))
7458 if l == label || l.is_none() =>
7459 {
7460 if let Some(cb) = continue_block {
7461 let _ = self.exec_block_smart(cb);
7462 }
7463 if let Some(step) = step {
7464 self.eval_expr(step)?;
7465 }
7466 continue 'outer;
7467 }
7468 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7469 if l == label || l.is_none() =>
7470 {
7471 continue 'inner;
7472 }
7473 Err(e) => {
7474 self.scope_pop_hook();
7475 return Err(e);
7476 }
7477 }
7478 }
7479 if let Some(cb) = continue_block {
7480 let _ = self.exec_block_smart(cb);
7481 }
7482 if let Some(step) = step {
7483 self.eval_expr(step)?;
7484 }
7485 }
7486 self.scope_pop_hook();
7487 Ok(PerlValue::UNDEF)
7488 }
7489 StmtKind::Foreach {
7490 var,
7491 list,
7492 body,
7493 label,
7494 continue_block,
7495 } => {
7496 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
7497 let items = list_val.to_list();
7498 self.scope_push_hook();
7499 self.scope.declare_scalar(var, PerlValue::UNDEF);
7500 self.english_note_lexical_scalar(var);
7501 let mut i = 0usize;
7502 'outer: while i < items.len() {
7503 self.scope
7504 .set_scalar(var, items[i].clone())
7505 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
7506 'inner: loop {
7507 match self.exec_block_smart(body) {
7508 Ok(_) => break 'inner,
7509 Err(FlowOrError::Flow(Flow::Last(ref l)))
7510 if l == label || l.is_none() =>
7511 {
7512 break 'outer;
7513 }
7514 Err(FlowOrError::Flow(Flow::Next(ref l)))
7515 if l == label || l.is_none() =>
7516 {
7517 if let Some(cb) = continue_block {
7518 let _ = self.exec_block_smart(cb);
7519 }
7520 i += 1;
7521 continue 'outer;
7522 }
7523 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7524 if l == label || l.is_none() =>
7525 {
7526 continue 'inner;
7527 }
7528 Err(e) => {
7529 self.scope_pop_hook();
7530 return Err(e);
7531 }
7532 }
7533 }
7534 if let Some(cb) = continue_block {
7535 let _ = self.exec_block_smart(cb);
7536 }
7537 i += 1;
7538 }
7539 self.scope_pop_hook();
7540 Ok(PerlValue::UNDEF)
7541 }
7542 StmtKind::SubDecl {
7543 name,
7544 params,
7545 body,
7546 prototype,
7547 } => {
7548 let key = self.qualify_sub_key(name);
7549 let captured = self.scope.capture();
7550 let closure_env = if captured.is_empty() {
7551 None
7552 } else {
7553 Some(captured)
7554 };
7555 let mut sub = PerlSub {
7556 name: name.clone(),
7557 params: params.clone(),
7558 body: body.clone(),
7559 closure_env,
7560 prototype: prototype.clone(),
7561 fib_like: None,
7562 };
7563 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
7564 self.subs.insert(key, Arc::new(sub));
7565 Ok(PerlValue::UNDEF)
7566 }
7567 StmtKind::StructDecl { def } => {
7568 if self.struct_defs.contains_key(&def.name) {
7569 return Err(PerlError::runtime(
7570 format!("duplicate struct `{}`", def.name),
7571 stmt.line,
7572 )
7573 .into());
7574 }
7575 self.struct_defs
7576 .insert(def.name.clone(), Arc::new(def.clone()));
7577 Ok(PerlValue::UNDEF)
7578 }
7579 StmtKind::EnumDecl { def } => {
7580 if self.enum_defs.contains_key(&def.name) {
7581 return Err(PerlError::runtime(
7582 format!("duplicate enum `{}`", def.name),
7583 stmt.line,
7584 )
7585 .into());
7586 }
7587 self.enum_defs
7588 .insert(def.name.clone(), Arc::new(def.clone()));
7589 Ok(PerlValue::UNDEF)
7590 }
7591 StmtKind::ClassDecl { def } => {
7592 if self.class_defs.contains_key(&def.name) {
7593 return Err(PerlError::runtime(
7594 format!("duplicate class `{}`", def.name),
7595 stmt.line,
7596 )
7597 .into());
7598 }
7599 for parent_name in &def.extends {
7601 if let Some(parent_def) = self.class_defs.get(parent_name) {
7602 if parent_def.is_final {
7603 return Err(PerlError::runtime(
7604 format!("cannot extend final class `{}`", parent_name),
7605 stmt.line,
7606 )
7607 .into());
7608 }
7609 for m in &def.methods {
7611 if let Some(parent_method) = parent_def.method(&m.name) {
7612 if parent_method.is_final {
7613 return Err(PerlError::runtime(
7614 format!(
7615 "cannot override final method `{}` from class `{}`",
7616 m.name, parent_name
7617 ),
7618 stmt.line,
7619 )
7620 .into());
7621 }
7622 }
7623 }
7624 }
7625 }
7626 let mut def = def.clone();
7628 for trait_name in &def.implements.clone() {
7629 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
7630 for required in trait_def.required_methods() {
7631 let has_method = def.methods.iter().any(|m| m.name == required.name);
7632 if !has_method {
7633 return Err(PerlError::runtime(
7634 format!(
7635 "class `{}` implements trait `{}` but does not define required method `{}`",
7636 def.name, trait_name, required.name
7637 ),
7638 stmt.line,
7639 )
7640 .into());
7641 }
7642 }
7643 for tm in &trait_def.methods {
7645 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
7646 def.methods.push(tm.clone());
7647 }
7648 }
7649 }
7650 }
7651 if !def.is_abstract {
7654 for parent_name in &def.extends.clone() {
7655 if let Some(parent_def) = self.class_defs.get(parent_name) {
7656 if parent_def.is_abstract {
7657 for m in &parent_def.methods {
7658 if m.body.is_none()
7659 && !def.methods.iter().any(|dm| dm.name == m.name)
7660 {
7661 return Err(PerlError::runtime(
7662 format!(
7663 "class `{}` must implement abstract method `{}` from `{}`",
7664 def.name, m.name, parent_name
7665 ),
7666 stmt.line,
7667 )
7668 .into());
7669 }
7670 }
7671 }
7672 }
7673 }
7674 }
7675 for sf in &def.static_fields {
7677 let val = if let Some(ref expr) = sf.default {
7678 self.eval_expr(expr)?
7679 } else {
7680 PerlValue::UNDEF
7681 };
7682 let key = format!("{}::{}", def.name, sf.name);
7683 self.scope.declare_scalar(&key, val);
7684 }
7685 for m in &def.methods {
7687 if let Some(ref body) = m.body {
7688 let fq = format!("{}::{}", def.name, m.name);
7689 let sub = Arc::new(PerlSub {
7690 name: fq.clone(),
7691 params: m.params.clone(),
7692 body: body.clone(),
7693 closure_env: None,
7694 prototype: None,
7695 fib_like: None,
7696 });
7697 self.subs.insert(fq, sub);
7698 }
7699 }
7700 if !def.extends.is_empty() {
7702 let isa_key = format!("{}::ISA", def.name);
7703 let parents: Vec<PerlValue> = def
7704 .extends
7705 .iter()
7706 .map(|p| PerlValue::string(p.clone()))
7707 .collect();
7708 self.scope.declare_array(&isa_key, parents);
7709 }
7710 let arc_def = Arc::new(def);
7711 self.class_defs
7712 .insert(arc_def.name.clone(), Arc::clone(&arc_def));
7713 crate::serialize_normalize::register_class_def(arc_def);
7717 Ok(PerlValue::UNDEF)
7718 }
7719 StmtKind::TraitDecl { def } => {
7720 if self.trait_defs.contains_key(&def.name) {
7721 return Err(PerlError::runtime(
7722 format!("duplicate trait `{}`", def.name),
7723 stmt.line,
7724 )
7725 .into());
7726 }
7727 self.trait_defs
7728 .insert(def.name.clone(), Arc::new(def.clone()));
7729 Ok(PerlValue::UNDEF)
7730 }
7731 StmtKind::My(decls) | StmtKind::Our(decls) => {
7732 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
7733 if decls.len() > 1 && decls[0].initializer.is_some() {
7736 let val = self.eval_expr_ctx(
7737 decls[0].initializer.as_ref().unwrap(),
7738 WantarrayCtx::List,
7739 )?;
7740 let items = val.to_list();
7741 let mut idx = 0;
7742 for decl in decls {
7743 match decl.sigil {
7744 Sigil::Scalar => {
7745 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7746 let skey = if is_our {
7747 self.stash_scalar_name_for_package(&decl.name)
7748 } else {
7749 decl.name.clone()
7750 };
7751 self.scope.declare_scalar_frozen(
7752 &skey,
7753 v,
7754 decl.frozen,
7755 decl.type_annotation.clone(),
7756 )?;
7757 self.english_note_lexical_scalar(&decl.name);
7758 if is_our {
7759 self.note_our_scalar(&decl.name);
7760 }
7761 idx += 1;
7762 }
7763 Sigil::Array => {
7764 let rest: Vec<PerlValue> = items[idx..].to_vec();
7766 idx = items.len();
7767 if is_our {
7768 self.record_exporter_our_array_name(&decl.name, &rest);
7769 }
7770 let aname = self.stash_array_name_for_package(&decl.name);
7771 self.scope.declare_array(&aname, rest);
7772 }
7773 Sigil::Hash => {
7774 let rest: Vec<PerlValue> = items[idx..].to_vec();
7775 idx = items.len();
7776 let mut map = IndexMap::new();
7777 let mut i = 0;
7778 while i + 1 < rest.len() {
7779 map.insert(rest[i].to_string(), rest[i + 1].clone());
7780 i += 2;
7781 }
7782 self.scope.declare_hash(&decl.name, map);
7783 }
7784 Sigil::Typeglob => {
7785 return Err(PerlError::runtime(
7786 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
7787 stmt.line,
7788 )
7789 .into());
7790 }
7791 }
7792 }
7793 } else {
7794 for decl in decls {
7796 let compound_init = decl
7800 .initializer
7801 .as_ref()
7802 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
7803
7804 if compound_init {
7805 match decl.sigil {
7806 Sigil::Typeglob => {
7807 return Err(PerlError::runtime(
7808 "compound assignment on typeglob declaration is not supported",
7809 stmt.line,
7810 )
7811 .into());
7812 }
7813 Sigil::Scalar => {
7814 let skey = if is_our {
7815 self.stash_scalar_name_for_package(&decl.name)
7816 } else {
7817 decl.name.clone()
7818 };
7819 self.scope.declare_scalar_frozen(
7820 &skey,
7821 PerlValue::UNDEF,
7822 decl.frozen,
7823 decl.type_annotation.clone(),
7824 )?;
7825 self.english_note_lexical_scalar(&decl.name);
7826 if is_our {
7827 self.note_our_scalar(&decl.name);
7828 }
7829 let init = decl.initializer.as_ref().unwrap();
7830 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7831 }
7832 Sigil::Array => {
7833 let aname = self.stash_array_name_for_package(&decl.name);
7834 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
7835 let init = decl.initializer.as_ref().unwrap();
7836 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7837 if is_our {
7838 let items = self.scope.get_array(&aname);
7839 self.record_exporter_our_array_name(&decl.name, &items);
7840 }
7841 }
7842 Sigil::Hash => {
7843 self.scope.declare_hash_frozen(
7844 &decl.name,
7845 IndexMap::new(),
7846 decl.frozen,
7847 );
7848 let init = decl.initializer.as_ref().unwrap();
7849 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7850 }
7851 }
7852 continue;
7853 }
7854
7855 let val = if let Some(init) = &decl.initializer {
7856 let ctx = match decl.sigil {
7857 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7858 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7859 };
7860 self.eval_expr_ctx(init, ctx)?
7861 } else {
7862 PerlValue::UNDEF
7863 };
7864 match decl.sigil {
7865 Sigil::Typeglob => {
7866 return Err(PerlError::runtime(
7867 "`my *FH` / typeglob declaration is not supported",
7868 stmt.line,
7869 )
7870 .into());
7871 }
7872 Sigil::Scalar => {
7873 let skey = if is_our {
7874 self.stash_scalar_name_for_package(&decl.name)
7875 } else {
7876 decl.name.clone()
7877 };
7878 self.scope.declare_scalar_frozen(
7879 &skey,
7880 val,
7881 decl.frozen,
7882 decl.type_annotation.clone(),
7883 )?;
7884 self.english_note_lexical_scalar(&decl.name);
7885 if is_our {
7886 self.note_our_scalar(&decl.name);
7887 }
7888 }
7889 Sigil::Array => {
7890 let items = val.to_list();
7891 if is_our {
7892 self.record_exporter_our_array_name(&decl.name, &items);
7893 }
7894 let aname = self.stash_array_name_for_package(&decl.name);
7895 self.scope.declare_array_frozen(&aname, items, decl.frozen);
7896 }
7897 Sigil::Hash => {
7898 let items = val.to_list();
7899 let mut map = IndexMap::new();
7900 let mut i = 0;
7901 while i + 1 < items.len() {
7902 let k = items[i].to_string();
7903 let v = items[i + 1].clone();
7904 map.insert(k, v);
7905 i += 2;
7906 }
7907 self.scope.declare_hash_frozen(&decl.name, map, decl.frozen);
7908 }
7909 }
7910 }
7911 }
7912 Ok(PerlValue::UNDEF)
7913 }
7914 StmtKind::State(decls) => {
7915 for decl in decls {
7918 let state_key = format!("{}:{}", stmt.line, decl.name);
7919 match decl.sigil {
7920 Sigil::Scalar => {
7921 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
7922 self.scope.declare_scalar(&decl.name, prev);
7924 } else {
7925 let val = if let Some(init) = &decl.initializer {
7927 self.eval_expr(init)?
7928 } else {
7929 PerlValue::UNDEF
7930 };
7931 self.state_vars.insert(state_key.clone(), val.clone());
7932 self.scope.declare_scalar(&decl.name, val);
7933 }
7934 if let Some(frame) = self.state_bindings_stack.last_mut() {
7936 frame.push((decl.name.clone(), state_key));
7937 }
7938 }
7939 _ => {
7940 let val = if let Some(init) = &decl.initializer {
7942 self.eval_expr(init)?
7943 } else {
7944 PerlValue::UNDEF
7945 };
7946 match decl.sigil {
7947 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
7948 Sigil::Hash => {
7949 let items = val.to_list();
7950 let mut map = IndexMap::new();
7951 let mut i = 0;
7952 while i + 1 < items.len() {
7953 map.insert(items[i].to_string(), items[i + 1].clone());
7954 i += 2;
7955 }
7956 self.scope.declare_hash(&decl.name, map);
7957 }
7958 _ => {}
7959 }
7960 }
7961 }
7962 }
7963 Ok(PerlValue::UNDEF)
7964 }
7965 StmtKind::Local(decls) => {
7966 if decls.len() > 1 && decls[0].initializer.is_some() {
7967 let val = self.eval_expr_ctx(
7968 decls[0].initializer.as_ref().unwrap(),
7969 WantarrayCtx::List,
7970 )?;
7971 let items = val.to_list();
7972 let mut idx = 0;
7973 for decl in decls {
7974 match decl.sigil {
7975 Sigil::Scalar => {
7976 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7977 idx += 1;
7978 self.scope.local_set_scalar(&decl.name, v)?;
7979 }
7980 Sigil::Array => {
7981 let rest: Vec<PerlValue> = items[idx..].to_vec();
7982 idx = items.len();
7983 self.scope.local_set_array(&decl.name, rest)?;
7984 }
7985 Sigil::Hash => {
7986 let rest: Vec<PerlValue> = items[idx..].to_vec();
7987 idx = items.len();
7988 if decl.name == "ENV" {
7989 self.materialize_env_if_needed();
7990 }
7991 let mut map = IndexMap::new();
7992 let mut i = 0;
7993 while i + 1 < rest.len() {
7994 map.insert(rest[i].to_string(), rest[i + 1].clone());
7995 i += 2;
7996 }
7997 self.scope.local_set_hash(&decl.name, map)?;
7998 }
7999 Sigil::Typeglob => {
8000 return Err(PerlError::runtime(
8001 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
8002 stmt.line,
8003 )
8004 .into());
8005 }
8006 }
8007 }
8008 Ok(val)
8009 } else {
8010 let mut last_val = PerlValue::UNDEF;
8011 for decl in decls {
8012 let val = if let Some(init) = &decl.initializer {
8013 let ctx = match decl.sigil {
8014 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8015 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8016 };
8017 self.eval_expr_ctx(init, ctx)?
8018 } else {
8019 PerlValue::UNDEF
8020 };
8021 last_val = val.clone();
8022 match decl.sigil {
8023 Sigil::Typeglob => {
8024 let old = self.glob_handle_alias.remove(&decl.name);
8025 if let Some(frame) = self.glob_restore_frames.last_mut() {
8026 frame.push((decl.name.clone(), old));
8027 }
8028 if let Some(init) = &decl.initializer {
8029 if let ExprKind::Typeglob(rhs) = &init.kind {
8030 self.glob_handle_alias
8031 .insert(decl.name.clone(), rhs.clone());
8032 } else {
8033 return Err(PerlError::runtime(
8034 "local *GLOB = *OTHER — right side must be a typeglob",
8035 stmt.line,
8036 )
8037 .into());
8038 }
8039 }
8040 }
8041 Sigil::Scalar => {
8042 if Self::is_special_scalar_name_for_set(&decl.name) {
8048 let old = self.get_special_var(&decl.name);
8049 if let Some(frame) = self.special_var_restore_frames.last_mut()
8050 {
8051 frame.push((decl.name.clone(), old));
8052 }
8053 self.set_special_var(&decl.name, &val)
8054 .map_err(|e| e.at_line(stmt.line))?;
8055 }
8056 self.scope.local_set_scalar(&decl.name, val)?;
8057 }
8058 Sigil::Array => {
8059 self.scope.local_set_array(&decl.name, val.to_list())?;
8060 }
8061 Sigil::Hash => {
8062 if decl.name == "ENV" {
8063 self.materialize_env_if_needed();
8064 }
8065 let items = val.to_list();
8066 let mut map = IndexMap::new();
8067 let mut i = 0;
8068 while i + 1 < items.len() {
8069 let k = items[i].to_string();
8070 let v = items[i + 1].clone();
8071 map.insert(k, v);
8072 i += 2;
8073 }
8074 self.scope.local_set_hash(&decl.name, map)?;
8075 }
8076 }
8077 }
8078 Ok(last_val)
8079 }
8080 }
8081 StmtKind::LocalExpr {
8082 target,
8083 initializer,
8084 } => {
8085 let rhs_name = |init: &Expr| -> PerlResult<Option<String>> {
8086 match &init.kind {
8087 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
8088 _ => Err(PerlError::runtime(
8089 "local *GLOB = *OTHER — right side must be a typeglob",
8090 stmt.line,
8091 )),
8092 }
8093 };
8094 match &target.kind {
8095 ExprKind::Typeglob(name) => {
8096 let rhs = if let Some(init) = initializer {
8097 rhs_name(init)?
8098 } else {
8099 None
8100 };
8101 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
8102 return Ok(PerlValue::UNDEF);
8103 }
8104 ExprKind::Deref {
8105 expr,
8106 kind: Sigil::Typeglob,
8107 } => {
8108 let lhs = self.eval_expr(expr)?.to_string();
8109 let rhs = if let Some(init) = initializer {
8110 rhs_name(init)?
8111 } else {
8112 None
8113 };
8114 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8115 return Ok(PerlValue::UNDEF);
8116 }
8117 ExprKind::TypeglobExpr(e) => {
8118 let lhs = self.eval_expr(e)?.to_string();
8119 let rhs = if let Some(init) = initializer {
8120 rhs_name(init)?
8121 } else {
8122 None
8123 };
8124 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8125 return Ok(PerlValue::UNDEF);
8126 }
8127 _ => {}
8128 }
8129 let val = if let Some(init) = initializer {
8130 let ctx = match &target.kind {
8131 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
8132 _ => WantarrayCtx::Scalar,
8133 };
8134 self.eval_expr_ctx(init, ctx)?
8135 } else {
8136 PerlValue::UNDEF
8137 };
8138 match &target.kind {
8139 ExprKind::ScalarVar(name) => {
8140 if Self::is_special_scalar_name_for_set(name) {
8143 let old = self.get_special_var(name);
8144 if let Some(frame) = self.special_var_restore_frames.last_mut() {
8145 frame.push((name.clone(), old));
8146 }
8147 self.set_special_var(name, &val)
8148 .map_err(|e| e.at_line(stmt.line))?;
8149 }
8150 self.scope.local_set_scalar(name, val.clone())?;
8151 }
8152 ExprKind::ArrayVar(name) => {
8153 self.scope.local_set_array(name, val.to_list())?;
8154 }
8155 ExprKind::HashVar(name) => {
8156 if name == "ENV" {
8157 self.materialize_env_if_needed();
8158 }
8159 let items = val.to_list();
8160 let mut map = IndexMap::new();
8161 let mut i = 0;
8162 while i + 1 < items.len() {
8163 map.insert(items[i].to_string(), items[i + 1].clone());
8164 i += 2;
8165 }
8166 self.scope.local_set_hash(name, map)?;
8167 }
8168 ExprKind::HashElement { hash, key } => {
8169 let ks = self.eval_expr(key)?.to_string();
8170 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
8171 }
8172 ExprKind::ArrayElement { array, index } => {
8173 self.check_strict_array_var(array, stmt.line)?;
8174 let aname = self.stash_array_name_for_package(array);
8175 let idx = self.eval_expr(index)?.to_int();
8176 self.scope
8177 .local_set_array_element(&aname, idx, val.clone())?;
8178 }
8179 _ => {
8180 return Err(PerlError::runtime(
8181 format!(
8182 "local on this lvalue is not supported yet ({:?})",
8183 target.kind
8184 ),
8185 stmt.line,
8186 )
8187 .into());
8188 }
8189 }
8190 Ok(val)
8191 }
8192 StmtKind::MySync(decls) => {
8193 for decl in decls {
8194 let val = if let Some(init) = &decl.initializer {
8195 self.eval_expr(init)?
8196 } else {
8197 PerlValue::UNDEF
8198 };
8199 match decl.sigil {
8200 Sigil::Typeglob => {
8201 return Err(PerlError::runtime(
8202 "`mysync` does not support typeglob variables",
8203 stmt.line,
8204 )
8205 .into());
8206 }
8207 Sigil::Scalar => {
8208 let stored = if val.is_mysync_deque_or_heap() {
8211 val
8212 } else {
8213 PerlValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(val)))
8214 };
8215 self.scope.declare_scalar(&decl.name, stored);
8216 }
8217 Sigil::Array => {
8218 self.scope.declare_atomic_array(&decl.name, val.to_list());
8219 }
8220 Sigil::Hash => {
8221 let items = val.to_list();
8222 let mut map = IndexMap::new();
8223 let mut i = 0;
8224 while i + 1 < items.len() {
8225 map.insert(items[i].to_string(), items[i + 1].clone());
8226 i += 2;
8227 }
8228 self.scope.declare_atomic_hash(&decl.name, map);
8229 }
8230 }
8231 }
8232 Ok(PerlValue::UNDEF)
8233 }
8234 StmtKind::OurSync(decls) => {
8235 for decl in decls {
8242 let val = if let Some(init) = &decl.initializer {
8243 self.eval_expr(init)?
8244 } else {
8245 PerlValue::UNDEF
8246 };
8247 match decl.sigil {
8248 Sigil::Typeglob => {
8249 return Err(PerlError::runtime(
8250 "`oursync` does not support typeglob variables",
8251 stmt.line,
8252 )
8253 .into());
8254 }
8255 Sigil::Scalar => {
8256 let stash = self.stash_scalar_name_for_package(&decl.name);
8257 let stored = if val.is_mysync_deque_or_heap() {
8258 val
8259 } else {
8260 PerlValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(val)))
8261 };
8262 self.scope.declare_scalar(&stash, stored);
8263 self.english_note_lexical_scalar(&decl.name);
8264 self.note_our_scalar(&decl.name);
8265 }
8266 Sigil::Array => {
8267 let stash = self.stash_array_name_for_package(&decl.name);
8268 self.scope.declare_atomic_array(&stash, val.to_list());
8269 self.english_note_lexical_scalar(&decl.name);
8270 self.note_our_scalar(&decl.name);
8271 }
8272 Sigil::Hash => {
8273 let items = val.to_list();
8274 let mut map = IndexMap::new();
8275 let mut i = 0;
8276 while i + 1 < items.len() {
8277 map.insert(items[i].to_string(), items[i + 1].clone());
8278 i += 2;
8279 }
8280 self.scope.declare_atomic_hash(&decl.name, map);
8283 self.english_note_lexical_scalar(&decl.name);
8284 self.note_our_scalar(&decl.name);
8285 }
8286 }
8287 }
8288 Ok(PerlValue::UNDEF)
8289 }
8290 StmtKind::Package { name } => {
8291 let _ = self
8293 .scope
8294 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
8295 Ok(PerlValue::UNDEF)
8296 }
8297 StmtKind::UsePerlVersion { .. } => Ok(PerlValue::UNDEF),
8298 StmtKind::Use { .. } => {
8299 Ok(PerlValue::UNDEF)
8301 }
8302 StmtKind::UseOverload { pairs } => {
8303 self.install_use_overload_pairs(pairs);
8304 Ok(PerlValue::UNDEF)
8305 }
8306 StmtKind::No { .. } => {
8307 Ok(PerlValue::UNDEF)
8309 }
8310 StmtKind::Return(val) => {
8311 let v = if let Some(e) = val {
8312 self.eval_expr_ctx(e, self.wantarray_kind)?
8316 } else {
8317 PerlValue::UNDEF
8318 };
8319 Err(Flow::Return(v).into())
8320 }
8321 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
8322 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
8323 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
8324 StmtKind::Block(block) => self.exec_block(block),
8325 StmtKind::Begin(_)
8326 | StmtKind::UnitCheck(_)
8327 | StmtKind::Check(_)
8328 | StmtKind::Init(_)
8329 | StmtKind::End(_) => Ok(PerlValue::UNDEF),
8330 StmtKind::Empty => Ok(PerlValue::UNDEF),
8331 StmtKind::Goto { target } => {
8332 if let ExprKind::SubroutineRef(name) = &target.kind {
8334 return Err(Flow::GotoSub(name.clone()).into());
8335 }
8336 Err(PerlError::runtime("goto reached outside goto-aware block", stmt.line).into())
8337 }
8338 StmtKind::EvalTimeout { timeout, body } => {
8339 let secs = self.eval_expr(timeout)?.to_number();
8340 self.eval_timeout_block(body, secs, stmt.line)
8341 }
8342 StmtKind::Tie {
8343 target,
8344 class,
8345 args,
8346 } => {
8347 let kind = match &target {
8348 TieTarget::Scalar(_) => 0u8,
8349 TieTarget::Array(_) => 1u8,
8350 TieTarget::Hash(_) => 2u8,
8351 };
8352 let name = match &target {
8353 TieTarget::Scalar(s) => s.as_str(),
8354 TieTarget::Array(a) => a.as_str(),
8355 TieTarget::Hash(h) => h.as_str(),
8356 };
8357 let mut vals = vec![self.eval_expr(class)?];
8358 for a in args {
8359 vals.push(self.eval_expr(a)?);
8360 }
8361 self.tie_execute(kind, name, vals, stmt.line)
8362 .map_err(Into::into)
8363 }
8364 StmtKind::TryCatch {
8365 try_block,
8366 catch_var,
8367 catch_block,
8368 finally_block,
8369 } => match self.exec_block(try_block) {
8370 Ok(v) => {
8371 if let Some(fb) = finally_block {
8372 self.exec_block(fb)?;
8373 }
8374 Ok(v)
8375 }
8376 Err(FlowOrError::Error(e)) => {
8377 if matches!(e.kind, ErrorKind::Exit(_)) {
8378 return Err(FlowOrError::Error(e));
8379 }
8380 self.scope_push_hook();
8381 self.scope
8382 .declare_scalar(catch_var, PerlValue::string(e.to_string()));
8383 self.english_note_lexical_scalar(catch_var);
8384 let r = self.exec_block(catch_block);
8385 self.scope_pop_hook();
8386 if let Some(fb) = finally_block {
8387 self.exec_block(fb)?;
8388 }
8389 r
8390 }
8391 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
8392 },
8393 StmtKind::Given { topic, body } => self.exec_given(topic, body),
8394 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(PerlError::runtime(
8395 "when/default may only appear inside a given block",
8396 stmt.line,
8397 )
8398 .into()),
8399 StmtKind::FormatDecl { .. } => {
8400 Ok(PerlValue::UNDEF)
8402 }
8403 StmtKind::AdviceDecl {
8404 kind,
8405 pattern,
8406 body,
8407 } => {
8408 let id = self.next_intercept_id;
8414 self.next_intercept_id = id.saturating_add(1);
8415 self.intercepts.push(crate::aop::Intercept {
8416 id,
8417 kind: *kind,
8418 pattern: pattern.clone(),
8419 body: body.clone(),
8420 body_block_idx: u16::MAX,
8421 });
8422 Ok(PerlValue::UNDEF)
8423 }
8424 StmtKind::Continue(block) => self.exec_block_smart(block),
8425 }
8426 }
8427
8428 #[inline]
8429 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
8430 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
8431 }
8432
8433 pub(crate) fn scalar_compound_assign_scalar_target(
8437 &mut self,
8438 name: &str,
8439 op: BinOp,
8440 rhs: PerlValue,
8441 ) -> Result<PerlValue, PerlError> {
8442 if op == BinOp::Concat {
8443 return self.scope.scalar_concat_inplace(name, &rhs);
8444 }
8445 self.scope
8446 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs))
8447 }
8448
8449 fn compound_scalar_binop(old: &PerlValue, op: BinOp, rhs: &PerlValue) -> PerlValue {
8450 match op {
8451 BinOp::Add => {
8452 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8453 PerlValue::integer(a.wrapping_add(b))
8454 } else {
8455 PerlValue::float(old.to_number() + rhs.to_number())
8456 }
8457 }
8458 BinOp::Sub => {
8459 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8460 PerlValue::integer(a.wrapping_sub(b))
8461 } else {
8462 PerlValue::float(old.to_number() - rhs.to_number())
8463 }
8464 }
8465 BinOp::Mul => {
8466 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8467 PerlValue::integer(a.wrapping_mul(b))
8468 } else {
8469 PerlValue::float(old.to_number() * rhs.to_number())
8470 }
8471 }
8472 BinOp::BitAnd => {
8473 if let Some(s) = crate::value::set_intersection(old, rhs) {
8474 s
8475 } else {
8476 PerlValue::integer(old.to_int() & rhs.to_int())
8477 }
8478 }
8479 BinOp::BitOr => {
8480 if let Some(s) = crate::value::set_union(old, rhs) {
8481 s
8482 } else {
8483 PerlValue::integer(old.to_int() | rhs.to_int())
8484 }
8485 }
8486 BinOp::BitXor => PerlValue::integer(old.to_int() ^ rhs.to_int()),
8487 BinOp::ShiftLeft => PerlValue::integer(old.to_int() << rhs.to_int()),
8488 BinOp::ShiftRight => PerlValue::integer(old.to_int() >> rhs.to_int()),
8489 BinOp::Div => PerlValue::float(old.to_number() / rhs.to_number()),
8490 BinOp::Mod => {
8491 let b = rhs.to_int();
8496 if b == 0 {
8497 PerlValue::integer(0)
8498 } else {
8499 PerlValue::integer(crate::value::perl_mod_i64(old.to_int(), b))
8500 }
8501 }
8502 BinOp::Pow => PerlValue::float(old.to_number().powf(rhs.to_number())),
8503 BinOp::LogOr => {
8504 if old.is_true() {
8505 old.clone()
8506 } else {
8507 rhs.clone()
8508 }
8509 }
8510 BinOp::DefinedOr => {
8511 if !old.is_undef() {
8512 old.clone()
8513 } else {
8514 rhs.clone()
8515 }
8516 }
8517 BinOp::LogAnd => {
8518 if old.is_true() {
8519 rhs.clone()
8520 } else {
8521 old.clone()
8522 }
8523 }
8524 _ => PerlValue::float(old.to_number() + rhs.to_number()),
8525 }
8526 }
8527
8528 fn eval_hash_slice_key_components(
8532 &mut self,
8533 key_expr: &Expr,
8534 ) -> Result<Vec<String>, FlowOrError> {
8535 let v = if matches!(
8536 key_expr.kind,
8537 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
8538 ) {
8539 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8540 } else {
8541 self.eval_expr(key_expr)?
8542 };
8543 if let Some(vv) = v.as_array_vec() {
8544 Ok(vv.iter().map(|x| x.to_string()).collect())
8545 } else {
8546 Ok(vec![v.to_string()])
8547 }
8548 }
8549
8550 pub(crate) fn symbolic_deref(
8552 &mut self,
8553 val: PerlValue,
8554 kind: Sigil,
8555 line: usize,
8556 ) -> ExecResult {
8557 match kind {
8558 Sigil::Scalar => {
8559 if let Some(name) = val.as_scalar_binding_name() {
8560 return Ok(self.get_special_var(&name));
8561 }
8562 if let Some(r) = val.as_scalar_ref() {
8563 return Ok(r.read().clone());
8564 }
8565 if let Some(r) = val.as_array_ref() {
8567 return Ok(PerlValue::array(r.read().clone()));
8568 }
8569 if let Some(name) = val.as_array_binding_name() {
8570 return Ok(PerlValue::array(self.scope.get_array(&name)));
8571 }
8572 if let Some(r) = val.as_hash_ref() {
8573 return Ok(PerlValue::hash(r.read().clone()));
8574 }
8575 if let Some(name) = val.as_hash_binding_name() {
8576 self.touch_env_hash(&name);
8577 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
8578 }
8579 if let Some(s) = val.as_str() {
8580 if self.strict_refs {
8581 return Err(PerlError::runtime(
8582 format!(
8583 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
8584 s
8585 ),
8586 line,
8587 )
8588 .into());
8589 }
8590 return Ok(self.get_special_var(&s));
8591 }
8592 Err(PerlError::runtime("Can't dereference non-reference as scalar", line).into())
8593 }
8594 Sigil::Array => {
8595 if let Some(r) = val.as_array_ref() {
8596 return Ok(PerlValue::array(r.read().clone()));
8597 }
8598 if let Some(name) = val.as_array_binding_name() {
8599 return Ok(PerlValue::array(self.scope.get_array(&name)));
8600 }
8601 if val.is_undef() {
8602 if self.strict_refs {
8603 return Err(PerlError::runtime(
8604 "Can't use an undefined value as an ARRAY reference",
8605 line,
8606 )
8607 .into());
8608 }
8609 return Ok(PerlValue::array(vec![]));
8610 }
8611 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
8617 let s = val.to_string();
8618 if self.strict_refs {
8619 return Err(PerlError::runtime(
8620 format!(
8621 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8622 s
8623 ),
8624 line,
8625 )
8626 .into());
8627 }
8628 return Ok(PerlValue::array(self.scope.get_array(&s)));
8629 }
8630 Err(PerlError::runtime("Can't dereference non-reference as array", line).into())
8631 }
8632 Sigil::Hash => {
8633 if let Some(r) = val.as_hash_ref() {
8634 return Ok(PerlValue::hash(r.read().clone()));
8635 }
8636 if let Some(name) = val.as_hash_binding_name() {
8637 self.touch_env_hash(&name);
8638 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
8639 }
8640 if let Some(c) = val.as_class_inst() {
8648 let all_fields = self.collect_class_fields_full(&c.def);
8649 let values = c.get_values();
8650 let mut map = IndexMap::new();
8651 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
8652 if let Some(v) = values.get(i) {
8653 map.insert(name.clone(), v.clone());
8654 }
8655 }
8656 return Ok(PerlValue::hash(map));
8657 }
8658 if let Some(s) = val.as_struct_inst() {
8661 let values = s.get_values();
8662 let mut map = IndexMap::new();
8663 for (i, field) in s.def.fields.iter().enumerate() {
8664 if let Some(v) = values.get(i) {
8665 map.insert(field.name.clone(), v.clone());
8666 }
8667 }
8668 return Ok(PerlValue::hash(map));
8669 }
8670 if let Some(b) = val.as_blessed_ref() {
8675 let inner = b.data.read().clone();
8676 if let Some(r) = inner.as_hash_ref() {
8677 return Ok(PerlValue::hash(r.read().clone()));
8678 }
8679 if let Some(h) = inner.as_hash_map() {
8680 return Ok(PerlValue::hash(h));
8681 }
8682 }
8683 if val.is_undef() {
8684 if self.strict_refs {
8685 return Err(PerlError::runtime(
8686 "Can't use an undefined value as a HASH reference",
8687 line,
8688 )
8689 .into());
8690 }
8691 return Ok(PerlValue::hash(IndexMap::new()));
8692 }
8693 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
8694 let s = val.to_string();
8695 if self.strict_refs {
8696 return Err(PerlError::runtime(
8697 format!(
8698 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8699 s
8700 ),
8701 line,
8702 )
8703 .into());
8704 }
8705 self.touch_env_hash(&s);
8706 return Ok(PerlValue::hash(self.scope.get_hash(&s)));
8707 }
8708 Err(PerlError::runtime("Can't dereference non-reference as hash", line).into())
8709 }
8710 Sigil::Typeglob => {
8711 if let Some(s) = val.as_str() {
8712 return Ok(PerlValue::string(self.resolve_io_handle_name(&s)));
8713 }
8714 Err(PerlError::runtime("Can't dereference non-reference as typeglob", line).into())
8715 }
8716 }
8717 }
8718
8719 #[inline]
8722 pub(crate) fn peel_array_ref_for_list_join(&self, v: PerlValue) -> PerlValue {
8723 if let Some(r) = v.as_array_ref() {
8724 return PerlValue::array(r.read().clone());
8725 }
8726 v
8727 }
8728
8729 pub(crate) fn make_array_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
8731 if let Some(a) = val.as_array_ref() {
8732 return Ok(PerlValue::array_ref(Arc::clone(&a)));
8733 }
8734 if let Some(name) = val.as_array_binding_name() {
8735 return Ok(PerlValue::array_binding_ref(name));
8736 }
8737 if let Some(s) = val.as_str() {
8738 if self.strict_refs {
8739 return Err(PerlError::runtime(
8740 format!(
8741 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8742 s
8743 ),
8744 line,
8745 )
8746 .into());
8747 }
8748 return Ok(PerlValue::array_binding_ref(s.to_string()));
8749 }
8750 if let Some(r) = val.as_scalar_ref() {
8751 let inner = r.read().clone();
8752 return self.make_array_ref_alias(inner, line);
8753 }
8754 Err(PerlError::runtime("Can't make array reference from value", line).into())
8755 }
8756
8757 pub(crate) fn make_hash_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
8759 if let Some(h) = val.as_hash_ref() {
8760 return Ok(PerlValue::hash_ref(Arc::clone(&h)));
8761 }
8762 if let Some(name) = val.as_hash_binding_name() {
8763 return Ok(PerlValue::hash_binding_ref(name));
8764 }
8765 if let Some(s) = val.as_str() {
8766 if self.strict_refs {
8767 return Err(PerlError::runtime(
8768 format!(
8769 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8770 s
8771 ),
8772 line,
8773 )
8774 .into());
8775 }
8776 return Ok(PerlValue::hash_binding_ref(s.to_string()));
8777 }
8778 if let Some(r) = val.as_scalar_ref() {
8779 let inner = r.read().clone();
8780 return self.make_hash_ref_alias(inner, line);
8781 }
8782 Err(PerlError::runtime("Can't make hash reference from value", line).into())
8783 }
8784
8785 pub(crate) fn process_case_escapes(s: &str) -> String {
8788 if !s.contains('\\') {
8790 return s.to_string();
8791 }
8792 let mut result = String::with_capacity(s.len());
8793 let mut chars = s.chars().peekable();
8794 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
8798 if c == '\\' {
8799 match chars.peek() {
8800 Some(&'U') => {
8801 chars.next();
8802 mode = Some('U');
8803 continue;
8804 }
8805 Some(&'L') => {
8806 chars.next();
8807 mode = Some('L');
8808 continue;
8809 }
8810 Some(&'Q') => {
8811 chars.next();
8812 mode = Some('Q');
8813 continue;
8814 }
8815 Some(&'E') => {
8816 chars.next();
8817 mode = None;
8818 next_char_mod = None;
8819 continue;
8820 }
8821 Some(&'u') => {
8822 chars.next();
8823 next_char_mod = Some('u');
8824 continue;
8825 }
8826 Some(&'l') => {
8827 chars.next();
8828 next_char_mod = Some('l');
8829 continue;
8830 }
8831 _ => {}
8832 }
8833 }
8834
8835 let ch = c;
8836
8837 if let Some(m) = next_char_mod.take() {
8839 let transformed = match m {
8840 'u' => ch.to_uppercase().next().unwrap_or(ch),
8841 'l' => ch.to_lowercase().next().unwrap_or(ch),
8842 _ => ch,
8843 };
8844 result.push(transformed);
8845 } else {
8846 match mode {
8848 Some('U') => {
8849 for uc in ch.to_uppercase() {
8850 result.push(uc);
8851 }
8852 }
8853 Some('L') => {
8854 for lc in ch.to_lowercase() {
8855 result.push(lc);
8856 }
8857 }
8858 Some('Q') => {
8859 if !ch.is_ascii_alphanumeric() && ch != '_' {
8860 result.push('\\');
8861 }
8862 result.push(ch);
8863 }
8864 None | Some(_) => {
8865 result.push(ch);
8866 }
8867 }
8868 }
8869 }
8870 result
8871 }
8872
8873 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
8874 let line = expr.line;
8875 match &expr.kind {
8876 ExprKind::Integer(n) => Ok(PerlValue::integer(*n)),
8877 ExprKind::Float(f) => Ok(PerlValue::float(*f)),
8878 ExprKind::String(s) => {
8879 let processed = Self::process_case_escapes(s);
8880 Ok(PerlValue::string(processed))
8881 }
8882 ExprKind::Bareword(s) => {
8883 if s == "__PACKAGE__" {
8884 return Ok(PerlValue::string(self.current_package()));
8885 }
8886 if let Some(sub) = self.resolve_sub_by_name(s) {
8887 return self.call_sub(&sub, vec![], ctx, line);
8888 }
8889 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
8891 return r.map_err(Into::into);
8892 }
8893 Ok(PerlValue::string(s.clone()))
8894 }
8895 ExprKind::Undef => Ok(PerlValue::UNDEF),
8896 ExprKind::MagicConst(MagicConstKind::File) => Ok(PerlValue::string(self.file.clone())),
8897 ExprKind::MagicConst(MagicConstKind::Line) => Ok(PerlValue::integer(expr.line as i64)),
8898 ExprKind::MagicConst(MagicConstKind::Sub) => {
8899 if let Some(sub) = self.current_sub_stack.last().cloned() {
8900 Ok(PerlValue::code_ref(sub))
8901 } else {
8902 Ok(PerlValue::UNDEF)
8903 }
8904 }
8905 ExprKind::Regex(pattern, flags) => {
8906 if ctx == WantarrayCtx::Void {
8907 let topic = self.scope.get_scalar("_");
8909 let s = topic.to_string();
8910 self.regex_match_execute(s, pattern, flags, false, "_", line)
8911 } else {
8912 let re = self.compile_regex(pattern, flags, line)?;
8913 Ok(PerlValue::regex(re, pattern.clone(), flags.clone()))
8914 }
8915 }
8916 ExprKind::QW(words) => Ok(PerlValue::array(
8917 words.iter().map(|w| PerlValue::string(w.clone())).collect(),
8918 )),
8919
8920 ExprKind::InterpolatedString(parts) => {
8922 let mut raw_result = String::new();
8923 for part in parts {
8924 match part {
8925 StringPart::Literal(s) => raw_result.push_str(s),
8926 StringPart::ScalarVar(name) => {
8927 self.check_strict_scalar_var(name, line)?;
8928 let val = self.get_special_var(name);
8929 let s = self.stringify_value(val, line)?;
8930 raw_result.push_str(&s);
8931 }
8932 StringPart::ArrayVar(name) => {
8933 self.check_strict_array_var(name, line)?;
8934 let aname = self.stash_array_name_for_package(name);
8935 let arr = self.scope.get_array(&aname);
8936 let mut parts = Vec::with_capacity(arr.len());
8937 for v in &arr {
8938 parts.push(self.stringify_value(v.clone(), line)?);
8939 }
8940 let sep = self.list_separator.clone();
8941 raw_result.push_str(&parts.join(&sep));
8942 }
8943 StringPart::Expr(e) => {
8944 if let ExprKind::ArraySlice { array, .. } = &e.kind {
8945 self.check_strict_array_var(array, line)?;
8946 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8947 let val = self.peel_array_ref_for_list_join(val);
8948 let list = val.to_list();
8949 let sep = self.list_separator.clone();
8950 let mut parts = Vec::with_capacity(list.len());
8951 for v in list {
8952 parts.push(self.stringify_value(v, line)?);
8953 }
8954 raw_result.push_str(&parts.join(&sep));
8955 } else if let ExprKind::Deref {
8956 kind: Sigil::Array, ..
8957 } = &e.kind
8958 {
8959 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8960 let val = self.peel_array_ref_for_list_join(val);
8961 let list = val.to_list();
8962 let sep = self.list_separator.clone();
8963 let mut parts = Vec::with_capacity(list.len());
8964 for v in list {
8965 parts.push(self.stringify_value(v, line)?);
8966 }
8967 raw_result.push_str(&parts.join(&sep));
8968 } else {
8969 let val = self.eval_expr(e)?;
8970 let s = self.stringify_value(val, line)?;
8971 raw_result.push_str(&s);
8972 }
8973 }
8974 }
8975 }
8976 let result = Self::process_case_escapes(&raw_result);
8977 Ok(PerlValue::string(result))
8978 }
8979
8980 ExprKind::ScalarVar(name) => {
8982 self.check_strict_scalar_var(name, line)?;
8983 let stor = self.tree_scalar_storage_name(name);
8984 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
8985 let class = obj
8986 .as_blessed_ref()
8987 .map(|b| b.class.clone())
8988 .unwrap_or_default();
8989 let full = format!("{}::FETCH", class);
8990 if let Some(sub) = self.subs.get(&full).cloned() {
8991 return self.call_sub(&sub, vec![obj], ctx, line);
8992 }
8993 }
8994 Ok(self.get_special_var(&stor))
8995 }
8996 ExprKind::ArrayVar(name) => {
8997 self.check_strict_array_var(name, line)?;
8998 let aname = self.stash_array_name_for_package(name);
8999 let arr = self.scope.get_array(&aname);
9000 if ctx == WantarrayCtx::List {
9001 Ok(PerlValue::array(arr))
9002 } else {
9003 Ok(PerlValue::integer(arr.len() as i64))
9004 }
9005 }
9006 ExprKind::HashVar(name) => {
9007 self.check_strict_hash_var(name, line)?;
9008 self.touch_env_hash(name);
9009 let h = self.scope.get_hash(name);
9010 let pv = PerlValue::hash(h);
9011 if ctx == WantarrayCtx::List {
9012 Ok(pv)
9013 } else {
9014 Ok(pv.scalar_context())
9015 }
9016 }
9017 ExprKind::Typeglob(name) => {
9018 let n = self.resolve_io_handle_name(name);
9019 Ok(PerlValue::string(n))
9020 }
9021 ExprKind::TypeglobExpr(e) => {
9022 let name = self.eval_expr(e)?.to_string();
9023 let n = self.resolve_io_handle_name(&name);
9024 Ok(PerlValue::string(n))
9025 }
9026 ExprKind::ArrayElement { array, index } => {
9027 if let Some(real) = array.strip_prefix("__topicstr__") {
9032 let s = self.scope.get_scalar(real).to_string();
9033 if let ExprKind::Range {
9034 from,
9035 to,
9036 exclusive,
9037 step,
9038 } = &index.kind
9039 {
9040 let n = s.chars().count() as i64;
9041 let mut from_i = self.eval_expr(from)?.to_int();
9042 let mut to_i = self.eval_expr(to)?.to_int();
9043 let step_i = match step {
9044 Some(e) => self.eval_expr(e)?.to_int(),
9045 None => 1,
9046 };
9047 if from_i < 0 {
9048 from_i += n
9049 }
9050 if to_i < 0 {
9051 to_i += n
9052 }
9053 if *exclusive {
9054 to_i -= 1
9055 }
9056 let chars: Vec<char> = s.chars().collect();
9057 let mut out = String::new();
9058 if step_i > 0 {
9059 let mut i = from_i;
9060 while i <= to_i && i < n {
9061 if i >= 0 {
9062 out.push(chars[i as usize]);
9063 }
9064 i += step_i;
9065 }
9066 } else if step_i < 0 {
9067 let mut i = from_i;
9068 while i >= to_i && i >= 0 {
9069 if i < n {
9070 out.push(chars[i as usize]);
9071 }
9072 i += step_i;
9073 }
9074 }
9075 return Ok(PerlValue::string(out));
9076 }
9077 let idx = self.eval_expr(index)?.to_int();
9078 let n = s.chars().count() as i64;
9079 let i = if idx < 0 { idx + n } else { idx };
9080 return Ok(if i >= 0 && i < n {
9081 s.chars()
9082 .nth(i as usize)
9083 .map(|c| PerlValue::string(c.to_string()))
9084 .unwrap_or(PerlValue::UNDEF)
9085 } else {
9086 PerlValue::UNDEF
9087 });
9088 }
9089 self.check_strict_array_var(array, line)?;
9090 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9095 if let ExprKind::Range {
9096 from,
9097 to,
9098 exclusive,
9099 step,
9100 } = &index.kind
9101 {
9102 let aname_check = self.stash_array_name_for_package(array);
9103 let prefer_scalar =
9104 array == "_" || self.scope.get_array(&aname_check).is_empty();
9105 if prefer_scalar {
9106 let s = self.scope.get_scalar(array).to_string();
9107 if !s.is_empty() {
9108 let n = s.chars().count() as i64;
9109 let mut from_i = self.eval_expr(from)?.to_int();
9110 let mut to_i = self.eval_expr(to)?.to_int();
9111 let step_i = match step {
9112 Some(e) => self.eval_expr(e)?.to_int(),
9113 None => 1,
9114 };
9115 if from_i < 0 {
9116 from_i += n
9117 }
9118 if to_i < 0 {
9119 to_i += n
9120 }
9121 if *exclusive {
9122 to_i -= 1
9123 }
9124 let chars: Vec<char> = s.chars().collect();
9125 let mut out = String::new();
9126 if step_i > 0 {
9127 let mut i = from_i;
9128 while i <= to_i && i < n {
9129 if i >= 0 {
9130 out.push(chars[i as usize]);
9131 }
9132 i += step_i;
9133 }
9134 } else if step_i < 0 {
9135 let mut i = from_i;
9136 while i >= to_i && i >= 0 {
9137 if i < n {
9138 out.push(chars[i as usize]);
9139 }
9140 i += step_i;
9141 }
9142 }
9143 return Ok(PerlValue::string(out));
9144 }
9145 }
9146 }
9147 }
9148 let idx = self.eval_expr(index)?.to_int();
9149 let aname = self.stash_array_name_for_package(array);
9150 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
9151 let class = obj
9152 .as_blessed_ref()
9153 .map(|b| b.class.clone())
9154 .unwrap_or_default();
9155 let full = format!("{}::FETCH", class);
9156 if let Some(sub) = self.subs.get(&full).cloned() {
9157 let arg_vals = vec![obj, PerlValue::integer(idx)];
9158 return self.call_sub(&sub, arg_vals, ctx, line);
9159 }
9160 }
9161 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9170 let prefer_scalar = self.scope.get_array(&aname).is_empty();
9171 if prefer_scalar {
9172 let s = self.scope.get_scalar(array).to_string();
9173 if !s.is_empty() {
9174 let n = s.chars().count() as i64;
9175 let i = if idx < 0 { idx + n } else { idx };
9176 if i >= 0 && i < n {
9177 if let Some(c) = s.chars().nth(i as usize) {
9178 return Ok(PerlValue::string(c.to_string()));
9179 }
9180 }
9181 return Ok(PerlValue::UNDEF);
9182 }
9183 }
9184 }
9185 Ok(self.scope.get_array_element(&aname, idx))
9186 }
9187 ExprKind::HashElement { hash, key } => {
9188 self.check_strict_hash_var(hash, line)?;
9189 let k = self.eval_expr(key)?.to_string();
9190 self.touch_env_hash(hash);
9191 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
9192 let class = obj
9193 .as_blessed_ref()
9194 .map(|b| b.class.clone())
9195 .unwrap_or_default();
9196 let full = format!("{}::FETCH", class);
9197 if let Some(sub) = self.subs.get(&full).cloned() {
9198 let arg_vals = vec![obj, PerlValue::string(k)];
9199 return self.call_sub(&sub, arg_vals, ctx, line);
9200 }
9201 }
9202 Ok(self.scope.get_hash_element(hash, &k))
9203 }
9204 ExprKind::ArraySlice { array, indices } => {
9205 self.check_strict_array_var(array, line)?;
9206 let aname = self.stash_array_name_for_package(array);
9207 let flat = self.flatten_array_slice_index_specs(indices)?;
9208 let mut result = Vec::with_capacity(flat.len());
9209 for idx in flat {
9210 result.push(self.scope.get_array_element(&aname, idx));
9211 }
9212 Ok(PerlValue::array(result))
9213 }
9214 ExprKind::HashSlice { hash, keys } => {
9215 self.check_strict_hash_var(hash, line)?;
9216 self.touch_env_hash(hash);
9217 let mut result = Vec::new();
9218 for key_expr in keys {
9219 for k in self.eval_hash_slice_key_components(key_expr)? {
9220 result.push(self.scope.get_hash_element(hash, &k));
9221 }
9222 }
9223 Ok(PerlValue::array(result))
9224 }
9225 ExprKind::HashKvSlice { hash, keys } => {
9226 self.check_strict_hash_var(hash, line)?;
9229 self.touch_env_hash(hash);
9230 let mut result = Vec::new();
9231 for key_expr in keys {
9232 for k in self.eval_hash_slice_key_components(key_expr)? {
9233 let v = self.scope.get_hash_element(hash, &k);
9234 result.push(PerlValue::string(k));
9235 result.push(v);
9236 }
9237 }
9238 Ok(PerlValue::array(result))
9239 }
9240 ExprKind::HashSliceDeref { container, keys } => {
9241 let hv = self.eval_expr(container)?;
9242 let mut key_vals = Vec::with_capacity(keys.len());
9243 for key_expr in keys {
9244 let v = if matches!(
9245 key_expr.kind,
9246 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
9247 ) {
9248 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
9249 } else {
9250 self.eval_expr(key_expr)?
9251 };
9252 key_vals.push(v);
9253 }
9254 self.hash_slice_deref_values(&hv, &key_vals, line)
9255 }
9256 ExprKind::AnonymousListSlice { source, indices } => {
9257 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
9258 let items = list_val.to_list();
9259 let flat = self.flatten_array_slice_index_specs(indices)?;
9260 let mut out = Vec::with_capacity(flat.len());
9261 for idx in flat {
9262 let i = if idx < 0 {
9263 (items.len() as i64 + idx) as usize
9264 } else {
9265 idx as usize
9266 };
9267 out.push(items.get(i).cloned().unwrap_or(PerlValue::UNDEF));
9268 }
9269 let arr = PerlValue::array(out);
9270 if ctx != WantarrayCtx::List {
9271 let v = arr.to_list();
9272 Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF))
9273 } else {
9274 Ok(arr)
9275 }
9276 }
9277
9278 ExprKind::ScalarRef(inner) => match &inner.kind {
9280 ExprKind::ScalarVar(name) => Ok(PerlValue::scalar_binding_ref(name.clone())),
9281 ExprKind::ArrayVar(name) => {
9282 self.check_strict_array_var(name, line)?;
9283 let aname = self.stash_array_name_for_package(name);
9284 let arc = self.scope.promote_array_to_shared(&aname);
9287 Ok(PerlValue::array_ref(arc))
9288 }
9289 ExprKind::HashVar(name) => {
9290 self.check_strict_hash_var(name, line)?;
9291 let arc = self.scope.promote_hash_to_shared(name);
9292 Ok(PerlValue::hash_ref(arc))
9293 }
9294 ExprKind::Deref {
9295 expr: e,
9296 kind: Sigil::Array,
9297 } => {
9298 let v = self.eval_expr(e)?;
9299 self.make_array_ref_alias(v, line)
9300 }
9301 ExprKind::Deref {
9302 expr: e,
9303 kind: Sigil::Hash,
9304 } => {
9305 let v = self.eval_expr(e)?;
9306 self.make_hash_ref_alias(v, line)
9307 }
9308 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
9309 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
9310 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
9311 }
9312 ExprKind::HashSliceDeref { .. } => {
9313 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
9314 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
9315 }
9316 _ => {
9317 let val = self.eval_expr(inner)?;
9318 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
9319 }
9320 },
9321 ExprKind::ArrayRef(elems) => {
9322 let mut arr = Vec::with_capacity(elems.len());
9326 for e in elems {
9327 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9328 let v = self.scope.resolve_container_binding_ref(v);
9329 if let Some(vec) = v.as_array_vec() {
9330 arr.extend(vec);
9331 } else {
9332 arr.push(v);
9333 }
9334 }
9335 Ok(PerlValue::array_ref(Arc::new(RwLock::new(arr))))
9336 }
9337 ExprKind::HashRef(pairs) => {
9338 let mut map = IndexMap::new();
9341 for (k, v) in pairs {
9342 let key_str = self.eval_expr(k)?.to_string();
9343 if key_str == "__HASH_SPREAD__" {
9344 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
9346 let items = spread.to_list();
9347 let mut i = 0;
9348 while i + 1 < items.len() {
9349 map.insert(items[i].to_string(), items[i + 1].clone());
9350 i += 2;
9351 }
9352 } else {
9353 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
9354 map.insert(key_str, val);
9355 }
9356 }
9357 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map))))
9358 }
9359 ExprKind::CodeRef { params, body } => {
9360 let captured = self.scope.capture();
9361 Ok(PerlValue::code_ref(Arc::new(PerlSub {
9362 name: "__ANON__".to_string(),
9363 params: params.clone(),
9364 body: body.clone(),
9365 closure_env: Some(captured),
9366 prototype: None,
9367 fib_like: None,
9368 })))
9369 }
9370 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
9371 ExprKind::SubroutineCodeRef(name) => {
9372 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
9373 PerlError::runtime(self.undefined_subroutine_resolve_message(name), line)
9374 })?;
9375 Ok(PerlValue::code_ref(sub))
9376 }
9377 ExprKind::DynamicSubCodeRef(expr) => {
9378 let name = self.eval_expr(expr)?.to_string();
9379 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
9380 PerlError::runtime(self.undefined_subroutine_resolve_message(&name), line)
9381 })?;
9382 Ok(PerlValue::code_ref(sub))
9383 }
9384 ExprKind::Deref { expr, kind } => {
9385 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
9386 let val = self.eval_expr(expr)?;
9387 let n = self.array_deref_len(val, line)?;
9388 return Ok(PerlValue::integer(n));
9389 }
9390 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
9391 let val = self.eval_expr(expr)?;
9392 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
9393 return Ok(h.scalar_context());
9394 }
9395 let val = self.eval_expr(expr)?;
9396 self.symbolic_deref(val, *kind, line)
9397 }
9398 ExprKind::ArrowDeref { expr, index, kind } => {
9399 match kind {
9400 DerefKind::Array => {
9401 let container = self.eval_arrow_array_base(expr, line)?;
9402 if let ExprKind::List(indices) = &index.kind {
9403 let mut out = Vec::with_capacity(indices.len());
9404 for ix in indices {
9405 let idx = self.eval_expr(ix)?.to_int();
9406 out.push(self.read_arrow_array_element(
9407 container.clone(),
9408 idx,
9409 line,
9410 )?);
9411 }
9412 let arr = PerlValue::array(out);
9413 if ctx != WantarrayCtx::List {
9414 let v = arr.to_list();
9415 return Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF));
9416 }
9417 return Ok(arr);
9418 }
9419 let idx = self.eval_expr(index)?.to_int();
9420 self.read_arrow_array_element(container, idx, line)
9421 }
9422 DerefKind::Hash => {
9423 let val = self.eval_arrow_hash_base(expr, line)?;
9424 let key = self.eval_expr(index)?.to_string();
9425 self.read_arrow_hash_element(val, key.as_str(), line)
9426 }
9427 DerefKind::Call => {
9428 let val = self.eval_expr(expr)?;
9430 if let ExprKind::List(ref arg_exprs) = index.kind {
9431 let mut args = Vec::new();
9432 for a in arg_exprs {
9433 args.push(self.eval_expr(a)?);
9434 }
9435 let callable = if let Some(inner) = val.as_scalar_ref() {
9437 inner.read().clone()
9438 } else {
9439 val
9440 };
9441 if let Some(sub) = callable.as_code_ref() {
9442 return self.call_sub(&sub, args, ctx, line);
9443 }
9444 Err(PerlError::runtime("Not a code reference", line).into())
9445 } else {
9446 Err(PerlError::runtime("Invalid call deref", line).into())
9447 }
9448 }
9449 }
9450 }
9451
9452 ExprKind::BinOp { left, op, right } => {
9454 match op {
9456 BinOp::BindMatch => {
9457 let lv = self.eval_expr(left)?;
9458 let rv = self.eval_expr(right)?;
9459 let s = lv.to_string();
9460 let pat = rv.to_string();
9461 return self.regex_match_execute(s, &pat, "", false, "_", line);
9462 }
9463 BinOp::BindNotMatch => {
9464 let lv = self.eval_expr(left)?;
9465 let rv = self.eval_expr(right)?;
9466 let s = lv.to_string();
9467 let pat = rv.to_string();
9468 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
9469 return Ok(PerlValue::integer(if m.is_true() { 0 } else { 1 }));
9470 }
9471 BinOp::LogAnd | BinOp::LogAndWord => {
9472 match &left.kind {
9473 ExprKind::Regex(_, _) => {
9474 if !self.eval_boolean_rvalue_condition(left)? {
9475 return Ok(PerlValue::string(String::new()));
9476 }
9477 }
9478 _ => {
9479 let lv = self.eval_expr(left)?;
9480 if !lv.is_true() {
9481 return Ok(lv);
9482 }
9483 }
9484 }
9485 return match &right.kind {
9486 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
9487 if self.eval_boolean_rvalue_condition(right)? {
9488 1
9489 } else {
9490 0
9491 },
9492 )),
9493 _ => self.eval_expr(right),
9494 };
9495 }
9496 BinOp::LogOr | BinOp::LogOrWord => {
9497 match &left.kind {
9498 ExprKind::Regex(_, _) => {
9499 if self.eval_boolean_rvalue_condition(left)? {
9500 return Ok(PerlValue::integer(1));
9501 }
9502 }
9503 _ => {
9504 let lv = self.eval_expr(left)?;
9505 if lv.is_true() {
9506 return Ok(lv);
9507 }
9508 }
9509 }
9510 return match &right.kind {
9511 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
9512 if self.eval_boolean_rvalue_condition(right)? {
9513 1
9514 } else {
9515 0
9516 },
9517 )),
9518 _ => self.eval_expr(right),
9519 };
9520 }
9521 BinOp::DefinedOr => {
9522 let lv = self.eval_expr(left)?;
9523 if !lv.is_undef() {
9524 return Ok(lv);
9525 }
9526 return self.eval_expr(right);
9527 }
9528 _ => {}
9529 }
9530 let lv = self.eval_expr(left)?;
9531 let rv = self.eval_expr(right)?;
9532 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
9533 return r;
9534 }
9535 self.eval_binop(*op, &lv, &rv, line)
9536 }
9537
9538 ExprKind::UnaryOp { op, expr } => match op {
9540 UnaryOp::PreIncrement => {
9541 if let ExprKind::ScalarVar(name) = &expr.kind {
9542 self.check_strict_scalar_var(name, line)?;
9543 let n = self.resolved_scalar_storage_name(name);
9544 return Ok(self
9545 .scope
9546 .atomic_mutate(&n, perl_inc)
9547 .map_err(|e| e.at_line(line))?);
9548 }
9549 if let ExprKind::Deref { kind, .. } = &expr.kind {
9550 if matches!(kind, Sigil::Array | Sigil::Hash) {
9551 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
9552 *kind, true, true, line,
9553 ));
9554 }
9555 }
9556 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
9557 let href = self.eval_expr(container)?;
9558 let mut key_vals = Vec::with_capacity(keys.len());
9559 for key_expr in keys {
9560 key_vals.push(self.eval_expr(key_expr)?);
9561 }
9562 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
9563 }
9564 if let ExprKind::ArrowDeref {
9565 expr: arr_expr,
9566 index,
9567 kind: DerefKind::Array,
9568 } = &expr.kind
9569 {
9570 if let ExprKind::List(indices) = &index.kind {
9571 let container = self.eval_arrow_array_base(arr_expr, line)?;
9572 let mut idxs = Vec::with_capacity(indices.len());
9573 for ix in indices {
9574 idxs.push(self.eval_expr(ix)?.to_int());
9575 }
9576 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
9577 }
9578 }
9579 let val = self.eval_expr(expr)?;
9580 let new_val = perl_inc(&val);
9581 self.assign_value(expr, new_val.clone())?;
9582 Ok(new_val)
9583 }
9584 UnaryOp::PreDecrement => {
9585 if let ExprKind::ScalarVar(name) = &expr.kind {
9586 self.check_strict_scalar_var(name, line)?;
9587 let n = self.resolved_scalar_storage_name(name);
9588 return Ok(self
9589 .scope
9590 .atomic_mutate(&n, |v| PerlValue::integer(v.to_int() - 1))
9591 .map_err(|e| e.at_line(line))?);
9592 }
9593 if let ExprKind::Deref { kind, .. } = &expr.kind {
9594 if matches!(kind, Sigil::Array | Sigil::Hash) {
9595 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
9596 *kind, true, false, line,
9597 ));
9598 }
9599 }
9600 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
9601 let href = self.eval_expr(container)?;
9602 let mut key_vals = Vec::with_capacity(keys.len());
9603 for key_expr in keys {
9604 key_vals.push(self.eval_expr(key_expr)?);
9605 }
9606 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
9607 }
9608 if let ExprKind::ArrowDeref {
9609 expr: arr_expr,
9610 index,
9611 kind: DerefKind::Array,
9612 } = &expr.kind
9613 {
9614 if let ExprKind::List(indices) = &index.kind {
9615 let container = self.eval_arrow_array_base(arr_expr, line)?;
9616 let mut idxs = Vec::with_capacity(indices.len());
9617 for ix in indices {
9618 idxs.push(self.eval_expr(ix)?.to_int());
9619 }
9620 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
9621 }
9622 }
9623 let val = self.eval_expr(expr)?;
9624 let new_val = PerlValue::integer(val.to_int() - 1);
9625 self.assign_value(expr, new_val.clone())?;
9626 Ok(new_val)
9627 }
9628 _ => {
9629 match op {
9630 UnaryOp::LogNot | UnaryOp::LogNotWord => {
9631 if let ExprKind::Regex(pattern, flags) = &expr.kind {
9632 let topic = self.scope.get_scalar("_");
9633 let rl = expr.line;
9634 let s = topic.to_string();
9635 let v =
9636 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
9637 return Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }));
9638 }
9639 }
9640 _ => {}
9641 }
9642 let val = self.eval_expr(expr)?;
9643 match op {
9644 UnaryOp::Negate => {
9645 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
9646 return r;
9647 }
9648 if let Some(n) = val.as_integer() {
9649 Ok(PerlValue::integer(-n))
9650 } else {
9651 Ok(PerlValue::float(-val.to_number()))
9652 }
9653 }
9654 UnaryOp::LogNot => {
9655 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
9656 let pv = r?;
9657 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
9658 }
9659 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
9660 }
9661 UnaryOp::BitNot => Ok(PerlValue::integer(!val.to_int())),
9662 UnaryOp::LogNotWord => {
9663 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
9664 let pv = r?;
9665 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
9666 }
9667 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
9668 }
9669 UnaryOp::Ref => {
9670 if let ExprKind::ScalarVar(name) = &expr.kind {
9671 return Ok(PerlValue::scalar_binding_ref(name.clone()));
9672 }
9673 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
9674 }
9675 _ => unreachable!(),
9676 }
9677 }
9678 },
9679
9680 ExprKind::PostfixOp { expr, op } => {
9681 if let ExprKind::ScalarVar(name) = &expr.kind {
9684 self.check_strict_scalar_var(name, line)?;
9685 let n = self.resolved_scalar_storage_name(name);
9686 let f: fn(&PerlValue) -> PerlValue = match op {
9687 PostfixOp::Increment => |v| perl_inc(v),
9688 PostfixOp::Decrement => |v| PerlValue::integer(v.to_int() - 1),
9689 };
9690 return Ok(self
9691 .scope
9692 .atomic_mutate_post(&n, f)
9693 .map_err(|e| e.at_line(line))?);
9694 }
9695 if let ExprKind::Deref { kind, .. } = &expr.kind {
9696 if matches!(kind, Sigil::Array | Sigil::Hash) {
9697 let is_inc = matches!(op, PostfixOp::Increment);
9698 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
9699 *kind, false, is_inc, line,
9700 ));
9701 }
9702 }
9703 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
9704 let href = self.eval_expr(container)?;
9705 let mut key_vals = Vec::with_capacity(keys.len());
9706 for key_expr in keys {
9707 key_vals.push(self.eval_expr(key_expr)?);
9708 }
9709 let kind_byte = match op {
9710 PostfixOp::Increment => 2u8,
9711 PostfixOp::Decrement => 3u8,
9712 };
9713 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
9714 }
9715 if let ExprKind::ArrowDeref {
9716 expr: arr_expr,
9717 index,
9718 kind: DerefKind::Array,
9719 } = &expr.kind
9720 {
9721 if let ExprKind::List(indices) = &index.kind {
9722 let container = self.eval_arrow_array_base(arr_expr, line)?;
9723 let mut idxs = Vec::with_capacity(indices.len());
9724 for ix in indices {
9725 idxs.push(self.eval_expr(ix)?.to_int());
9726 }
9727 let kind_byte = match op {
9728 PostfixOp::Increment => 2u8,
9729 PostfixOp::Decrement => 3u8,
9730 };
9731 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
9732 }
9733 }
9734 let val = self.eval_expr(expr)?;
9735 let old = val.clone();
9736 let new_val = match op {
9737 PostfixOp::Increment => perl_inc(&val),
9738 PostfixOp::Decrement => PerlValue::integer(val.to_int() - 1),
9739 };
9740 self.assign_value(expr, new_val)?;
9741 Ok(old)
9742 }
9743
9744 ExprKind::Assign { target, value } => {
9746 if let ExprKind::Typeglob(lhs) = &target.kind {
9747 if let ExprKind::Typeglob(rhs) = &value.kind {
9748 self.copy_typeglob_slots(lhs, rhs, line)?;
9749 return self.eval_expr(value);
9750 }
9751 }
9752 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
9753 self.assign_value(target, val.clone())?;
9754 Ok(val)
9755 }
9756 ExprKind::CompoundAssign { target, op, value } => {
9757 if let ExprKind::ScalarVar(name) = &target.kind {
9760 self.check_strict_scalar_var(name, line)?;
9761 let n = self.resolved_scalar_storage_name(name);
9762 let op = *op;
9763 let rhs = match op {
9764 BinOp::LogOr => {
9765 let old = self.scope.get_scalar(&n);
9766 if old.is_true() {
9767 return Ok(old);
9768 }
9769 self.eval_expr(value)?
9770 }
9771 BinOp::DefinedOr => {
9772 let old = self.scope.get_scalar(&n);
9773 if !old.is_undef() {
9774 return Ok(old);
9775 }
9776 self.eval_expr(value)?
9777 }
9778 BinOp::LogAnd => {
9779 let old = self.scope.get_scalar(&n);
9780 if !old.is_true() {
9781 return Ok(old);
9782 }
9783 self.eval_expr(value)?
9784 }
9785 _ => self.eval_expr(value)?,
9786 };
9787 return Ok(self.scalar_compound_assign_scalar_target(&n, op, rhs)?);
9788 }
9789 let rhs = self.eval_expr(value)?;
9790 if let ExprKind::HashElement { hash, key } = &target.kind {
9792 self.check_strict_hash_var(hash, line)?;
9793 let k = self.eval_expr(key)?.to_string();
9794 let op = *op;
9795 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
9796 BinOp::Add => {
9797 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9798 PerlValue::integer(a.wrapping_add(b))
9799 } else {
9800 PerlValue::float(old.to_number() + rhs.to_number())
9801 }
9802 }
9803 BinOp::Sub => {
9804 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9805 PerlValue::integer(a.wrapping_sub(b))
9806 } else {
9807 PerlValue::float(old.to_number() - rhs.to_number())
9808 }
9809 }
9810 BinOp::Concat => {
9811 let mut s = old.to_string();
9812 rhs.append_to(&mut s);
9813 PerlValue::string(s)
9814 }
9815 _ => PerlValue::float(old.to_number() + rhs.to_number()),
9816 })?);
9817 }
9818 if let ExprKind::ArrayElement { array, index } = &target.kind {
9820 self.check_strict_array_var(array, line)?;
9821 let idx = self.eval_expr(index)?.to_int();
9822 let op = *op;
9823 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
9824 BinOp::Add => {
9825 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9826 PerlValue::integer(a.wrapping_add(b))
9827 } else {
9828 PerlValue::float(old.to_number() + rhs.to_number())
9829 }
9830 }
9831 BinOp::Sub => {
9832 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9833 PerlValue::integer(a.wrapping_sub(b))
9834 } else {
9835 PerlValue::float(old.to_number() - rhs.to_number())
9836 }
9837 }
9838 BinOp::Mul => {
9839 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9840 PerlValue::integer(a.wrapping_mul(b))
9841 } else {
9842 PerlValue::float(old.to_number() * rhs.to_number())
9843 }
9844 }
9845 BinOp::Div => PerlValue::float(old.to_number() / rhs.to_number()),
9846 BinOp::Mod => {
9847 let a = old.to_int();
9852 let b = rhs.to_int();
9853 if b == 0 {
9854 PerlValue::integer(0)
9855 } else {
9856 PerlValue::integer(crate::value::perl_mod_i64(a, b))
9857 }
9858 }
9859 BinOp::Concat => {
9860 let mut s = old.to_string();
9861 rhs.append_to(&mut s);
9862 PerlValue::string(s)
9863 }
9864 BinOp::Pow => PerlValue::float(old.to_number().powf(rhs.to_number())),
9865 BinOp::BitAnd => PerlValue::integer(old.to_int() & rhs.to_int()),
9866 BinOp::BitOr => PerlValue::integer(old.to_int() | rhs.to_int()),
9867 BinOp::BitXor => PerlValue::integer(old.to_int() ^ rhs.to_int()),
9868 BinOp::ShiftLeft => PerlValue::integer(old.to_int() << rhs.to_int()),
9869 BinOp::ShiftRight => PerlValue::integer(old.to_int() >> rhs.to_int()),
9870 _ => PerlValue::float(old.to_number() + rhs.to_number()),
9871 })?);
9872 }
9873 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
9874 let href = self.eval_expr(container)?;
9875 let mut key_vals = Vec::with_capacity(keys.len());
9876 for key_expr in keys {
9877 key_vals.push(self.eval_expr(key_expr)?);
9878 }
9879 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
9880 }
9881 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
9882 if let ExprKind::Deref {
9883 expr: inner,
9884 kind: Sigil::Array,
9885 } = &source.kind
9886 {
9887 let container = self.eval_arrow_array_base(inner, line)?;
9888 let idxs = self.flatten_array_slice_index_specs(indices)?;
9889 return self
9890 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
9891 }
9892 }
9893 if let ExprKind::ArrowDeref {
9894 expr: arr_expr,
9895 index,
9896 kind: DerefKind::Array,
9897 } = &target.kind
9898 {
9899 if let ExprKind::List(indices) = &index.kind {
9900 let container = self.eval_arrow_array_base(arr_expr, line)?;
9901 let mut idxs = Vec::with_capacity(indices.len());
9902 for ix in indices {
9903 idxs.push(self.eval_expr(ix)?.to_int());
9904 }
9905 return self
9906 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
9907 }
9908 }
9909 let old = self.eval_expr(target)?;
9910 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
9911 self.assign_value(target, new_val.clone())?;
9912 Ok(new_val)
9913 }
9914
9915 ExprKind::Ternary {
9919 condition,
9920 then_expr,
9921 else_expr,
9922 } => {
9923 if self.eval_boolean_rvalue_condition(condition)? {
9924 self.eval_expr_ctx(then_expr, ctx)
9925 } else {
9926 self.eval_expr_ctx(else_expr, ctx)
9927 }
9928 }
9929
9930 ExprKind::Range {
9932 from,
9933 to,
9934 exclusive,
9935 step,
9936 } => {
9937 if ctx == WantarrayCtx::List {
9938 let f = self.eval_expr(from)?;
9939 let t = self.eval_expr(to)?;
9940 if let Some(s) = step {
9941 let step_val = self.eval_expr(s)?.to_int();
9942 let from_i = f.to_int();
9943 let to_i = t.to_int();
9944 let list = if step_val == 0 {
9945 vec![]
9946 } else if step_val > 0 {
9947 (from_i..=to_i)
9948 .step_by(step_val as usize)
9949 .map(PerlValue::integer)
9950 .collect()
9951 } else {
9952 std::iter::successors(Some(from_i), |&x| {
9953 let next = x - step_val.abs();
9954 if next >= to_i {
9955 Some(next)
9956 } else {
9957 None
9958 }
9959 })
9960 .map(PerlValue::integer)
9961 .collect()
9962 };
9963 Ok(PerlValue::array(list))
9964 } else {
9965 let list = perl_list_range_expand(f, t);
9966 Ok(PerlValue::array(list))
9967 }
9968 } else {
9969 let key = std::ptr::from_ref(expr) as usize;
9970 match (&from.kind, &to.kind) {
9971 (
9972 ExprKind::Regex(left_pat, left_flags),
9973 ExprKind::Regex(right_pat, right_flags),
9974 ) => {
9975 let dot = self.scalar_flipflop_dot_line();
9976 let subject = self.scope.get_scalar("_").to_string();
9977 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9978 |e| match e {
9979 FlowOrError::Error(err) => err,
9980 FlowOrError::Flow(_) => PerlError::runtime(
9981 "unexpected flow in regex flip-flop",
9982 line,
9983 ),
9984 },
9985 )?;
9986 let right_re = self
9987 .compile_regex(right_pat, right_flags, line)
9988 .map_err(|e| match e {
9989 FlowOrError::Error(err) => err,
9990 FlowOrError::Flow(_) => PerlError::runtime(
9991 "unexpected flow in regex flip-flop",
9992 line,
9993 ),
9994 })?;
9995 let left_m = left_re.is_match(&subject);
9996 let right_m = right_re.is_match(&subject);
9997 let st = self.flip_flop_tree.entry(key).or_default();
9998 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9999 &mut st.active,
10000 &mut st.exclusive_left_line,
10001 *exclusive,
10002 dot,
10003 left_m,
10004 right_m,
10005 )))
10006 }
10007 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
10008 let dot = self.scalar_flipflop_dot_line();
10009 let subject = self.scope.get_scalar("_").to_string();
10010 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10011 |e| match e {
10012 FlowOrError::Error(err) => err,
10013 FlowOrError::Flow(_) => PerlError::runtime(
10014 "unexpected flow in regex/eof flip-flop",
10015 line,
10016 ),
10017 },
10018 )?;
10019 let left_m = left_re.is_match(&subject);
10020 let right_m = self.eof_without_arg_is_true();
10021 let st = self.flip_flop_tree.entry(key).or_default();
10022 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
10023 &mut st.active,
10024 &mut st.exclusive_left_line,
10025 *exclusive,
10026 dot,
10027 left_m,
10028 right_m,
10029 )))
10030 }
10031 (
10032 ExprKind::Regex(left_pat, left_flags),
10033 ExprKind::Integer(_) | ExprKind::Float(_),
10034 ) => {
10035 let dot = self.scalar_flipflop_dot_line();
10036 let right = self.eval_expr(to)?.to_int();
10037 let subject = self.scope.get_scalar("_").to_string();
10038 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10039 |e| match e {
10040 FlowOrError::Error(err) => err,
10041 FlowOrError::Flow(_) => PerlError::runtime(
10042 "unexpected flow in regex flip-flop",
10043 line,
10044 ),
10045 },
10046 )?;
10047 let left_m = left_re.is_match(&subject);
10048 let right_m = dot == right;
10049 let st = self.flip_flop_tree.entry(key).or_default();
10050 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
10051 &mut st.active,
10052 &mut st.exclusive_left_line,
10053 *exclusive,
10054 dot,
10055 left_m,
10056 right_m,
10057 )))
10058 }
10059 (ExprKind::Regex(left_pat, left_flags), _) => {
10060 if let ExprKind::Eof(Some(_)) = &to.kind {
10061 return Err(FlowOrError::Error(PerlError::runtime(
10062 "regex flip-flop with eof(HANDLE) is not supported",
10063 line,
10064 )));
10065 }
10066 let dot = self.scalar_flipflop_dot_line();
10067 let subject = self.scope.get_scalar("_").to_string();
10068 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10069 |e| match e {
10070 FlowOrError::Error(err) => err,
10071 FlowOrError::Flow(_) => PerlError::runtime(
10072 "unexpected flow in regex flip-flop",
10073 line,
10074 ),
10075 },
10076 )?;
10077 let left_m = left_re.is_match(&subject);
10078 let right_m = self.eval_boolean_rvalue_condition(to)?;
10079 let st = self.flip_flop_tree.entry(key).or_default();
10080 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
10081 &mut st.active,
10082 &mut st.exclusive_left_line,
10083 *exclusive,
10084 dot,
10085 left_m,
10086 right_m,
10087 )))
10088 }
10089 _ => {
10090 let left = self.eval_expr(from)?.to_int();
10091 let right = self.eval_expr(to)?.to_int();
10092 let dot = self.scalar_flipflop_dot_line();
10093 let st = self.flip_flop_tree.entry(key).or_default();
10094 if !st.active {
10095 if dot == left {
10096 st.active = true;
10097 if *exclusive {
10098 st.exclusive_left_line = Some(dot);
10099 } else {
10100 st.exclusive_left_line = None;
10101 if dot == right {
10102 st.active = false;
10103 }
10104 }
10105 return Ok(PerlValue::integer(1));
10106 }
10107 return Ok(PerlValue::integer(0));
10108 }
10109 if let Some(ll) = st.exclusive_left_line {
10110 if dot == right && dot > ll {
10111 st.active = false;
10112 st.exclusive_left_line = None;
10113 }
10114 } else if dot == right {
10115 st.active = false;
10116 }
10117 Ok(PerlValue::integer(1))
10118 }
10119 }
10120 }
10121 }
10122
10123 ExprKind::SliceRange { from, to, step } => {
10129 let f = match from {
10130 Some(e) => self.eval_expr(e)?,
10131 None => {
10132 return Err(PerlError::runtime(
10133 "open-ended slice range cannot be evaluated outside slice subscript",
10134 line,
10135 )
10136 .into());
10137 }
10138 };
10139 let t = match to {
10140 Some(e) => self.eval_expr(e)?,
10141 None => {
10142 return Err(PerlError::runtime(
10143 "open-ended slice range cannot be evaluated outside slice subscript",
10144 line,
10145 )
10146 .into());
10147 }
10148 };
10149 let list = if let Some(s) = step {
10150 let sv = self.eval_expr(s)?;
10151 crate::value::perl_list_range_expand_stepped(f, t, sv)
10152 } else {
10153 perl_list_range_expand(f, t)
10154 };
10155 Ok(PerlValue::array(list))
10156 }
10157
10158 ExprKind::Repeat {
10160 expr,
10161 count,
10162 list_repeat,
10163 } => {
10164 let n = self.eval_expr(count)?.to_int().max(0) as usize;
10165 if *list_repeat {
10166 let saved = self.wantarray_kind;
10168 self.wantarray_kind = WantarrayCtx::List;
10169 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10170 self.wantarray_kind = saved;
10171 let items: Vec<PerlValue> = val.as_array_vec().unwrap_or_else(|| vec![val]);
10172 let mut result = Vec::with_capacity(items.len() * n);
10173 for _ in 0..n {
10174 result.extend(items.iter().cloned());
10175 }
10176 Ok(PerlValue::array(result))
10177 } else {
10178 let val = self.eval_expr(expr)?;
10180 Ok(PerlValue::string(val.to_string().repeat(n)))
10181 }
10182 }
10183
10184 ExprKind::MyExpr { keyword, decls } => {
10189 let stmt_kind = match keyword.as_str() {
10192 "my" => StmtKind::My(decls.clone()),
10193 "our" => StmtKind::Our(decls.clone()),
10194 "state" => StmtKind::State(decls.clone()),
10195 "local" => StmtKind::Local(decls.clone()),
10196 _ => StmtKind::My(decls.clone()),
10197 };
10198 let stmt = Statement {
10199 label: None,
10200 kind: stmt_kind,
10201 line,
10202 };
10203 self.exec_statement(&stmt)?;
10204 let first = decls.first().ok_or_else(|| {
10208 FlowOrError::Error(PerlError::runtime("MyExpr: empty decl list", line))
10209 })?;
10210 Ok(match first.sigil {
10211 Sigil::Scalar => self.scope.get_scalar(&first.name),
10212 Sigil::Array => PerlValue::array(self.scope.get_array(&first.name)),
10213 Sigil::Hash => {
10214 let h = self.scope.get_hash(&first.name);
10215 let mut flat: Vec<PerlValue> = Vec::with_capacity(h.len() * 2);
10216 for (k, v) in h {
10217 flat.push(PerlValue::string(k));
10218 flat.push(v);
10219 }
10220 PerlValue::array(flat)
10221 }
10222 Sigil::Typeglob => PerlValue::UNDEF,
10223 })
10224 }
10225
10226 ExprKind::FuncCall { name, args } => {
10228 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
10231 if matches!(dispatch_name, "read") && args.len() >= 3 {
10233 let fh_val = self.eval_expr(&args[0])?;
10234 let fh = fh_val
10235 .as_io_handle_name()
10236 .unwrap_or_else(|| fh_val.to_string());
10237 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
10238 let offset = if args.len() > 3 {
10239 self.eval_expr(&args[3])?.to_int().max(0) as usize
10240 } else {
10241 0
10242 };
10243 let var_name = match &args[1].kind {
10245 ExprKind::ScalarVar(n) => n.clone(),
10246 _ => self.eval_expr(&args[1])?.to_string(),
10247 };
10248 let mut buf = vec![0u8; len];
10249 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
10250 slot.lock().read(&mut buf).unwrap_or(0)
10251 } else if fh == "STDIN" {
10252 std::io::stdin().read(&mut buf).unwrap_or(0)
10253 } else {
10254 return Err(PerlError::runtime(
10255 format!("read: unopened handle {}", fh),
10256 line,
10257 )
10258 .into());
10259 };
10260 buf.truncate(n);
10261 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
10262 if offset > 0 {
10263 let mut existing = self.scope.get_scalar(&var_name).to_string();
10264 while existing.len() < offset {
10265 existing.push('\0');
10266 }
10267 existing.push_str(&read_str);
10268 let _ = self
10269 .scope
10270 .set_scalar(&var_name, PerlValue::string(existing));
10271 } else {
10272 let _ = self
10273 .scope
10274 .set_scalar(&var_name, PerlValue::string(read_str));
10275 }
10276 return Ok(PerlValue::integer(n as i64));
10277 }
10278 if matches!(dispatch_name, "group_by" | "chunk_by") {
10279 if args.len() != 2 {
10280 return Err(PerlError::runtime(
10281 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
10282 line,
10283 )
10284 .into());
10285 }
10286 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
10287 }
10288 if matches!(dispatch_name, "puniq" | "pfirst" | "pany") {
10289 let mut arg_vals = Vec::with_capacity(args.len());
10290 for a in args {
10291 arg_vals.push(self.eval_expr(a)?);
10292 }
10293 let saved_wa = self.wantarray_kind;
10294 self.wantarray_kind = ctx;
10295 let r = self.eval_par_list_call(dispatch_name, &arg_vals, ctx, line);
10296 self.wantarray_kind = saved_wa;
10297 return r.map_err(Into::into);
10298 }
10299 let arg_vals = if matches!(dispatch_name, "any" | "all" | "none" | "first")
10300 || matches!(
10301 dispatch_name,
10302 "take_while"
10303 | "drop_while"
10304 | "skip_while"
10305 | "reject"
10306 | "grepv"
10307 | "tap"
10308 | "peek"
10309 )
10310 || matches!(
10311 dispatch_name,
10312 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
10313 ) {
10314 if args.len() != 2 {
10315 return Err(PerlError::runtime(
10316 format!("{}: expected BLOCK, LIST", name),
10317 line,
10318 )
10319 .into());
10320 }
10321 let cr = self.eval_expr(&args[0])?;
10322 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
10323 let mut v = vec![cr];
10324 v.extend(list_src.to_list());
10325 v
10326 } else if matches!(
10327 dispatch_name,
10328 "zip"
10329 | "zip_longest"
10330 | "zip_shortest"
10331 | "mesh"
10332 | "mesh_longest"
10333 | "mesh_shortest"
10334 ) {
10335 let mut v = Vec::with_capacity(args.len());
10336 for a in args {
10337 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
10338 }
10339 v
10340 } else if matches!(
10341 dispatch_name,
10342 "uniq"
10343 | "distinct"
10344 | "uniqstr"
10345 | "uniqint"
10346 | "uniqnum"
10347 | "flatten"
10348 | "set"
10349 | "list_count"
10350 | "list_size"
10351 | "count"
10352 | "size"
10353 | "cnt"
10354 | "with_index"
10355 | "shuffle"
10356 | "sum"
10357 | "sum0"
10358 | "product"
10359 | "min"
10360 | "max"
10361 | "minstr"
10362 | "maxstr"
10363 | "mean"
10364 | "median"
10365 | "mode"
10366 | "stddev"
10367 | "variance"
10368 | "pairs"
10369 | "unpairs"
10370 | "pairkeys"
10371 | "pairvalues"
10372 ) {
10373 let mut list_out = Vec::new();
10377 if args.len() == 1 {
10378 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
10379 } else {
10380 for a in args {
10381 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
10382 }
10383 }
10384 list_out
10385 } else if matches!(dispatch_name, "take" | "head" | "tail" | "drop") {
10386 if args.is_empty() {
10387 return Err(PerlError::runtime(
10388 "take/head/tail/drop: need LIST..., N or unary N",
10389 line,
10390 )
10391 .into());
10392 }
10393 let mut arg_vals = Vec::with_capacity(args.len());
10394 if args.len() == 1 {
10395 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10397 } else {
10398 for a in &args[..args.len() - 1] {
10399 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
10400 }
10401 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
10402 }
10403 arg_vals
10404 } else if matches!(dispatch_name, "chunked" | "windowed") {
10405 let mut list_out = Vec::new();
10406 match args.len() {
10407 0 => {
10408 return Err(PerlError::runtime(
10409 format!("{name}: expected (LIST, N) or unary N after |>"),
10410 line,
10411 )
10412 .into());
10413 }
10414 1 => {
10415 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10417 }
10418 2 => {
10419 list_out.extend(
10420 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
10421 );
10422 list_out.push(self.eval_expr(&args[1])?);
10423 }
10424 _ => {
10425 return Err(PerlError::runtime(
10426 format!(
10427 "{name}: expected exactly (LIST, N); use one list expression then size"
10428 ),
10429 line,
10430 )
10431 .into());
10432 }
10433 }
10434 list_out
10435 } else {
10436 let mut arg_vals = Vec::with_capacity(args.len());
10439 for a in args {
10440 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
10441 if let Some(items) = v.as_array_vec() {
10442 arg_vals.extend(items);
10443 } else {
10444 arg_vals.push(v);
10445 }
10446 }
10447 arg_vals
10448 };
10449 let saved_wa = self.wantarray_kind;
10451 self.wantarray_kind = ctx;
10452 if !crate::compat_mode() {
10455 if matches!(
10456 dispatch_name,
10457 "take_while"
10458 | "drop_while"
10459 | "skip_while"
10460 | "reject"
10461 | "grepv"
10462 | "tap"
10463 | "peek"
10464 ) {
10465 let r =
10466 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
10467 self.wantarray_kind = saved_wa;
10468 return r.map_err(Into::into);
10469 }
10470 if let Some(r) =
10471 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
10472 {
10473 self.wantarray_kind = saved_wa;
10474 return r.map_err(Into::into);
10475 }
10476 }
10477 if let Some(sub) = self.resolve_sub_by_name(name) {
10478 self.wantarray_kind = saved_wa;
10479 let args = self.with_topic_default_args(arg_vals);
10480 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
10481 return self.call_sub_with_package(&sub, args, ctx, line, pkg);
10482 }
10483 if crate::compat_mode() {
10485 if matches!(
10486 dispatch_name,
10487 "take_while"
10488 | "drop_while"
10489 | "skip_while"
10490 | "reject"
10491 | "grepv"
10492 | "tap"
10493 | "peek"
10494 ) {
10495 let r =
10496 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
10497 self.wantarray_kind = saved_wa;
10498 return r.map_err(Into::into);
10499 }
10500 if let Some(r) =
10501 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
10502 {
10503 self.wantarray_kind = saved_wa;
10504 return r.map_err(Into::into);
10505 }
10506 }
10507 self.wantarray_kind = saved_wa;
10508 self.call_named_sub(name, arg_vals, line, ctx)
10509 }
10510 ExprKind::IndirectCall {
10511 target,
10512 args,
10513 ampersand: _,
10514 pass_caller_arglist,
10515 } => {
10516 let tval = self.eval_expr(target)?;
10517 let arg_vals = if *pass_caller_arglist {
10518 self.scope.get_array("_")
10519 } else {
10520 let mut v = Vec::with_capacity(args.len());
10521 for a in args {
10522 v.push(self.eval_expr(a)?);
10523 }
10524 v
10525 };
10526 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
10527 }
10528 ExprKind::MethodCall {
10529 object,
10530 method,
10531 args,
10532 super_call,
10533 } => {
10534 let obj = self.eval_expr(object)?;
10535 let mut arg_vals = vec![obj.clone()];
10536 for a in args {
10537 arg_vals.push(self.eval_expr(a)?);
10538 }
10539 if let Some(r) =
10540 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
10541 {
10542 return r.map_err(Into::into);
10543 }
10544 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
10545 return r.map_err(Into::into);
10546 }
10547 let class = if let Some(b) = obj.as_blessed_ref() {
10549 b.class.clone()
10550 } else if let Some(s) = obj.as_str() {
10551 s } else {
10553 return Err(PerlError::runtime("Can't call method on non-object", line).into());
10554 };
10555 if method == "VERSION" && !*super_call {
10556 if let Some(ver) = self.package_version_scalar(class.as_str())? {
10557 return Ok(ver);
10558 }
10559 }
10560 if !*super_call {
10562 match method.as_str() {
10563 "isa" => {
10564 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
10565 let mro = self.mro_linearize(&class);
10566 let result = mro.iter().any(|c| c == &target);
10567 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
10568 }
10569 "can" => {
10570 let target_method =
10571 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
10572 let found = self
10573 .resolve_method_full_name(&class, &target_method, false)
10574 .and_then(|fq| self.subs.get(&fq))
10575 .is_some();
10576 if found {
10577 return Ok(PerlValue::code_ref(Arc::new(PerlSub {
10578 name: target_method,
10579 params: vec![],
10580 body: vec![],
10581 closure_env: None,
10582 prototype: None,
10583 fib_like: None,
10584 })));
10585 } else {
10586 return Ok(PerlValue::UNDEF);
10587 }
10588 }
10589 "DOES" => {
10590 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
10591 let mro = self.mro_linearize(&class);
10592 let result = mro.iter().any(|c| c == &target);
10593 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
10594 }
10595 _ => {}
10596 }
10597 }
10598 let full_name = self
10599 .resolve_method_full_name(&class, method, *super_call)
10600 .ok_or_else(|| {
10601 PerlError::runtime(
10602 format!(
10603 "Can't locate method \"{}\" for invocant \"{}\"",
10604 method, class
10605 ),
10606 line,
10607 )
10608 })?;
10609 if let Some(sub) = self.subs.get(&full_name).cloned() {
10610 self.call_sub(&sub, arg_vals, ctx, line)
10611 } else if method == "new" && !*super_call {
10612 self.builtin_new(&class, arg_vals, line)
10614 } else if let Some(r) =
10615 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
10616 {
10617 r
10618 } else {
10619 Err(PerlError::runtime(
10620 format!(
10621 "Can't locate method \"{}\" in package \"{}\"",
10622 method, class
10623 ),
10624 line,
10625 )
10626 .into())
10627 }
10628 }
10629
10630 ExprKind::Print { handle, args } => {
10632 self.exec_print(handle.as_deref(), args, false, line)
10633 }
10634 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
10635 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
10636 ExprKind::Die(args) => {
10637 if args.is_empty() {
10638 let current = self.scope.get_scalar("@");
10640 let msg = if current.is_undef() || current.to_string().is_empty() {
10641 let mut m = "Died".to_string();
10642 m.push_str(&self.die_warn_at_suffix(line));
10643 m.push('\n');
10644 m
10645 } else {
10646 current.to_string()
10647 };
10648 self.fire_pseudosig_die(&msg, line)?;
10649 return Err(PerlError::die(msg, line).into());
10650 }
10651 if args.len() == 1 {
10653 let v = self.eval_expr(&args[0])?;
10654 if v.as_hash_ref().is_some()
10655 || v.as_blessed_ref().is_some()
10656 || v.as_array_ref().is_some()
10657 || v.as_code_ref().is_some()
10658 {
10659 let msg = v.to_string();
10660 self.fire_pseudosig_die(&msg, line)?;
10661 return Err(PerlError::die_with_value(v, msg, line).into());
10662 }
10663 }
10664 let mut msg = String::new();
10665 for a in args {
10666 let v = self.eval_expr(a)?;
10667 msg.push_str(&v.to_string());
10668 }
10669 if msg.is_empty() {
10670 msg = "Died".to_string();
10671 }
10672 if !msg.ends_with('\n') {
10673 msg.push_str(&self.die_warn_at_suffix(line));
10674 msg.push('\n');
10675 }
10676 self.fire_pseudosig_die(&msg, line)?;
10677 Err(PerlError::die(msg, line).into())
10678 }
10679 ExprKind::Warn(args) => {
10680 let mut msg = String::new();
10681 for a in args {
10682 let v = self.eval_expr(a)?;
10683 msg.push_str(&v.to_string());
10684 }
10685 if msg.is_empty() {
10686 msg = "Warning: something's wrong".to_string();
10687 }
10688 if !msg.ends_with('\n') {
10689 msg.push_str(&self.die_warn_at_suffix(line));
10690 msg.push('\n');
10691 }
10692 self.fire_pseudosig_warn(&msg, line)?;
10693 Ok(PerlValue::integer(1))
10694 }
10695
10696 ExprKind::Match {
10698 expr,
10699 pattern,
10700 flags,
10701 scalar_g,
10702 delim: _,
10703 } => {
10704 let val = self.eval_expr(expr)?;
10705 if val.is_iterator() {
10706 let source = crate::map_stream::into_pull_iter(val);
10707 let re = self.compile_regex(pattern, flags, line)?;
10708 let global = flags.contains('g');
10709 if global {
10710 return Ok(PerlValue::iterator(std::sync::Arc::new(
10711 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
10712 )));
10713 } else {
10714 return Ok(PerlValue::iterator(std::sync::Arc::new(
10715 crate::map_stream::MatchStreamIterator::new(source, re),
10716 )));
10717 }
10718 }
10719 let s = val.to_string();
10720 let pos_key = match &expr.kind {
10721 ExprKind::ScalarVar(n) => n.as_str(),
10722 _ => "_",
10723 };
10724 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
10725 }
10726 ExprKind::Substitution {
10727 expr,
10728 pattern,
10729 replacement,
10730 flags,
10731 delim: _,
10732 } => {
10733 let val = self.eval_expr(expr)?;
10734 if val.is_iterator() {
10735 let source = crate::map_stream::into_pull_iter(val);
10736 let re = self.compile_regex(pattern, flags, line)?;
10737 let global = flags.contains('g');
10738 return Ok(PerlValue::iterator(std::sync::Arc::new(
10739 crate::map_stream::SubstStreamIterator::new(
10740 source,
10741 re,
10742 normalize_replacement_backrefs(replacement),
10743 global,
10744 ),
10745 )));
10746 }
10747 let s = val.to_string();
10748 self.regex_subst_execute(
10749 s,
10750 pattern,
10751 replacement.as_str(),
10752 flags.as_str(),
10753 expr,
10754 line,
10755 )
10756 }
10757 ExprKind::Transliterate {
10758 expr,
10759 from,
10760 to,
10761 flags,
10762 delim: _,
10763 } => {
10764 let val = self.eval_expr(expr)?;
10765 if val.is_iterator() {
10766 let source = crate::map_stream::into_pull_iter(val);
10767 return Ok(PerlValue::iterator(std::sync::Arc::new(
10768 crate::map_stream::TransliterateStreamIterator::new(
10769 source, from, to, flags,
10770 ),
10771 )));
10772 }
10773 let s = val.to_string();
10774 self.regex_transliterate_execute(
10775 s,
10776 from.as_str(),
10777 to.as_str(),
10778 flags.as_str(),
10779 expr,
10780 line,
10781 )
10782 }
10783
10784 ExprKind::MapExpr {
10786 block,
10787 list,
10788 flatten_array_refs,
10789 stream,
10790 } => {
10791 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10792 if *stream {
10793 let out =
10794 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
10795 if ctx == WantarrayCtx::List {
10796 return Ok(out);
10797 }
10798 return Ok(PerlValue::integer(out.to_list().len() as i64));
10799 }
10800 let items = list_val.to_list();
10801 if items.len() == 1 {
10802 if let Some(p) = items[0].as_pipeline() {
10803 if *flatten_array_refs {
10804 return Err(PerlError::runtime(
10805 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
10806 line,
10807 )
10808 .into());
10809 }
10810 let sub = self.anon_coderef_from_block(block);
10811 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
10812 return Ok(PerlValue::pipeline(Arc::clone(&p)));
10813 }
10814 }
10815 let mut result = Vec::new();
10820 for item in items {
10821 self.scope.set_topic(item);
10822 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
10823 result.extend(val.map_flatten_outputs(*flatten_array_refs));
10824 }
10825 if ctx == WantarrayCtx::List {
10826 Ok(PerlValue::array(result))
10827 } else {
10828 Ok(PerlValue::integer(result.len() as i64))
10829 }
10830 }
10831 ExprKind::ForEachExpr { block, list } => {
10832 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10833 if list_val.is_iterator() {
10835 let iter = list_val.into_iterator();
10836 let mut count = 0i64;
10837 while let Some(item) = iter.next_item() {
10838 count += 1;
10839 self.scope.set_topic(item);
10840 self.exec_block(block)?;
10841 }
10842 return Ok(PerlValue::integer(count));
10843 }
10844 let items = list_val.to_list();
10845 let count = items.len();
10846 for item in items {
10847 self.scope.set_topic(item);
10848 self.exec_block(block)?;
10849 }
10850 Ok(PerlValue::integer(count as i64))
10851 }
10852 ExprKind::MapExprComma {
10853 expr,
10854 list,
10855 flatten_array_refs,
10856 stream,
10857 } => {
10858 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10859 if *stream {
10860 let out =
10861 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
10862 if ctx == WantarrayCtx::List {
10863 return Ok(out);
10864 }
10865 return Ok(PerlValue::integer(out.to_list().len() as i64));
10866 }
10867 let items = list_val.to_list();
10868 let mut result = Vec::new();
10869 for item in items {
10870 self.scope.set_topic_local(item.clone());
10876 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10877 let val = if !crate::compat_mode() {
10881 if let Some(sub) = val.as_code_ref() {
10882 let sub = sub.clone();
10883 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::List, line)?
10884 } else {
10885 val
10886 }
10887 } else {
10888 val
10889 };
10890 result.extend(val.map_flatten_outputs(*flatten_array_refs));
10891 }
10892 if ctx == WantarrayCtx::List {
10893 Ok(PerlValue::array(result))
10894 } else {
10895 Ok(PerlValue::integer(result.len() as i64))
10896 }
10897 }
10898 ExprKind::GrepExpr {
10899 block,
10900 list,
10901 keyword,
10902 } => {
10903 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10904 if keyword.is_stream() {
10905 let out = self.filter_stream_block_output(list_val, block, line)?;
10906 if ctx == WantarrayCtx::List {
10907 return Ok(out);
10908 }
10909 return Ok(PerlValue::integer(out.to_list().len() as i64));
10910 }
10911 let items = list_val.to_list();
10912 if items.len() == 1 {
10913 if let Some(p) = items[0].as_pipeline() {
10914 let sub = self.anon_coderef_from_block(block);
10915 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
10916 return Ok(PerlValue::pipeline(Arc::clone(&p)));
10917 }
10918 }
10919 let mut result = Vec::new();
10920 for item in items {
10921 self.scope.set_topic(item.clone());
10922 let val = self.exec_block(block)?;
10923 let keep = if let Some(re) = val.as_regex() {
10926 re.is_match(&item.to_string())
10927 } else {
10928 val.is_true()
10929 };
10930 if keep {
10931 result.push(item);
10932 }
10933 }
10934 if ctx == WantarrayCtx::List {
10935 Ok(PerlValue::array(result))
10936 } else {
10937 Ok(PerlValue::integer(result.len() as i64))
10938 }
10939 }
10940 ExprKind::GrepExprComma {
10941 expr,
10942 list,
10943 keyword,
10944 } => {
10945 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10946 if keyword.is_stream() {
10947 let out = self.filter_stream_expr_output(list_val, expr, line)?;
10948 if ctx == WantarrayCtx::List {
10949 return Ok(out);
10950 }
10951 return Ok(PerlValue::integer(out.to_list().len() as i64));
10952 }
10953 let items = list_val.to_list();
10954 let mut result = Vec::new();
10955 for item in items {
10956 self.scope.set_topic_local(item.clone());
10960 let val = self.eval_expr(expr)?;
10961 let val = if !crate::compat_mode() {
10965 if let Some(sub) = val.as_code_ref() {
10966 let sub = sub.clone();
10967 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?
10968 } else {
10969 val
10970 }
10971 } else {
10972 val
10973 };
10974 let keep = if let Some(re) = val.as_regex() {
10975 re.is_match(&item.to_string())
10976 } else {
10977 val.is_true()
10978 };
10979 if keep {
10980 result.push(item);
10981 }
10982 }
10983 if ctx == WantarrayCtx::List {
10984 Ok(PerlValue::array(result))
10985 } else {
10986 Ok(PerlValue::integer(result.len() as i64))
10987 }
10988 }
10989 ExprKind::SortExpr { cmp, list } => {
10990 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10991 let mut items = list_val.to_list();
10992 match cmp {
10993 Some(SortComparator::Code(code_expr)) => {
10994 let sub = self.eval_expr(code_expr)?;
10995 let Some(sub) = sub.as_code_ref() else {
10996 return Err(PerlError::runtime(
10997 "sort: comparator must be a code reference",
10998 line,
10999 )
11000 .into());
11001 };
11002 let sub = sub.clone();
11003 items.sort_by(|a, b| {
11004 self.scope.set_sort_pair(a.clone(), b.clone());
11009 match self.call_sub(&sub, vec![a.clone(), b.clone()], ctx, line) {
11010 Ok(v) => {
11011 let n = v.to_int();
11012 if n < 0 {
11013 Ordering::Less
11014 } else if n > 0 {
11015 Ordering::Greater
11016 } else {
11017 Ordering::Equal
11018 }
11019 }
11020 Err(_) => Ordering::Equal,
11021 }
11022 });
11023 }
11024 Some(SortComparator::Block(cmp_block)) => {
11025 if let Some(mode) = detect_sort_block_fast(cmp_block) {
11026 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
11027 } else {
11028 let cmp_block = cmp_block.clone();
11029 items.sort_by(|a, b| {
11030 self.scope.set_sort_pair(a.clone(), b.clone());
11031 match self.exec_block(&cmp_block) {
11032 Ok(v) => {
11033 let n = v.to_int();
11034 if n < 0 {
11035 Ordering::Less
11036 } else if n > 0 {
11037 Ordering::Greater
11038 } else {
11039 Ordering::Equal
11040 }
11041 }
11042 Err(_) => Ordering::Equal,
11043 }
11044 });
11045 }
11046 }
11047 None => {
11048 items.sort_by_key(|a| a.to_string());
11049 }
11050 }
11051 Ok(PerlValue::array(items))
11052 }
11053 ExprKind::Rev(expr) => {
11054 let val = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11056 if val.is_iterator() {
11057 return Ok(PerlValue::iterator(Arc::new(
11058 crate::value::RevIterator::new(val.into_iterator()),
11059 )));
11060 }
11061 if let Some(s) = crate::value::set_payload(&val) {
11062 let mut out = crate::value::PerlSet::new();
11063 for (k, v) in s.iter().rev() {
11064 out.insert(k.clone(), v.clone());
11065 }
11066 return Ok(PerlValue::set(Arc::new(out)));
11067 }
11068 if let Some(ar) = val.as_array_ref() {
11069 let items: Vec<_> = ar.read().iter().rev().cloned().collect();
11070 return Ok(PerlValue::array_ref(Arc::new(parking_lot::RwLock::new(
11071 items,
11072 ))));
11073 }
11074 if let Some(hr) = val.as_hash_ref() {
11075 let mut out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
11076 for (k, v) in hr.read().iter() {
11077 out.insert(v.to_string(), PerlValue::string(k.clone()));
11078 }
11079 return Ok(PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(out))));
11080 }
11081 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11083 if let Some(hm) = val.as_hash_map() {
11084 let mut out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
11085 for (k, v) in hm.iter() {
11086 out.insert(v.to_string(), PerlValue::string(k.clone()));
11087 }
11088 return Ok(PerlValue::hash(out));
11089 }
11090 if val.as_array_vec().is_some() {
11091 let mut items = val.to_list();
11092 items.reverse();
11093 Ok(PerlValue::array(items))
11094 } else {
11095 let items = val.to_list();
11096 if items.len() > 1 {
11097 let mut items = items;
11098 items.reverse();
11099 Ok(PerlValue::array(items))
11100 } else {
11101 let s = val.to_string();
11102 Ok(PerlValue::string(s.chars().rev().collect()))
11103 }
11104 }
11105 }
11106 ExprKind::ReverseExpr(list) => {
11107 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11108 match ctx {
11109 WantarrayCtx::List => {
11110 let mut items = val.to_list();
11111 items.reverse();
11112 Ok(PerlValue::array(items))
11113 }
11114 _ => {
11115 let items = val.to_list();
11116 let s: String = items.iter().map(|v| v.to_string()).collect();
11117 Ok(PerlValue::string(s.chars().rev().collect()))
11118 }
11119 }
11120 }
11121
11122 ExprKind::ParLinesExpr {
11124 path,
11125 callback,
11126 progress,
11127 } => self.eval_par_lines_expr(
11128 path.as_ref(),
11129 callback.as_ref(),
11130 progress.as_deref(),
11131 line,
11132 ),
11133 ExprKind::ParWalkExpr {
11134 path,
11135 callback,
11136 progress,
11137 } => {
11138 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
11139 }
11140 ExprKind::PwatchExpr { path, callback } => {
11141 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
11142 }
11143 ExprKind::PMapExpr {
11144 block,
11145 list,
11146 progress,
11147 flat_outputs,
11148 on_cluster,
11149 stream,
11150 } => {
11151 let show_progress = progress
11152 .as_ref()
11153 .map(|p| self.eval_expr(p))
11154 .transpose()?
11155 .map(|v| v.is_true())
11156 .unwrap_or(false);
11157 let list_val = self.eval_expr(list)?;
11158 if let Some(cluster_e) = on_cluster {
11159 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
11160 return self.eval_pmap_remote(
11161 cluster_val,
11162 list_val,
11163 show_progress,
11164 block,
11165 *flat_outputs,
11166 line,
11167 );
11168 }
11169 if *stream {
11170 let source = crate::map_stream::into_pull_iter(list_val);
11171 let sub = self.anon_coderef_from_block(block);
11172 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
11173 return Ok(PerlValue::iterator(Arc::new(
11174 crate::map_stream::PMapStreamIterator::new(
11175 source,
11176 sub,
11177 self.subs.clone(),
11178 capture,
11179 atomic_arrays,
11180 atomic_hashes,
11181 *flat_outputs,
11182 ),
11183 )));
11184 }
11185 let items = list_val.to_list();
11186 let block = block.clone();
11187 let subs = self.subs.clone();
11188 let (scope_capture, atomic_arrays, atomic_hashes) =
11189 self.scope.capture_with_atomics();
11190 let pmap_progress = PmapProgress::new(show_progress, items.len());
11191
11192 if *flat_outputs {
11193 let mut indexed: Vec<(usize, Vec<PerlValue>)> = items
11194 .into_par_iter()
11195 .enumerate()
11196 .map(|(i, item)| {
11197 let mut local_interp = VMHelper::new();
11198 local_interp.subs = subs.clone();
11199 local_interp.scope.restore_capture(&scope_capture);
11200 local_interp
11201 .scope
11202 .restore_atomics(&atomic_arrays, &atomic_hashes);
11203 local_interp.enable_parallel_guard();
11204 local_interp.scope.set_topic(item);
11205 let val = match local_interp.exec_block(&block) {
11206 Ok(val) => val,
11207 Err(_) => PerlValue::UNDEF,
11208 };
11209 let chunk = val.map_flatten_outputs(true);
11210 pmap_progress.tick();
11211 (i, chunk)
11212 })
11213 .collect();
11214 pmap_progress.finish();
11215 indexed.sort_by_key(|(i, _)| *i);
11216 let results: Vec<PerlValue> =
11217 indexed.into_iter().flat_map(|(_, v)| v).collect();
11218 Ok(PerlValue::array(results))
11219 } else {
11220 let results: Vec<PerlValue> = items
11221 .into_par_iter()
11222 .map(|item| {
11223 let mut local_interp = VMHelper::new();
11224 local_interp.subs = subs.clone();
11225 local_interp.scope.restore_capture(&scope_capture);
11226 local_interp
11227 .scope
11228 .restore_atomics(&atomic_arrays, &atomic_hashes);
11229 local_interp.enable_parallel_guard();
11230 local_interp.scope.set_topic(item);
11231 let val = match local_interp.exec_block(&block) {
11232 Ok(val) => val,
11233 Err(_) => PerlValue::UNDEF,
11234 };
11235 pmap_progress.tick();
11236 val
11237 })
11238 .collect();
11239 pmap_progress.finish();
11240 Ok(PerlValue::array(results))
11241 }
11242 }
11243 ExprKind::PMapChunkedExpr {
11244 chunk_size,
11245 block,
11246 list,
11247 progress,
11248 } => {
11249 let show_progress = progress
11250 .as_ref()
11251 .map(|p| self.eval_expr(p))
11252 .transpose()?
11253 .map(|v| v.is_true())
11254 .unwrap_or(false);
11255 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
11256 let list_val = self.eval_expr(list)?;
11257 let items = list_val.to_list();
11258 let block = block.clone();
11259 let subs = self.subs.clone();
11260 let (scope_capture, atomic_arrays, atomic_hashes) =
11261 self.scope.capture_with_atomics();
11262
11263 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = items
11264 .chunks(chunk_n)
11265 .enumerate()
11266 .map(|(i, c)| (i, c.to_vec()))
11267 .collect();
11268
11269 let n_chunks = indexed_chunks.len();
11270 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
11271
11272 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
11273 .into_par_iter()
11274 .map(|(chunk_idx, chunk)| {
11275 let mut local_interp = VMHelper::new();
11276 local_interp.subs = subs.clone();
11277 local_interp.scope.restore_capture(&scope_capture);
11278 local_interp
11279 .scope
11280 .restore_atomics(&atomic_arrays, &atomic_hashes);
11281 local_interp.enable_parallel_guard();
11282 let mut out = Vec::with_capacity(chunk.len());
11283 for item in chunk {
11284 local_interp.scope.set_topic(item);
11285 match local_interp.exec_block(&block) {
11286 Ok(val) => out.push(val),
11287 Err(_) => out.push(PerlValue::UNDEF),
11288 }
11289 }
11290 pmap_progress.tick();
11291 (chunk_idx, out)
11292 })
11293 .collect();
11294
11295 pmap_progress.finish();
11296 chunk_results.sort_by_key(|(i, _)| *i);
11297 let results: Vec<PerlValue> =
11298 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
11299 Ok(PerlValue::array(results))
11300 }
11301 ExprKind::PGrepExpr {
11302 block,
11303 list,
11304 progress,
11305 stream,
11306 } => {
11307 let show_progress = progress
11308 .as_ref()
11309 .map(|p| self.eval_expr(p))
11310 .transpose()?
11311 .map(|v| v.is_true())
11312 .unwrap_or(false);
11313 let list_val = self.eval_expr(list)?;
11314 if *stream {
11315 let source = crate::map_stream::into_pull_iter(list_val);
11316 let sub = self.anon_coderef_from_block(block);
11317 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
11318 return Ok(PerlValue::iterator(Arc::new(
11319 crate::map_stream::PGrepStreamIterator::new(
11320 source,
11321 sub,
11322 self.subs.clone(),
11323 capture,
11324 atomic_arrays,
11325 atomic_hashes,
11326 ),
11327 )));
11328 }
11329 let items = list_val.to_list();
11330 let block = block.clone();
11331 let subs = self.subs.clone();
11332 let (scope_capture, atomic_arrays, atomic_hashes) =
11333 self.scope.capture_with_atomics();
11334 let pmap_progress = PmapProgress::new(show_progress, items.len());
11335
11336 let results: Vec<PerlValue> = items
11337 .into_par_iter()
11338 .filter_map(|item| {
11339 let mut local_interp = VMHelper::new();
11340 local_interp.subs = subs.clone();
11341 local_interp.scope.restore_capture(&scope_capture);
11342 local_interp
11343 .scope
11344 .restore_atomics(&atomic_arrays, &atomic_hashes);
11345 local_interp.enable_parallel_guard();
11346 local_interp.scope.set_topic(item.clone());
11347 let keep = match local_interp.exec_block(&block) {
11348 Ok(val) => val.is_true(),
11349 Err(_) => false,
11350 };
11351 pmap_progress.tick();
11352 if keep {
11353 Some(item)
11354 } else {
11355 None
11356 }
11357 })
11358 .collect();
11359 pmap_progress.finish();
11360 Ok(PerlValue::array(results))
11361 }
11362 ExprKind::ParExpr { block, list } => {
11363 let list_val = self.eval_expr(list)?;
11374 let n_threads = rayon::current_num_threads().clamp(1, 8);
11375 let chunks = par_chunk_value(&list_val, n_threads);
11376 if chunks.len() < 2 {
11377 self.scope.set_topic(list_val);
11380 let v = self.exec_block(block)?;
11381 return Ok(v);
11382 }
11383 let block_clone = block.clone();
11384 let subs = self.subs.clone();
11385 let (scope_capture, atomic_arrays, atomic_hashes) =
11386 self.scope.capture_with_atomics();
11387 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
11388 let err_w = Arc::clone(&first_err);
11389 let per_chunk: Vec<Vec<PerlValue>> = chunks
11390 .into_par_iter()
11391 .map(|chunk| {
11392 if err_w.lock().is_some() {
11393 return Vec::new();
11394 }
11395 let mut local_interp = VMHelper::new();
11396 local_interp.subs = subs.clone();
11397 local_interp.scope.restore_capture(&scope_capture);
11398 local_interp
11399 .scope
11400 .restore_atomics(&atomic_arrays, &atomic_hashes);
11401 local_interp.enable_parallel_guard();
11402 local_interp.scope.set_topic(chunk);
11403 match local_interp.exec_block(&block_clone) {
11404 Ok(v) => v.map_flatten_outputs(true),
11405 Err(e) => {
11406 let mut g = err_w.lock();
11407 if g.is_none() {
11408 *g = Some(format!("par: {:?}", e));
11409 }
11410 Vec::new()
11411 }
11412 }
11413 })
11414 .collect();
11415 if let Some(msg) = first_err.lock().take() {
11416 return Err(FlowOrError::Error(PerlError::runtime(msg, line)));
11417 }
11418 let total: usize = per_chunk.iter().map(|v| v.len()).sum();
11419 let mut out = Vec::with_capacity(total);
11420 for v in per_chunk {
11421 out.extend(v);
11422 }
11423 Ok(PerlValue::array(out))
11424 }
11425 ExprKind::ParReduceExpr {
11426 extract_block,
11427 reduce_block,
11428 list,
11429 } => {
11430 let list_val = self.eval_expr(list)?;
11440 let n_threads = rayon::current_num_threads().clamp(1, 8);
11441 let chunks = par_chunk_value(&list_val, n_threads);
11442 if chunks.len() < 2 {
11443 self.scope.declare_array("_", vec![list_val.clone()]);
11447 self.scope.set_topic(list_val);
11448 return self.exec_block(extract_block);
11449 }
11450 let extract = extract_block.clone();
11451 let subs = self.subs.clone();
11452 let (scope_capture, atomic_arrays, atomic_hashes) =
11453 self.scope.capture_with_atomics();
11454 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
11455 let err_w = Arc::clone(&first_err);
11456 let per_chunk: Vec<PerlValue> = chunks
11457 .into_par_iter()
11458 .map(|chunk| {
11459 if err_w.lock().is_some() {
11460 return PerlValue::UNDEF;
11461 }
11462 let mut local = VMHelper::new();
11463 local.subs = subs.clone();
11464 local.scope.restore_capture(&scope_capture);
11465 local.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
11466 local.enable_parallel_guard();
11467 local.scope.declare_array("_", vec![chunk.clone()]);
11473 local.scope.set_topic(chunk);
11474 match local.exec_block(&extract) {
11475 Ok(v) => v,
11476 Err(e) => {
11477 let mut g = err_w.lock();
11478 if g.is_none() {
11479 *g = Some(format!("par_reduce: {:?}", e));
11480 }
11481 PerlValue::UNDEF
11482 }
11483 }
11484 })
11485 .collect();
11486 if let Some(msg) = first_err.lock().take() {
11487 return Err(FlowOrError::Error(PerlError::runtime(msg, line)));
11488 }
11489 if per_chunk.is_empty() {
11490 return Ok(PerlValue::UNDEF);
11491 }
11492 if let Some(rb) = reduce_block {
11494 let mut acc = per_chunk[0].clone();
11495 for v in per_chunk.into_iter().skip(1) {
11496 self.scope.declare_scalar("a", acc.clone());
11497 self.scope.declare_scalar("b", v);
11498 acc = self.exec_block(rb)?;
11499 }
11500 return Ok(acc);
11501 }
11502 Ok(par_reduce_auto_merge(per_chunk))
11504 }
11505 ExprKind::PForExpr {
11506 block,
11507 list,
11508 progress,
11509 } => {
11510 let show_progress = progress
11511 .as_ref()
11512 .map(|p| self.eval_expr(p))
11513 .transpose()?
11514 .map(|v| v.is_true())
11515 .unwrap_or(false);
11516 let list_val = self.eval_expr(list)?;
11517 let items = list_val.to_list();
11518 let block = block.clone();
11519 let subs = self.subs.clone();
11520 let (scope_capture, atomic_arrays, atomic_hashes) =
11521 self.scope.capture_with_atomics();
11522
11523 let pmap_progress = PmapProgress::new(show_progress, items.len());
11524 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
11525 items.into_par_iter().for_each(|item| {
11526 if first_err.lock().is_some() {
11527 return;
11528 }
11529 let mut local_interp = VMHelper::new();
11530 local_interp.subs = subs.clone();
11531 local_interp.scope.restore_capture(&scope_capture);
11532 local_interp
11533 .scope
11534 .restore_atomics(&atomic_arrays, &atomic_hashes);
11535 local_interp.enable_parallel_guard();
11536 local_interp.scope.set_topic(item);
11537 match local_interp.exec_block(&block) {
11538 Ok(_) => {}
11539 Err(e) => {
11540 let stryke = match e {
11541 FlowOrError::Error(stryke) => stryke,
11542 FlowOrError::Flow(_) => PerlError::runtime(
11543 "return/last/next/redo not supported inside pfor block",
11544 line,
11545 ),
11546 };
11547 let mut g = first_err.lock();
11548 if g.is_none() {
11549 *g = Some(stryke);
11550 }
11551 }
11552 }
11553 pmap_progress.tick();
11554 });
11555 pmap_progress.finish();
11556 if let Some(e) = first_err.lock().take() {
11557 return Err(FlowOrError::Error(e));
11558 }
11559 Ok(PerlValue::UNDEF)
11560 }
11561 ExprKind::FanExpr {
11562 count,
11563 block,
11564 progress,
11565 capture,
11566 } => {
11567 let show_progress = progress
11568 .as_ref()
11569 .map(|p| self.eval_expr(p))
11570 .transpose()?
11571 .map(|v| v.is_true())
11572 .unwrap_or(false);
11573 let n = match count {
11574 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
11575 None => self.parallel_thread_count(),
11576 };
11577 let block = block.clone();
11578 let subs = self.subs.clone();
11579 let (scope_capture, atomic_arrays, atomic_hashes) =
11580 self.scope.capture_with_atomics();
11581
11582 let fan_progress = FanProgress::new(show_progress, n);
11583 if *capture {
11584 if n == 0 {
11585 return Ok(PerlValue::array(Vec::new()));
11586 }
11587 let pairs: Vec<(usize, ExecResult)> = (0..n)
11588 .into_par_iter()
11589 .map(|i| {
11590 fan_progress.start_worker(i);
11591 let mut local_interp = VMHelper::new();
11592 local_interp.subs = subs.clone();
11593 local_interp.suppress_stdout = show_progress;
11594 local_interp.scope.restore_capture(&scope_capture);
11595 local_interp
11596 .scope
11597 .restore_atomics(&atomic_arrays, &atomic_hashes);
11598 local_interp.enable_parallel_guard();
11599 local_interp.scope.set_topic(PerlValue::integer(i as i64));
11600 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
11601 let res = local_interp.exec_block(&block);
11602 crate::parallel_trace::fan_worker_set_index(None);
11603 fan_progress.finish_worker(i);
11604 (i, res)
11605 })
11606 .collect();
11607 fan_progress.finish();
11608 let mut pairs = pairs;
11609 pairs.sort_by_key(|(i, _)| *i);
11610 let mut out = Vec::with_capacity(n);
11611 for (_, r) in pairs {
11612 match r {
11613 Ok(v) => out.push(v),
11614 Err(e) => return Err(e),
11615 }
11616 }
11617 return Ok(PerlValue::array(out));
11618 }
11619 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
11620 (0..n).into_par_iter().for_each(|i| {
11621 if first_err.lock().is_some() {
11622 return;
11623 }
11624 fan_progress.start_worker(i);
11625 let mut local_interp = VMHelper::new();
11626 local_interp.subs = subs.clone();
11627 local_interp.suppress_stdout = show_progress;
11628 local_interp.scope.restore_capture(&scope_capture);
11629 local_interp
11630 .scope
11631 .restore_atomics(&atomic_arrays, &atomic_hashes);
11632 local_interp.enable_parallel_guard();
11633 local_interp.scope.set_topic(PerlValue::integer(i as i64));
11634 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
11635 match local_interp.exec_block(&block) {
11636 Ok(_) => {}
11637 Err(e) => {
11638 let stryke = match e {
11639 FlowOrError::Error(stryke) => stryke,
11640 FlowOrError::Flow(_) => PerlError::runtime(
11641 "return/last/next/redo not supported inside fan block",
11642 line,
11643 ),
11644 };
11645 let mut g = first_err.lock();
11646 if g.is_none() {
11647 *g = Some(stryke);
11648 }
11649 }
11650 }
11651 crate::parallel_trace::fan_worker_set_index(None);
11652 fan_progress.finish_worker(i);
11653 });
11654 fan_progress.finish();
11655 if let Some(e) = first_err.lock().take() {
11656 return Err(FlowOrError::Error(e));
11657 }
11658 Ok(PerlValue::UNDEF)
11659 }
11660 ExprKind::RetryBlock {
11661 body,
11662 times,
11663 backoff,
11664 } => self.eval_retry_block(body, times, *backoff, line),
11665 ExprKind::RateLimitBlock {
11666 slot,
11667 max,
11668 window,
11669 body,
11670 } => self.eval_rate_limit_block(*slot, max, window, body, line),
11671 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
11672 ExprKind::GenBlock { body } => {
11673 let g = Arc::new(PerlGenerator {
11674 block: body.clone(),
11675 pc: Mutex::new(0),
11676 scope_started: Mutex::new(false),
11677 exhausted: Mutex::new(false),
11678 });
11679 Ok(PerlValue::generator(g))
11680 }
11681 ExprKind::Yield(e) => {
11682 if !self.in_generator {
11683 return Err(PerlError::runtime("yield outside gen block", line).into());
11684 }
11685 let v = self.eval_expr(e)?;
11686 Err(FlowOrError::Flow(Flow::Yield(v)))
11687 }
11688 ExprKind::AlgebraicMatch { subject, arms } => {
11689 self.eval_algebraic_match(subject, arms, line)
11690 }
11691 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
11692 Ok(self.spawn_async_block(body))
11693 }
11694 ExprKind::Trace { body } => {
11695 crate::parallel_trace::trace_enter();
11696 let out = self.exec_block(body);
11697 crate::parallel_trace::trace_leave();
11698 out
11699 }
11700 ExprKind::Spinner { message, body } => {
11701 use std::io::Write as _;
11702 let msg = self.eval_expr(message)?.to_string();
11703 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
11704 let done2 = done.clone();
11705 let handle = std::thread::spawn(move || {
11706 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
11707 let mut i = 0;
11708 let stderr = std::io::stderr();
11709 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
11710 {
11711 let stdout = std::io::stdout();
11712 let _stdout_lock = stdout.lock();
11713 let mut err = stderr.lock();
11714 let _ = write!(
11715 err,
11716 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
11717 frames[i % frames.len()],
11718 msg
11719 );
11720 let _ = err.flush();
11721 }
11722 std::thread::sleep(std::time::Duration::from_millis(80));
11723 i += 1;
11724 }
11725 let mut err = stderr.lock();
11726 let _ = write!(err, "\r\x1b[2K");
11727 let _ = err.flush();
11728 });
11729 let result = self.exec_block(body);
11730 done.store(true, std::sync::atomic::Ordering::Relaxed);
11731 let _ = handle.join();
11732 result
11733 }
11734 ExprKind::Timer { body } => {
11735 let start = std::time::Instant::now();
11736 self.exec_block(body)?;
11737 let ms = start.elapsed().as_secs_f64() * 1000.0;
11738 Ok(PerlValue::float(ms))
11739 }
11740 ExprKind::Bench { body, times } => {
11741 let n = self.eval_expr(times)?.to_int();
11742 if n < 0 {
11743 return Err(PerlError::runtime(
11744 "bench: iteration count must be non-negative",
11745 line,
11746 )
11747 .into());
11748 }
11749 self.run_bench_block(body, n as usize, line)
11750 }
11751 ExprKind::Await(expr) => {
11752 let v = self.eval_expr(expr)?;
11753 if let Some(t) = v.as_async_task() {
11754 t.await_result().map_err(FlowOrError::from)
11755 } else {
11756 Ok(v)
11757 }
11758 }
11759 ExprKind::Slurp(e) => {
11760 let path = self.eval_expr(e)?.to_string();
11761 crate::perl_fs::read_file_text_or_glob(&path)
11762 .map(PerlValue::string)
11763 .map_err(|e| {
11764 FlowOrError::Error(PerlError::runtime(format!("slurp: {}", e), line))
11765 })
11766 }
11767 ExprKind::Capture(e) => {
11768 let cmd = self.eval_expr(e)?.to_string();
11769 let output = Command::new("sh")
11770 .arg("-c")
11771 .arg(&cmd)
11772 .output()
11773 .map_err(|e| {
11774 FlowOrError::Error(PerlError::runtime(format!("capture: {}", e), line))
11775 })?;
11776 self.record_child_exit_status(output.status);
11777 let exitcode = output.status.code().unwrap_or(-1) as i64;
11778 let stdout = decode_utf8_or_latin1(&output.stdout);
11779 let stderr = decode_utf8_or_latin1(&output.stderr);
11780 Ok(PerlValue::capture(Arc::new(CaptureResult {
11781 stdout,
11782 stderr,
11783 exitcode,
11784 })))
11785 }
11786 ExprKind::Qx(e) => {
11787 let cmd = self.eval_expr(e)?.to_string();
11788 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)
11789 }
11790 ExprKind::FetchUrl(e) => {
11791 let url = self.eval_expr(e)?.to_string();
11792 ureq::get(&url)
11793 .call()
11794 .map_err(|e| {
11795 FlowOrError::Error(PerlError::runtime(format!("fetch_url: {}", e), line))
11796 })
11797 .and_then(|r| {
11798 r.into_string().map(PerlValue::string).map_err(|e| {
11799 FlowOrError::Error(PerlError::runtime(
11800 format!("fetch_url: {}", e),
11801 line,
11802 ))
11803 })
11804 })
11805 }
11806 ExprKind::Pchannel { capacity } => {
11807 if let Some(c) = capacity {
11808 let n = self.eval_expr(c)?.to_int().max(1) as usize;
11809 Ok(crate::pchannel::create_bounded_pair(n))
11810 } else {
11811 Ok(crate::pchannel::create_pair())
11812 }
11813 }
11814 ExprKind::PSortExpr {
11815 cmp,
11816 list,
11817 progress,
11818 } => {
11819 let show_progress = progress
11820 .as_ref()
11821 .map(|p| self.eval_expr(p))
11822 .transpose()?
11823 .map(|v| v.is_true())
11824 .unwrap_or(false);
11825 let list_val = self.eval_expr(list)?;
11826 let mut items = list_val.to_list();
11827 let pmap_progress = PmapProgress::new(show_progress, 2);
11828 pmap_progress.tick();
11829 if let Some(cmp_block) = cmp {
11830 if let Some(mode) = detect_sort_block_fast(cmp_block) {
11831 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
11832 } else {
11833 let cmp_block = cmp_block.clone();
11834 let subs = self.subs.clone();
11835 let scope_capture = self.scope.capture();
11836 items.par_sort_by(|a, b| {
11837 let mut local_interp = VMHelper::new();
11838 local_interp.subs = subs.clone();
11839 local_interp.scope.restore_capture(&scope_capture);
11840 local_interp.scope.set_sort_pair(a.clone(), b.clone());
11841 match local_interp.exec_block(&cmp_block) {
11842 Ok(v) => {
11843 let n = v.to_int();
11844 if n < 0 {
11845 std::cmp::Ordering::Less
11846 } else if n > 0 {
11847 std::cmp::Ordering::Greater
11848 } else {
11849 std::cmp::Ordering::Equal
11850 }
11851 }
11852 Err(_) => std::cmp::Ordering::Equal,
11853 }
11854 });
11855 }
11856 } else {
11857 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
11858 }
11859 pmap_progress.tick();
11860 pmap_progress.finish();
11861 Ok(PerlValue::array(items))
11862 }
11863
11864 ExprKind::ReduceExpr { block, list } => {
11865 let list_val = self.eval_expr(list)?;
11866 let items = list_val.to_list();
11867 if items.is_empty() {
11868 return Ok(PerlValue::UNDEF);
11869 }
11870 if items.len() == 1 {
11871 return Ok(items.into_iter().next().unwrap());
11872 }
11873 let block = block.clone();
11874 let subs = self.subs.clone();
11875 let scope_capture = self.scope.capture();
11876 let mut acc = items[0].clone();
11877 for b in items.into_iter().skip(1) {
11878 let mut local_interp = VMHelper::new();
11879 local_interp.subs = subs.clone();
11880 local_interp.scope.restore_capture(&scope_capture);
11881 local_interp.scope.set_sort_pair(acc, b);
11882 acc = match local_interp.exec_block(&block) {
11883 Ok(val) => val,
11884 Err(_) => PerlValue::UNDEF,
11885 };
11886 }
11887 Ok(acc)
11888 }
11889
11890 ExprKind::PReduceExpr {
11891 block,
11892 list,
11893 progress,
11894 } => {
11895 let show_progress = progress
11896 .as_ref()
11897 .map(|p| self.eval_expr(p))
11898 .transpose()?
11899 .map(|v| v.is_true())
11900 .unwrap_or(false);
11901 let list_val = self.eval_expr(list)?;
11902 let items = list_val.to_list();
11903 if items.is_empty() {
11904 return Ok(PerlValue::UNDEF);
11905 }
11906 if items.len() == 1 {
11907 return Ok(items.into_iter().next().unwrap());
11908 }
11909 let block = block.clone();
11910 let subs = self.subs.clone();
11911 let scope_capture = self.scope.capture();
11912 let pmap_progress = PmapProgress::new(show_progress, items.len());
11913
11914 let result = items
11915 .into_par_iter()
11916 .map(|x| {
11917 pmap_progress.tick();
11918 x
11919 })
11920 .reduce_with(|a, b| {
11921 let mut local_interp = VMHelper::new();
11922 local_interp.subs = subs.clone();
11923 local_interp.scope.restore_capture(&scope_capture);
11924 local_interp.scope.set_sort_pair(a, b);
11925 match local_interp.exec_block(&block) {
11926 Ok(val) => val,
11927 Err(_) => PerlValue::UNDEF,
11928 }
11929 });
11930 pmap_progress.finish();
11931 Ok(result.unwrap_or(PerlValue::UNDEF))
11932 }
11933
11934 ExprKind::PReduceInitExpr {
11935 init,
11936 block,
11937 list,
11938 progress,
11939 } => {
11940 let show_progress = progress
11941 .as_ref()
11942 .map(|p| self.eval_expr(p))
11943 .transpose()?
11944 .map(|v| v.is_true())
11945 .unwrap_or(false);
11946 let init_val = self.eval_expr(init)?;
11947 let list_val = self.eval_expr(list)?;
11948 let items = list_val.to_list();
11949 if items.is_empty() {
11950 return Ok(init_val);
11951 }
11952 let block = block.clone();
11953 let subs = self.subs.clone();
11954 let scope_capture = self.scope.capture();
11955 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
11956 if items.len() == 1 {
11957 return Ok(fold_preduce_init_step(
11958 &subs,
11959 cap,
11960 &block,
11961 preduce_init_fold_identity(&init_val),
11962 items.into_iter().next().unwrap(),
11963 ));
11964 }
11965 let pmap_progress = PmapProgress::new(show_progress, items.len());
11966 let result = items
11967 .into_par_iter()
11968 .fold(
11969 || preduce_init_fold_identity(&init_val),
11970 |acc, item| {
11971 pmap_progress.tick();
11972 fold_preduce_init_step(&subs, cap, &block, acc, item)
11973 },
11974 )
11975 .reduce(
11976 || preduce_init_fold_identity(&init_val),
11977 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
11978 );
11979 pmap_progress.finish();
11980 Ok(result)
11981 }
11982
11983 ExprKind::PMapReduceExpr {
11984 map_block,
11985 reduce_block,
11986 list,
11987 progress,
11988 } => {
11989 let show_progress = progress
11990 .as_ref()
11991 .map(|p| self.eval_expr(p))
11992 .transpose()?
11993 .map(|v| v.is_true())
11994 .unwrap_or(false);
11995 let list_val = self.eval_expr(list)?;
11996 let items = list_val.to_list();
11997 if items.is_empty() {
11998 return Ok(PerlValue::UNDEF);
11999 }
12000 let map_block = map_block.clone();
12001 let reduce_block = reduce_block.clone();
12002 let subs = self.subs.clone();
12003 let scope_capture = self.scope.capture();
12004 if items.len() == 1 {
12005 let mut local_interp = VMHelper::new();
12006 local_interp.subs = subs.clone();
12007 local_interp.scope.restore_capture(&scope_capture);
12008 local_interp.scope.set_topic(items[0].clone());
12009 return match local_interp.exec_block_no_scope(&map_block) {
12010 Ok(v) => Ok(v),
12011 Err(_) => Ok(PerlValue::UNDEF),
12012 };
12013 }
12014 let pmap_progress = PmapProgress::new(show_progress, items.len());
12015 let result = items
12016 .into_par_iter()
12017 .map(|item| {
12018 let mut local_interp = VMHelper::new();
12019 local_interp.subs = subs.clone();
12020 local_interp.scope.restore_capture(&scope_capture);
12021 local_interp.scope.set_topic(item);
12022 let val = match local_interp.exec_block_no_scope(&map_block) {
12023 Ok(val) => val,
12024 Err(_) => PerlValue::UNDEF,
12025 };
12026 pmap_progress.tick();
12027 val
12028 })
12029 .reduce_with(|a, b| {
12030 let mut local_interp = VMHelper::new();
12031 local_interp.subs = subs.clone();
12032 local_interp.scope.restore_capture(&scope_capture);
12033 local_interp.scope.set_sort_pair(a, b);
12034 match local_interp.exec_block_no_scope(&reduce_block) {
12035 Ok(val) => val,
12036 Err(_) => PerlValue::UNDEF,
12037 }
12038 });
12039 pmap_progress.finish();
12040 Ok(result.unwrap_or(PerlValue::UNDEF))
12041 }
12042
12043 ExprKind::PcacheExpr {
12044 block,
12045 list,
12046 progress,
12047 } => {
12048 let show_progress = progress
12049 .as_ref()
12050 .map(|p| self.eval_expr(p))
12051 .transpose()?
12052 .map(|v| v.is_true())
12053 .unwrap_or(false);
12054 let list_val = self.eval_expr(list)?;
12055 let items = list_val.to_list();
12056 let block = block.clone();
12057 let subs = self.subs.clone();
12058 let scope_capture = self.scope.capture();
12059 let cache = &*crate::pcache::GLOBAL_PCACHE;
12060 let pmap_progress = PmapProgress::new(show_progress, items.len());
12061 let results: Vec<PerlValue> = items
12062 .into_par_iter()
12063 .map(|item| {
12064 let k = crate::pcache::cache_key(&item);
12065 if let Some(v) = cache.get(&k) {
12066 pmap_progress.tick();
12067 return v.clone();
12068 }
12069 let mut local_interp = VMHelper::new();
12070 local_interp.subs = subs.clone();
12071 local_interp.scope.restore_capture(&scope_capture);
12072 local_interp.scope.set_topic(item.clone());
12073 let val = match local_interp.exec_block_no_scope(&block) {
12074 Ok(v) => v,
12075 Err(_) => PerlValue::UNDEF,
12076 };
12077 cache.insert(k, val.clone());
12078 pmap_progress.tick();
12079 val
12080 })
12081 .collect();
12082 pmap_progress.finish();
12083 Ok(PerlValue::array(results))
12084 }
12085
12086 ExprKind::PselectExpr { receivers, timeout } => {
12087 let mut rx_vals = Vec::with_capacity(receivers.len());
12088 for r in receivers {
12089 rx_vals.push(self.eval_expr(r)?);
12090 }
12091 let dur = if let Some(t) = timeout.as_ref() {
12092 Some(std::time::Duration::from_secs_f64(
12093 self.eval_expr(t)?.to_number().max(0.0),
12094 ))
12095 } else {
12096 None
12097 };
12098 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
12099 &rx_vals, dur, line,
12100 )?)
12101 }
12102
12103 ExprKind::Push { array, values } => {
12105 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
12106 }
12107 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
12108 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
12109 ExprKind::Unshift { array, values } => {
12110 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
12111 }
12112 ExprKind::Splice {
12113 array,
12114 offset,
12115 length,
12116 replacement,
12117 } => self.eval_splice_expr(
12118 array.as_ref(),
12119 offset.as_deref(),
12120 length.as_deref(),
12121 replacement.as_slice(),
12122 ctx,
12123 line,
12124 ),
12125 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
12126 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
12127 ExprKind::Keys(expr) => {
12128 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
12129 let keys = Self::keys_from_value(val, line)?;
12130 if ctx == WantarrayCtx::List {
12131 Ok(keys)
12132 } else {
12133 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
12134 Ok(PerlValue::integer(n as i64))
12135 }
12136 }
12137 ExprKind::Values(expr) => {
12138 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
12139 let vals = Self::values_from_value(val, line)?;
12140 if ctx == WantarrayCtx::List {
12141 Ok(vals)
12142 } else {
12143 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
12144 Ok(PerlValue::integer(n as i64))
12145 }
12146 }
12147 ExprKind::Each(_) => {
12148 Ok(PerlValue::array(vec![]))
12150 }
12151
12152 ExprKind::Chomp(expr) => {
12154 let val = self.eval_expr(expr)?;
12155 self.chomp_inplace_execute(val, expr)
12156 }
12157 ExprKind::Chop(expr) => {
12158 let val = self.eval_expr(expr)?;
12159 self.chop_inplace_execute(val, expr)
12160 }
12161 ExprKind::Length(expr) => {
12162 let val = self.eval_expr(expr)?;
12163 Ok(if let Some(a) = val.as_array_vec() {
12164 PerlValue::integer(a.len() as i64)
12165 } else if let Some(h) = val.as_hash_map() {
12166 PerlValue::integer(h.len() as i64)
12167 } else if let Some(b) = val.as_bytes_arc() {
12168 PerlValue::integer(b.len() as i64)
12170 } else {
12171 let s = val.to_string();
12172 let n = if self.utf8_pragma {
12173 s.chars().count()
12174 } else {
12175 s.len()
12176 };
12177 PerlValue::integer(n as i64)
12178 })
12179 }
12180 ExprKind::Substr {
12181 string,
12182 offset,
12183 length,
12184 replacement,
12185 } => self.eval_substr_expr(
12186 string.as_ref(),
12187 offset.as_ref(),
12188 length.as_deref(),
12189 replacement.as_deref(),
12190 line,
12191 ),
12192 ExprKind::Index {
12193 string,
12194 substr,
12195 position,
12196 } => {
12197 let s = self.eval_expr(string)?.to_string();
12198 let sub = self.eval_expr(substr)?.to_string();
12199 let pos = if let Some(p) = position {
12200 self.eval_expr(p)?.to_int() as usize
12201 } else {
12202 0
12203 };
12204 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
12205 Ok(PerlValue::integer(result))
12206 }
12207 ExprKind::Rindex {
12208 string,
12209 substr,
12210 position,
12211 } => {
12212 let s = self.eval_expr(string)?.to_string();
12213 let sub = self.eval_expr(substr)?.to_string();
12214 let end = if let Some(p) = position {
12215 self.eval_expr(p)?.to_int() as usize + sub.len()
12216 } else {
12217 s.len()
12218 };
12219 let search = &s[..end.min(s.len())];
12220 let result = search.rfind(&sub).map(|i| i as i64).unwrap_or(-1);
12221 Ok(PerlValue::integer(result))
12222 }
12223 ExprKind::Sprintf { format, args } => {
12224 let fmt = self.eval_expr(format)?.to_string();
12225 let mut arg_vals = Vec::new();
12228 for a in args {
12229 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
12230 if let Some(items) = v.as_array_vec() {
12231 arg_vals.extend(items);
12232 } else {
12233 arg_vals.push(v);
12234 }
12235 }
12236 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
12237 Ok(PerlValue::string(s))
12238 }
12239 ExprKind::JoinExpr { separator, list } => {
12240 let sep = self.eval_expr(separator)?.to_string();
12241 let items = if let ExprKind::List(exprs) = &list.kind {
12245 let saved = self.wantarray_kind;
12246 self.wantarray_kind = WantarrayCtx::List;
12247 let mut vals = Vec::new();
12248 for e in exprs {
12249 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
12250 if let Some(items) = v.as_array_vec() {
12251 vals.extend(items);
12252 } else if v.is_iterator() {
12253 vals.extend(v.into_iterator().collect_all());
12257 } else {
12258 vals.push(v);
12259 }
12260 }
12261 self.wantarray_kind = saved;
12262 vals
12263 } else {
12264 let saved = self.wantarray_kind;
12265 self.wantarray_kind = WantarrayCtx::List;
12266 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12267 self.wantarray_kind = saved;
12268 if let Some(items) = v.as_array_vec() {
12269 items
12270 } else if v.is_iterator() {
12271 v.into_iterator().collect_all()
12275 } else {
12276 vec![v]
12277 }
12278 };
12279 let mut strs = Vec::with_capacity(items.len());
12280 for v in &items {
12281 strs.push(self.stringify_value(v.clone(), line)?);
12282 }
12283 Ok(PerlValue::string(strs.join(&sep)))
12284 }
12285 ExprKind::SplitExpr {
12286 pattern,
12287 string,
12288 limit,
12289 } => {
12290 let pat_val = self.eval_expr(pattern)?;
12291 let pat = pat_val
12295 .regex_src_and_flags()
12296 .map(|(s, _)| s)
12297 .unwrap_or_else(|| pat_val.to_string());
12298 let s = self.eval_expr(string)?.to_string();
12299 let lim_opt: Option<i64> = limit
12308 .as_ref()
12309 .map(|l| self.eval_expr(l).map(|v| v.to_int()))
12310 .transpose()?;
12311 let re = self.compile_regex(&pat, "", line)?;
12312 let mut parts: Vec<String> = match lim_opt {
12313 Some(l) if l > 0 => re.splitn_strings(&s, l as usize),
12314 _ => re.split_strings(&s),
12315 };
12316
12317 if pat.is_empty() && parts.first().is_some_and(|p| p.is_empty()) {
12323 parts.remove(0);
12324 }
12325 let strip_trailing = matches!(lim_opt, None | Some(0));
12329 if strip_trailing {
12330 while parts.last().is_some_and(|p| p.is_empty()) {
12331 parts.pop();
12332 }
12333 }
12334
12335 Ok(PerlValue::array(
12336 parts.into_iter().map(PerlValue::string).collect(),
12337 ))
12338 }
12339
12340 ExprKind::Abs(expr) => {
12342 let val = self.eval_expr(expr)?;
12343 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
12344 return r;
12345 }
12346 Ok(PerlValue::float(val.to_number().abs()))
12347 }
12348 ExprKind::Int(expr) => {
12349 let val = self.eval_expr(expr)?;
12350 Ok(PerlValue::integer(val.to_number() as i64))
12351 }
12352 ExprKind::Sqrt(expr) => {
12353 let val = self.eval_expr(expr)?;
12354 Ok(PerlValue::float(val.to_number().sqrt()))
12355 }
12356 ExprKind::Sin(expr) => {
12357 let val = self.eval_expr(expr)?;
12358 Ok(PerlValue::float(val.to_number().sin()))
12359 }
12360 ExprKind::Cos(expr) => {
12361 let val = self.eval_expr(expr)?;
12362 Ok(PerlValue::float(val.to_number().cos()))
12363 }
12364 ExprKind::Atan2 { y, x } => {
12365 let yv = self.eval_expr(y)?.to_number();
12366 let xv = self.eval_expr(x)?.to_number();
12367 Ok(PerlValue::float(yv.atan2(xv)))
12368 }
12369 ExprKind::Exp(expr) => {
12370 let val = self.eval_expr(expr)?;
12371 Ok(PerlValue::float(val.to_number().exp()))
12372 }
12373 ExprKind::Log(expr) => {
12374 let val = self.eval_expr(expr)?;
12375 Ok(PerlValue::float(val.to_number().ln()))
12376 }
12377 ExprKind::Rand(upper) => {
12378 let u = match upper {
12379 Some(e) => self.eval_expr(e)?.to_number(),
12380 None => 1.0,
12381 };
12382 Ok(PerlValue::float(self.perl_rand(u)))
12383 }
12384 ExprKind::Srand(seed) => {
12385 let s = match seed {
12386 Some(e) => Some(self.eval_expr(e)?.to_number()),
12387 None => None,
12388 };
12389 Ok(PerlValue::integer(self.perl_srand(s)))
12390 }
12391 ExprKind::Hex(expr) => {
12392 let val = self.eval_expr(expr)?.to_string();
12393 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
12394 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
12395 Ok(PerlValue::integer(n))
12396 }
12397 ExprKind::Oct(expr) => {
12398 let val = self.eval_expr(expr)?.to_string();
12399 let s = val.trim();
12400 let n = if s.starts_with("0x") || s.starts_with("0X") {
12401 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
12402 } else if s.starts_with("0b") || s.starts_with("0B") {
12403 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
12404 } else if s.starts_with("0o") || s.starts_with("0O") {
12405 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
12406 } else {
12407 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
12408 };
12409 Ok(PerlValue::integer(n))
12410 }
12411
12412 ExprKind::Lc(expr) => Ok(PerlValue::string(
12414 self.eval_expr(expr)?.to_string().to_lowercase(),
12415 )),
12416 ExprKind::Uc(expr) => Ok(PerlValue::string(
12417 self.eval_expr(expr)?.to_string().to_uppercase(),
12418 )),
12419 ExprKind::Lcfirst(expr) => {
12420 let s = self.eval_expr(expr)?.to_string();
12421 let mut chars = s.chars();
12422 let result = match chars.next() {
12423 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
12424 None => String::new(),
12425 };
12426 Ok(PerlValue::string(result))
12427 }
12428 ExprKind::Ucfirst(expr) => {
12429 let s = self.eval_expr(expr)?.to_string();
12430 let mut chars = s.chars();
12431 let result = match chars.next() {
12432 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
12433 None => String::new(),
12434 };
12435 Ok(PerlValue::string(result))
12436 }
12437 ExprKind::Fc(expr) => Ok(PerlValue::string(default_case_fold_str(
12438 &self.eval_expr(expr)?.to_string(),
12439 ))),
12440 ExprKind::Crypt { plaintext, salt } => {
12441 let p = self.eval_expr(plaintext)?.to_string();
12442 let sl = self.eval_expr(salt)?.to_string();
12443 Ok(PerlValue::string(perl_crypt(&p, &sl)))
12444 }
12445 ExprKind::Pos(e) => {
12446 let key = match e {
12447 None => "_".to_string(),
12448 Some(expr) => match &expr.kind {
12449 ExprKind::ScalarVar(n) => n.clone(),
12450 _ => self.eval_expr(expr)?.to_string(),
12451 },
12452 };
12453 Ok(self
12454 .regex_pos
12455 .get(&key)
12456 .copied()
12457 .flatten()
12458 .map(|p| PerlValue::integer(p as i64))
12459 .unwrap_or(PerlValue::UNDEF))
12460 }
12461 ExprKind::Study(expr) => {
12462 let s = self.eval_expr(expr)?.to_string();
12463 Ok(Self::study_return_value(&s))
12464 }
12465
12466 ExprKind::Defined(expr) => {
12468 if let ExprKind::SubroutineRef(name) = &expr.kind {
12470 let exists = self.resolve_sub_by_name(name).is_some();
12471 return Ok(PerlValue::integer(if exists { 1 } else { 0 }));
12472 }
12473 let val = self.eval_expr(expr)?;
12474 Ok(PerlValue::integer(if val.is_undef() { 0 } else { 1 }))
12475 }
12476 ExprKind::Ref(expr) => {
12477 let val = self.eval_expr(expr)?;
12478 Ok(val.ref_type())
12479 }
12480 ExprKind::ScalarContext(expr) => {
12481 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
12482 Ok(v.scalar_context())
12483 }
12484
12485 ExprKind::Chr(expr) => {
12487 let n = self.eval_expr(expr)?.to_int() as u32;
12488 Ok(PerlValue::string(
12489 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
12490 ))
12491 }
12492 ExprKind::Ord(expr) => {
12493 let s = self.eval_expr(expr)?.to_string();
12494 Ok(PerlValue::integer(
12495 s.chars().next().map(|c| c as i64).unwrap_or(0),
12496 ))
12497 }
12498
12499 ExprKind::OpenMyHandle { .. } => Err(PerlError::runtime(
12501 "internal: `open my $fh` handle used outside open()",
12502 line,
12503 )
12504 .into()),
12505 ExprKind::Open { handle, mode, file } => {
12506 if let ExprKind::OpenMyHandle { name } = &handle.kind {
12507 self.scope
12508 .declare_scalar_frozen(name, PerlValue::UNDEF, false, None)?;
12509 self.english_note_lexical_scalar(name);
12510 let mode_s = self.eval_expr(mode)?.to_string();
12511 let file_opt = if let Some(f) = file {
12512 Some(self.eval_expr(f)?.to_string())
12513 } else {
12514 None
12515 };
12516 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
12517 self.scope.set_scalar(name, ret.clone())?;
12518 return Ok(ret);
12519 }
12520 let handle_s = self.eval_expr(handle)?.to_string();
12521 let handle_name = self.resolve_io_handle_name(&handle_s);
12522 let mode_s = self.eval_expr(mode)?.to_string();
12523 let file_opt = if let Some(f) = file {
12524 Some(self.eval_expr(f)?.to_string())
12525 } else {
12526 None
12527 };
12528 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
12529 .map_err(Into::into)
12530 }
12531 ExprKind::Close(expr) => {
12532 let s = self.eval_expr(expr)?.to_string();
12533 let name = self.resolve_io_handle_name(&s);
12534 self.close_builtin_execute(name).map_err(Into::into)
12535 }
12536 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
12537 self.readline_builtin_execute_list(handle.as_deref())
12538 } else {
12539 self.readline_builtin_execute(handle.as_deref())
12540 }
12541 .map_err(Into::into),
12542 ExprKind::Eof(expr) => match expr {
12543 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
12544 Some(e) => {
12545 let name = self.eval_expr(e)?;
12546 self.eof_builtin_execute(&[name], line).map_err(Into::into)
12547 }
12548 },
12549
12550 ExprKind::Opendir { handle, path } => {
12551 let h = self.eval_expr(handle)?.to_string();
12552 let p = self.eval_expr(path)?.to_string();
12553 Ok(self.opendir_handle(&h, &p))
12554 }
12555 ExprKind::Readdir(e) => {
12556 let h = self.eval_expr(e)?.to_string();
12557 Ok(if ctx == WantarrayCtx::List {
12558 self.readdir_handle_list(&h)
12559 } else {
12560 self.readdir_handle(&h)
12561 })
12562 }
12563 ExprKind::Closedir(e) => {
12564 let h = self.eval_expr(e)?.to_string();
12565 Ok(self.closedir_handle(&h))
12566 }
12567 ExprKind::Rewinddir(e) => {
12568 let h = self.eval_expr(e)?.to_string();
12569 Ok(self.rewinddir_handle(&h))
12570 }
12571 ExprKind::Telldir(e) => {
12572 let h = self.eval_expr(e)?.to_string();
12573 Ok(self.telldir_handle(&h))
12574 }
12575 ExprKind::Seekdir { handle, position } => {
12576 let h = self.eval_expr(handle)?.to_string();
12577 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
12578 Ok(self.seekdir_handle(&h, pos))
12579 }
12580
12581 ExprKind::FileTest { op, expr } => {
12583 let path = self.eval_expr(expr)?.to_string();
12584 if matches!(op, 'M' | 'A' | 'C') {
12586 #[cfg(unix)]
12587 {
12588 return match crate::perl_fs::filetest_age_days(&path, *op) {
12589 Some(days) => Ok(PerlValue::float(days)),
12590 None => Ok(PerlValue::UNDEF),
12591 };
12592 }
12593 #[cfg(not(unix))]
12594 return Ok(PerlValue::UNDEF);
12595 }
12596 if *op == 's' {
12598 return match std::fs::metadata(&path) {
12599 Ok(m) => Ok(PerlValue::integer(m.len() as i64)),
12600 Err(_) => Ok(PerlValue::UNDEF),
12601 };
12602 }
12603 let result = match op {
12604 'e' => std::path::Path::new(&path).exists(),
12605 'f' => std::path::Path::new(&path).is_file(),
12606 'd' => std::path::Path::new(&path).is_dir(),
12607 'l' => std::path::Path::new(&path).is_symlink(),
12608 #[cfg(unix)]
12609 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
12610 #[cfg(not(unix))]
12611 'r' => std::fs::metadata(&path).is_ok(),
12612 #[cfg(unix)]
12613 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
12614 #[cfg(not(unix))]
12615 'w' => std::fs::metadata(&path).is_ok(),
12616 #[cfg(unix)]
12617 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
12618 #[cfg(not(unix))]
12619 'x' => false,
12620 #[cfg(unix)]
12621 'o' => crate::perl_fs::filetest_owned_effective(&path),
12622 #[cfg(not(unix))]
12623 'o' => false,
12624 #[cfg(unix)]
12625 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
12626 #[cfg(not(unix))]
12627 'R' => false,
12628 #[cfg(unix)]
12629 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
12630 #[cfg(not(unix))]
12631 'W' => false,
12632 #[cfg(unix)]
12633 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
12634 #[cfg(not(unix))]
12635 'X' => false,
12636 #[cfg(unix)]
12637 'O' => crate::perl_fs::filetest_owned_real(&path),
12638 #[cfg(not(unix))]
12639 'O' => false,
12640 'z' => std::fs::metadata(&path)
12641 .map(|m| m.len() == 0)
12642 .unwrap_or(true),
12643 't' => crate::perl_fs::filetest_is_tty(&path),
12644 #[cfg(unix)]
12645 'p' => crate::perl_fs::filetest_is_pipe(&path),
12646 #[cfg(not(unix))]
12647 'p' => false,
12648 #[cfg(unix)]
12649 'S' => crate::perl_fs::filetest_is_socket(&path),
12650 #[cfg(not(unix))]
12651 'S' => false,
12652 #[cfg(unix)]
12653 'b' => crate::perl_fs::filetest_is_block_device(&path),
12654 #[cfg(not(unix))]
12655 'b' => false,
12656 #[cfg(unix)]
12657 'c' => crate::perl_fs::filetest_is_char_device(&path),
12658 #[cfg(not(unix))]
12659 'c' => false,
12660 #[cfg(unix)]
12661 'u' => crate::perl_fs::filetest_is_setuid(&path),
12662 #[cfg(not(unix))]
12663 'u' => false,
12664 #[cfg(unix)]
12665 'g' => crate::perl_fs::filetest_is_setgid(&path),
12666 #[cfg(not(unix))]
12667 'g' => false,
12668 #[cfg(unix)]
12669 'k' => crate::perl_fs::filetest_is_sticky(&path),
12670 #[cfg(not(unix))]
12671 'k' => false,
12672 'T' => crate::perl_fs::filetest_is_text(&path),
12673 'B' => crate::perl_fs::filetest_is_binary(&path),
12674 _ => false,
12675 };
12676 Ok(PerlValue::integer(if result { 1 } else { 0 }))
12677 }
12678
12679 ExprKind::System(args) => {
12681 let mut cmd_args = Vec::new();
12682 for a in args {
12683 cmd_args.push(self.eval_expr(a)?.to_string());
12684 }
12685 if cmd_args.is_empty() {
12686 return Ok(PerlValue::integer(-1));
12687 }
12688 let status = Command::new("sh")
12689 .arg("-c")
12690 .arg(cmd_args.join(" "))
12691 .status();
12692 match status {
12693 Ok(s) => {
12694 self.record_child_exit_status(s);
12695 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
12696 }
12697 Err(e) => {
12698 self.apply_io_error_to_errno(&e);
12699 Ok(PerlValue::integer(-1))
12700 }
12701 }
12702 }
12703 ExprKind::Exec(args) => {
12704 let mut cmd_args = Vec::new();
12705 for a in args {
12706 cmd_args.push(self.eval_expr(a)?.to_string());
12707 }
12708 if cmd_args.is_empty() {
12709 return Ok(PerlValue::integer(-1));
12710 }
12711 let status = Command::new("sh")
12712 .arg("-c")
12713 .arg(cmd_args.join(" "))
12714 .status();
12715 match status {
12716 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
12717 Err(e) => {
12718 self.apply_io_error_to_errno(&e);
12719 Ok(PerlValue::integer(-1))
12720 }
12721 }
12722 }
12723 ExprKind::Eval(expr) => {
12724 self.eval_nesting += 1;
12725 let out = match &expr.kind {
12726 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
12727 Ok(v) => {
12728 self.clear_eval_error();
12729 Ok(v)
12730 }
12731 Err(FlowOrError::Error(e)) => {
12732 self.set_eval_error_from_perl_error(&e);
12733 Ok(PerlValue::UNDEF)
12734 }
12735 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
12736 },
12737 _ => {
12738 let code = self.eval_expr(expr)?.to_string();
12739 match crate::parse_and_run_string(&code, self) {
12741 Ok(v) => {
12742 self.clear_eval_error();
12743 Ok(v)
12744 }
12745 Err(e) => {
12746 self.set_eval_error(e.to_string());
12747 Ok(PerlValue::UNDEF)
12748 }
12749 }
12750 }
12751 };
12752 self.eval_nesting -= 1;
12753 out
12754 }
12755 ExprKind::Do(expr) => match &expr.kind {
12756 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
12757 _ => {
12758 let val = self.eval_expr(expr)?;
12759 let filename = val.to_string();
12760 match read_file_text_perl_compat(&filename) {
12761 Ok(code) => {
12762 let code = crate::data_section::strip_perl_end_marker(&code);
12763 match crate::parse_and_run_string_in_file(code, self, &filename) {
12764 Ok(v) => Ok(v),
12765 Err(e) => {
12766 self.set_eval_error(e.to_string());
12767 Ok(PerlValue::UNDEF)
12768 }
12769 }
12770 }
12771 Err(e) => {
12772 self.apply_io_error_to_errno(&e);
12773 Ok(PerlValue::UNDEF)
12774 }
12775 }
12776 }
12777 },
12778 ExprKind::Require(expr) => {
12779 let spec = self.eval_expr(expr)?.to_string();
12780 self.require_execute(&spec, line)
12781 .map_err(FlowOrError::Error)
12782 }
12783 ExprKind::Exit(code) => {
12784 let c = if let Some(e) = code {
12785 self.eval_expr(e)?.to_int() as i32
12786 } else {
12787 0
12788 };
12789 Err(PerlError::new(ErrorKind::Exit(c), "", line, &self.file).into())
12790 }
12791 ExprKind::Chdir(expr) => {
12792 let path = self.eval_expr(expr)?.to_string();
12793 match std::env::set_current_dir(&path) {
12794 Ok(_) => Ok(PerlValue::integer(1)),
12795 Err(e) => {
12796 self.apply_io_error_to_errno(&e);
12797 Ok(PerlValue::integer(0))
12798 }
12799 }
12800 }
12801 ExprKind::Mkdir { path, mode: _ } => {
12802 let p = self.eval_expr(path)?.to_string();
12803 match std::fs::create_dir(&p) {
12804 Ok(_) => Ok(PerlValue::integer(1)),
12805 Err(e) => {
12806 self.apply_io_error_to_errno(&e);
12807 Ok(PerlValue::integer(0))
12808 }
12809 }
12810 }
12811 ExprKind::Unlink(args) => {
12812 let mut count = 0i64;
12813 for a in args {
12814 let path = self.eval_expr(a)?.to_string();
12815 if std::fs::remove_file(&path).is_ok() {
12816 count += 1;
12817 }
12818 }
12819 Ok(PerlValue::integer(count))
12820 }
12821 ExprKind::Rename { old, new } => {
12822 let o = self.eval_expr(old)?.to_string();
12823 let n = self.eval_expr(new)?.to_string();
12824 Ok(crate::perl_fs::rename_paths(&o, &n))
12825 }
12826 ExprKind::Chmod(args) => {
12827 let mode = self.eval_expr(&args[0])?.to_int();
12828 let mut paths = Vec::new();
12829 for a in &args[1..] {
12830 paths.push(self.eval_expr(a)?.to_string());
12831 }
12832 Ok(PerlValue::integer(crate::perl_fs::chmod_paths(
12833 &paths, mode,
12834 )))
12835 }
12836 ExprKind::Chown(args) => {
12837 let uid = self.eval_expr(&args[0])?.to_int();
12838 let gid = self.eval_expr(&args[1])?.to_int();
12839 let mut paths = Vec::new();
12840 for a in &args[2..] {
12841 paths.push(self.eval_expr(a)?.to_string());
12842 }
12843 Ok(PerlValue::integer(crate::perl_fs::chown_paths(
12844 &paths, uid, gid,
12845 )))
12846 }
12847 ExprKind::Stat(e) => {
12848 let path = self.eval_expr(e)?.to_string();
12849 Ok(crate::perl_fs::stat_path(&path, false))
12850 }
12851 ExprKind::Lstat(e) => {
12852 let path = self.eval_expr(e)?.to_string();
12853 Ok(crate::perl_fs::stat_path(&path, true))
12854 }
12855 ExprKind::Link { old, new } => {
12856 let o = self.eval_expr(old)?.to_string();
12857 let n = self.eval_expr(new)?.to_string();
12858 Ok(crate::perl_fs::link_hard(&o, &n))
12859 }
12860 ExprKind::Symlink { old, new } => {
12861 let o = self.eval_expr(old)?.to_string();
12862 let n = self.eval_expr(new)?.to_string();
12863 Ok(crate::perl_fs::link_sym(&o, &n))
12864 }
12865 ExprKind::Readlink(e) => {
12866 let path = self.eval_expr(e)?.to_string();
12867 Ok(crate::perl_fs::read_link(&path))
12868 }
12869 ExprKind::Files(args) => {
12870 let dir = if args.is_empty() {
12871 ".".to_string()
12872 } else {
12873 self.eval_expr(&args[0])?.to_string()
12874 };
12875 Ok(crate::perl_fs::list_files(&dir))
12876 }
12877 ExprKind::Filesf(args) => {
12878 let dir = if args.is_empty() {
12879 ".".to_string()
12880 } else {
12881 self.eval_expr(&args[0])?.to_string()
12882 };
12883 Ok(crate::perl_fs::list_filesf(&dir))
12884 }
12885 ExprKind::FilesfRecursive(args) => {
12886 let dir = if args.is_empty() {
12887 ".".to_string()
12888 } else {
12889 self.eval_expr(&args[0])?.to_string()
12890 };
12891 Ok(PerlValue::iterator(Arc::new(
12892 crate::value::FsWalkIterator::new(&dir, true),
12893 )))
12894 }
12895 ExprKind::Dirs(args) => {
12896 let dir = if args.is_empty() {
12897 ".".to_string()
12898 } else {
12899 self.eval_expr(&args[0])?.to_string()
12900 };
12901 Ok(crate::perl_fs::list_dirs(&dir))
12902 }
12903 ExprKind::DirsRecursive(args) => {
12904 let dir = if args.is_empty() {
12905 ".".to_string()
12906 } else {
12907 self.eval_expr(&args[0])?.to_string()
12908 };
12909 Ok(PerlValue::iterator(Arc::new(
12910 crate::value::FsWalkIterator::new(&dir, false),
12911 )))
12912 }
12913 ExprKind::SymLinks(args) => {
12914 let dir = if args.is_empty() {
12915 ".".to_string()
12916 } else {
12917 self.eval_expr(&args[0])?.to_string()
12918 };
12919 Ok(crate::perl_fs::list_sym_links(&dir))
12920 }
12921 ExprKind::Sockets(args) => {
12922 let dir = if args.is_empty() {
12923 ".".to_string()
12924 } else {
12925 self.eval_expr(&args[0])?.to_string()
12926 };
12927 Ok(crate::perl_fs::list_sockets(&dir))
12928 }
12929 ExprKind::Pipes(args) => {
12930 let dir = if args.is_empty() {
12931 ".".to_string()
12932 } else {
12933 self.eval_expr(&args[0])?.to_string()
12934 };
12935 Ok(crate::perl_fs::list_pipes(&dir))
12936 }
12937 ExprKind::BlockDevices(args) => {
12938 let dir = if args.is_empty() {
12939 ".".to_string()
12940 } else {
12941 self.eval_expr(&args[0])?.to_string()
12942 };
12943 Ok(crate::perl_fs::list_block_devices(&dir))
12944 }
12945 ExprKind::CharDevices(args) => {
12946 let dir = if args.is_empty() {
12947 ".".to_string()
12948 } else {
12949 self.eval_expr(&args[0])?.to_string()
12950 };
12951 Ok(crate::perl_fs::list_char_devices(&dir))
12952 }
12953 ExprKind::Executables(args) => {
12954 let dir = if args.is_empty() {
12955 ".".to_string()
12956 } else {
12957 self.eval_expr(&args[0])?.to_string()
12958 };
12959 Ok(crate::perl_fs::list_executables(&dir))
12960 }
12961 ExprKind::Glob(args) => {
12962 let mut pats = Vec::new();
12963 for a in args {
12964 pats.push(self.eval_expr(a)?.to_string());
12965 }
12966 Ok(crate::perl_fs::glob_patterns(&pats))
12967 }
12968 ExprKind::GlobPar { args, progress } => {
12969 let mut pats = Vec::new();
12970 for a in args {
12971 pats.push(self.eval_expr(a)?.to_string());
12972 }
12973 let show_progress = progress
12974 .as_ref()
12975 .map(|p| self.eval_expr(p))
12976 .transpose()?
12977 .map(|v| v.is_true())
12978 .unwrap_or(false);
12979 if show_progress {
12980 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
12981 } else {
12982 Ok(crate::perl_fs::glob_par_patterns(&pats))
12983 }
12984 }
12985 ExprKind::ParSed { args, progress } => {
12986 let has_progress = progress.is_some();
12987 let mut vals: Vec<PerlValue> = Vec::new();
12988 for a in args {
12989 vals.push(self.eval_expr(a)?);
12990 }
12991 if let Some(p) = progress {
12992 vals.push(self.eval_expr(p.as_ref())?);
12993 }
12994 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
12995 }
12996 ExprKind::Bless { ref_expr, class } => {
12997 let val = self.eval_expr(ref_expr)?;
12998 let class_name = if let Some(c) = class {
12999 self.eval_expr(c)?.to_string()
13000 } else {
13001 self.scope.get_scalar("__PACKAGE__").to_string()
13002 };
13003 Ok(PerlValue::blessed(Arc::new(
13004 crate::value::BlessedRef::new_blessed(class_name, val),
13005 )))
13006 }
13007 ExprKind::Caller(_) => {
13008 Ok(PerlValue::array(vec![
13010 PerlValue::string("main".into()),
13011 PerlValue::string(self.file.clone()),
13012 PerlValue::integer(line as i64),
13013 ]))
13014 }
13015 ExprKind::Wantarray => Ok(match self.wantarray_kind {
13016 WantarrayCtx::Void => PerlValue::UNDEF,
13017 WantarrayCtx::Scalar => PerlValue::integer(0),
13018 WantarrayCtx::List => PerlValue::integer(1),
13019 }),
13020
13021 ExprKind::List(exprs) => {
13022 if ctx == WantarrayCtx::Scalar {
13024 if let Some(last) = exprs.last() {
13025 for e in &exprs[..exprs.len() - 1] {
13027 self.eval_expr(e)?;
13028 }
13029 return self.eval_expr(last);
13030 } else {
13031 return Ok(PerlValue::UNDEF);
13032 }
13033 }
13034 let mut vals = Vec::new();
13035 for e in exprs {
13036 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
13037 if let Some(items) = v.as_array_vec() {
13038 vals.extend(items);
13039 } else {
13040 vals.push(v);
13041 }
13042 }
13043 if vals.len() == 1 {
13044 Ok(vals.pop().unwrap())
13045 } else {
13046 Ok(PerlValue::array(vals))
13047 }
13048 }
13049
13050 ExprKind::PostfixIf { expr, condition } => {
13052 if self.eval_postfix_condition(condition)? {
13053 self.eval_expr(expr)
13054 } else {
13055 Ok(PerlValue::UNDEF)
13056 }
13057 }
13058 ExprKind::PostfixUnless { expr, condition } => {
13059 if !self.eval_postfix_condition(condition)? {
13060 self.eval_expr(expr)
13061 } else {
13062 Ok(PerlValue::UNDEF)
13063 }
13064 }
13065 ExprKind::PostfixWhile { expr, condition } => {
13066 let is_do_block = matches!(
13069 &expr.kind,
13070 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
13071 );
13072 let mut last = PerlValue::UNDEF;
13073 if is_do_block {
13074 loop {
13075 last = self.eval_expr(expr)?;
13076 if !self.eval_postfix_condition(condition)? {
13077 break;
13078 }
13079 }
13080 } else {
13081 loop {
13082 if !self.eval_postfix_condition(condition)? {
13083 break;
13084 }
13085 last = self.eval_expr(expr)?;
13086 }
13087 }
13088 Ok(last)
13089 }
13090 ExprKind::PostfixUntil { expr, condition } => {
13091 let is_do_block = matches!(
13092 &expr.kind,
13093 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
13094 );
13095 let mut last = PerlValue::UNDEF;
13096 if is_do_block {
13097 loop {
13098 last = self.eval_expr(expr)?;
13099 if self.eval_postfix_condition(condition)? {
13100 break;
13101 }
13102 }
13103 } else {
13104 loop {
13105 if self.eval_postfix_condition(condition)? {
13106 break;
13107 }
13108 last = self.eval_expr(expr)?;
13109 }
13110 }
13111 Ok(last)
13112 }
13113 ExprKind::PostfixForeach { expr, list } => {
13114 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
13115 let mut last = PerlValue::UNDEF;
13116 for item in items {
13117 self.scope.set_topic(item);
13118 last = self.eval_expr(expr)?;
13119 }
13120 Ok(last)
13121 }
13122 }
13123 }
13124
13125 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
13128 match op {
13129 BinOp::Add => Some("+"),
13130 BinOp::Sub => Some("-"),
13131 BinOp::Mul => Some("*"),
13132 BinOp::Div => Some("/"),
13133 BinOp::Mod => Some("%"),
13134 BinOp::Pow => Some("**"),
13135 BinOp::Concat => Some("."),
13136 BinOp::StrEq => Some("eq"),
13137 BinOp::NumEq => Some("=="),
13138 BinOp::StrNe => Some("ne"),
13139 BinOp::NumNe => Some("!="),
13140 BinOp::StrLt => Some("lt"),
13141 BinOp::StrGt => Some("gt"),
13142 BinOp::StrLe => Some("le"),
13143 BinOp::StrGe => Some("ge"),
13144 BinOp::NumLt => Some("<"),
13145 BinOp::NumGt => Some(">"),
13146 BinOp::NumLe => Some("<="),
13147 BinOp::NumGe => Some(">="),
13148 BinOp::Spaceship => Some("<=>"),
13149 BinOp::StrCmp => Some("cmp"),
13150 _ => None,
13151 }
13152 }
13153
13154 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
13156 map.get("").or_else(|| map.get("\"\""))
13157 }
13158
13159 pub(crate) fn stringify_value(
13161 &mut self,
13162 v: PerlValue,
13163 line: usize,
13164 ) -> Result<String, FlowOrError> {
13165 if let Some(r) = self.try_overload_stringify(&v, line) {
13166 let pv = r?;
13167 return Ok(pv.to_string());
13168 }
13169 Ok(v.to_string())
13170 }
13171
13172 pub(crate) fn perl_sprintf_stringify(
13174 &mut self,
13175 fmt: &str,
13176 args: &[PerlValue],
13177 line: usize,
13178 ) -> Result<String, FlowOrError> {
13179 let (out, pending_n) = {
13181 let mut stringify = |v: &PerlValue| -> Result<String, FlowOrError> {
13182 self.stringify_value(v.clone(), line)
13183 };
13184 perl_sprintf_format_full(fmt, args, &mut stringify)?
13185 };
13186 for (target, count) in pending_n {
13188 self.assign_scalar_ref_deref(target, PerlValue::integer(count), line)?;
13189 }
13190 Ok(out)
13191 }
13192
13193 pub(crate) fn render_format_template(
13195 &mut self,
13196 tmpl: &crate::format::FormatTemplate,
13197 line: usize,
13198 ) -> Result<String, FlowOrError> {
13199 use crate::format::{FormatRecord, PictureSegment};
13200 let mut buf = String::new();
13201 for rec in &tmpl.records {
13202 match rec {
13203 FormatRecord::Literal(s) => {
13204 buf.push_str(s);
13205 buf.push('\n');
13206 }
13207 FormatRecord::Picture { segments, exprs } => {
13208 let mut vals: Vec<String> = Vec::new();
13209 for e in exprs {
13210 let v = self.eval_expr(e)?;
13211 vals.push(self.stringify_value(v, line)?);
13212 }
13213 let mut vi = 0usize;
13214 let mut line_out = String::new();
13215 for seg in segments {
13216 match seg {
13217 PictureSegment::Literal(t) => line_out.push_str(t),
13218 PictureSegment::Field {
13219 width,
13220 align,
13221 kind: _,
13222 } => {
13223 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
13224 vi += 1;
13225 line_out.push_str(&crate::format::pad_field(s, *width, *align));
13226 }
13227 }
13228 }
13229 buf.push_str(line_out.trim_end());
13230 buf.push('\n');
13231 }
13232 }
13233 }
13234 Ok(buf)
13235 }
13236
13237 pub(crate) fn resolve_write_output_handle(
13239 &self,
13240 v: &PerlValue,
13241 line: usize,
13242 ) -> PerlResult<String> {
13243 if let Some(n) = v.as_io_handle_name() {
13244 let n = self.resolve_io_handle_name(&n);
13245 if self.is_bound_handle(&n) {
13246 return Ok(n);
13247 }
13248 }
13249 if let Some(s) = v.as_str() {
13250 if self.is_bound_handle(&s) {
13251 return Ok(self.resolve_io_handle_name(&s));
13252 }
13253 }
13254 let s = v.to_string();
13255 if self.is_bound_handle(&s) {
13256 return Ok(self.resolve_io_handle_name(&s));
13257 }
13258 Err(PerlError::runtime(
13259 format!("write: invalid or unopened filehandle {}", s),
13260 line,
13261 ))
13262 }
13263
13264 pub(crate) fn write_format_execute(
13268 &mut self,
13269 args: &[PerlValue],
13270 line: usize,
13271 ) -> PerlResult<PerlValue> {
13272 let handle_name = match args.len() {
13273 0 => self.default_print_handle.clone(),
13274 1 => self.resolve_write_output_handle(&args[0], line)?,
13275 _ => {
13276 return Err(PerlError::runtime("write: too many arguments", line));
13277 }
13278 };
13279 let pkg = self.current_package();
13280 let mut fmt_name = self.scope.get_scalar("~").to_string();
13281 if fmt_name.is_empty() {
13282 fmt_name = "STDOUT".to_string();
13283 }
13284 let key = format!("{}::{}", pkg, fmt_name);
13285 let tmpl = self
13286 .format_templates
13287 .get(&key)
13288 .map(Arc::clone)
13289 .ok_or_else(|| {
13290 PerlError::runtime(
13291 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
13292 line,
13293 )
13294 })?;
13295 let out = self
13296 .render_format_template(&tmpl, line)
13297 .map_err(|e| match e {
13298 FlowOrError::Error(e) => e,
13299 FlowOrError::Flow(_) => PerlError::runtime("write: unexpected control flow", line),
13300 })?;
13301 self.write_formatted_print(handle_name.as_str(), &out, line)?;
13302 Ok(PerlValue::integer(1))
13303 }
13304
13305 pub(crate) fn try_overload_stringify(
13306 &mut self,
13307 v: &PerlValue,
13308 line: usize,
13309 ) -> Option<ExecResult> {
13310 if let Some(c) = v.as_class_inst() {
13312 let method_name = c
13313 .def
13314 .method("stringify")
13315 .or_else(|| c.def.method("\"\""))
13316 .filter(|m| m.body.is_some())?;
13317 let body = method_name.body.clone().unwrap();
13318 let params = method_name.params.clone();
13319 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
13320 }
13321 let br = v.as_blessed_ref()?;
13322 let class = br.class.clone();
13323 let map = self.overload_table.get(&class)?;
13324 let sub_short = Self::overload_stringify_method(map)?;
13325 let fq = format!("{}::{}", class, sub_short);
13326 let sub = self.subs.get(&fq)?.clone();
13327 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
13328 }
13329
13330 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
13332 match key {
13333 "+" => Some("op_add"),
13334 "-" => Some("op_sub"),
13335 "*" => Some("op_mul"),
13336 "/" => Some("op_div"),
13337 "%" => Some("op_mod"),
13338 "**" => Some("op_pow"),
13339 "." => Some("op_concat"),
13340 "==" => Some("op_eq"),
13341 "!=" => Some("op_ne"),
13342 "<" => Some("op_lt"),
13343 ">" => Some("op_gt"),
13344 "<=" => Some("op_le"),
13345 ">=" => Some("op_ge"),
13346 "<=>" => Some("op_spaceship"),
13347 "eq" => Some("op_str_eq"),
13348 "ne" => Some("op_str_ne"),
13349 "lt" => Some("op_str_lt"),
13350 "gt" => Some("op_str_gt"),
13351 "le" => Some("op_str_le"),
13352 "ge" => Some("op_str_ge"),
13353 "cmp" => Some("op_cmp"),
13354 _ => None,
13355 }
13356 }
13357
13358 pub(crate) fn try_overload_binop(
13359 &mut self,
13360 op: BinOp,
13361 lv: &PerlValue,
13362 rv: &PerlValue,
13363 line: usize,
13364 ) -> Option<ExecResult> {
13365 let key = Self::overload_key_for_binop(op)?;
13366 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
13368 (Some(c.def.clone()), lv.clone(), rv.clone())
13369 } else if let Some(c) = rv.as_class_inst() {
13370 (Some(c.def.clone()), rv.clone(), lv.clone())
13371 } else {
13372 (None, lv.clone(), rv.clone())
13373 };
13374 if let Some(ref def) = ci_def {
13375 if let Some(method_name) = Self::overload_method_name_for_key(key) {
13376 if let Some((m, _)) = self.find_class_method(def, method_name) {
13377 if let Some(ref body) = m.body {
13378 let params = m.params.clone();
13379 return Some(self.call_class_method(
13380 body,
13381 ¶ms,
13382 vec![invocant, other],
13383 line,
13384 ));
13385 }
13386 }
13387 }
13388 }
13389 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
13391 (br.class.clone(), lv.clone(), rv.clone())
13392 } else if let Some(br) = rv.as_blessed_ref() {
13393 (br.class.clone(), rv.clone(), lv.clone())
13394 } else {
13395 return None;
13396 };
13397 let map = self.overload_table.get(&class)?;
13398 let sub_short = if let Some(s) = map.get(key) {
13399 s.clone()
13400 } else if let Some(nm) = map.get("nomethod") {
13401 let fq = format!("{}::{}", class, nm);
13402 let sub = self.subs.get(&fq)?.clone();
13403 return Some(self.call_sub(
13404 &sub,
13405 vec![invocant, other, PerlValue::string(key.to_string())],
13406 WantarrayCtx::Scalar,
13407 line,
13408 ));
13409 } else {
13410 return None;
13411 };
13412 let fq = format!("{}::{}", class, sub_short);
13413 let sub = self.subs.get(&fq)?.clone();
13414 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
13415 }
13416
13417 pub(crate) fn try_overload_unary_dispatch(
13419 &mut self,
13420 op_key: &str,
13421 val: &PerlValue,
13422 line: usize,
13423 ) -> Option<ExecResult> {
13424 if let Some(c) = val.as_class_inst() {
13426 let method_name = match op_key {
13427 "neg" => "op_neg",
13428 "bool" => "op_bool",
13429 "abs" => "op_abs",
13430 "0+" => "op_numify",
13431 _ => return None,
13432 };
13433 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
13434 if let Some(ref body) = m.body {
13435 let params = m.params.clone();
13436 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
13437 }
13438 }
13439 return None;
13440 }
13441 let br = val.as_blessed_ref()?;
13443 let class = br.class.clone();
13444 let map = self.overload_table.get(&class)?;
13445 if let Some(s) = map.get(op_key) {
13446 let fq = format!("{}::{}", class, s);
13447 let sub = self.subs.get(&fq)?.clone();
13448 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
13449 }
13450 if let Some(nm) = map.get("nomethod") {
13451 let fq = format!("{}::{}", class, nm);
13452 let sub = self.subs.get(&fq)?.clone();
13453 return Some(self.call_sub(
13454 &sub,
13455 vec![val.clone(), PerlValue::string(op_key.to_string())],
13456 WantarrayCtx::Scalar,
13457 line,
13458 ));
13459 }
13460 None
13461 }
13462
13463 #[inline]
13464 fn eval_binop(
13465 &mut self,
13466 op: BinOp,
13467 lv: &PerlValue,
13468 rv: &PerlValue,
13469 _line: usize,
13470 ) -> ExecResult {
13471 Ok(match op {
13472 BinOp::Add => {
13475 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13476 PerlValue::integer(a.wrapping_add(b))
13477 } else {
13478 PerlValue::float(lv.to_number() + rv.to_number())
13479 }
13480 }
13481 BinOp::Sub => {
13482 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13483 PerlValue::integer(a.wrapping_sub(b))
13484 } else {
13485 PerlValue::float(lv.to_number() - rv.to_number())
13486 }
13487 }
13488 BinOp::Mul => {
13489 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13490 PerlValue::integer(a.wrapping_mul(b))
13491 } else {
13492 PerlValue::float(lv.to_number() * rv.to_number())
13493 }
13494 }
13495 BinOp::Div => {
13496 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13497 if b == 0 {
13498 return Err(
13499 PerlError::division_by_zero("Illegal division by zero", _line).into(),
13500 );
13501 }
13502 if a % b == 0 {
13503 PerlValue::integer(a / b)
13504 } else {
13505 PerlValue::float(a as f64 / b as f64)
13506 }
13507 } else {
13508 let d = rv.to_number();
13509 if d == 0.0 {
13510 return Err(
13511 PerlError::division_by_zero("Illegal division by zero", _line).into(),
13512 );
13513 }
13514 PerlValue::float(lv.to_number() / d)
13515 }
13516 }
13517 BinOp::Mod => {
13518 let d = rv.to_int();
13519 if d == 0 {
13520 return Err(PerlError::division_by_zero("Illegal modulus zero", _line).into());
13521 }
13522 PerlValue::integer(crate::value::perl_mod_i64(lv.to_int(), d))
13523 }
13524 BinOp::Pow => {
13525 if crate::compat_mode() || crate::bigint_pragma() {
13529 crate::value::compat_pow(lv, rv)
13530 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13531 let int_pow = (b >= 0)
13532 .then(|| u32::try_from(b).ok())
13533 .flatten()
13534 .and_then(|bu| a.checked_pow(bu))
13535 .map(PerlValue::integer);
13536 int_pow.unwrap_or_else(|| PerlValue::float(lv.to_number().powf(rv.to_number())))
13537 } else {
13538 PerlValue::float(lv.to_number().powf(rv.to_number()))
13539 }
13540 }
13541 BinOp::Concat => {
13542 let mut s = String::new();
13543 lv.append_to(&mut s);
13544 rv.append_to(&mut s);
13545 PerlValue::string(s)
13546 }
13547 BinOp::NumEq => {
13548 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
13550 if a.def.name != b.def.name {
13551 PerlValue::integer(0)
13552 } else {
13553 let av = a.get_values();
13554 let bv = b.get_values();
13555 let eq = av.len() == bv.len()
13556 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
13557 PerlValue::integer(if eq { 1 } else { 0 })
13558 }
13559 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13560 PerlValue::integer(if a == b { 1 } else { 0 })
13561 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
13562 PerlValue::integer(if lv.to_string() == rv.to_string() {
13568 1
13569 } else {
13570 0
13571 })
13572 } else {
13573 PerlValue::integer(if lv.to_number() == rv.to_number() {
13574 1
13575 } else {
13576 0
13577 })
13578 }
13579 }
13580 BinOp::NumNe => {
13581 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13582 PerlValue::integer(if a != b { 1 } else { 0 })
13583 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
13584 PerlValue::integer(if lv.to_string() != rv.to_string() {
13585 1
13586 } else {
13587 0
13588 })
13589 } else {
13590 PerlValue::integer(if lv.to_number() != rv.to_number() {
13591 1
13592 } else {
13593 0
13594 })
13595 }
13596 }
13597 BinOp::NumLt => {
13598 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13599 PerlValue::integer(if a < b { 1 } else { 0 })
13600 } else {
13601 PerlValue::integer(if lv.to_number() < rv.to_number() {
13602 1
13603 } else {
13604 0
13605 })
13606 }
13607 }
13608 BinOp::NumGt => {
13609 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13610 PerlValue::integer(if a > b { 1 } else { 0 })
13611 } else {
13612 PerlValue::integer(if lv.to_number() > rv.to_number() {
13613 1
13614 } else {
13615 0
13616 })
13617 }
13618 }
13619 BinOp::NumLe => {
13620 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13621 PerlValue::integer(if a <= b { 1 } else { 0 })
13622 } else {
13623 PerlValue::integer(if lv.to_number() <= rv.to_number() {
13624 1
13625 } else {
13626 0
13627 })
13628 }
13629 }
13630 BinOp::NumGe => {
13631 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13632 PerlValue::integer(if a >= b { 1 } else { 0 })
13633 } else {
13634 PerlValue::integer(if lv.to_number() >= rv.to_number() {
13635 1
13636 } else {
13637 0
13638 })
13639 }
13640 }
13641 BinOp::Spaceship => {
13642 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13643 PerlValue::integer(if a < b {
13644 -1
13645 } else if a > b {
13646 1
13647 } else {
13648 0
13649 })
13650 } else {
13651 let a = lv.to_number();
13652 let b = rv.to_number();
13653 PerlValue::integer(if a < b {
13654 -1
13655 } else if a > b {
13656 1
13657 } else {
13658 0
13659 })
13660 }
13661 }
13662 BinOp::StrEq => PerlValue::integer(if lv.to_string() == rv.to_string() {
13663 1
13664 } else {
13665 0
13666 }),
13667 BinOp::StrNe => PerlValue::integer(if lv.to_string() != rv.to_string() {
13668 1
13669 } else {
13670 0
13671 }),
13672 BinOp::StrLt => PerlValue::integer(if lv.to_string() < rv.to_string() {
13673 1
13674 } else {
13675 0
13676 }),
13677 BinOp::StrGt => PerlValue::integer(if lv.to_string() > rv.to_string() {
13678 1
13679 } else {
13680 0
13681 }),
13682 BinOp::StrLe => PerlValue::integer(if lv.to_string() <= rv.to_string() {
13683 1
13684 } else {
13685 0
13686 }),
13687 BinOp::StrGe => PerlValue::integer(if lv.to_string() >= rv.to_string() {
13688 1
13689 } else {
13690 0
13691 }),
13692 BinOp::StrCmp => {
13693 let cmp = lv.to_string().cmp(&rv.to_string());
13694 PerlValue::integer(match cmp {
13695 std::cmp::Ordering::Less => -1,
13696 std::cmp::Ordering::Greater => 1,
13697 std::cmp::Ordering::Equal => 0,
13698 })
13699 }
13700 BinOp::BitAnd => {
13701 if let Some(s) = crate::value::set_intersection(lv, rv) {
13702 s
13703 } else {
13704 PerlValue::integer(lv.to_int() & rv.to_int())
13705 }
13706 }
13707 BinOp::BitOr => {
13708 if let Some(s) = crate::value::set_union(lv, rv) {
13709 s
13710 } else {
13711 PerlValue::integer(lv.to_int() | rv.to_int())
13712 }
13713 }
13714 BinOp::BitXor => PerlValue::integer(lv.to_int() ^ rv.to_int()),
13715 BinOp::ShiftLeft => PerlValue::integer(lv.to_int() << rv.to_int()),
13716 BinOp::ShiftRight => PerlValue::integer(lv.to_int() >> rv.to_int()),
13717 BinOp::LogAnd
13719 | BinOp::LogOr
13720 | BinOp::DefinedOr
13721 | BinOp::LogAndWord
13722 | BinOp::LogOrWord => unreachable!(),
13723 BinOp::BindMatch | BinOp::BindNotMatch => {
13724 unreachable!("regex bind handled in eval_expr BinOp arm")
13725 }
13726 })
13727 }
13728
13729 fn err_modify_symbolic_aggregate_deref_inc_dec(
13733 kind: Sigil,
13734 is_pre: bool,
13735 is_inc: bool,
13736 line: usize,
13737 ) -> FlowOrError {
13738 let agg = match kind {
13739 Sigil::Array => "array",
13740 Sigil::Hash => "hash",
13741 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
13742 };
13743 let op = match (is_pre, is_inc) {
13744 (true, true) => "preincrement (++)",
13745 (true, false) => "predecrement (--)",
13746 (false, true) => "postincrement (++)",
13747 (false, false) => "postdecrement (--)",
13748 };
13749 FlowOrError::Error(PerlError::runtime(
13750 format!("Can't modify {agg} dereference in {op}"),
13751 line,
13752 ))
13753 }
13754
13755 pub(crate) fn symbolic_scalar_ref_postfix(
13757 &mut self,
13758 ref_val: PerlValue,
13759 decrement: bool,
13760 line: usize,
13761 ) -> Result<PerlValue, FlowOrError> {
13762 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
13763 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
13764 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
13765 Ok(old)
13766 }
13767
13768 pub(crate) fn assign_scalar_ref_deref(
13771 &mut self,
13772 ref_val: PerlValue,
13773 val: PerlValue,
13774 line: usize,
13775 ) -> ExecResult {
13776 if let Some(name) = ref_val.as_scalar_binding_name() {
13777 self.set_special_var(&name, &val)
13778 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13779 return Ok(PerlValue::UNDEF);
13780 }
13781 if let Some(r) = ref_val.as_scalar_ref() {
13782 *r.write() = val;
13783 return Ok(PerlValue::UNDEF);
13784 }
13785 if ref_val.is_integer_like() || ref_val.is_float_like() || ref_val.is_string_like() {
13788 let s = ref_val.to_string();
13789 if self.strict_refs {
13790 return Err(PerlError::runtime(
13791 format!(
13792 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
13793 s
13794 ),
13795 line,
13796 )
13797 .into());
13798 }
13799 self.set_special_var(&s, &val)
13800 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13801 return Ok(PerlValue::UNDEF);
13802 }
13803 Err(PerlError::runtime("Can't assign to non-scalar reference", line).into())
13804 }
13805
13806 pub(crate) fn assign_symbolic_array_ref_deref(
13808 &mut self,
13809 ref_val: PerlValue,
13810 val: PerlValue,
13811 line: usize,
13812 ) -> ExecResult {
13813 if let Some(a) = ref_val.as_array_ref() {
13814 *a.write() = val.to_list();
13815 return Ok(PerlValue::UNDEF);
13816 }
13817 if let Some(name) = ref_val.as_array_binding_name() {
13818 self.scope
13819 .set_array(&name, val.to_list())
13820 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13821 return Ok(PerlValue::UNDEF);
13822 }
13823 if let Some(s) = ref_val.as_str() {
13824 if self.strict_refs {
13825 return Err(PerlError::runtime(
13826 format!(
13827 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
13828 s
13829 ),
13830 line,
13831 )
13832 .into());
13833 }
13834 self.scope
13835 .set_array(&s, val.to_list())
13836 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13837 return Ok(PerlValue::UNDEF);
13838 }
13839 Err(PerlError::runtime("Can't assign to non-array reference", line).into())
13840 }
13841
13842 pub(crate) fn assign_symbolic_typeglob_ref_deref(
13845 &mut self,
13846 ref_val: PerlValue,
13847 val: PerlValue,
13848 line: usize,
13849 ) -> ExecResult {
13850 let lhs_name = if let Some(s) = ref_val.as_str() {
13851 if self.strict_refs {
13852 return Err(PerlError::runtime(
13853 format!(
13854 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
13855 s
13856 ),
13857 line,
13858 )
13859 .into());
13860 }
13861 s.to_string()
13862 } else {
13863 return Err(
13864 PerlError::runtime("Can't assign to non-glob symbolic reference", line).into(),
13865 );
13866 };
13867 let is_coderef = val.as_code_ref().is_some()
13868 || val
13869 .as_scalar_ref()
13870 .map(|r| r.read().as_code_ref().is_some())
13871 .unwrap_or(false);
13872 if is_coderef {
13873 return self.assign_typeglob_value(&lhs_name, val, line);
13874 }
13875 let rhs_key = val.to_string();
13876 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
13877 .map_err(FlowOrError::Error)?;
13878 Ok(PerlValue::UNDEF)
13879 }
13880
13881 pub(crate) fn assign_symbolic_hash_ref_deref(
13883 &mut self,
13884 ref_val: PerlValue,
13885 val: PerlValue,
13886 line: usize,
13887 ) -> ExecResult {
13888 let items = val.to_list();
13889 let mut map = IndexMap::new();
13890 let mut i = 0;
13891 while i + 1 < items.len() {
13892 map.insert(items[i].to_string(), items[i + 1].clone());
13893 i += 2;
13894 }
13895 if let Some(h) = ref_val.as_hash_ref() {
13896 *h.write() = map;
13897 return Ok(PerlValue::UNDEF);
13898 }
13899 if let Some(name) = ref_val.as_hash_binding_name() {
13900 self.touch_env_hash(&name);
13901 self.scope
13902 .set_hash(&name, map)
13903 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13904 return Ok(PerlValue::UNDEF);
13905 }
13906 if let Some(s) = ref_val.as_str() {
13907 if self.strict_refs {
13908 return Err(PerlError::runtime(
13909 format!(
13910 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
13911 s
13912 ),
13913 line,
13914 )
13915 .into());
13916 }
13917 self.touch_env_hash(&s);
13918 self.scope
13919 .set_hash(&s, map)
13920 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13921 return Ok(PerlValue::UNDEF);
13922 }
13923 Err(PerlError::runtime("Can't assign to non-hash reference", line).into())
13924 }
13925
13926 pub(crate) fn assign_arrow_hash_deref(
13928 &mut self,
13929 container: PerlValue,
13930 key: String,
13931 val: PerlValue,
13932 line: usize,
13933 ) -> ExecResult {
13934 if let Some(b) = container.as_blessed_ref() {
13935 let mut data = b.data.write();
13936 if let Some(r) = data.as_hash_ref() {
13937 r.write().insert(key, val);
13938 return Ok(PerlValue::UNDEF);
13939 }
13940 if let Some(mut map) = data.as_hash_map() {
13941 map.insert(key, val);
13942 *data = PerlValue::hash(map);
13943 return Ok(PerlValue::UNDEF);
13944 }
13945 return Err(PerlError::runtime("Can't assign into non-hash blessed ref", line).into());
13946 }
13947 if let Some(r) = container.as_hash_ref() {
13948 r.write().insert(key, val);
13949 return Ok(PerlValue::UNDEF);
13950 }
13951 if let Some(name) = container.as_hash_binding_name() {
13952 self.touch_env_hash(&name);
13953 self.scope
13954 .set_hash_element(&name, &key, val)
13955 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13956 return Ok(PerlValue::UNDEF);
13957 }
13958 Err(PerlError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
13959 }
13960
13961 pub(crate) fn eval_arrow_array_base(
13964 &mut self,
13965 expr: &Expr,
13966 _line: usize,
13967 ) -> Result<PerlValue, FlowOrError> {
13968 match &expr.kind {
13969 ExprKind::Deref {
13970 expr: inner,
13971 kind: Sigil::Array | Sigil::Scalar,
13972 } => self.eval_expr(inner),
13973 _ => self.eval_expr(expr),
13974 }
13975 }
13976
13977 pub(crate) fn eval_arrow_hash_base(
13979 &mut self,
13980 expr: &Expr,
13981 _line: usize,
13982 ) -> Result<PerlValue, FlowOrError> {
13983 match &expr.kind {
13984 ExprKind::Deref {
13985 expr: inner,
13986 kind: Sigil::Scalar,
13987 } => self.eval_expr(inner),
13988 _ => self.eval_expr(expr),
13989 }
13990 }
13991
13992 pub(crate) fn read_arrow_array_element(
13994 &self,
13995 container: PerlValue,
13996 idx: i64,
13997 line: usize,
13998 ) -> Result<PerlValue, FlowOrError> {
13999 if let Some(a) = container.as_array_ref() {
14000 let arr = a.read();
14001 let i = if idx < 0 {
14002 (arr.len() as i64 + idx) as usize
14003 } else {
14004 idx as usize
14005 };
14006 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
14007 }
14008 if let Some(name) = container.as_array_binding_name() {
14009 return Ok(self.scope.get_array_element(&name, idx));
14010 }
14011 if let Some(arr) = container.as_array_vec() {
14012 let i = if idx < 0 {
14013 (arr.len() as i64 + idx) as usize
14014 } else {
14015 idx as usize
14016 };
14017 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
14018 }
14019 if let Some(b) = container.as_blessed_ref() {
14022 let inner = b.data.read().clone();
14023 if let Some(a) = inner.as_array_ref() {
14024 let arr = a.read();
14025 let i = if idx < 0 {
14026 (arr.len() as i64 + idx) as usize
14027 } else {
14028 idx as usize
14029 };
14030 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
14031 }
14032 }
14033 Err(PerlError::runtime("Can't use arrow deref on non-array-ref", line).into())
14034 }
14035
14036 pub(crate) fn read_arrow_hash_element(
14038 &mut self,
14039 container: PerlValue,
14040 key: &str,
14041 line: usize,
14042 ) -> Result<PerlValue, FlowOrError> {
14043 if let Some(r) = container.as_hash_ref() {
14044 let h = r.read();
14045 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
14046 }
14047 if let Some(name) = container.as_hash_binding_name() {
14048 self.touch_env_hash(&name);
14049 return Ok(self.scope.get_hash_element(&name, key));
14050 }
14051 if let Some(b) = container.as_blessed_ref() {
14052 let data = b.data.read();
14053 if let Some(v) = data.hash_get(key) {
14054 return Ok(v);
14055 }
14056 if let Some(r) = data.as_hash_ref() {
14057 let h = r.read();
14058 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
14059 }
14060 return Err(PerlError::runtime(
14061 "Can't access hash field on non-hash blessed ref",
14062 line,
14063 )
14064 .into());
14065 }
14066 if let Some(s) = container.as_struct_inst() {
14068 if let Some(idx) = s.def.field_index(key) {
14069 return Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF));
14070 }
14071 return Err(PerlError::runtime(
14072 format!("struct {} has no field `{}`", s.def.name, key),
14073 line,
14074 )
14075 .into());
14076 }
14077 if let Some(c) = container.as_class_inst() {
14079 if let Some(idx) = c.def.field_index(key) {
14080 return Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF));
14081 }
14082 return Err(PerlError::runtime(
14083 format!("class {} has no field `{}`", c.def.name, key),
14084 line,
14085 )
14086 .into());
14087 }
14088 Err(PerlError::runtime("Can't use arrow deref on non-hash-ref", line).into())
14089 }
14090
14091 pub(crate) fn arrow_array_postfix(
14093 &mut self,
14094 container: PerlValue,
14095 idx: i64,
14096 decrement: bool,
14097 line: usize,
14098 ) -> Result<PerlValue, FlowOrError> {
14099 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
14100 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14101 self.assign_arrow_array_deref(container, idx, new_val, line)?;
14102 Ok(old)
14103 }
14104
14105 pub(crate) fn arrow_hash_postfix(
14107 &mut self,
14108 container: PerlValue,
14109 key: String,
14110 decrement: bool,
14111 line: usize,
14112 ) -> Result<PerlValue, FlowOrError> {
14113 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
14114 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14115 self.assign_arrow_hash_deref(container, key, new_val, line)?;
14116 Ok(old)
14117 }
14118
14119 pub(crate) fn resolve_bareword_rvalue(
14127 &mut self,
14128 name: &str,
14129 want: WantarrayCtx,
14130 line: usize,
14131 ) -> Result<PerlValue, FlowOrError> {
14132 if name == "__PACKAGE__" {
14133 return Ok(PerlValue::string(self.current_package()));
14134 }
14135 if let Some(sub) = self.resolve_sub_by_name(name) {
14136 return self.call_sub(&sub, vec![], want, line);
14137 }
14138 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
14140 return r.map_err(Into::into);
14141 }
14142 Ok(PerlValue::string(name.to_string()))
14143 }
14144
14145 pub(crate) fn arrow_array_slice_values(
14149 &mut self,
14150 container: PerlValue,
14151 indices: &[i64],
14152 line: usize,
14153 ) -> Result<PerlValue, FlowOrError> {
14154 let mut out = Vec::with_capacity(indices.len());
14155 for &idx in indices {
14156 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
14157 out.push(v);
14158 }
14159 Ok(PerlValue::array(out))
14160 }
14161
14162 pub(crate) fn assign_arrow_array_slice(
14166 &mut self,
14167 container: PerlValue,
14168 indices: Vec<i64>,
14169 val: PerlValue,
14170 line: usize,
14171 ) -> Result<PerlValue, FlowOrError> {
14172 if indices.is_empty() {
14173 return Err(PerlError::runtime("assign to empty array slice", line).into());
14174 }
14175 let vals = val.to_list();
14176 for (i, idx) in indices.iter().enumerate() {
14177 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
14178 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
14179 }
14180 Ok(PerlValue::UNDEF)
14181 }
14182
14183 pub(crate) fn flatten_array_slice_index_specs(
14185 &mut self,
14186 indices: &[Expr],
14187 ) -> Result<Vec<i64>, FlowOrError> {
14188 let mut out = Vec::new();
14189 for idx_expr in indices {
14190 let v = if matches!(
14191 idx_expr.kind,
14192 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
14193 ) {
14194 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
14195 } else {
14196 self.eval_expr(idx_expr)?
14197 };
14198 if let Some(list) = v.as_array_vec() {
14199 for idx in list {
14200 out.push(idx.to_int());
14201 }
14202 } else {
14203 out.push(v.to_int());
14204 }
14205 }
14206 Ok(out)
14207 }
14208
14209 pub(crate) fn assign_named_array_slice(
14211 &mut self,
14212 stash_array_name: &str,
14213 indices: Vec<i64>,
14214 val: PerlValue,
14215 line: usize,
14216 ) -> Result<PerlValue, FlowOrError> {
14217 if indices.is_empty() {
14218 return Err(PerlError::runtime("assign to empty array slice", line).into());
14219 }
14220 let vals = val.to_list();
14221 for (i, idx) in indices.iter().enumerate() {
14222 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
14223 self.scope
14224 .set_array_element(stash_array_name, *idx, v)
14225 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14226 }
14227 Ok(PerlValue::UNDEF)
14228 }
14229
14230 pub(crate) fn compound_assign_arrow_array_slice(
14233 &mut self,
14234 container: PerlValue,
14235 indices: Vec<i64>,
14236 op: BinOp,
14237 rhs: PerlValue,
14238 line: usize,
14239 ) -> Result<PerlValue, FlowOrError> {
14240 if indices.is_empty() {
14241 return Err(PerlError::runtime("assign to empty array slice", line).into());
14242 }
14243 let last_idx = *indices.last().expect("non-empty indices");
14244 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
14245 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
14246 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
14247 Ok(new_val)
14248 }
14249
14250 pub(crate) fn arrow_array_slice_inc_dec(
14255 &mut self,
14256 container: PerlValue,
14257 indices: Vec<i64>,
14258 kind: u8,
14259 line: usize,
14260 ) -> Result<PerlValue, FlowOrError> {
14261 if indices.is_empty() {
14262 return Err(
14263 PerlError::runtime("array slice increment needs at least one index", line).into(),
14264 );
14265 }
14266 let last_idx = *indices.last().expect("non-empty indices");
14267 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
14268 let new_val = if kind & 1 == 0 {
14269 PerlValue::integer(last_old.to_int() + 1)
14270 } else {
14271 PerlValue::integer(last_old.to_int() - 1)
14272 };
14273 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
14274 Ok(if kind < 2 { new_val } else { last_old })
14275 }
14276
14277 pub(crate) fn named_array_slice_inc_dec(
14280 &mut self,
14281 stash_array_name: &str,
14282 indices: Vec<i64>,
14283 kind: u8,
14284 line: usize,
14285 ) -> Result<PerlValue, FlowOrError> {
14286 let last_idx = *indices.last().ok_or_else(|| {
14287 PerlError::runtime("array slice increment needs at least one index", line)
14288 })?;
14289 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
14290 let new_val = if kind & 1 == 0 {
14291 PerlValue::integer(last_old.to_int() + 1)
14292 } else {
14293 PerlValue::integer(last_old.to_int() - 1)
14294 };
14295 self.scope
14296 .set_array_element(stash_array_name, last_idx, new_val.clone())
14297 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14298 Ok(if kind < 2 { new_val } else { last_old })
14299 }
14300
14301 pub(crate) fn compound_assign_named_array_slice(
14303 &mut self,
14304 stash_array_name: &str,
14305 indices: Vec<i64>,
14306 op: BinOp,
14307 rhs: PerlValue,
14308 line: usize,
14309 ) -> Result<PerlValue, FlowOrError> {
14310 if indices.is_empty() {
14311 return Err(PerlError::runtime("assign to empty array slice", line).into());
14312 }
14313 let last_idx = *indices.last().expect("non-empty indices");
14314 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
14315 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
14316 self.scope
14317 .set_array_element(stash_array_name, last_idx, new_val.clone())
14318 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14319 Ok(new_val)
14320 }
14321
14322 pub(crate) fn assign_arrow_array_deref(
14324 &mut self,
14325 container: PerlValue,
14326 idx: i64,
14327 val: PerlValue,
14328 line: usize,
14329 ) -> ExecResult {
14330 if let Some(a) = container.as_array_ref() {
14331 let mut arr = a.write();
14332 let i = if idx < 0 {
14333 (arr.len() as i64 + idx) as usize
14334 } else {
14335 idx as usize
14336 };
14337 if i >= arr.len() {
14338 arr.resize(i + 1, PerlValue::UNDEF);
14339 }
14340 arr[i] = val;
14341 return Ok(PerlValue::UNDEF);
14342 }
14343 if let Some(name) = container.as_array_binding_name() {
14344 self.scope
14345 .set_array_element(&name, idx, val)
14346 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14347 return Ok(PerlValue::UNDEF);
14348 }
14349 Err(PerlError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
14350 }
14351
14352 pub(crate) fn assign_typeglob_value(
14354 &mut self,
14355 name: &str,
14356 val: PerlValue,
14357 line: usize,
14358 ) -> ExecResult {
14359 let sub = if let Some(c) = val.as_code_ref() {
14360 Some(c)
14361 } else if let Some(r) = val.as_scalar_ref() {
14362 r.read().as_code_ref().map(|c| Arc::clone(&c))
14363 } else {
14364 None
14365 };
14366 if let Some(sub) = sub {
14367 let lhs_sub = self.qualify_typeglob_sub_key(name);
14368 self.subs.insert(lhs_sub, sub);
14369 return Ok(PerlValue::UNDEF);
14370 }
14371 Err(PerlError::runtime(
14372 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
14373 line,
14374 )
14375 .into())
14376 }
14377
14378 fn assign_value(&mut self, target: &Expr, val: PerlValue) -> ExecResult {
14379 match &target.kind {
14380 ExprKind::Substr {
14386 string,
14387 offset,
14388 length,
14389 replacement: None,
14390 } => {
14391 let s = self.eval_expr(string)?.to_string();
14392 let off = self.eval_expr(offset)?.to_int();
14393 let start = if off < 0 {
14394 (s.len() as i64 + off).max(0) as usize
14395 } else {
14396 (off as usize).min(s.len())
14397 };
14398 let len = if let Some(l) = length {
14399 let lv = self.eval_expr(l)?.to_int();
14400 if lv < 0 {
14401 let remaining = s.len().saturating_sub(start) as i64;
14402 (remaining + lv).max(0) as usize
14403 } else {
14404 lv as usize
14405 }
14406 } else {
14407 s.len().saturating_sub(start)
14408 };
14409 let end = start.saturating_add(len).min(s.len());
14410 let mut new_s = String::with_capacity(s.len());
14411 new_s.push_str(&s[..start]);
14412 new_s.push_str(&val.to_string());
14413 new_s.push_str(&s[end..]);
14414 self.assign_value(string, PerlValue::string(new_s))?;
14415 Ok(PerlValue::UNDEF)
14416 }
14417 ExprKind::MyExpr { decls, .. } => {
14424 let first = decls.first().ok_or_else(|| {
14425 FlowOrError::Error(PerlError::runtime(
14426 "assign_value: empty MyExpr decl list",
14427 target.line,
14428 ))
14429 })?;
14430 match first.sigil {
14431 Sigil::Scalar => {
14432 let stor = self.tree_scalar_storage_name(&first.name);
14433 self.set_special_var(&stor, &val)
14434 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
14435 Ok(PerlValue::UNDEF)
14436 }
14437 Sigil::Array => {
14438 self.scope.set_array(&first.name, val.to_list())?;
14439 Ok(PerlValue::UNDEF)
14440 }
14441 Sigil::Hash => {
14442 let items = val.to_list();
14443 let mut map = IndexMap::new();
14444 let mut i = 0;
14445 while i + 1 < items.len() {
14446 map.insert(items[i].to_string(), items[i + 1].clone());
14447 i += 2;
14448 }
14449 self.scope.set_hash(&first.name, map)?;
14450 Ok(PerlValue::UNDEF)
14451 }
14452 Sigil::Typeglob => Ok(PerlValue::UNDEF),
14453 }
14454 }
14455 ExprKind::ScalarVar(name) => {
14456 let stor = self.tree_scalar_storage_name(name);
14457 if self.scope.is_scalar_frozen(&stor) {
14458 return Err(FlowOrError::Error(PerlError::runtime(
14459 format!("Modification of a frozen value: ${}", name),
14460 target.line,
14461 )));
14462 }
14463 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
14464 let class = obj
14465 .as_blessed_ref()
14466 .map(|b| b.class.clone())
14467 .unwrap_or_default();
14468 let full = format!("{}::STORE", class);
14469 if let Some(sub) = self.subs.get(&full).cloned() {
14470 let arg_vals = vec![obj, val];
14471 return match self.call_sub(
14472 &sub,
14473 arg_vals,
14474 WantarrayCtx::Scalar,
14475 target.line,
14476 ) {
14477 Ok(_) => Ok(PerlValue::UNDEF),
14478 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
14479 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
14480 };
14481 }
14482 }
14483 self.set_special_var(&stor, &val)
14484 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
14485 Ok(PerlValue::UNDEF)
14486 }
14487 ExprKind::ArrayVar(name) => {
14488 if self.scope.is_array_frozen(name) {
14489 return Err(PerlError::runtime(
14490 format!("Modification of a frozen value: @{}", name),
14491 target.line,
14492 )
14493 .into());
14494 }
14495 if self.strict_vars
14496 && !name.contains("::")
14497 && !self.scope.array_binding_exists(name)
14498 {
14499 return Err(PerlError::runtime(
14500 format!(
14501 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
14502 name, name
14503 ),
14504 target.line,
14505 )
14506 .into());
14507 }
14508 self.scope.set_array(name, val.to_list())?;
14509 Ok(PerlValue::UNDEF)
14510 }
14511 ExprKind::HashVar(name) => {
14512 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
14513 {
14514 return Err(PerlError::runtime(
14515 format!(
14516 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
14517 name, name
14518 ),
14519 target.line,
14520 )
14521 .into());
14522 }
14523 let items = val.to_list();
14524 let mut map = IndexMap::new();
14525 let mut i = 0;
14526 while i + 1 < items.len() {
14527 map.insert(items[i].to_string(), items[i + 1].clone());
14528 i += 2;
14529 }
14530 self.scope.set_hash(name, map)?;
14531 Ok(PerlValue::UNDEF)
14532 }
14533 ExprKind::ArrayElement { array, index } => {
14534 if self.strict_vars
14535 && !array.contains("::")
14536 && !self.scope.array_binding_exists(array)
14537 {
14538 return Err(PerlError::runtime(
14539 format!(
14540 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
14541 array, array
14542 ),
14543 target.line,
14544 )
14545 .into());
14546 }
14547 if self.scope.is_array_frozen(array) {
14548 return Err(PerlError::runtime(
14549 format!("Modification of a frozen value: @{}", array),
14550 target.line,
14551 )
14552 .into());
14553 }
14554 let idx = self.eval_expr(index)?.to_int();
14555 let aname = self.stash_array_name_for_package(array);
14556 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
14557 let class = obj
14558 .as_blessed_ref()
14559 .map(|b| b.class.clone())
14560 .unwrap_or_default();
14561 let full = format!("{}::STORE", class);
14562 if let Some(sub) = self.subs.get(&full).cloned() {
14563 let arg_vals = vec![obj, PerlValue::integer(idx), val];
14564 return match self.call_sub(
14565 &sub,
14566 arg_vals,
14567 WantarrayCtx::Scalar,
14568 target.line,
14569 ) {
14570 Ok(_) => Ok(PerlValue::UNDEF),
14571 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
14572 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
14573 };
14574 }
14575 }
14576 self.scope.set_array_element(&aname, idx, val)?;
14577 Ok(PerlValue::UNDEF)
14578 }
14579 ExprKind::ArraySlice { array, indices } => {
14580 if indices.is_empty() {
14581 return Err(
14582 PerlError::runtime("assign to empty array slice", target.line).into(),
14583 );
14584 }
14585 self.check_strict_array_var(array, target.line)?;
14586 if self.scope.is_array_frozen(array) {
14587 return Err(PerlError::runtime(
14588 format!("Modification of a frozen value: @{}", array),
14589 target.line,
14590 )
14591 .into());
14592 }
14593 let aname = self.stash_array_name_for_package(array);
14594 let flat = self.flatten_array_slice_index_specs(indices)?;
14595 self.assign_named_array_slice(&aname, flat, val, target.line)
14596 }
14597 ExprKind::HashElement { hash, key } => {
14598 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
14599 {
14600 return Err(PerlError::runtime(
14601 format!(
14602 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
14603 hash, hash
14604 ),
14605 target.line,
14606 )
14607 .into());
14608 }
14609 if self.scope.is_hash_frozen(hash) {
14610 return Err(PerlError::runtime(
14611 format!("Modification of a frozen value: %%{}", hash),
14612 target.line,
14613 )
14614 .into());
14615 }
14616 let k = self.eval_expr(key)?.to_string();
14617 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
14618 let class = obj
14619 .as_blessed_ref()
14620 .map(|b| b.class.clone())
14621 .unwrap_or_default();
14622 let full = format!("{}::STORE", class);
14623 if let Some(sub) = self.subs.get(&full).cloned() {
14624 let arg_vals = vec![obj, PerlValue::string(k), val];
14625 return match self.call_sub(
14626 &sub,
14627 arg_vals,
14628 WantarrayCtx::Scalar,
14629 target.line,
14630 ) {
14631 Ok(_) => Ok(PerlValue::UNDEF),
14632 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
14633 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
14634 };
14635 }
14636 }
14637 self.scope.set_hash_element(hash, &k, val)?;
14638 Ok(PerlValue::UNDEF)
14639 }
14640 ExprKind::HashSlice { hash, keys } => {
14641 if keys.is_empty() {
14642 return Err(
14643 PerlError::runtime("assign to empty hash slice", target.line).into(),
14644 );
14645 }
14646 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
14647 {
14648 return Err(PerlError::runtime(
14649 format!(
14650 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
14651 hash, hash
14652 ),
14653 target.line,
14654 )
14655 .into());
14656 }
14657 if self.scope.is_hash_frozen(hash) {
14658 return Err(PerlError::runtime(
14659 format!("Modification of a frozen value: %%{}", hash),
14660 target.line,
14661 )
14662 .into());
14663 }
14664 let mut key_vals = Vec::with_capacity(keys.len());
14665 for key_expr in keys {
14666 let v = if matches!(
14667 key_expr.kind,
14668 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
14669 ) {
14670 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
14671 } else {
14672 self.eval_expr(key_expr)?
14673 };
14674 key_vals.push(v);
14675 }
14676 self.assign_named_hash_slice(hash, key_vals, val, target.line)
14677 }
14678 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
14679 ExprKind::TypeglobExpr(e) => {
14680 let name = self.eval_expr(e)?.to_string();
14681 let synthetic = Expr {
14682 kind: ExprKind::Typeglob(name),
14683 line: target.line,
14684 };
14685 self.assign_value(&synthetic, val)
14686 }
14687 ExprKind::AnonymousListSlice { source, indices } => {
14688 if let ExprKind::Deref {
14689 expr: inner,
14690 kind: Sigil::Array,
14691 } = &source.kind
14692 {
14693 let container = self.eval_arrow_array_base(inner, target.line)?;
14694 let vals = val.to_list();
14695 let n = indices.len().min(vals.len());
14696 for i in 0..n {
14697 let idx = self.eval_expr(&indices[i])?.to_int();
14698 self.assign_arrow_array_deref(
14699 container.clone(),
14700 idx,
14701 vals[i].clone(),
14702 target.line,
14703 )?;
14704 }
14705 return Ok(PerlValue::UNDEF);
14706 }
14707 Err(
14708 PerlError::runtime("assign to list slice: unsupported base", target.line)
14709 .into(),
14710 )
14711 }
14712 ExprKind::ArrowDeref {
14713 expr,
14714 index,
14715 kind: DerefKind::Hash,
14716 } => {
14717 let key = self.eval_expr(index)?.to_string();
14718 let container = self.eval_expr(expr)?;
14719 self.assign_arrow_hash_deref(container, key, val, target.line)
14720 }
14721 ExprKind::ArrowDeref {
14722 expr,
14723 index,
14724 kind: DerefKind::Array,
14725 } => {
14726 let container = self.eval_arrow_array_base(expr, target.line)?;
14727 if let ExprKind::List(indices) = &index.kind {
14728 let vals = val.to_list();
14729 let n = indices.len().min(vals.len());
14730 for i in 0..n {
14731 let idx = self.eval_expr(&indices[i])?.to_int();
14732 self.assign_arrow_array_deref(
14733 container.clone(),
14734 idx,
14735 vals[i].clone(),
14736 target.line,
14737 )?;
14738 }
14739 return Ok(PerlValue::UNDEF);
14740 }
14741 let idx = self.eval_expr(index)?.to_int();
14742 self.assign_arrow_array_deref(container, idx, val, target.line)
14743 }
14744 ExprKind::HashSliceDeref { container, keys } => {
14745 let href = self.eval_expr(container)?;
14746 let mut key_vals = Vec::with_capacity(keys.len());
14747 for key_expr in keys {
14748 key_vals.push(self.eval_expr(key_expr)?);
14749 }
14750 self.assign_hash_slice_deref(href, key_vals, val, target.line)
14751 }
14752 ExprKind::Deref {
14753 expr,
14754 kind: Sigil::Scalar,
14755 } => {
14756 let ref_val = self.eval_expr(expr)?;
14757 self.assign_scalar_ref_deref(ref_val, val, target.line)
14758 }
14759 ExprKind::Deref {
14760 expr,
14761 kind: Sigil::Array,
14762 } => {
14763 let ref_val = self.eval_expr(expr)?;
14764 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
14765 }
14766 ExprKind::Deref {
14767 expr,
14768 kind: Sigil::Hash,
14769 } => {
14770 let ref_val = self.eval_expr(expr)?;
14771 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
14772 }
14773 ExprKind::Deref {
14774 expr,
14775 kind: Sigil::Typeglob,
14776 } => {
14777 let ref_val = self.eval_expr(expr)?;
14778 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
14779 }
14780 ExprKind::Pos(inner) => {
14781 let key = match inner {
14782 None => "_".to_string(),
14783 Some(expr) => match &expr.kind {
14784 ExprKind::ScalarVar(n) => n.clone(),
14785 _ => self.eval_expr(expr)?.to_string(),
14786 },
14787 };
14788 if val.is_undef() {
14789 self.regex_pos.insert(key, None);
14790 } else {
14791 let u = val.to_int().max(0) as usize;
14792 self.regex_pos.insert(key, Some(u));
14793 }
14794 Ok(PerlValue::UNDEF)
14795 }
14796 ExprKind::List(targets) => {
14799 let items = val.to_list();
14800 for (i, t) in targets.iter().enumerate() {
14801 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
14802 self.assign_value(t, v)?;
14803 }
14804 Ok(PerlValue::UNDEF)
14805 }
14806 ExprKind::Assign { target, .. } => self.assign_value(target, val),
14809 _ => Ok(PerlValue::UNDEF),
14810 }
14811 }
14812
14813 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
14815 (name.starts_with('#') && name.len() > 1)
14816 || name.starts_with('^')
14817 || matches!(
14818 name,
14819 "$$" | "0"
14820 | "!"
14821 | "@"
14822 | "/"
14823 | "\\"
14824 | ","
14825 | "."
14826 | "]"
14827 | ";"
14828 | "ARGV"
14829 | "^I"
14830 | "^D"
14831 | "^P"
14832 | "^S"
14833 | "^W"
14834 | "^O"
14835 | "^T"
14836 | "^V"
14837 | "^E"
14838 | "^H"
14839 | "^WARNING_BITS"
14840 | "^GLOBAL_PHASE"
14841 | "^MATCH"
14842 | "^PREMATCH"
14843 | "^POSTMATCH"
14844 | "^LAST_SUBMATCH_RESULT"
14845 | "<"
14846 | ">"
14847 | "("
14848 | ")"
14849 | "?"
14850 | "|"
14851 | "\""
14852 | "+"
14853 | "%"
14854 | "="
14855 | "-"
14856 | ":"
14857 | "*"
14858 | "INC"
14859 )
14860 || crate::english::is_known_alias(name)
14861 }
14862
14863 #[inline]
14868 pub(crate) fn resolved_scalar_storage_name(&self, name: &str) -> String {
14873 self.tree_scalar_storage_name(self.english_scalar_name(name))
14874 }
14875
14876 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
14877 if !self.english_enabled {
14878 return name;
14879 }
14880 if self
14881 .english_lexical_scalars
14882 .iter()
14883 .any(|s| s.contains(name))
14884 {
14885 return name;
14886 }
14887 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
14888 return short;
14889 }
14890 name
14891 }
14892
14893 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
14895 (name.starts_with('#') && name.len() > 1)
14900 || name.starts_with('^')
14901 || matches!(
14902 name,
14903 "0" | "/"
14904 | "\\"
14905 | ","
14906 | ";"
14907 | "\""
14908 | "%"
14909 | "="
14910 | "-"
14911 | ":"
14912 | "*"
14913 | "INC"
14914 | "^I"
14915 | "^D"
14916 | "^P"
14917 | "^W"
14918 | "^H"
14919 | "^WARNING_BITS"
14920 | "$$"
14921 | "]"
14922 | "^S"
14923 | "ARGV"
14924 | "|"
14925 | "+"
14926 | "?"
14927 | "!"
14928 | "@"
14929 | "."
14930 )
14931 || crate::english::is_known_alias(name)
14932 }
14933
14934 pub(crate) fn get_special_var(&self, name: &str) -> PerlValue {
14935 let name = if !crate::compat_mode() {
14937 match name {
14938 "NR" => ".",
14939 "RS" => "/",
14940 "OFS" => ",",
14941 "ORS" => "\\",
14942 "NF" => {
14943 let len = self.scope.array_len("F");
14944 return PerlValue::integer(len as i64);
14945 }
14946 _ => self.english_scalar_name(name),
14947 }
14948 } else {
14949 self.english_scalar_name(name)
14950 };
14951 match name {
14952 "$$" => PerlValue::integer(std::process::id() as i64),
14953 "_" => self.scope.get_scalar("_"),
14954 "^MATCH" => PerlValue::string(self.last_match.clone()),
14955 "^PREMATCH" => PerlValue::string(self.prematch.clone()),
14956 "^POSTMATCH" => PerlValue::string(self.postmatch.clone()),
14957 "^LAST_SUBMATCH_RESULT" => PerlValue::string(self.last_paren_match.clone()),
14958 "0" => PerlValue::string(self.program_name.clone()),
14959 "!" => PerlValue::errno_dual(self.errno_code, self.errno.clone()),
14960 "@" => {
14961 if let Some(ref v) = self.eval_error_value {
14962 v.clone()
14963 } else {
14964 PerlValue::errno_dual(self.eval_error_code, self.eval_error.clone())
14965 }
14966 }
14967 "/" => match &self.irs {
14968 Some(s) => PerlValue::string(s.clone()),
14969 None => PerlValue::UNDEF,
14970 },
14971 "\\" => PerlValue::string(self.ors.clone()),
14972 "," => PerlValue::string(self.ofs.clone()),
14973 "." => {
14974 if self.last_readline_handle.is_empty() {
14976 if self.line_number == 0 {
14977 PerlValue::UNDEF
14978 } else {
14979 PerlValue::integer(self.line_number)
14980 }
14981 } else {
14982 PerlValue::integer(
14983 *self
14984 .handle_line_numbers
14985 .get(&self.last_readline_handle)
14986 .unwrap_or(&0),
14987 )
14988 }
14989 }
14990 "]" => PerlValue::float(perl_bracket_version()),
14991 ";" => PerlValue::string(self.subscript_sep.clone()),
14992 "ARGV" => PerlValue::string(self.argv_current_file.clone()),
14993 "^I" => PerlValue::string(self.inplace_edit.clone()),
14994 "^D" => PerlValue::integer(self.debug_flags),
14995 "^P" => PerlValue::integer(self.perl_debug_flags),
14996 "^S" => PerlValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
14997 "^W" => PerlValue::integer(if self.warnings { 1 } else { 0 }),
14998 "^O" => PerlValue::string(perl_osname()),
14999 "^T" => PerlValue::integer(self.script_start_time),
15000 "^V" => PerlValue::string(perl_version_v_string()),
15001 "^E" => PerlValue::string(extended_os_error_string()),
15002 "^H" => PerlValue::integer(self.compile_hints),
15003 "^WARNING_BITS" => PerlValue::integer(self.warning_bits),
15004 "^GLOBAL_PHASE" => PerlValue::string(self.global_phase.clone()),
15005 "<" | ">" => PerlValue::integer(unix_id_for_special(name)),
15006 "(" | ")" => PerlValue::string(unix_group_list_for_special(name)),
15007 "?" => PerlValue::integer(self.child_exit_status),
15008 "|" => PerlValue::integer(if self.output_autoflush { 1 } else { 0 }),
15009 "\"" => PerlValue::string(self.list_separator.clone()),
15010 "+" => PerlValue::string(self.last_paren_match.clone()),
15011 "%" => PerlValue::integer(self.format_page_number),
15012 "=" => PerlValue::integer(self.format_lines_per_page),
15013 "-" => PerlValue::integer(self.format_lines_left),
15014 ":" => PerlValue::string(self.format_line_break_chars.clone()),
15015 "*" => PerlValue::integer(if self.multiline_match { 1 } else { 0 }),
15016 "^" => PerlValue::string(self.format_top_name.clone()),
15017 "INC" => PerlValue::integer(self.inc_hook_index),
15018 "^A" => PerlValue::string(self.accumulator_format.clone()),
15019 "^C" => PerlValue::integer(if self.sigint_pending_caret.replace(false) {
15020 1
15021 } else {
15022 0
15023 }),
15024 "^F" => PerlValue::integer(self.max_system_fd),
15025 "^L" => PerlValue::string(self.formfeed_string.clone()),
15026 "^M" => PerlValue::string(self.emergency_memory.clone()),
15027 "^N" => PerlValue::string(self.last_subpattern_name.clone()),
15028 "^X" => PerlValue::string(self.executable_path.clone()),
15029 "^TAINT" | "^TAINTED" => PerlValue::integer(0),
15031 "^UNICODE" => PerlValue::integer(if self.utf8_pragma { 1 } else { 0 }),
15032 "^OPEN" => PerlValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
15033 "^UTF8LOCALE" => PerlValue::integer(0),
15034 "^UTF8CACHE" => PerlValue::integer(-1),
15035 _ if name.starts_with('^') && name.len() > 1 => self
15036 .special_caret_scalars
15037 .get(name)
15038 .cloned()
15039 .unwrap_or(PerlValue::UNDEF),
15040 _ if name.starts_with('#') && name.len() > 1 => {
15041 let arr = &name[1..];
15042 let aname = self.stash_array_name_for_package(arr);
15043 let len = self.scope.array_len(&aname);
15044 PerlValue::integer(len as i64 - 1)
15045 }
15046 _ => self.scope.get_scalar(name),
15047 }
15048 }
15049
15050 pub(crate) fn set_special_var(&mut self, name: &str, val: &PerlValue) -> Result<(), PerlError> {
15051 let name = self.english_scalar_name(name);
15052 match name {
15053 "!" => {
15054 let code = val.to_int() as i32;
15055 self.errno_code = code;
15056 self.errno = if code == 0 {
15057 String::new()
15058 } else {
15059 std::io::Error::from_raw_os_error(code).to_string()
15060 };
15061 }
15062 "@" => {
15063 if let Some((code, msg)) = val.errno_dual_parts() {
15064 self.eval_error_code = code;
15065 self.eval_error = msg;
15066 } else {
15067 self.eval_error = val.to_string();
15068 let mut code = val.to_int() as i32;
15069 if code == 0 && !self.eval_error.is_empty() {
15070 code = 1;
15071 }
15072 self.eval_error_code = code;
15073 }
15074 }
15075 "." => {
15076 let n = val.to_int();
15079 if self.last_readline_handle.is_empty() {
15080 self.line_number = n;
15081 } else {
15082 self.handle_line_numbers
15083 .insert(self.last_readline_handle.clone(), n);
15084 }
15085 }
15086 "0" => self.program_name = val.to_string(),
15087 "/" => {
15088 self.irs = if val.is_undef() {
15089 None
15090 } else {
15091 Some(val.to_string())
15092 }
15093 }
15094 "\\" => self.ors = val.to_string(),
15095 "," => self.ofs = val.to_string(),
15096 ";" => self.subscript_sep = val.to_string(),
15097 "\"" => self.list_separator = val.to_string(),
15098 "%" => self.format_page_number = val.to_int(),
15099 "=" => self.format_lines_per_page = val.to_int(),
15100 "-" => self.format_lines_left = val.to_int(),
15101 ":" => self.format_line_break_chars = val.to_string(),
15102 "*" => self.multiline_match = val.to_int() != 0,
15103 "^" => self.format_top_name = val.to_string(),
15104 "INC" => self.inc_hook_index = val.to_int(),
15105 "^A" => self.accumulator_format = val.to_string(),
15106 "^F" => self.max_system_fd = val.to_int(),
15107 "^L" => self.formfeed_string = val.to_string(),
15108 "^M" => self.emergency_memory = val.to_string(),
15109 "^I" => self.inplace_edit = val.to_string(),
15110 "^D" => self.debug_flags = val.to_int(),
15111 "^P" => self.perl_debug_flags = val.to_int(),
15112 "^W" => self.warnings = val.to_int() != 0,
15113 "^H" => self.compile_hints = val.to_int(),
15114 "^WARNING_BITS" => self.warning_bits = val.to_int(),
15115 "|" => {
15116 self.output_autoflush = val.to_int() != 0;
15117 if self.output_autoflush {
15118 let _ = io::stdout().flush();
15119 }
15120 }
15121 "$$"
15123 | "]"
15124 | "^S"
15125 | "ARGV"
15126 | "?"
15127 | "^O"
15128 | "^T"
15129 | "^V"
15130 | "^E"
15131 | "^GLOBAL_PHASE"
15132 | "^MATCH"
15133 | "^PREMATCH"
15134 | "^POSTMATCH"
15135 | "^LAST_SUBMATCH_RESULT"
15136 | "^C"
15137 | "^N"
15138 | "^X"
15139 | "^TAINT"
15140 | "^TAINTED"
15141 | "^UNICODE"
15142 | "^UTF8LOCALE"
15143 | "^UTF8CACHE"
15144 | "+"
15145 | "<"
15146 | ">"
15147 | "("
15148 | ")" => {}
15149 _ if name.starts_with('^') && name.len() > 1 => {
15150 self.special_caret_scalars
15151 .insert(name.to_string(), val.clone());
15152 }
15153 _ if name.starts_with('#') && name.len() > 1 => {
15154 let arr = &name[1..];
15157 let aname = self.stash_array_name_for_package(arr);
15158 let new_last = val.to_int();
15159 let new_len = if new_last < 0 {
15160 0
15161 } else {
15162 (new_last as usize) + 1
15163 };
15164 let mut current = self.scope.get_array(&aname);
15165 current.resize(new_len, PerlValue::UNDEF);
15166 self.scope.set_array(&aname, current)?;
15167 }
15168 _ => self.scope.set_scalar(name, val.clone())?,
15169 }
15170 Ok(())
15171 }
15172
15173 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
15174 match &expr.kind {
15175 ExprKind::ArrayVar(name) => Ok(name.clone()),
15176 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(PerlError::runtime("Expected array", expr.line).into()),
15178 }
15179 }
15180
15181 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
15183 match &expr.kind {
15184 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
15185 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
15186 _ => expr,
15187 }
15188 }
15189
15190 fn try_eval_array_deref_container(
15192 &mut self,
15193 expr: &Expr,
15194 ) -> Result<Option<PerlValue>, FlowOrError> {
15195 let e = Self::peel_array_builtin_operand(expr);
15196 if let ExprKind::Deref {
15197 expr: inner,
15198 kind: Sigil::Array,
15199 } = &e.kind
15200 {
15201 return Ok(Some(self.eval_or_autoviv_array_ref(inner)?));
15202 }
15203 Ok(None)
15204 }
15205
15206 fn eval_or_autoviv_array_ref(&mut self, inner: &Expr) -> Result<PerlValue, FlowOrError> {
15210 let line = inner.line;
15211 let val = self.eval_expr(inner)?;
15212 if !val.is_undef() {
15213 return Ok(val);
15214 }
15215 let new_ref = PerlValue::array_ref(Arc::new(RwLock::new(Vec::new())));
15216 match &inner.kind {
15217 ExprKind::ScalarVar(name) => {
15218 self.scope
15219 .set_scalar(name, new_ref.clone())
15220 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15221 Ok(new_ref)
15222 }
15223 ExprKind::HashElement { hash, key } => {
15224 let k = self.eval_expr(key)?.to_string();
15225 self.scope
15226 .set_hash_element(hash, &k, new_ref.clone())
15227 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15228 Ok(new_ref)
15229 }
15230 ExprKind::ArrayElement { array, index } => {
15231 let i = self.eval_expr(index)?.to_int();
15232 self.scope
15233 .set_array_element(array, i, new_ref.clone())
15234 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15235 Ok(new_ref)
15236 }
15237 _ => Ok(val),
15238 }
15239 }
15240
15241 fn current_package(&self) -> String {
15243 let s = self.scope.get_scalar("__PACKAGE__").to_string();
15244 if s.is_empty() {
15245 "main".to_string()
15246 } else {
15247 s
15248 }
15249 }
15250
15251 pub(crate) fn package_version_scalar(
15254 &mut self,
15255 package: &str,
15256 ) -> PerlResult<Option<PerlValue>> {
15257 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
15258 let _ = self
15259 .scope
15260 .set_scalar("__PACKAGE__", PerlValue::string(package.to_string()));
15261 let ver = self.get_special_var("VERSION");
15262 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
15263 Ok(if ver.is_undef() { None } else { Some(ver) })
15264 }
15265
15266 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<PerlSub>> {
15268 let root = if start_package.is_empty() {
15269 "main"
15270 } else {
15271 start_package
15272 };
15273 for pkg in self.mro_linearize(root) {
15274 let key = if pkg == "main" {
15275 "AUTOLOAD".to_string()
15276 } else {
15277 format!("{}::AUTOLOAD", pkg)
15278 };
15279 if let Some(s) = self.subs.get(&key) {
15280 return Some(s.clone());
15281 }
15282 }
15283 None
15284 }
15285
15286 pub(crate) fn try_autoload_call(
15291 &mut self,
15292 missing_name: &str,
15293 args: Vec<PerlValue>,
15294 line: usize,
15295 want: WantarrayCtx,
15296 method_invocant_class: Option<&str>,
15297 ) -> Option<ExecResult> {
15298 let pkg = self.current_package();
15299 let full = if missing_name.contains("::") {
15300 missing_name.to_string()
15301 } else {
15302 format!("{}::{}", pkg, missing_name)
15303 };
15304 let start_pkg = method_invocant_class.unwrap_or_else(|| {
15305 full.rsplit_once("::")
15306 .map(|(p, _)| p)
15307 .filter(|p| !p.is_empty())
15308 .unwrap_or("main")
15309 });
15310 let sub = self.resolve_autoload_sub(start_pkg)?;
15311 if let Err(e) = self
15312 .scope
15313 .set_scalar("AUTOLOAD", PerlValue::string(full.clone()))
15314 {
15315 return Some(Err(e.into()));
15316 }
15317 Some(self.call_sub(&sub, args, want, line))
15318 }
15319
15320 pub(crate) fn with_topic_default_args(&self, args: Vec<PerlValue>) -> Vec<PerlValue> {
15321 if args.is_empty() {
15322 vec![self.scope.get_scalar("_").clone()]
15323 } else {
15324 args
15325 }
15326 }
15327
15328 pub(crate) fn dispatch_indirect_call(
15331 &mut self,
15332 target: PerlValue,
15333 arg_vals: Vec<PerlValue>,
15334 want: WantarrayCtx,
15335 line: usize,
15336 ) -> ExecResult {
15337 if let Some(sub) = target.as_code_ref() {
15338 return self.call_sub(&sub, arg_vals, want, line);
15339 }
15340 if let Some(name) = target.as_str() {
15341 return self.call_named_sub(&name, arg_vals, line, want);
15342 }
15343 Err(PerlError::runtime("Can't use non-code reference as a subroutine", line).into())
15344 }
15345
15346 pub(crate) fn call_bare_list_builtin(
15351 &mut self,
15352 name: &str,
15353 args: Vec<PerlValue>,
15354 line: usize,
15355 want: WantarrayCtx,
15356 ) -> ExecResult {
15357 let canonical = match name {
15358 "distinct" | "uq" => "uniq",
15359 "shuf" => "shuffle",
15360 "chk" => "chunked",
15361 "win" => "windowed",
15362 "zp" => "zip",
15363 "fst" => "first",
15364 "rd" => "reduce",
15365 "med" => "median",
15366 "std" => "stddev",
15367 "var" => "variance",
15368 other => other,
15369 };
15370 match crate::list_builtins::dispatch_by_name(self, canonical, &args, want) {
15375 Some(r) => r,
15376 None => Err(PerlError::runtime(
15377 format!("internal: not a stryke list builtin: {name}"),
15378 line,
15379 )
15380 .into()),
15381 }
15382 }
15383
15384 fn call_named_sub(
15385 &mut self,
15386 name: &str,
15387 args: Vec<PerlValue>,
15388 line: usize,
15389 want: WantarrayCtx,
15390 ) -> ExecResult {
15391 if let Some(sub) = self.resolve_sub_by_name(name) {
15392 let args = self.with_topic_default_args(args);
15393 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
15397 return self.call_sub_with_package(&sub, args, want, line, pkg);
15398 }
15399 match name {
15400 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
15401 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
15402 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
15403 | "none" | "notall" | "first" | "fst" | "reduce" | "rd" | "reductions" | "sum"
15404 | "sum0" | "product" | "min" | "max" | "minstr" | "maxstr" | "mean" | "median"
15405 | "med" | "mode" | "stddev" | "std" | "variance" | "var" | "pairs" | "unpairs"
15406 | "pairkeys" | "pairvalues" | "pairgrep" | "pairmap" | "pairfirst" | "blessed"
15407 | "refaddr" | "reftype" | "weaken" | "unweaken" | "isweak" | "set_subname"
15408 | "subname" | "unicode_to_native" => {
15409 self.call_bare_list_builtin(name, args, line, want)
15410 }
15411 "deque" => {
15412 if !args.is_empty() {
15413 return Err(PerlError::runtime("deque() takes no arguments", line).into());
15414 }
15415 Ok(PerlValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
15416 }
15417 "defer__internal" => {
15418 if args.len() != 1 {
15419 return Err(PerlError::runtime(
15420 "defer__internal expects one coderef argument",
15421 line,
15422 )
15423 .into());
15424 }
15425 self.scope.push_defer(args[0].clone());
15426 Ok(PerlValue::UNDEF)
15427 }
15428 "heap" => {
15429 if args.len() != 1 {
15430 return Err(
15431 PerlError::runtime("heap() expects one comparator sub", line).into(),
15432 );
15433 }
15434 if let Some(sub) = args[0].as_code_ref() {
15435 Ok(PerlValue::heap(Arc::new(Mutex::new(PerlHeap {
15436 items: Vec::new(),
15437 cmp: Arc::clone(&sub),
15438 }))))
15439 } else {
15440 Err(PerlError::runtime("heap() requires a code reference", line).into())
15441 }
15442 }
15443 "pipeline" => {
15444 let mut items = Vec::new();
15445 for v in args {
15446 if let Some(a) = v.as_array_vec() {
15447 items.extend(a);
15448 } else {
15449 items.push(v);
15450 }
15451 }
15452 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15453 source: items,
15454 ops: Vec::new(),
15455 has_scalar_terminal: false,
15456 par_stream: false,
15457 streaming: false,
15458 streaming_workers: 0,
15459 streaming_buffer: 256,
15460 }))))
15461 }
15462 "par_pipeline" => {
15463 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
15464 return crate::par_pipeline::run_par_pipeline(self, &args, line)
15465 .map_err(Into::into);
15466 }
15467 Ok(self.builtin_par_pipeline_stream(&args, line)?)
15468 }
15469 "par_pipeline_stream" => {
15470 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
15471 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
15472 .map_err(Into::into);
15473 }
15474 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
15475 }
15476 "ppool" => {
15477 if args.len() != 1 {
15478 return Err(PerlError::runtime(
15479 "ppool() expects one argument (worker count)",
15480 line,
15481 )
15482 .into());
15483 }
15484 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
15485 }
15486 "barrier" => {
15487 if args.len() != 1 {
15488 return Err(PerlError::runtime(
15489 "barrier() expects one argument (party count)",
15490 line,
15491 )
15492 .into());
15493 }
15494 let n = args[0].to_int().max(1) as usize;
15495 Ok(PerlValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
15496 }
15497 "cluster" => {
15498 let items = if args.len() == 1 {
15499 args[0].to_list()
15500 } else {
15501 args.to_vec()
15502 };
15503 let c = RemoteCluster::from_list_args(&items)
15504 .map_err(|msg| PerlError::runtime(msg, line))?;
15505 Ok(PerlValue::remote_cluster(Arc::new(c)))
15506 }
15507 _ => {
15508 if let Some(method_name) = name.strip_prefix("static::") {
15510 let self_val = self.scope.get_scalar("self");
15511 if let Some(c) = self_val.as_class_inst() {
15512 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
15513 if let Some(ref body) = m.body {
15514 let params = m.params.clone();
15515 let mut call_args = vec![self_val.clone()];
15516 call_args.extend(args);
15517 return match self.call_class_method(body, ¶ms, call_args, line)
15518 {
15519 Ok(v) => Ok(v),
15520 Err(FlowOrError::Error(e)) => Err(e.into()),
15521 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15522 Err(e) => Err(e),
15523 };
15524 }
15525 }
15526 return Err(PerlError::runtime(
15527 format!(
15528 "static::{} — method not found on class {}",
15529 method_name, c.def.name
15530 ),
15531 line,
15532 )
15533 .into());
15534 }
15535 return Err(PerlError::runtime(
15536 "static:: can only be used inside a class method",
15537 line,
15538 )
15539 .into());
15540 }
15541 if let Some(def) = self.struct_defs.get(name).cloned() {
15543 return self.struct_construct(&def, args, line);
15544 }
15545 if let Some(def) = self.class_defs.get(name).cloned() {
15547 return self.class_construct(&def, args, line);
15548 }
15549 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
15551 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
15552 return self.enum_construct(&def, variant_name, args, line);
15553 }
15554 }
15555 if let Some((class_name, member_name)) = name.rsplit_once("::") {
15557 if let Some(def) = self.class_defs.get(class_name).cloned() {
15558 if let Some(m) = def.method(member_name) {
15560 if m.is_static {
15561 if let Some(ref body) = m.body {
15562 let params = m.params.clone();
15563 return match self.call_static_class_method(
15564 body,
15565 ¶ms,
15566 args.clone(),
15567 line,
15568 ) {
15569 Ok(v) => Ok(v),
15570 Err(FlowOrError::Error(e)) => Err(e.into()),
15571 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15572 Err(e) => Err(e),
15573 };
15574 }
15575 }
15576 }
15577 if def.static_fields.iter().any(|sf| sf.name == member_name) {
15579 let key = format!("{}::{}", class_name, member_name);
15580 match args.len() {
15581 0 => {
15582 let val = self.scope.get_scalar(&key);
15583 return Ok(val);
15584 }
15585 1 => {
15586 let _ = self.scope.set_scalar(&key, args[0].clone());
15587 return Ok(args[0].clone());
15588 }
15589 _ => {
15590 return Err(PerlError::runtime(
15591 format!(
15592 "static field `{}::{}` takes 0 or 1 arguments",
15593 class_name, member_name
15594 ),
15595 line,
15596 )
15597 .into());
15598 }
15599 }
15600 }
15601 }
15602 }
15603 let args = self.with_topic_default_args(args);
15604 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
15605 return r;
15606 }
15607 Err(PerlError::runtime(self.undefined_subroutine_call_message(name), line).into())
15608 }
15609 }
15610 }
15611
15612 pub(crate) fn struct_construct(
15614 &mut self,
15615 def: &Arc<StructDef>,
15616 args: Vec<PerlValue>,
15617 line: usize,
15618 ) -> ExecResult {
15619 let is_named = args.len() >= 2
15622 && args.len().is_multiple_of(2)
15623 && args.iter().step_by(2).all(|v| {
15624 let s = v.to_string();
15625 def.field_index(&s).is_some()
15626 });
15627
15628 let provided = if is_named {
15629 let mut pairs = Vec::new();
15631 let mut i = 0;
15632 while i + 1 < args.len() {
15633 let k = args[i].to_string();
15634 let v = args[i + 1].clone();
15635 pairs.push((k, v));
15636 i += 2;
15637 }
15638 pairs
15639 } else {
15640 def.fields
15642 .iter()
15643 .zip(args.iter())
15644 .map(|(f, v)| (f.name.clone(), v.clone()))
15645 .collect()
15646 };
15647
15648 let mut defaults = Vec::with_capacity(def.fields.len());
15650 for field in &def.fields {
15651 if let Some(ref expr) = field.default {
15652 let val = self.eval_expr(expr)?;
15653 defaults.push(Some(val));
15654 } else {
15655 defaults.push(None);
15656 }
15657 }
15658
15659 Ok(crate::native_data::struct_new_with_defaults(
15660 def, &provided, &defaults, line,
15661 )?)
15662 }
15663
15664 pub(crate) fn class_construct(
15666 &mut self,
15667 def: &Arc<ClassDef>,
15668 args: Vec<PerlValue>,
15669 _line: usize,
15670 ) -> ExecResult {
15671 use crate::value::ClassInstance;
15672
15673 if def.is_abstract {
15675 return Err(PerlError::runtime(
15676 format!("cannot instantiate abstract class `{}`", def.name),
15677 _line,
15678 )
15679 .into());
15680 }
15681
15682 let all_fields = self.collect_class_fields(def);
15684
15685 let is_named = args.len() >= 2
15687 && args.len().is_multiple_of(2)
15688 && args.iter().step_by(2).all(|v| {
15689 let s = v.to_string();
15690 all_fields.iter().any(|(name, _, _)| name == &s)
15691 });
15692
15693 let provided: Vec<(String, PerlValue)> = if is_named {
15694 let mut pairs = Vec::new();
15695 let mut i = 0;
15696 while i + 1 < args.len() {
15697 let k = args[i].to_string();
15698 let v = args[i + 1].clone();
15699 pairs.push((k, v));
15700 i += 2;
15701 }
15702 pairs
15703 } else {
15704 all_fields
15705 .iter()
15706 .zip(args.iter())
15707 .map(|((name, _, _), v)| (name.clone(), v.clone()))
15708 .collect()
15709 };
15710
15711 let mut values = Vec::with_capacity(all_fields.len());
15713 for (name, default, ty) in &all_fields {
15714 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
15715 val.clone()
15716 } else if let Some(ref expr) = default {
15717 self.eval_expr(expr)?
15718 } else {
15719 PerlValue::UNDEF
15720 };
15721 ty.check_value(&val).map_err(|msg| {
15722 PerlError::type_error(
15723 format!("class {} field `{}`: {}", def.name, name, msg),
15724 _line,
15725 )
15726 })?;
15727 values.push(val);
15728 }
15729
15730 let isa_chain = self.mro_linearize(&def.name);
15732 let instance = PerlValue::class_inst(Arc::new(ClassInstance::new_with_isa(
15733 Arc::clone(def),
15734 values,
15735 isa_chain,
15736 )));
15737
15738 let build_chain = self.collect_build_chain(def);
15740 if !build_chain.is_empty() {
15741 for (body, params) in &build_chain {
15742 let call_args = vec![instance.clone()];
15743 match self.call_class_method(body, params, call_args, _line) {
15744 Ok(_) => {}
15745 Err(FlowOrError::Flow(Flow::Return(_))) => {}
15746 Err(e) => return Err(e),
15747 }
15748 }
15749 }
15750
15751 Ok(instance)
15752 }
15753
15754 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
15756 let mut chain = Vec::new();
15757 for parent_name in &def.extends {
15759 if let Some(parent_def) = self.class_defs.get(parent_name) {
15760 chain.extend(self.collect_build_chain(parent_def));
15761 }
15762 }
15763 if let Some(m) = def.method("BUILD") {
15765 if let Some(ref body) = m.body {
15766 chain.push((body.clone(), m.params.clone()));
15767 }
15768 }
15769 chain
15770 }
15771
15772 pub(crate) fn deep_to_hash_value(&self, v: &PerlValue) -> PerlValue {
15778 if let Some(c) = v.as_class_inst() {
15780 let all_fields = self.collect_class_fields_full(&c.def);
15781 let values = c.get_values();
15782 let mut map = IndexMap::new();
15783 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
15784 if let Some(elem) = values.get(i) {
15785 map.insert(name.clone(), self.deep_to_hash_value(elem));
15786 }
15787 }
15788 return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
15789 }
15790 if let Some(s) = v.as_struct_inst() {
15792 let values = s.get_values();
15793 let mut map = IndexMap::new();
15794 for (i, field) in s.def.fields.iter().enumerate() {
15795 if let Some(elem) = values.get(i) {
15796 map.insert(field.name.clone(), self.deep_to_hash_value(elem));
15797 }
15798 }
15799 return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
15800 }
15801 if let Some(r) = v.as_hash_ref() {
15803 let inner = r.read().clone();
15804 let mut map = IndexMap::new();
15805 for (k, val) in inner.into_iter() {
15806 map.insert(k, self.deep_to_hash_value(&val));
15807 }
15808 return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
15809 }
15810 if let Some(r) = v.as_array_ref() {
15812 let inner = r.read().clone();
15813 let out: Vec<PerlValue> = inner.iter().map(|e| self.deep_to_hash_value(e)).collect();
15814 return PerlValue::array_ref(Arc::new(RwLock::new(out)));
15815 }
15816 v.clone()
15820 }
15821
15822 fn collect_class_fields(
15825 &self,
15826 def: &ClassDef,
15827 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
15828 self.collect_class_fields_full(def)
15829 .into_iter()
15830 .map(|(name, default, ty, _, _)| (name, default, ty))
15831 .collect()
15832 }
15833
15834 fn collect_class_fields_full(
15836 &self,
15837 def: &ClassDef,
15838 ) -> Vec<(
15839 String,
15840 Option<Expr>,
15841 crate::ast::PerlTypeName,
15842 crate::ast::Visibility,
15843 String,
15844 )> {
15845 let mut all_fields = Vec::new();
15846
15847 for parent_name in &def.extends {
15848 if let Some(parent_def) = self.class_defs.get(parent_name) {
15849 let parent_fields = self.collect_class_fields_full(parent_def);
15850 all_fields.extend(parent_fields);
15851 }
15852 }
15853
15854 for field in &def.fields {
15855 all_fields.push((
15856 field.name.clone(),
15857 field.default.clone(),
15858 field.ty.clone(),
15859 field.visibility,
15860 def.name.clone(),
15861 ));
15862 }
15863
15864 all_fields
15865 }
15866
15867 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
15869 for parent_name in &def.extends {
15871 if let Some(parent_def) = self.class_defs.get(parent_name) {
15872 self.collect_class_method_names(parent_def, names);
15873 }
15874 }
15875 for m in &def.methods {
15877 if !m.is_static && !names.contains(&m.name) {
15878 names.push(m.name.clone());
15879 }
15880 }
15881 }
15882
15883 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
15885 let mut chain = Vec::new();
15886 if let Some(m) = def.method("DESTROY") {
15888 if let Some(ref body) = m.body {
15889 chain.push((body.clone(), m.params.clone()));
15890 }
15891 }
15892 for parent_name in &def.extends {
15894 if let Some(parent_def) = self.class_defs.get(parent_name) {
15895 chain.extend(self.collect_destroy_chain(parent_def));
15896 }
15897 }
15898 chain
15899 }
15900
15901 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
15903 if let Some(def) = self.class_defs.get(child) {
15904 for parent in &def.extends {
15905 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
15906 return true;
15907 }
15908 }
15909 }
15910 false
15911 }
15912
15913 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
15915 if let Some(m) = def.method(method) {
15917 return Some((m.clone(), def.name.clone()));
15918 }
15919 for parent_name in &def.extends {
15921 if let Some(parent_def) = self.class_defs.get(parent_name) {
15922 if let Some(result) = self.find_class_method(parent_def, method) {
15923 return Some(result);
15924 }
15925 }
15926 }
15927 None
15928 }
15929
15930 pub(crate) fn enum_construct(
15932 &mut self,
15933 def: &Arc<EnumDef>,
15934 variant_name: &str,
15935 args: Vec<PerlValue>,
15936 line: usize,
15937 ) -> ExecResult {
15938 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
15939 FlowOrError::Error(PerlError::runtime(
15940 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
15941 line,
15942 ))
15943 })?;
15944 let variant = &def.variants[variant_idx];
15945 let data = if variant.ty.is_some() {
15946 if args.is_empty() {
15947 return Err(PerlError::runtime(
15948 format!(
15949 "enum variant `{}::{}` requires data",
15950 def.name, variant_name
15951 ),
15952 line,
15953 )
15954 .into());
15955 }
15956 if args.len() == 1 {
15957 args.into_iter().next().unwrap()
15958 } else {
15959 PerlValue::array(args)
15960 }
15961 } else {
15962 if !args.is_empty() {
15963 return Err(PerlError::runtime(
15964 format!(
15965 "enum variant `{}::{}` does not take data",
15966 def.name, variant_name
15967 ),
15968 line,
15969 )
15970 .into());
15971 }
15972 PerlValue::UNDEF
15973 };
15974 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
15975 Ok(PerlValue::enum_inst(Arc::new(inst)))
15976 }
15977
15978 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
15980 matches!(name, "STDIN" | "STDOUT" | "STDERR")
15981 || self.input_handles.contains_key(name)
15982 || self.output_handles.contains_key(name)
15983 || self.io_file_slots.contains_key(name)
15984 || self.pipe_children.contains_key(name)
15985 }
15986
15987 pub(crate) fn io_handle_method(
15989 &mut self,
15990 name: &str,
15991 method: &str,
15992 args: &[PerlValue],
15993 line: usize,
15994 ) -> PerlResult<PerlValue> {
15995 match method {
15996 "print" => self.io_handle_print(name, args, false, line),
15997 "say" => self.io_handle_print(name, args, true, line),
15998 "printf" => self.io_handle_printf(name, args, line),
15999 "getline" | "readline" => {
16000 if !args.is_empty() {
16001 return Err(PerlError::runtime(
16002 format!("{}: too many arguments", method),
16003 line,
16004 ));
16005 }
16006 self.readline_builtin_execute(Some(name))
16007 }
16008 "close" => {
16009 if !args.is_empty() {
16010 return Err(PerlError::runtime("close: too many arguments", line));
16011 }
16012 self.close_builtin_execute(name.to_string())
16013 }
16014 "eof" => {
16015 if !args.is_empty() {
16016 return Err(PerlError::runtime("eof: too many arguments", line));
16017 }
16018 let at_eof = !self.has_input_handle(name);
16019 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
16020 }
16021 "getc" => {
16022 if !args.is_empty() {
16023 return Err(PerlError::runtime("getc: too many arguments", line));
16024 }
16025 match crate::builtins::try_builtin(
16026 self,
16027 "getc",
16028 &[PerlValue::string(name.to_string())],
16029 line,
16030 ) {
16031 Some(r) => r,
16032 None => Err(PerlError::runtime("getc: not available", line)),
16033 }
16034 }
16035 "binmode" => match crate::builtins::try_builtin(
16036 self,
16037 "binmode",
16038 &[PerlValue::string(name.to_string())],
16039 line,
16040 ) {
16041 Some(r) => r,
16042 None => Err(PerlError::runtime("binmode: not available", line)),
16043 },
16044 "fileno" => match crate::builtins::try_builtin(
16045 self,
16046 "fileno",
16047 &[PerlValue::string(name.to_string())],
16048 line,
16049 ) {
16050 Some(r) => r,
16051 None => Err(PerlError::runtime("fileno: not available", line)),
16052 },
16053 "flush" => {
16054 if !args.is_empty() {
16055 return Err(PerlError::runtime("flush: too many arguments", line));
16056 }
16057 self.io_handle_flush(name, line)
16058 }
16059 _ => Err(PerlError::runtime(
16060 format!("Unknown method for filehandle: {}", method),
16061 line,
16062 )),
16063 }
16064 }
16065
16066 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> PerlResult<PerlValue> {
16067 match handle_name {
16068 "STDOUT" => {
16069 let _ = IoWrite::flush(&mut io::stdout());
16070 }
16071 "STDERR" => {
16072 let _ = IoWrite::flush(&mut io::stderr());
16073 }
16074 name => {
16075 if let Some(writer) = self.output_handles.get_mut(name) {
16076 let _ = IoWrite::flush(&mut *writer);
16077 } else {
16078 return Err(PerlError::runtime(
16079 format!("flush on unopened filehandle {}", name),
16080 line,
16081 ));
16082 }
16083 }
16084 }
16085 Ok(PerlValue::integer(1))
16086 }
16087
16088 fn io_handle_print(
16089 &mut self,
16090 handle_name: &str,
16091 args: &[PerlValue],
16092 newline: bool,
16093 line: usize,
16094 ) -> PerlResult<PerlValue> {
16095 if newline && (self.feature_bits & FEAT_SAY) == 0 {
16096 return Err(PerlError::runtime(
16097 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
16098 line,
16099 ));
16100 }
16101 let mut output = String::new();
16102 if args.is_empty() {
16103 output.push_str(&self.scope.get_scalar("_").to_string());
16105 } else {
16106 for (i, val) in args.iter().enumerate() {
16107 if i > 0 && !self.ofs.is_empty() {
16108 output.push_str(&self.ofs);
16109 }
16110 output.push_str(&val.to_string());
16111 }
16112 }
16113 if newline {
16114 output.push('\n');
16115 }
16116 output.push_str(&self.ors);
16117
16118 self.write_formatted_print(handle_name, &output, line)?;
16119 Ok(PerlValue::integer(1))
16120 }
16121
16122 pub(crate) fn write_formatted_print(
16125 &mut self,
16126 handle_name: &str,
16127 output: &str,
16128 line: usize,
16129 ) -> PerlResult<()> {
16130 match handle_name {
16131 "STDOUT" => {
16132 if !self.suppress_stdout {
16133 print!("{}", output);
16134 if self.output_autoflush {
16135 let _ = io::stdout().flush();
16136 }
16137 }
16138 }
16139 "STDERR" => {
16140 eprint!("{}", output);
16141 let _ = io::stderr().flush();
16142 }
16143 name => {
16144 if let Some(writer) = self.output_handles.get_mut(name) {
16145 let _ = writer.write_all(output.as_bytes());
16146 if self.output_autoflush {
16147 let _ = writer.flush();
16148 }
16149 } else {
16150 return Err(PerlError::runtime(
16151 format!("print on unopened filehandle {}", name),
16152 line,
16153 ));
16154 }
16155 }
16156 }
16157 Ok(())
16158 }
16159
16160 fn io_handle_printf(
16161 &mut self,
16162 handle_name: &str,
16163 args: &[PerlValue],
16164 line: usize,
16165 ) -> PerlResult<PerlValue> {
16166 let (fmt, rest): (String, &[PerlValue]) = if args.is_empty() {
16167 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
16168 Ok(s) => s,
16169 Err(FlowOrError::Error(e)) => return Err(e),
16170 Err(FlowOrError::Flow(_)) => {
16171 return Err(PerlError::runtime(
16172 "printf: unexpected control flow in sprintf",
16173 line,
16174 ));
16175 }
16176 };
16177 (s, &[])
16178 } else {
16179 (args[0].to_string(), &args[1..])
16180 };
16181 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
16182 Ok(s) => s,
16183 Err(FlowOrError::Error(e)) => return Err(e),
16184 Err(FlowOrError::Flow(_)) => {
16185 return Err(PerlError::runtime(
16186 "printf: unexpected control flow in sprintf",
16187 line,
16188 ));
16189 }
16190 };
16191 match handle_name {
16192 "STDOUT" => {
16193 if !self.suppress_stdout {
16194 print!("{}", output);
16195 if self.output_autoflush {
16196 let _ = IoWrite::flush(&mut io::stdout());
16197 }
16198 }
16199 }
16200 "STDERR" => {
16201 eprint!("{}", output);
16202 let _ = IoWrite::flush(&mut io::stderr());
16203 }
16204 name => {
16205 if let Some(writer) = self.output_handles.get_mut(name) {
16206 let _ = writer.write_all(output.as_bytes());
16207 if self.output_autoflush {
16208 let _ = writer.flush();
16209 }
16210 } else {
16211 return Err(PerlError::runtime(
16212 format!("printf on unopened filehandle {}", name),
16213 line,
16214 ));
16215 }
16216 }
16217 }
16218 Ok(PerlValue::integer(1))
16219 }
16220
16221 pub(crate) fn try_native_method(
16223 &mut self,
16224 receiver: &PerlValue,
16225 method: &str,
16226 args: &[PerlValue],
16227 line: usize,
16228 ) -> Option<PerlResult<PerlValue>> {
16229 if let Some(name) = receiver.as_io_handle_name() {
16230 return Some(self.io_handle_method(&name, method, args, line));
16231 }
16232 if let Some(ref s) = receiver.as_str() {
16233 if self.is_bound_handle(s) {
16234 return Some(self.io_handle_method(s, method, args, line));
16235 }
16236 }
16237 if let Some(c) = receiver.as_sqlite_conn() {
16238 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
16239 }
16240 if let Some(s) = receiver.as_struct_inst() {
16241 if let Some(idx) = s.def.field_index(method) {
16243 match args.len() {
16244 0 => {
16245 return Some(Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF)));
16246 }
16247 1 => {
16248 let field = &s.def.fields[idx];
16249 let new_val = args[0].clone();
16250 if let Err(msg) = field.ty.check_value(&new_val) {
16251 return Some(Err(PerlError::type_error(
16252 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
16253 line,
16254 )));
16255 }
16256 s.set_field(idx, new_val.clone());
16257 return Some(Ok(new_val));
16258 }
16259 _ => {
16260 return Some(Err(PerlError::runtime(
16261 format!(
16262 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
16263 method,
16264 args.len()
16265 ),
16266 line,
16267 )));
16268 }
16269 }
16270 }
16271 match method {
16273 "with" => {
16274 let mut new_values = s.get_values();
16276 let mut i = 0;
16277 while i + 1 < args.len() {
16278 let k = args[i].to_string();
16279 let v = args[i + 1].clone();
16280 if let Some(idx) = s.def.field_index(&k) {
16281 let field = &s.def.fields[idx];
16282 if let Err(msg) = field.ty.check_value(&v) {
16283 return Some(Err(PerlError::type_error(
16284 format!(
16285 "struct {} field `{}`: {}",
16286 s.def.name, field.name, msg
16287 ),
16288 line,
16289 )));
16290 }
16291 new_values[idx] = v;
16292 } else {
16293 return Some(Err(PerlError::runtime(
16294 format!("struct {}: unknown field `{}`", s.def.name, k),
16295 line,
16296 )));
16297 }
16298 i += 2;
16299 }
16300 return Some(Ok(PerlValue::struct_inst(Arc::new(
16301 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
16302 ))));
16303 }
16304 "to_hash" => {
16305 if !args.is_empty() {
16307 return Some(Err(PerlError::runtime(
16308 "struct to_hash takes no arguments",
16309 line,
16310 )));
16311 }
16312 let mut map = IndexMap::new();
16313 let values = s.get_values();
16314 for (i, field) in s.def.fields.iter().enumerate() {
16315 map.insert(field.name.clone(), values[i].clone());
16316 }
16317 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
16318 }
16319 "to_hash_rec" | "to_hash_deep" => {
16320 if !args.is_empty() {
16323 return Some(Err(PerlError::runtime(
16324 "struct to_hash_rec takes no arguments",
16325 line,
16326 )));
16327 }
16328 return Some(Ok(self.deep_to_hash_value(receiver)));
16329 }
16330 "fields" => {
16331 if !args.is_empty() {
16333 return Some(Err(PerlError::runtime(
16334 "struct fields takes no arguments",
16335 line,
16336 )));
16337 }
16338 let names: Vec<PerlValue> = s
16339 .def
16340 .fields
16341 .iter()
16342 .map(|f| PerlValue::string(f.name.clone()))
16343 .collect();
16344 return Some(Ok(PerlValue::array(names)));
16345 }
16346 "clone" => {
16347 if !args.is_empty() {
16349 return Some(Err(PerlError::runtime(
16350 "struct clone takes no arguments",
16351 line,
16352 )));
16353 }
16354 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
16355 return Some(Ok(PerlValue::struct_inst(Arc::new(
16356 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
16357 ))));
16358 }
16359 _ => {}
16360 }
16361 if let Some(m) = s.def.method(method) {
16363 let body = m.body.clone();
16364 let params = m.params.clone();
16365 let mut call_args = vec![receiver.clone()];
16367 call_args.extend(args.iter().cloned());
16368 return Some(
16369 match self.call_struct_method(&body, ¶ms, call_args, line) {
16370 Ok(v) => Ok(v),
16371 Err(FlowOrError::Error(e)) => Err(e),
16372 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16373 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
16374 "unexpected control flow in struct method",
16375 line,
16376 )),
16377 },
16378 );
16379 }
16380 return None;
16381 }
16382 if let Some(c) = receiver.as_class_inst() {
16384 let all_fields_full = self.collect_class_fields_full(&c.def);
16386 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
16387 .iter()
16388 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
16389 .collect();
16390
16391 if let Some(idx) = all_fields_full
16393 .iter()
16394 .position(|(name, _, _, _, _)| name == method)
16395 {
16396 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
16397
16398 match vis {
16400 crate::ast::Visibility::Private => {
16401 let caller_class = self
16403 .scope
16404 .get_scalar("self")
16405 .as_class_inst()
16406 .map(|ci| ci.def.name.clone());
16407 if caller_class.as_deref() != Some(owner_class.as_str()) {
16408 return Some(Err(PerlError::runtime(
16409 format!("field `{}` of class {} is private", method, owner_class),
16410 line,
16411 )));
16412 }
16413 }
16414 crate::ast::Visibility::Protected => {
16415 let caller_class = self
16417 .scope
16418 .get_scalar("self")
16419 .as_class_inst()
16420 .map(|ci| ci.def.name.clone());
16421 let allowed = caller_class.as_deref().is_some_and(|caller| {
16422 caller == owner_class || self.class_inherits_from(caller, owner_class)
16423 });
16424 if !allowed {
16425 return Some(Err(PerlError::runtime(
16426 format!("field `{}` of class {} is protected", method, owner_class),
16427 line,
16428 )));
16429 }
16430 }
16431 crate::ast::Visibility::Public => {}
16432 }
16433
16434 match args.len() {
16435 0 => {
16436 return Some(Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF)));
16437 }
16438 1 => {
16439 let new_val = args[0].clone();
16440 if let Err(msg) = ty.check_value(&new_val) {
16441 return Some(Err(PerlError::type_error(
16442 format!("class {} field `{}`: {}", c.def.name, method, msg),
16443 line,
16444 )));
16445 }
16446 c.set_field(idx, new_val.clone());
16447 return Some(Ok(new_val));
16448 }
16449 _ => {
16450 return Some(Err(PerlError::runtime(
16451 format!(
16452 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
16453 method,
16454 args.len()
16455 ),
16456 line,
16457 )));
16458 }
16459 }
16460 }
16461 match method {
16463 "with" => {
16464 let mut new_values = c.get_values();
16465 let mut i = 0;
16466 while i + 1 < args.len() {
16467 let k = args[i].to_string();
16468 let v = args[i + 1].clone();
16469 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
16470 let (_, _, ref ty) = all_fields[idx];
16471 if let Err(msg) = ty.check_value(&v) {
16472 return Some(Err(PerlError::type_error(
16473 format!("class {} field `{}`: {}", c.def.name, k, msg),
16474 line,
16475 )));
16476 }
16477 new_values[idx] = v;
16478 } else {
16479 return Some(Err(PerlError::runtime(
16480 format!("class {}: unknown field `{}`", c.def.name, k),
16481 line,
16482 )));
16483 }
16484 i += 2;
16485 }
16486 return Some(Ok(PerlValue::class_inst(Arc::new(
16487 crate::value::ClassInstance::new_with_isa(
16488 Arc::clone(&c.def),
16489 new_values,
16490 c.isa_chain.clone(),
16491 ),
16492 ))));
16493 }
16494 "to_hash" => {
16495 if !args.is_empty() {
16496 return Some(Err(PerlError::runtime(
16497 "class to_hash takes no arguments",
16498 line,
16499 )));
16500 }
16501 let mut map = IndexMap::new();
16502 let values = c.get_values();
16503 for (i, (name, _, _)) in all_fields.iter().enumerate() {
16504 if let Some(v) = values.get(i) {
16505 map.insert(name.clone(), v.clone());
16506 }
16507 }
16508 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
16509 }
16510 "to_hash_rec" | "to_hash_deep" => {
16511 if !args.is_empty() {
16516 return Some(Err(PerlError::runtime(
16517 "class to_hash_rec takes no arguments",
16518 line,
16519 )));
16520 }
16521 return Some(Ok(self.deep_to_hash_value(receiver)));
16522 }
16523 "fields" => {
16524 if !args.is_empty() {
16525 return Some(Err(PerlError::runtime(
16526 "class fields takes no arguments",
16527 line,
16528 )));
16529 }
16530 let names: Vec<PerlValue> = all_fields
16531 .iter()
16532 .map(|(name, _, _)| PerlValue::string(name.clone()))
16533 .collect();
16534 return Some(Ok(PerlValue::array(names)));
16535 }
16536 "clone" => {
16537 if !args.is_empty() {
16538 return Some(Err(PerlError::runtime(
16539 "class clone takes no arguments",
16540 line,
16541 )));
16542 }
16543 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
16544 return Some(Ok(PerlValue::class_inst(Arc::new(
16545 crate::value::ClassInstance::new_with_isa(
16546 Arc::clone(&c.def),
16547 new_values,
16548 c.isa_chain.clone(),
16549 ),
16550 ))));
16551 }
16552 "isa" => {
16553 if args.len() != 1 {
16554 return Some(Err(PerlError::runtime("isa requires one argument", line)));
16555 }
16556 let class_name = args[0].to_string();
16557 let is_a = c.isa(&class_name);
16558 return Some(Ok(if is_a {
16559 PerlValue::integer(1)
16560 } else {
16561 PerlValue::string(String::new())
16562 }));
16563 }
16564 "does" => {
16565 if args.len() != 1 {
16566 return Some(Err(PerlError::runtime("does requires one argument", line)));
16567 }
16568 let trait_name = args[0].to_string();
16569 let implements = c.def.implements.contains(&trait_name);
16570 return Some(Ok(if implements {
16571 PerlValue::integer(1)
16572 } else {
16573 PerlValue::string(String::new())
16574 }));
16575 }
16576 "methods" => {
16577 if !args.is_empty() {
16578 return Some(Err(PerlError::runtime("methods takes no arguments", line)));
16579 }
16580 let mut names = Vec::new();
16581 self.collect_class_method_names(&c.def, &mut names);
16582 let values: Vec<PerlValue> = names.into_iter().map(PerlValue::string).collect();
16583 return Some(Ok(PerlValue::array(values)));
16584 }
16585 "superclass" => {
16586 if !args.is_empty() {
16587 return Some(Err(PerlError::runtime(
16588 "superclass takes no arguments",
16589 line,
16590 )));
16591 }
16592 let parents: Vec<PerlValue> = c
16593 .def
16594 .extends
16595 .iter()
16596 .map(|s| PerlValue::string(s.clone()))
16597 .collect();
16598 return Some(Ok(PerlValue::array(parents)));
16599 }
16600 "destroy" => {
16601 let destroy_chain = self.collect_destroy_chain(&c.def);
16603 for (body, params) in &destroy_chain {
16604 let call_args = vec![receiver.clone()];
16605 match self.call_class_method(body, params, call_args, line) {
16606 Ok(_) => {}
16607 Err(FlowOrError::Flow(Flow::Return(_))) => {}
16608 Err(FlowOrError::Error(e)) => return Some(Err(e)),
16609 Err(_) => {}
16610 }
16611 }
16612 return Some(Ok(PerlValue::UNDEF));
16613 }
16614 _ => {}
16615 }
16616 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
16618 match m.visibility {
16620 crate::ast::Visibility::Private => {
16621 let caller_class = self
16622 .scope
16623 .get_scalar("self")
16624 .as_class_inst()
16625 .map(|ci| ci.def.name.clone());
16626 if caller_class.as_deref() != Some(owner_class.as_str()) {
16627 return Some(Err(PerlError::runtime(
16628 format!("method `{}` of class {} is private", method, owner_class),
16629 line,
16630 )));
16631 }
16632 }
16633 crate::ast::Visibility::Protected => {
16634 let caller_class = self
16635 .scope
16636 .get_scalar("self")
16637 .as_class_inst()
16638 .map(|ci| ci.def.name.clone());
16639 let allowed = caller_class.as_deref().is_some_and(|caller| {
16640 caller == owner_class.as_str()
16641 || self.class_inherits_from(caller, owner_class)
16642 });
16643 if !allowed {
16644 return Some(Err(PerlError::runtime(
16645 format!(
16646 "method `{}` of class {} is protected",
16647 method, owner_class
16648 ),
16649 line,
16650 )));
16651 }
16652 }
16653 crate::ast::Visibility::Public => {}
16654 }
16655 if let Some(ref body) = m.body {
16656 let params = m.params.clone();
16657 let mut call_args = vec![receiver.clone()];
16658 call_args.extend(args.iter().cloned());
16659 return Some(
16660 match self.call_class_method(body, ¶ms, call_args, line) {
16661 Ok(v) => Ok(v),
16662 Err(FlowOrError::Error(e)) => Err(e),
16663 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16664 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
16665 "unexpected control flow in class method",
16666 line,
16667 )),
16668 },
16669 );
16670 }
16671 }
16672 return None;
16673 }
16674 if let Some(d) = receiver.as_dataframe() {
16675 return Some(self.dataframe_method(d, method, args, line));
16676 }
16677 if let Some(s) = crate::value::set_payload(receiver) {
16678 return Some(self.set_method(s, method, args, line));
16679 }
16680 if let Some(d) = receiver.as_deque() {
16681 return Some(self.deque_method(d, method, args, line));
16682 }
16683 if let Some(h) = receiver.as_heap_pq() {
16684 return Some(self.heap_method(h, method, args, line));
16685 }
16686 if let Some(p) = receiver.as_pipeline() {
16687 return Some(self.pipeline_method(p, method, args, line));
16688 }
16689 if let Some(c) = receiver.as_capture() {
16690 return Some(self.capture_method(c, method, args, line));
16691 }
16692 if let Some(p) = receiver.as_ppool() {
16693 return Some(self.ppool_method(p, method, args, line));
16694 }
16695 if let Some(b) = receiver.as_barrier() {
16696 return Some(self.barrier_method(b, method, args, line));
16697 }
16698 if let Some(g) = receiver.as_generator() {
16699 if method == "next" {
16700 if !args.is_empty() {
16701 return Some(Err(PerlError::runtime(
16702 "generator->next takes no arguments",
16703 line,
16704 )));
16705 }
16706 return Some(self.generator_next(&g));
16707 }
16708 return None;
16709 }
16710 if let Some(arc) = receiver.as_atomic_arc() {
16711 let inner = arc.lock().clone();
16712 if let Some(d) = inner.as_deque() {
16713 return Some(self.deque_method(d, method, args, line));
16714 }
16715 if let Some(h) = inner.as_heap_pq() {
16716 return Some(self.heap_method(h, method, args, line));
16717 }
16718 }
16719 None
16720 }
16721
16722 fn dataframe_method(
16724 &mut self,
16725 d: Arc<Mutex<PerlDataFrame>>,
16726 method: &str,
16727 args: &[PerlValue],
16728 line: usize,
16729 ) -> PerlResult<PerlValue> {
16730 match method {
16731 "nrow" | "nrows" => {
16732 if !args.is_empty() {
16733 return Err(PerlError::runtime(
16734 format!("dataframe {} takes no arguments", method),
16735 line,
16736 ));
16737 }
16738 Ok(PerlValue::integer(d.lock().nrows() as i64))
16739 }
16740 "ncol" | "ncols" => {
16741 if !args.is_empty() {
16742 return Err(PerlError::runtime(
16743 format!("dataframe {} takes no arguments", method),
16744 line,
16745 ));
16746 }
16747 Ok(PerlValue::integer(d.lock().ncols() as i64))
16748 }
16749 "filter" => {
16750 if args.len() != 1 {
16751 return Err(PerlError::runtime(
16752 "dataframe filter expects 1 argument (sub)",
16753 line,
16754 ));
16755 }
16756 let Some(sub) = args[0].as_code_ref() else {
16757 return Err(PerlError::runtime(
16758 "dataframe filter expects a code reference",
16759 line,
16760 ));
16761 };
16762 let df_guard = d.lock();
16763 let n = df_guard.nrows();
16764 let mut keep = vec![false; n];
16765 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
16766 let row = df_guard.row_hashref(r);
16767 self.scope_push_hook();
16768 self.scope.set_topic(row);
16769 if let Some(ref env) = sub.closure_env {
16770 self.scope.restore_capture(env);
16771 }
16772 let pass = match self.exec_block_no_scope(&sub.body) {
16773 Ok(v) => v.is_true(),
16774 Err(_) => false,
16775 };
16776 self.scope_pop_hook();
16777 *row_keep = pass;
16778 }
16779 let columns = df_guard.columns.clone();
16780 let cols: Vec<Vec<PerlValue>> = (0..df_guard.ncols())
16781 .map(|i| {
16782 let mut out = Vec::new();
16783 for (r, pass_row) in keep.iter().enumerate().take(n) {
16784 if *pass_row {
16785 out.push(df_guard.cols[i][r].clone());
16786 }
16787 }
16788 out
16789 })
16790 .collect();
16791 let group_by = df_guard.group_by.clone();
16792 drop(df_guard);
16793 let new_df = PerlDataFrame {
16794 columns,
16795 cols,
16796 group_by,
16797 };
16798 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
16799 }
16800 "group_by" => {
16801 if args.len() != 1 {
16802 return Err(PerlError::runtime(
16803 "dataframe group_by expects 1 column name",
16804 line,
16805 ));
16806 }
16807 let key = args[0].to_string();
16808 let inner = d.lock();
16809 if inner.col_index(&key).is_none() {
16810 return Err(PerlError::runtime(
16811 format!("dataframe group_by: unknown column \"{}\"", key),
16812 line,
16813 ));
16814 }
16815 let new_df = PerlDataFrame {
16816 columns: inner.columns.clone(),
16817 cols: inner.cols.clone(),
16818 group_by: Some(key),
16819 };
16820 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
16821 }
16822 "sum" => {
16823 if args.len() != 1 {
16824 return Err(PerlError::runtime(
16825 "dataframe sum expects 1 column name",
16826 line,
16827 ));
16828 }
16829 let col_name = args[0].to_string();
16830 let inner = d.lock();
16831 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
16832 PerlError::runtime(
16833 format!("dataframe sum: unknown column \"{}\"", col_name),
16834 line,
16835 )
16836 })?;
16837 match &inner.group_by {
16838 Some(gcol) => {
16839 let gi = inner.col_index(gcol).ok_or_else(|| {
16840 PerlError::runtime(
16841 format!("dataframe sum: unknown group column \"{}\"", gcol),
16842 line,
16843 )
16844 })?;
16845 let mut acc: IndexMap<String, f64> = IndexMap::new();
16846 for r in 0..inner.nrows() {
16847 let k = inner.cols[gi][r].to_string();
16848 let v = inner.cols[val_idx][r].to_number();
16849 *acc.entry(k).or_insert(0.0) += v;
16850 }
16851 let keys: Vec<String> = acc.keys().cloned().collect();
16852 let sums: Vec<f64> = acc.values().copied().collect();
16853 let cols = vec![
16854 keys.into_iter().map(PerlValue::string).collect(),
16855 sums.into_iter().map(PerlValue::float).collect(),
16856 ];
16857 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
16858 let out = PerlDataFrame {
16859 columns,
16860 cols,
16861 group_by: None,
16862 };
16863 Ok(PerlValue::dataframe(Arc::new(Mutex::new(out))))
16864 }
16865 None => {
16866 let total: f64 = (0..inner.nrows())
16867 .map(|r| inner.cols[val_idx][r].to_number())
16868 .sum();
16869 Ok(PerlValue::float(total))
16870 }
16871 }
16872 }
16873 _ => Err(PerlError::runtime(
16874 format!("Unknown method for dataframe: {}", method),
16875 line,
16876 )),
16877 }
16878 }
16879
16880 fn set_method(
16882 &self,
16883 s: Arc<crate::value::PerlSet>,
16884 method: &str,
16885 args: &[PerlValue],
16886 line: usize,
16887 ) -> PerlResult<PerlValue> {
16888 match method {
16889 "has" | "contains" | "member" => {
16890 if args.len() != 1 {
16891 return Err(PerlError::runtime(
16892 "set->has expects one argument (element)",
16893 line,
16894 ));
16895 }
16896 let k = crate::value::set_member_key(&args[0]);
16897 Ok(PerlValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
16898 }
16899 "size" | "len" | "count" => {
16900 if !args.is_empty() {
16901 return Err(PerlError::runtime("set->size takes no arguments", line));
16902 }
16903 Ok(PerlValue::integer(s.len() as i64))
16904 }
16905 "values" | "list" | "elements" => {
16906 if !args.is_empty() {
16907 return Err(PerlError::runtime("set->values takes no arguments", line));
16908 }
16909 Ok(PerlValue::array(s.values().cloned().collect()))
16910 }
16911 _ => Err(PerlError::runtime(
16912 format!("Unknown method for set: {}", method),
16913 line,
16914 )),
16915 }
16916 }
16917
16918 fn deque_method(
16919 &mut self,
16920 d: Arc<Mutex<VecDeque<PerlValue>>>,
16921 method: &str,
16922 args: &[PerlValue],
16923 line: usize,
16924 ) -> PerlResult<PerlValue> {
16925 match method {
16926 "push_back" => {
16927 if args.len() != 1 {
16928 return Err(PerlError::runtime("push_back expects 1 argument", line));
16929 }
16930 d.lock().push_back(args[0].clone());
16931 Ok(PerlValue::integer(d.lock().len() as i64))
16932 }
16933 "push_front" => {
16934 if args.len() != 1 {
16935 return Err(PerlError::runtime("push_front expects 1 argument", line));
16936 }
16937 d.lock().push_front(args[0].clone());
16938 Ok(PerlValue::integer(d.lock().len() as i64))
16939 }
16940 "pop_back" => Ok(d.lock().pop_back().unwrap_or(PerlValue::UNDEF)),
16941 "pop_front" => Ok(d.lock().pop_front().unwrap_or(PerlValue::UNDEF)),
16942 "size" | "len" => Ok(PerlValue::integer(d.lock().len() as i64)),
16943 _ => Err(PerlError::runtime(
16944 format!("Unknown method for deque: {}", method),
16945 line,
16946 )),
16947 }
16948 }
16949
16950 fn heap_method(
16951 &mut self,
16952 h: Arc<Mutex<PerlHeap>>,
16953 method: &str,
16954 args: &[PerlValue],
16955 line: usize,
16956 ) -> PerlResult<PerlValue> {
16957 match method {
16958 "push" => {
16959 if args.len() != 1 {
16960 return Err(PerlError::runtime("heap push expects 1 argument", line));
16961 }
16962 let mut g = h.lock();
16963 let n = g.items.len();
16964 g.items.push(args[0].clone());
16965 let cmp = g.cmp.clone();
16966 drop(g);
16967 let mut g = h.lock();
16968 self.heap_sift_up(&mut g.items, &cmp, n);
16969 Ok(PerlValue::integer(g.items.len() as i64))
16970 }
16971 "pop" => {
16972 let mut g = h.lock();
16973 if g.items.is_empty() {
16974 return Ok(PerlValue::UNDEF);
16975 }
16976 let cmp = g.cmp.clone();
16977 let n = g.items.len();
16978 g.items.swap(0, n - 1);
16979 let v = g.items.pop().unwrap();
16980 if !g.items.is_empty() {
16981 self.heap_sift_down(&mut g.items, &cmp, 0);
16982 }
16983 Ok(v)
16984 }
16985 "peek" => Ok(h.lock().items.first().cloned().unwrap_or(PerlValue::UNDEF)),
16986 _ => Err(PerlError::runtime(
16987 format!("Unknown method for heap: {}", method),
16988 line,
16989 )),
16990 }
16991 }
16992
16993 fn ppool_method(
16994 &mut self,
16995 pool: PerlPpool,
16996 method: &str,
16997 args: &[PerlValue],
16998 line: usize,
16999 ) -> PerlResult<PerlValue> {
17000 match method {
17001 "submit" => pool.submit(self, args, line),
17002 "collect" => {
17003 if !args.is_empty() {
17004 return Err(PerlError::runtime("collect() takes no arguments", line));
17005 }
17006 pool.collect(line)
17007 }
17008 _ => Err(PerlError::runtime(
17009 format!("Unknown method for ppool: {}", method),
17010 line,
17011 )),
17012 }
17013 }
17014
17015 fn barrier_method(
17016 &self,
17017 barrier: PerlBarrier,
17018 method: &str,
17019 args: &[PerlValue],
17020 line: usize,
17021 ) -> PerlResult<PerlValue> {
17022 match method {
17023 "wait" => {
17024 if !args.is_empty() {
17025 return Err(PerlError::runtime("wait() takes no arguments", line));
17026 }
17027 let _ = barrier.0.wait();
17028 Ok(PerlValue::integer(1))
17029 }
17030 _ => Err(PerlError::runtime(
17031 format!("Unknown method for barrier: {}", method),
17032 line,
17033 )),
17034 }
17035 }
17036
17037 fn capture_method(
17038 &self,
17039 c: Arc<CaptureResult>,
17040 method: &str,
17041 args: &[PerlValue],
17042 line: usize,
17043 ) -> PerlResult<PerlValue> {
17044 if !args.is_empty() {
17045 return Err(PerlError::runtime(
17046 format!("capture: {} takes no arguments", method),
17047 line,
17048 ));
17049 }
17050 match method {
17051 "stdout" => Ok(PerlValue::string(c.stdout.clone())),
17052 "stderr" => Ok(PerlValue::string(c.stderr.clone())),
17053 "exitcode" => Ok(PerlValue::integer(c.exitcode)),
17054 "failed" => Ok(PerlValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
17055 _ => Err(PerlError::runtime(
17056 format!("Unknown method for capture: {}", method),
17057 line,
17058 )),
17059 }
17060 }
17061
17062 pub(crate) fn builtin_par_pipeline_stream(
17063 &mut self,
17064 args: &[PerlValue],
17065 _line: usize,
17066 ) -> PerlResult<PerlValue> {
17067 let mut items = Vec::new();
17068 for v in args {
17069 if let Some(a) = v.as_array_vec() {
17070 items.extend(a);
17071 } else {
17072 items.push(v.clone());
17073 }
17074 }
17075 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
17076 source: items,
17077 ops: Vec::new(),
17078 has_scalar_terminal: false,
17079 par_stream: true,
17080 streaming: false,
17081 streaming_workers: 0,
17082 streaming_buffer: 256,
17083 }))))
17084 }
17085
17086 pub(crate) fn builtin_par_pipeline_stream_new(
17089 &mut self,
17090 args: &[PerlValue],
17091 _line: usize,
17092 ) -> PerlResult<PerlValue> {
17093 let mut items = Vec::new();
17094 let mut workers: usize = 0;
17095 let mut buffer: usize = 256;
17096 let mut i = 0;
17098 while i < args.len() {
17099 let s = args[i].to_string();
17100 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
17101 let val = args[i + 1].to_int().max(1) as usize;
17102 if s == "workers" {
17103 workers = val;
17104 } else {
17105 buffer = val;
17106 }
17107 i += 2;
17108 } else if let Some(a) = args[i].as_array_vec() {
17109 items.extend(a);
17110 i += 1;
17111 } else {
17112 items.push(args[i].clone());
17113 i += 1;
17114 }
17115 }
17116 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
17117 source: items,
17118 ops: Vec::new(),
17119 has_scalar_terminal: false,
17120 par_stream: false,
17121 streaming: true,
17122 streaming_workers: workers,
17123 streaming_buffer: buffer,
17124 }))))
17125 }
17126
17127 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<PerlSub> {
17129 let line = 1usize;
17130 let body = vec![Statement {
17131 label: None,
17132 kind: StmtKind::Expression(Expr {
17133 kind: ExprKind::BinOp {
17134 left: Box::new(Expr {
17135 kind: ExprKind::ScalarVar("_".into()),
17136 line,
17137 }),
17138 op: BinOp::Mul,
17139 right: Box::new(Expr {
17140 kind: ExprKind::Integer(k),
17141 line,
17142 }),
17143 },
17144 line,
17145 }),
17146 line,
17147 }];
17148 Arc::new(PerlSub {
17149 name: "__pipeline_int_mul__".into(),
17150 params: vec![],
17151 body,
17152 closure_env: None,
17153 prototype: None,
17154 fib_like: None,
17155 })
17156 }
17157
17158 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<PerlSub> {
17159 let captured = self.scope.capture();
17160 Arc::new(PerlSub {
17161 name: "__ANON__".into(),
17162 params: vec![],
17163 body: block.clone(),
17164 closure_env: Some(captured),
17165 prototype: None,
17166 fib_like: None,
17167 })
17168 }
17169
17170 pub(crate) fn builtin_collect_execute(
17171 &mut self,
17172 args: &[PerlValue],
17173 line: usize,
17174 ) -> PerlResult<PerlValue> {
17175 if args.is_empty() {
17176 return Err(PerlError::runtime(
17177 "collect() expects at least one argument",
17178 line,
17179 ));
17180 }
17181 if args.len() == 1 {
17184 if let Some(p) = args[0].as_pipeline() {
17185 return self.pipeline_collect(&p, line);
17186 }
17187 return Ok(PerlValue::array(args[0].to_list()));
17188 }
17189 Ok(PerlValue::array(args.to_vec()))
17190 }
17191
17192 pub(crate) fn pipeline_push(
17193 &self,
17194 p: &Arc<Mutex<PipelineInner>>,
17195 op: PipelineOp,
17196 line: usize,
17197 ) -> PerlResult<()> {
17198 let mut g = p.lock();
17199 if g.has_scalar_terminal {
17200 return Err(PerlError::runtime(
17201 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
17202 line,
17203 ));
17204 }
17205 if matches!(
17206 &op,
17207 PipelineOp::PReduce { .. }
17208 | PipelineOp::PReduceInit { .. }
17209 | PipelineOp::PMapReduce { .. }
17210 ) {
17211 g.has_scalar_terminal = true;
17212 }
17213 g.ops.push(op);
17214 Ok(())
17215 }
17216
17217 fn pipeline_parse_sub_progress(
17218 args: &[PerlValue],
17219 line: usize,
17220 name: &str,
17221 ) -> PerlResult<(Arc<PerlSub>, bool)> {
17222 if args.is_empty() {
17223 return Err(PerlError::runtime(
17224 format!("pipeline {}: expects at least 1 argument (code ref)", name),
17225 line,
17226 ));
17227 }
17228 let Some(sub) = args[0].as_code_ref() else {
17229 return Err(PerlError::runtime(
17230 format!("pipeline {}: first argument must be a code reference", name),
17231 line,
17232 ));
17233 };
17234 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
17235 if args.len() > 2 {
17236 return Err(PerlError::runtime(
17237 format!(
17238 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
17239 name
17240 ),
17241 line,
17242 ));
17243 }
17244 Ok((sub, progress))
17245 }
17246
17247 pub(crate) fn pipeline_method(
17248 &mut self,
17249 p: Arc<Mutex<PipelineInner>>,
17250 method: &str,
17251 args: &[PerlValue],
17252 line: usize,
17253 ) -> PerlResult<PerlValue> {
17254 match method {
17255 "filter" | "f" | "grep" => {
17256 if args.len() != 1 {
17257 return Err(PerlError::runtime(
17258 "pipeline filter/grep expects 1 argument (sub)",
17259 line,
17260 ));
17261 }
17262 let Some(sub) = args[0].as_code_ref() else {
17263 return Err(PerlError::runtime(
17264 "pipeline filter/grep expects a code reference",
17265 line,
17266 ));
17267 };
17268 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
17269 Ok(PerlValue::pipeline(Arc::clone(&p)))
17270 }
17271 "map" => {
17272 if args.len() != 1 {
17273 return Err(PerlError::runtime(
17274 "pipeline map expects 1 argument (sub)",
17275 line,
17276 ));
17277 }
17278 let Some(sub) = args[0].as_code_ref() else {
17279 return Err(PerlError::runtime(
17280 "pipeline map expects a code reference",
17281 line,
17282 ));
17283 };
17284 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
17285 Ok(PerlValue::pipeline(Arc::clone(&p)))
17286 }
17287 "tap" | "peek" => {
17288 if args.len() != 1 {
17289 return Err(PerlError::runtime(
17290 "pipeline tap/peek expects 1 argument (sub)",
17291 line,
17292 ));
17293 }
17294 let Some(sub) = args[0].as_code_ref() else {
17295 return Err(PerlError::runtime(
17296 "pipeline tap/peek expects a code reference",
17297 line,
17298 ));
17299 };
17300 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
17301 Ok(PerlValue::pipeline(Arc::clone(&p)))
17302 }
17303 "take" => {
17304 if args.len() != 1 {
17305 return Err(PerlError::runtime("pipeline take expects 1 argument", line));
17306 }
17307 let n = args[0].to_int();
17308 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
17309 Ok(PerlValue::pipeline(Arc::clone(&p)))
17310 }
17311 "pmap" => {
17312 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
17313 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
17314 Ok(PerlValue::pipeline(Arc::clone(&p)))
17315 }
17316 "pgrep" => {
17317 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
17318 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
17319 Ok(PerlValue::pipeline(Arc::clone(&p)))
17320 }
17321 "pfor" => {
17322 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
17323 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
17324 Ok(PerlValue::pipeline(Arc::clone(&p)))
17325 }
17326 "pmap_chunked" => {
17327 if args.len() < 2 {
17328 return Err(PerlError::runtime(
17329 "pipeline pmap_chunked expects chunk size and a code reference",
17330 line,
17331 ));
17332 }
17333 let chunk = args[0].to_int().max(1);
17334 let Some(sub) = args[1].as_code_ref() else {
17335 return Err(PerlError::runtime(
17336 "pipeline pmap_chunked: second argument must be a code reference",
17337 line,
17338 ));
17339 };
17340 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
17341 if args.len() > 3 {
17342 return Err(PerlError::runtime(
17343 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
17344 line,
17345 ));
17346 }
17347 self.pipeline_push(
17348 &p,
17349 PipelineOp::PMapChunked {
17350 chunk,
17351 sub,
17352 progress,
17353 },
17354 line,
17355 )?;
17356 Ok(PerlValue::pipeline(Arc::clone(&p)))
17357 }
17358 "psort" => {
17359 let (cmp, progress) = match args.len() {
17360 0 => (None, false),
17361 1 => {
17362 if let Some(s) = args[0].as_code_ref() {
17363 (Some(s), false)
17364 } else {
17365 (None, args[0].is_true())
17366 }
17367 }
17368 2 => {
17369 let Some(s) = args[0].as_code_ref() else {
17370 return Err(PerlError::runtime(
17371 "pipeline psort: with two arguments, the first must be a comparator sub",
17372 line,
17373 ));
17374 };
17375 (Some(s), args[1].is_true())
17376 }
17377 _ => {
17378 return Err(PerlError::runtime(
17379 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
17380 line,
17381 ));
17382 }
17383 };
17384 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
17385 Ok(PerlValue::pipeline(Arc::clone(&p)))
17386 }
17387 "pcache" => {
17388 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
17389 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
17390 Ok(PerlValue::pipeline(Arc::clone(&p)))
17391 }
17392 "preduce" => {
17393 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
17394 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
17395 Ok(PerlValue::pipeline(Arc::clone(&p)))
17396 }
17397 "preduce_init" => {
17398 if args.len() < 2 {
17399 return Err(PerlError::runtime(
17400 "pipeline preduce_init expects init value and a code reference",
17401 line,
17402 ));
17403 }
17404 let init = args[0].clone();
17405 let Some(sub) = args[1].as_code_ref() else {
17406 return Err(PerlError::runtime(
17407 "pipeline preduce_init: second argument must be a code reference",
17408 line,
17409 ));
17410 };
17411 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
17412 if args.len() > 3 {
17413 return Err(PerlError::runtime(
17414 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
17415 line,
17416 ));
17417 }
17418 self.pipeline_push(
17419 &p,
17420 PipelineOp::PReduceInit {
17421 init,
17422 sub,
17423 progress,
17424 },
17425 line,
17426 )?;
17427 Ok(PerlValue::pipeline(Arc::clone(&p)))
17428 }
17429 "pmap_reduce" => {
17430 if args.len() < 2 {
17431 return Err(PerlError::runtime(
17432 "pipeline pmap_reduce expects map sub and reduce sub",
17433 line,
17434 ));
17435 }
17436 let Some(map) = args[0].as_code_ref() else {
17437 return Err(PerlError::runtime(
17438 "pipeline pmap_reduce: first argument must be a code reference (map)",
17439 line,
17440 ));
17441 };
17442 let Some(reduce) = args[1].as_code_ref() else {
17443 return Err(PerlError::runtime(
17444 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
17445 line,
17446 ));
17447 };
17448 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
17449 if args.len() > 3 {
17450 return Err(PerlError::runtime(
17451 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
17452 line,
17453 ));
17454 }
17455 self.pipeline_push(
17456 &p,
17457 PipelineOp::PMapReduce {
17458 map,
17459 reduce,
17460 progress,
17461 },
17462 line,
17463 )?;
17464 Ok(PerlValue::pipeline(Arc::clone(&p)))
17465 }
17466 "collect" => {
17467 if !args.is_empty() {
17468 return Err(PerlError::runtime(
17469 "pipeline collect takes no arguments",
17470 line,
17471 ));
17472 }
17473 self.pipeline_collect(&p, line)
17474 }
17475 _ => {
17476 if let Some(sub) = self.resolve_sub_by_name(method) {
17479 if !args.is_empty() {
17480 return Err(PerlError::runtime(
17481 format!(
17482 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
17483 method
17484 ),
17485 line,
17486 ));
17487 }
17488 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
17489 Ok(PerlValue::pipeline(Arc::clone(&p)))
17490 } else {
17491 Err(PerlError::runtime(
17492 format!("Unknown method for pipeline: {}", method),
17493 line,
17494 ))
17495 }
17496 }
17497 }
17498 }
17499
17500 fn pipeline_parallel_map(
17501 &mut self,
17502 items: Vec<PerlValue>,
17503 sub: &Arc<PerlSub>,
17504 progress: bool,
17505 ) -> Vec<PerlValue> {
17506 let subs = self.subs.clone();
17507 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
17508 let pmap_progress = PmapProgress::new(progress, items.len());
17509 let results: Vec<PerlValue> = items
17510 .into_par_iter()
17511 .map(|item| {
17512 let mut local_interp = VMHelper::new();
17513 local_interp.subs = subs.clone();
17514 local_interp.scope.restore_capture(&scope_capture);
17515 local_interp
17516 .scope
17517 .restore_atomics(&atomic_arrays, &atomic_hashes);
17518 local_interp.enable_parallel_guard();
17519 local_interp.scope.set_topic(item);
17520 local_interp.scope_push_hook();
17521 let val = match local_interp.exec_block_no_scope(&sub.body) {
17522 Ok(val) => val,
17523 Err(_) => PerlValue::UNDEF,
17524 };
17525 local_interp.scope_pop_hook();
17526 pmap_progress.tick();
17527 val
17528 })
17529 .collect();
17530 pmap_progress.finish();
17531 results
17532 }
17533
17534 fn pipeline_par_stream_filter(
17536 &mut self,
17537 items: Vec<PerlValue>,
17538 sub: &Arc<PerlSub>,
17539 ) -> Vec<PerlValue> {
17540 if items.is_empty() {
17541 return items;
17542 }
17543 let subs = self.subs.clone();
17544 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
17545 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
17546 let mut kept: Vec<(usize, PerlValue)> = indexed
17547 .into_par_iter()
17548 .filter_map(|(i, item)| {
17549 let mut local_interp = VMHelper::new();
17550 local_interp.subs = subs.clone();
17551 local_interp.scope.restore_capture(&scope_capture);
17552 local_interp
17553 .scope
17554 .restore_atomics(&atomic_arrays, &atomic_hashes);
17555 local_interp.enable_parallel_guard();
17556 local_interp.scope.set_topic(item.clone());
17557 local_interp.scope_push_hook();
17558 let keep = match local_interp.exec_block_no_scope(&sub.body) {
17559 Ok(val) => val.is_true(),
17560 Err(_) => false,
17561 };
17562 local_interp.scope_pop_hook();
17563 if keep {
17564 Some((i, item))
17565 } else {
17566 None
17567 }
17568 })
17569 .collect();
17570 kept.sort_by_key(|(i, _)| *i);
17571 kept.into_iter().map(|(_, x)| x).collect()
17572 }
17573
17574 fn pipeline_par_stream_map(
17576 &mut self,
17577 items: Vec<PerlValue>,
17578 sub: &Arc<PerlSub>,
17579 ) -> Vec<PerlValue> {
17580 if items.is_empty() {
17581 return items;
17582 }
17583 let subs = self.subs.clone();
17584 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
17585 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
17586 let mut mapped: Vec<(usize, PerlValue)> = indexed
17587 .into_par_iter()
17588 .map(|(i, item)| {
17589 let mut local_interp = VMHelper::new();
17590 local_interp.subs = subs.clone();
17591 local_interp.scope.restore_capture(&scope_capture);
17592 local_interp
17593 .scope
17594 .restore_atomics(&atomic_arrays, &atomic_hashes);
17595 local_interp.enable_parallel_guard();
17596 local_interp.scope.set_topic(item);
17597 local_interp.scope_push_hook();
17598 let val = match local_interp.exec_block_no_scope(&sub.body) {
17599 Ok(val) => val,
17600 Err(_) => PerlValue::UNDEF,
17601 };
17602 local_interp.scope_pop_hook();
17603 (i, val)
17604 })
17605 .collect();
17606 mapped.sort_by_key(|(i, _)| *i);
17607 mapped.into_iter().map(|(_, x)| x).collect()
17608 }
17609
17610 fn pipeline_collect(
17611 &mut self,
17612 p: &Arc<Mutex<PipelineInner>>,
17613 line: usize,
17614 ) -> PerlResult<PerlValue> {
17615 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
17616 let g = p.lock();
17617 (
17618 g.source.clone(),
17619 g.ops.clone(),
17620 g.par_stream,
17621 g.streaming,
17622 g.streaming_workers,
17623 g.streaming_buffer,
17624 )
17625 };
17626 if streaming {
17627 return self.pipeline_collect_streaming(
17628 v,
17629 &ops,
17630 streaming_workers,
17631 streaming_buffer,
17632 line,
17633 );
17634 }
17635 for op in ops {
17636 match op {
17637 PipelineOp::Filter(sub) => {
17638 if par_stream {
17639 v = self.pipeline_par_stream_filter(v, &sub);
17640 } else {
17641 let mut out = Vec::new();
17642 for item in v {
17643 self.scope_push_hook();
17644 self.scope.set_topic(item.clone());
17645 if let Some(ref env) = sub.closure_env {
17646 self.scope.restore_capture(env);
17647 }
17648 let keep = match self.exec_block_no_scope(&sub.body) {
17649 Ok(val) => val.is_true(),
17650 Err(_) => false,
17651 };
17652 self.scope_pop_hook();
17653 if keep {
17654 out.push(item);
17655 }
17656 }
17657 v = out;
17658 }
17659 }
17660 PipelineOp::Map(sub) => {
17661 if par_stream {
17662 v = self.pipeline_par_stream_map(v, &sub);
17663 } else {
17664 let mut out = Vec::new();
17665 for item in v {
17666 self.scope_push_hook();
17667 self.scope.set_topic(item);
17668 if let Some(ref env) = sub.closure_env {
17669 self.scope.restore_capture(env);
17670 }
17671 let mapped = match self.exec_block_no_scope(&sub.body) {
17672 Ok(val) => val,
17673 Err(_) => PerlValue::UNDEF,
17674 };
17675 self.scope_pop_hook();
17676 out.push(mapped);
17677 }
17678 v = out;
17679 }
17680 }
17681 PipelineOp::Tap(sub) => {
17682 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
17683 Ok(_) => {}
17684 Err(FlowOrError::Error(e)) => return Err(e),
17685 Err(FlowOrError::Flow(_)) => {
17686 return Err(PerlError::runtime(
17687 "tap: unsupported control flow in block",
17688 line,
17689 ));
17690 }
17691 }
17692 }
17693 PipelineOp::Take(n) => {
17694 let n = n.max(0) as usize;
17695 if v.len() > n {
17696 v.truncate(n);
17697 }
17698 }
17699 PipelineOp::PMap { sub, progress } => {
17700 v = self.pipeline_parallel_map(v, &sub, progress);
17701 }
17702 PipelineOp::PGrep { sub, progress } => {
17703 let subs = self.subs.clone();
17704 let (scope_capture, atomic_arrays, atomic_hashes) =
17705 self.scope.capture_with_atomics();
17706 let pmap_progress = PmapProgress::new(progress, v.len());
17707 v = v
17708 .into_par_iter()
17709 .filter_map(|item| {
17710 let mut local_interp = VMHelper::new();
17711 local_interp.subs = subs.clone();
17712 local_interp.scope.restore_capture(&scope_capture);
17713 local_interp
17714 .scope
17715 .restore_atomics(&atomic_arrays, &atomic_hashes);
17716 local_interp.enable_parallel_guard();
17717 local_interp.scope.set_topic(item.clone());
17718 local_interp.scope_push_hook();
17719 let keep = match local_interp.exec_block_no_scope(&sub.body) {
17720 Ok(val) => val.is_true(),
17721 Err(_) => false,
17722 };
17723 local_interp.scope_pop_hook();
17724 pmap_progress.tick();
17725 if keep {
17726 Some(item)
17727 } else {
17728 None
17729 }
17730 })
17731 .collect();
17732 pmap_progress.finish();
17733 }
17734 PipelineOp::PFor { sub, progress } => {
17735 let subs = self.subs.clone();
17736 let (scope_capture, atomic_arrays, atomic_hashes) =
17737 self.scope.capture_with_atomics();
17738 let pmap_progress = PmapProgress::new(progress, v.len());
17739 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
17740 v.clone().into_par_iter().for_each(|item| {
17741 if first_err.lock().is_some() {
17742 return;
17743 }
17744 let mut local_interp = VMHelper::new();
17745 local_interp.subs = subs.clone();
17746 local_interp.scope.restore_capture(&scope_capture);
17747 local_interp
17748 .scope
17749 .restore_atomics(&atomic_arrays, &atomic_hashes);
17750 local_interp.enable_parallel_guard();
17751 local_interp.scope.set_topic(item);
17752 local_interp.scope_push_hook();
17753 match local_interp.exec_block_no_scope(&sub.body) {
17754 Ok(_) => {}
17755 Err(e) => {
17756 let stryke = match e {
17757 FlowOrError::Error(stryke) => stryke,
17758 FlowOrError::Flow(_) => PerlError::runtime(
17759 "return/last/next/redo not supported inside pipeline pfor block",
17760 line,
17761 ),
17762 };
17763 let mut g = first_err.lock();
17764 if g.is_none() {
17765 *g = Some(stryke);
17766 }
17767 }
17768 }
17769 local_interp.scope_pop_hook();
17770 pmap_progress.tick();
17771 });
17772 pmap_progress.finish();
17773 let pfor_err = first_err.lock().take();
17774 if let Some(e) = pfor_err {
17775 return Err(e);
17776 }
17777 }
17778 PipelineOp::PMapChunked {
17779 chunk,
17780 sub,
17781 progress,
17782 } => {
17783 let chunk_n = chunk.max(1) as usize;
17784 let subs = self.subs.clone();
17785 let (scope_capture, atomic_arrays, atomic_hashes) =
17786 self.scope.capture_with_atomics();
17787 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = v
17788 .chunks(chunk_n)
17789 .enumerate()
17790 .map(|(i, c)| (i, c.to_vec()))
17791 .collect();
17792 let n_chunks = indexed_chunks.len();
17793 let pmap_progress = PmapProgress::new(progress, n_chunks);
17794 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
17795 .into_par_iter()
17796 .map(|(chunk_idx, chunk)| {
17797 let mut local_interp = VMHelper::new();
17798 local_interp.subs = subs.clone();
17799 local_interp.scope.restore_capture(&scope_capture);
17800 local_interp
17801 .scope
17802 .restore_atomics(&atomic_arrays, &atomic_hashes);
17803 local_interp.enable_parallel_guard();
17804 let mut out = Vec::with_capacity(chunk.len());
17805 for item in chunk {
17806 local_interp.scope.set_topic(item);
17807 local_interp.scope_push_hook();
17808 match local_interp.exec_block_no_scope(&sub.body) {
17809 Ok(val) => {
17810 local_interp.scope_pop_hook();
17811 out.push(val);
17812 }
17813 Err(_) => {
17814 local_interp.scope_pop_hook();
17815 out.push(PerlValue::UNDEF);
17816 }
17817 }
17818 }
17819 pmap_progress.tick();
17820 (chunk_idx, out)
17821 })
17822 .collect();
17823 pmap_progress.finish();
17824 chunk_results.sort_by_key(|(i, _)| *i);
17825 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
17826 }
17827 PipelineOp::PSort { cmp, progress } => {
17828 let pmap_progress = PmapProgress::new(progress, 2);
17829 pmap_progress.tick();
17830 match cmp {
17831 Some(cmp_block) => {
17832 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
17833 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
17834 } else {
17835 let subs = self.subs.clone();
17836 let scope_capture = self.scope.capture();
17837 v.par_sort_by(|a, b| {
17838 let mut local_interp = VMHelper::new();
17839 local_interp.subs = subs.clone();
17840 local_interp.scope.restore_capture(&scope_capture);
17841 local_interp.enable_parallel_guard();
17842 local_interp.scope.set_sort_pair(a.clone(), b.clone());
17843 local_interp.scope_push_hook();
17844 let ord =
17845 match local_interp.exec_block_no_scope(&cmp_block.body) {
17846 Ok(v) => {
17847 let n = v.to_int();
17848 if n < 0 {
17849 std::cmp::Ordering::Less
17850 } else if n > 0 {
17851 std::cmp::Ordering::Greater
17852 } else {
17853 std::cmp::Ordering::Equal
17854 }
17855 }
17856 Err(_) => std::cmp::Ordering::Equal,
17857 };
17858 local_interp.scope_pop_hook();
17859 ord
17860 });
17861 }
17862 }
17863 None => {
17864 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
17865 }
17866 }
17867 pmap_progress.tick();
17868 pmap_progress.finish();
17869 }
17870 PipelineOp::PCache { sub, progress } => {
17871 let subs = self.subs.clone();
17872 let scope_capture = self.scope.capture();
17873 let cache = &*crate::pcache::GLOBAL_PCACHE;
17874 let pmap_progress = PmapProgress::new(progress, v.len());
17875 v = v
17876 .into_par_iter()
17877 .map(|item| {
17878 let k = crate::pcache::cache_key(&item);
17879 if let Some(cached) = cache.get(&k) {
17880 pmap_progress.tick();
17881 return cached.clone();
17882 }
17883 let mut local_interp = VMHelper::new();
17884 local_interp.subs = subs.clone();
17885 local_interp.scope.restore_capture(&scope_capture);
17886 local_interp.enable_parallel_guard();
17887 local_interp.scope.set_topic(item.clone());
17888 local_interp.scope_push_hook();
17889 let val = match local_interp.exec_block_no_scope(&sub.body) {
17890 Ok(v) => v,
17891 Err(_) => PerlValue::UNDEF,
17892 };
17893 local_interp.scope_pop_hook();
17894 cache.insert(k, val.clone());
17895 pmap_progress.tick();
17896 val
17897 })
17898 .collect();
17899 pmap_progress.finish();
17900 }
17901 PipelineOp::PReduce { sub, progress } => {
17902 if v.is_empty() {
17903 return Ok(PerlValue::UNDEF);
17904 }
17905 if v.len() == 1 {
17906 return Ok(v.into_iter().next().unwrap());
17907 }
17908 let block = sub.body.clone();
17909 let subs = self.subs.clone();
17910 let scope_capture = self.scope.capture();
17911 let pmap_progress = PmapProgress::new(progress, v.len());
17912 let result = v
17913 .into_par_iter()
17914 .map(|x| {
17915 pmap_progress.tick();
17916 x
17917 })
17918 .reduce_with(|a, b| {
17919 let mut local_interp = VMHelper::new();
17920 local_interp.subs = subs.clone();
17921 local_interp.scope.restore_capture(&scope_capture);
17922 local_interp.enable_parallel_guard();
17923 local_interp.scope.set_sort_pair(a, b);
17924 match local_interp.exec_block(&block) {
17925 Ok(val) => val,
17926 Err(_) => PerlValue::UNDEF,
17927 }
17928 });
17929 pmap_progress.finish();
17930 return Ok(result.unwrap_or(PerlValue::UNDEF));
17931 }
17932 PipelineOp::PReduceInit {
17933 init,
17934 sub,
17935 progress,
17936 } => {
17937 if v.is_empty() {
17938 return Ok(init);
17939 }
17940 let block = sub.body.clone();
17941 let subs = self.subs.clone();
17942 let scope_capture = self.scope.capture();
17943 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
17944 if v.len() == 1 {
17945 return Ok(fold_preduce_init_step(
17946 &subs,
17947 cap,
17948 &block,
17949 preduce_init_fold_identity(&init),
17950 v.into_iter().next().unwrap(),
17951 ));
17952 }
17953 let pmap_progress = PmapProgress::new(progress, v.len());
17954 let result = v
17955 .into_par_iter()
17956 .fold(
17957 || preduce_init_fold_identity(&init),
17958 |acc, item| {
17959 pmap_progress.tick();
17960 fold_preduce_init_step(&subs, cap, &block, acc, item)
17961 },
17962 )
17963 .reduce(
17964 || preduce_init_fold_identity(&init),
17965 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
17966 );
17967 pmap_progress.finish();
17968 return Ok(result);
17969 }
17970 PipelineOp::PMapReduce {
17971 map,
17972 reduce,
17973 progress,
17974 } => {
17975 if v.is_empty() {
17976 return Ok(PerlValue::UNDEF);
17977 }
17978 let map_block = map.body.clone();
17979 let reduce_block = reduce.body.clone();
17980 let subs = self.subs.clone();
17981 let scope_capture = self.scope.capture();
17982 if v.len() == 1 {
17983 let mut local_interp = VMHelper::new();
17984 local_interp.subs = subs.clone();
17985 local_interp.scope.restore_capture(&scope_capture);
17986 local_interp.scope.set_topic(v[0].clone());
17987 return match local_interp.exec_block_no_scope(&map_block) {
17988 Ok(val) => Ok(val),
17989 Err(_) => Ok(PerlValue::UNDEF),
17990 };
17991 }
17992 let pmap_progress = PmapProgress::new(progress, v.len());
17993 let result = v
17994 .into_par_iter()
17995 .map(|item| {
17996 let mut local_interp = VMHelper::new();
17997 local_interp.subs = subs.clone();
17998 local_interp.scope.restore_capture(&scope_capture);
17999 local_interp.scope.set_topic(item);
18000 let val = match local_interp.exec_block_no_scope(&map_block) {
18001 Ok(val) => val,
18002 Err(_) => PerlValue::UNDEF,
18003 };
18004 pmap_progress.tick();
18005 val
18006 })
18007 .reduce_with(|a, b| {
18008 let mut local_interp = VMHelper::new();
18009 local_interp.subs = subs.clone();
18010 local_interp.scope.restore_capture(&scope_capture);
18011 local_interp.scope.set_sort_pair(a, b);
18012 match local_interp.exec_block_no_scope(&reduce_block) {
18013 Ok(val) => val,
18014 Err(_) => PerlValue::UNDEF,
18015 }
18016 });
18017 pmap_progress.finish();
18018 return Ok(result.unwrap_or(PerlValue::UNDEF));
18019 }
18020 }
18021 }
18022 Ok(PerlValue::array(v))
18023 }
18024
18025 fn pipeline_collect_streaming(
18028 &mut self,
18029 source: Vec<PerlValue>,
18030 ops: &[PipelineOp],
18031 workers_per_stage: usize,
18032 buffer: usize,
18033 line: usize,
18034 ) -> PerlResult<PerlValue> {
18035 use crossbeam::channel::{bounded, Receiver, Sender};
18036
18037 for op in ops {
18039 match op {
18040 PipelineOp::PSort { .. }
18041 | PipelineOp::PReduce { .. }
18042 | PipelineOp::PReduceInit { .. }
18043 | PipelineOp::PMapReduce { .. }
18044 | PipelineOp::PMapChunked { .. } => {
18045 return Err(PerlError::runtime(
18046 format!(
18047 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
18048 std::mem::discriminant(op)
18049 ),
18050 line,
18051 ));
18052 }
18053 _ => {}
18054 }
18055 }
18056
18057 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
18060 if streamable_ops.is_empty() {
18061 return Ok(PerlValue::array(source));
18062 }
18063
18064 let n_stages = streamable_ops.len();
18065 let wn = if workers_per_stage > 0 {
18066 workers_per_stage
18067 } else {
18068 self.parallel_thread_count()
18069 };
18070 let subs = self.subs.clone();
18071 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18072
18073 let mut channels: Vec<(Sender<PerlValue>, Receiver<PerlValue>)> =
18078 (0..=n_stages).map(|_| bounded(buffer)).collect();
18079
18080 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
18081 let take_done: Arc<std::sync::atomic::AtomicBool> =
18082 Arc::new(std::sync::atomic::AtomicBool::new(false));
18083
18084 let source_tx = channels[0].0.clone();
18087 let result_rx = channels[n_stages].1.clone();
18088 let results: Arc<Mutex<Vec<PerlValue>>> = Arc::new(Mutex::new(Vec::new()));
18089
18090 std::thread::scope(|scope| {
18091 let result_rx_c = result_rx.clone();
18094 let results_c = Arc::clone(&results);
18095 scope.spawn(move || {
18096 while let Ok(item) = result_rx_c.recv() {
18097 results_c.lock().push(item);
18098 }
18099 });
18100
18101 let err_s = Arc::clone(&err);
18103 let take_done_s = Arc::clone(&take_done);
18104 scope.spawn(move || {
18105 for item in source {
18106 if err_s.lock().is_some()
18107 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
18108 {
18109 break;
18110 }
18111 if source_tx.send(item).is_err() {
18112 break;
18113 }
18114 }
18115 });
18116
18117 for (stage_idx, op) in streamable_ops.iter().enumerate() {
18119 let rx = channels[stage_idx].1.clone();
18120 let tx = channels[stage_idx + 1].0.clone();
18121
18122 for _ in 0..wn {
18123 let rx = rx.clone();
18124 let tx = tx.clone();
18125 let subs = subs.clone();
18126 let capture = capture.clone();
18127 let atomic_arrays = atomic_arrays.clone();
18128 let atomic_hashes = atomic_hashes.clone();
18129 let err_w = Arc::clone(&err);
18130 let take_done_w = Arc::clone(&take_done);
18131
18132 match *op {
18133 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
18134 let sub = Arc::clone(sub);
18135 scope.spawn(move || {
18136 while let Ok(item) = rx.recv() {
18137 if err_w.lock().is_some() {
18138 break;
18139 }
18140 let mut interp = VMHelper::new();
18141 interp.subs = subs.clone();
18142 interp.scope.restore_capture(&capture);
18143 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
18144 interp.enable_parallel_guard();
18145 interp.scope.set_topic(item.clone());
18146 interp.scope_push_hook();
18147 let keep = match interp.exec_block_no_scope(&sub.body) {
18148 Ok(val) => val.is_true(),
18149 Err(_) => false,
18150 };
18151 interp.scope_pop_hook();
18152 if keep && tx.send(item).is_err() {
18153 break;
18154 }
18155 }
18156 });
18157 }
18158 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
18159 let sub = Arc::clone(sub);
18160 scope.spawn(move || {
18161 while let Ok(item) = rx.recv() {
18162 if err_w.lock().is_some() {
18163 break;
18164 }
18165 let mut interp = VMHelper::new();
18166 interp.subs = subs.clone();
18167 interp.scope.restore_capture(&capture);
18168 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
18169 interp.enable_parallel_guard();
18170 interp.scope.set_topic(item);
18171 interp.scope_push_hook();
18172 let mapped = match interp.exec_block_no_scope(&sub.body) {
18173 Ok(val) => val,
18174 Err(_) => PerlValue::UNDEF,
18175 };
18176 interp.scope_pop_hook();
18177 if tx.send(mapped).is_err() {
18178 break;
18179 }
18180 }
18181 });
18182 }
18183 PipelineOp::Take(n) => {
18184 let limit = (*n).max(0) as usize;
18185 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
18186 let count_w = Arc::clone(&count);
18187 scope.spawn(move || {
18188 while let Ok(item) = rx.recv() {
18189 let prev =
18190 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
18191 if prev >= limit {
18192 take_done_w
18193 .store(true, std::sync::atomic::Ordering::Relaxed);
18194 break;
18195 }
18196 if tx.send(item).is_err() {
18197 break;
18198 }
18199 }
18200 });
18201 break;
18203 }
18204 PipelineOp::PFor { ref sub, .. } => {
18205 let sub = Arc::clone(sub);
18206 scope.spawn(move || {
18207 while let Ok(item) = rx.recv() {
18208 if err_w.lock().is_some() {
18209 break;
18210 }
18211 let mut interp = VMHelper::new();
18212 interp.subs = subs.clone();
18213 interp.scope.restore_capture(&capture);
18214 interp
18215 .scope
18216 .restore_atomics(&atomic_arrays, &atomic_hashes);
18217 interp.enable_parallel_guard();
18218 interp.scope.set_topic(item.clone());
18219 interp.scope_push_hook();
18220 match interp.exec_block_no_scope(&sub.body) {
18221 Ok(_) => {}
18222 Err(e) => {
18223 let msg = match e {
18224 FlowOrError::Error(stryke) => stryke.to_string(),
18225 FlowOrError::Flow(_) => {
18226 "unexpected control flow in par_pipeline_stream pfor".into()
18227 }
18228 };
18229 let mut g = err_w.lock();
18230 if g.is_none() {
18231 *g = Some(msg);
18232 }
18233 interp.scope_pop_hook();
18234 break;
18235 }
18236 }
18237 interp.scope_pop_hook();
18238 if tx.send(item).is_err() {
18239 break;
18240 }
18241 }
18242 });
18243 }
18244 PipelineOp::Tap(ref sub) => {
18245 let sub = Arc::clone(sub);
18246 scope.spawn(move || {
18247 while let Ok(item) = rx.recv() {
18248 if err_w.lock().is_some() {
18249 break;
18250 }
18251 let mut interp = VMHelper::new();
18252 interp.subs = subs.clone();
18253 interp.scope.restore_capture(&capture);
18254 interp
18255 .scope
18256 .restore_atomics(&atomic_arrays, &atomic_hashes);
18257 interp.enable_parallel_guard();
18258 match interp.call_sub(
18259 &sub,
18260 vec![item.clone()],
18261 WantarrayCtx::Void,
18262 line,
18263 )
18264 {
18265 Ok(_) => {}
18266 Err(e) => {
18267 let msg = match e {
18268 FlowOrError::Error(stryke) => stryke.to_string(),
18269 FlowOrError::Flow(_) => {
18270 "unexpected control flow in par_pipeline_stream tap"
18271 .into()
18272 }
18273 };
18274 let mut g = err_w.lock();
18275 if g.is_none() {
18276 *g = Some(msg);
18277 }
18278 break;
18279 }
18280 }
18281 if tx.send(item).is_err() {
18282 break;
18283 }
18284 }
18285 });
18286 }
18287 PipelineOp::PCache { ref sub, .. } => {
18288 let sub = Arc::clone(sub);
18289 scope.spawn(move || {
18290 while let Ok(item) = rx.recv() {
18291 if err_w.lock().is_some() {
18292 break;
18293 }
18294 let k = crate::pcache::cache_key(&item);
18295 let val = if let Some(cached) =
18296 crate::pcache::GLOBAL_PCACHE.get(&k)
18297 {
18298 cached.clone()
18299 } else {
18300 let mut interp = VMHelper::new();
18301 interp.subs = subs.clone();
18302 interp.scope.restore_capture(&capture);
18303 interp
18304 .scope
18305 .restore_atomics(&atomic_arrays, &atomic_hashes);
18306 interp.enable_parallel_guard();
18307 interp.scope.set_topic(item);
18308 interp.scope_push_hook();
18309 let v = match interp.exec_block_no_scope(&sub.body) {
18310 Ok(v) => v,
18311 Err(_) => PerlValue::UNDEF,
18312 };
18313 interp.scope_pop_hook();
18314 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
18315 v
18316 };
18317 if tx.send(val).is_err() {
18318 break;
18319 }
18320 }
18321 });
18322 }
18323 _ => unreachable!(),
18325 }
18326 }
18327 }
18328
18329 channels.clear();
18333 drop(result_rx);
18334 });
18335
18336 if let Some(msg) = err.lock().take() {
18337 return Err(PerlError::runtime(msg, line));
18338 }
18339
18340 let results = std::mem::take(&mut *results.lock());
18341 Ok(PerlValue::array(results))
18342 }
18343
18344 fn heap_compare(&mut self, cmp: &Arc<PerlSub>, a: &PerlValue, b: &PerlValue) -> Ordering {
18345 self.scope_push_hook();
18346 if let Some(ref env) = cmp.closure_env {
18347 self.scope.restore_capture(env);
18348 }
18349 self.scope.set_sort_pair(a.clone(), b.clone());
18350 let ord = match self.exec_block_no_scope(&cmp.body) {
18351 Ok(v) => {
18352 let n = v.to_int();
18353 if n < 0 {
18354 Ordering::Less
18355 } else if n > 0 {
18356 Ordering::Greater
18357 } else {
18358 Ordering::Equal
18359 }
18360 }
18361 Err(_) => Ordering::Equal,
18362 };
18363 self.scope_pop_hook();
18364 ord
18365 }
18366
18367 fn heap_sift_up(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
18368 while i > 0 {
18369 let p = (i - 1) / 2;
18370 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
18371 break;
18372 }
18373 items.swap(i, p);
18374 i = p;
18375 }
18376 }
18377
18378 fn heap_sift_down(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
18379 let n = items.len();
18380 loop {
18381 let mut sm = i;
18382 let l = 2 * i + 1;
18383 let r = 2 * i + 2;
18384 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
18385 sm = l;
18386 }
18387 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
18388 sm = r;
18389 }
18390 if sm == i {
18391 break;
18392 }
18393 items.swap(i, sm);
18394 i = sm;
18395 }
18396 }
18397
18398 fn hash_for_signature_destruct(
18399 &mut self,
18400 v: &PerlValue,
18401 line: usize,
18402 ) -> PerlResult<IndexMap<String, PerlValue>> {
18403 let Some(m) = self.match_subject_as_hash(v) else {
18404 return Err(PerlError::runtime(
18405 format!(
18406 "sub signature hash destruct: expected HASH or HASH reference, got {}",
18407 v.ref_type()
18408 ),
18409 line,
18410 ));
18411 };
18412 Ok(m)
18413 }
18414
18415 pub(crate) fn apply_sub_signature(
18417 &mut self,
18418 sub: &PerlSub,
18419 argv: &[PerlValue],
18420 line: usize,
18421 ) -> PerlResult<()> {
18422 if sub.params.is_empty() {
18423 return Ok(());
18424 }
18425 let mut i = 0usize;
18426 for p in &sub.params {
18427 match p {
18428 SubSigParam::Scalar(name, ty, default) => {
18429 let val = if i < argv.len() {
18430 argv[i].clone()
18431 } else if let Some(default_expr) = default {
18432 match self.eval_expr(default_expr) {
18433 Ok(v) => v,
18434 Err(FlowOrError::Error(e)) => return Err(e),
18435 Err(FlowOrError::Flow(_)) => {
18436 return Err(PerlError::runtime(
18437 "unexpected control flow in parameter default",
18438 line,
18439 ))
18440 }
18441 }
18442 } else {
18443 PerlValue::UNDEF
18444 };
18445 i += 1;
18446 if let Some(t) = ty {
18447 if let Err(e) = t.check_value(&val) {
18448 return Err(PerlError::runtime(
18449 format!("sub parameter ${}: {}", name, e),
18450 line,
18451 ));
18452 }
18453 }
18454 let n = self.english_scalar_name(name);
18455 self.scope.declare_scalar(n, val);
18456 }
18457 SubSigParam::Array(name, default) => {
18458 let rest: Vec<PerlValue> = if i < argv.len() {
18459 let r = argv[i..].to_vec();
18460 i = argv.len();
18461 r
18462 } else if let Some(default_expr) = default {
18463 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
18464 Ok(v) => v,
18465 Err(FlowOrError::Error(e)) => return Err(e),
18466 Err(FlowOrError::Flow(_)) => {
18467 return Err(PerlError::runtime(
18468 "unexpected control flow in parameter default",
18469 line,
18470 ))
18471 }
18472 };
18473 val.to_list()
18474 } else {
18475 vec![]
18476 };
18477 let aname = self.stash_array_name_for_package(name);
18478 self.scope.declare_array(&aname, rest);
18479 }
18480 SubSigParam::Hash(name, default) => {
18481 let rest: Vec<PerlValue> = if i < argv.len() {
18482 let r = argv[i..].to_vec();
18483 i = argv.len();
18484 r
18485 } else if let Some(default_expr) = default {
18486 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
18487 Ok(v) => v,
18488 Err(FlowOrError::Error(e)) => return Err(e),
18489 Err(FlowOrError::Flow(_)) => {
18490 return Err(PerlError::runtime(
18491 "unexpected control flow in parameter default",
18492 line,
18493 ))
18494 }
18495 };
18496 val.to_list()
18497 } else {
18498 vec![]
18499 };
18500 let mut map = IndexMap::new();
18501 let mut j = 0;
18502 while j + 1 < rest.len() {
18503 map.insert(rest[j].to_string(), rest[j + 1].clone());
18504 j += 2;
18505 }
18506 self.scope.declare_hash(name, map);
18507 }
18508 SubSigParam::ArrayDestruct(elems) => {
18509 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
18510 i += 1;
18511 let Some(arr) = self.match_subject_as_array(&arg) else {
18512 return Err(PerlError::runtime(
18513 format!(
18514 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
18515 arg.ref_type()
18516 ),
18517 line,
18518 ));
18519 };
18520 let binds = self
18521 .match_array_pattern_elems(&arr, elems, line)
18522 .map_err(|e| match e {
18523 FlowOrError::Error(stryke) => stryke,
18524 FlowOrError::Flow(_) => PerlError::runtime(
18525 "unexpected flow in sub signature array destruct",
18526 line,
18527 ),
18528 })?;
18529 let Some(binds) = binds else {
18530 return Err(PerlError::runtime(
18531 "sub signature array destruct: length or element mismatch",
18532 line,
18533 ));
18534 };
18535 for b in binds {
18536 match b {
18537 PatternBinding::Scalar(name, v) => {
18538 let n = self.english_scalar_name(&name);
18539 self.scope.declare_scalar(n, v);
18540 }
18541 PatternBinding::Array(name, elems) => {
18542 self.scope.declare_array(&name, elems);
18543 }
18544 }
18545 }
18546 }
18547 SubSigParam::HashDestruct(pairs) => {
18548 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
18549 i += 1;
18550 let map = self.hash_for_signature_destruct(&arg, line)?;
18551 for (key, varname) in pairs {
18552 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
18553 let n = self.english_scalar_name(varname);
18554 self.scope.declare_scalar(n, v);
18555 }
18556 }
18557 }
18558 }
18559 Ok(())
18560 }
18561
18562 pub(crate) fn try_hof_dispatch(
18566 &mut self,
18567 sub: &PerlSub,
18568 args: &[PerlValue],
18569 want: WantarrayCtx,
18570 line: usize,
18571 ) -> Option<ExecResult> {
18572 let env = sub.closure_env.as_ref()?;
18573 fn env_get<'a>(env: &'a [(String, PerlValue)], key: &str) -> Option<&'a PerlValue> {
18574 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
18575 }
18576
18577 match sub.name.as_str() {
18578 "__comp__" => {
18580 let fns = env_get(env, "__comp_fns__")?.to_list();
18581 let mut val = args.first().cloned().unwrap_or(PerlValue::UNDEF);
18582 for f in fns.iter().rev() {
18583 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
18584 Ok(v) => val = v,
18585 Err(e) => return Some(Err(e)),
18586 }
18587 }
18588 Some(Ok(val))
18589 }
18590 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
18592 "__juxt__" => {
18594 let fns = env_get(env, "__juxt_fns__")?.to_list();
18595 let mut results = Vec::with_capacity(fns.len());
18596 for f in &fns {
18597 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
18598 Ok(v) => results.push(v),
18599 Err(e) => return Some(Err(e)),
18600 }
18601 }
18602 Some(Ok(PerlValue::array(results)))
18603 }
18604 "__partial__" => {
18606 let fn_val = env_get(env, "__partial_fn__")?.clone();
18607 let bound = env_get(env, "__partial_args__")?.to_list();
18608 let mut all_args = bound;
18609 all_args.extend_from_slice(args);
18610 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
18611 }
18612 "__complement__" => {
18614 let fn_val = env_get(env, "__complement_fn__")?.clone();
18615 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
18616 Ok(v) => Some(Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }))),
18617 Err(e) => Some(Err(e)),
18618 }
18619 }
18620 "__fnil__" => {
18622 let fn_val = env_get(env, "__fnil_fn__")?.clone();
18623 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
18624 let mut patched = args.to_vec();
18625 for (i, d) in defaults.iter().enumerate() {
18626 if i < patched.len() {
18627 if patched[i].is_undef() {
18628 patched[i] = d.clone();
18629 }
18630 } else {
18631 patched.push(d.clone());
18632 }
18633 }
18634 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
18635 }
18636 "__memoize__" => {
18638 let fn_val = env_get(env, "__memoize_fn__")?.clone();
18639 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
18640 let key = args
18641 .iter()
18642 .map(|a| a.to_string())
18643 .collect::<Vec<_>>()
18644 .join("\x00");
18645 if let Some(href) = cache_ref.as_hash_ref() {
18646 if let Some(cached) = href.read().get(&key) {
18647 return Some(Ok(cached.clone()));
18648 }
18649 }
18650 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
18651 Ok(v) => {
18652 if let Some(href) = cache_ref.as_hash_ref() {
18653 href.write().insert(key, v.clone());
18654 }
18655 Some(Ok(v))
18656 }
18657 Err(e) => Some(Err(e)),
18658 }
18659 }
18660 "__curry__" => {
18662 let fn_val = env_get(env, "__curry_fn__")?.clone();
18663 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
18664 let bound = env_get(env, "__curry_bound__")?.to_list();
18665 let mut all = bound;
18666 all.extend_from_slice(args);
18667 if all.len() >= arity {
18668 Some(self.dispatch_indirect_call(fn_val, all, want, line))
18669 } else {
18670 let curry_sub = PerlSub {
18671 name: "__curry__".to_string(),
18672 params: vec![],
18673 body: vec![],
18674 closure_env: Some(vec![
18675 ("__curry_fn__".to_string(), fn_val),
18676 (
18677 "__curry_arity__".to_string(),
18678 PerlValue::integer(arity as i64),
18679 ),
18680 ("__curry_bound__".to_string(), PerlValue::array(all)),
18681 ]),
18682 prototype: None,
18683 fib_like: None,
18684 };
18685 Some(Ok(PerlValue::code_ref(Arc::new(curry_sub))))
18686 }
18687 }
18688 "__once__" => {
18690 let cache_ref = env_get(env, "__once_cache__")?.clone();
18691 if let Some(href) = cache_ref.as_hash_ref() {
18692 let r = href.read();
18693 if r.contains_key("done") {
18694 return Some(Ok(r.get("val").cloned().unwrap_or(PerlValue::UNDEF)));
18695 }
18696 }
18697 let fn_val = env_get(env, "__once_fn__")?.clone();
18698 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
18699 Ok(v) => {
18700 if let Some(href) = cache_ref.as_hash_ref() {
18701 let mut w = href.write();
18702 w.insert("done".to_string(), PerlValue::integer(1));
18703 w.insert("val".to_string(), v.clone());
18704 }
18705 Some(Ok(v))
18706 }
18707 Err(e) => Some(Err(e)),
18708 }
18709 }
18710 _ => None,
18711 }
18712 }
18713
18714 pub(crate) fn call_sub(
18715 &mut self,
18716 sub: &PerlSub,
18717 args: Vec<PerlValue>,
18718 want: WantarrayCtx,
18719 line: usize,
18720 ) -> ExecResult {
18721 let pkg = sub.name.rsplit_once("::").map(|(p, _)| p.to_string());
18724 self.call_sub_with_package(sub, args, want, line, pkg)
18725 }
18726
18727 fn call_sub_with_package(
18731 &mut self,
18732 sub: &PerlSub,
18733 args: Vec<PerlValue>,
18734 want: WantarrayCtx,
18735 _line: usize,
18736 home_package: Option<String>,
18737 ) -> ExecResult {
18738 self.current_sub_stack.push(Arc::new(sub.clone()));
18740
18741 self.scope_push_hook();
18744 self.scope.declare_array("_", args.clone());
18745 if let Some(ref env) = sub.closure_env {
18746 self.scope.restore_capture(env);
18747 }
18748 if let Some(pkg) = home_package {
18754 self.scope
18755 .declare_scalar("__PACKAGE__", PerlValue::string(pkg));
18756 }
18757 self.scope.set_closure_args(&args);
18761 let argv = self.scope.take_sub_underscore().unwrap_or_default();
18763 self.apply_sub_signature(sub, &argv, _line)?;
18764 let saved = self.wantarray_kind;
18765 self.wantarray_kind = want;
18766 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
18767 self.wantarray_kind = saved;
18768 self.scope_pop_hook();
18769 self.current_sub_stack.pop();
18770 return match r {
18771 Ok(v) => Ok(v),
18772 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
18773 Err(e) => Err(e),
18774 };
18775 }
18776 if let Some(pat) = sub.fib_like.as_ref() {
18777 if argv.len() == 1 {
18778 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
18779 let t0 = self.profiler.is_some().then(std::time::Instant::now);
18780 if let Some(p) = &mut self.profiler {
18781 p.enter_sub(&sub.name);
18782 }
18783 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
18784 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
18785 p.exit_sub(t0.elapsed());
18786 }
18787 self.wantarray_kind = saved;
18788 self.scope_pop_hook();
18789 self.current_sub_stack.pop();
18790 return Ok(PerlValue::integer(n));
18791 }
18792 }
18793 }
18794 self.scope.declare_array("_", argv.clone());
18795 let t0 = self.profiler.is_some().then(std::time::Instant::now);
18798 if let Some(p) = &mut self.profiler {
18799 p.enter_sub(&sub.name);
18800 }
18801 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
18805 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
18806 p.exit_sub(t0.elapsed());
18807 }
18808 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
18810 Some(self.scope.get_array("_"))
18811 } else {
18812 None
18813 };
18814 self.wantarray_kind = saved;
18815 self.scope_pop_hook();
18816 self.current_sub_stack.pop();
18817 match result {
18818 Ok(v) => Ok(v),
18819 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
18820 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
18821 let goto_args = goto_args.unwrap_or_default();
18823 let fqn = if target_name.contains("::") {
18824 target_name.clone()
18825 } else {
18826 format!("{}::{}", self.current_package(), target_name)
18827 };
18828 if let Some(target_sub) = self
18829 .subs
18830 .get(&fqn)
18831 .cloned()
18832 .or_else(|| self.subs.get(&target_name).cloned())
18833 {
18834 self.call_sub(&target_sub, goto_args, want, _line)
18835 } else {
18836 Err(
18837 PerlError::runtime(format!("Undefined subroutine &{}", target_name), _line)
18838 .into(),
18839 )
18840 }
18841 }
18842 Err(FlowOrError::Flow(Flow::Yield(_))) => {
18843 Err(PerlError::runtime("yield is only valid inside gen { }", 0).into())
18844 }
18845 Err(e) => Err(e),
18846 }
18847 }
18848
18849 fn call_struct_method(
18851 &mut self,
18852 body: &Block,
18853 params: &[SubSigParam],
18854 args: Vec<PerlValue>,
18855 line: usize,
18856 ) -> ExecResult {
18857 self.scope_push_hook();
18858 self.scope.declare_array("_", args.clone());
18859 if let Some(self_val) = args.first() {
18861 self.scope.declare_scalar("self", self_val.clone());
18862 }
18863 self.scope.set_closure_args(&args);
18865 let user_args: Vec<PerlValue> = args.iter().skip(1).cloned().collect();
18867 self.apply_params_to_argv(params, &user_args, line)?;
18868 let result = self.exec_block_no_scope(body);
18869 self.scope_pop_hook();
18870 match result {
18871 Ok(v) => Ok(v),
18872 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
18873 Err(e) => Err(e),
18874 }
18875 }
18876
18877 pub(crate) fn call_class_method(
18879 &mut self,
18880 body: &Block,
18881 params: &[SubSigParam],
18882 args: Vec<PerlValue>,
18883 line: usize,
18884 ) -> ExecResult {
18885 self.call_class_method_inner(body, params, args, line, false)
18886 }
18887
18888 pub(crate) fn call_static_class_method(
18890 &mut self,
18891 body: &Block,
18892 params: &[SubSigParam],
18893 args: Vec<PerlValue>,
18894 line: usize,
18895 ) -> ExecResult {
18896 self.call_class_method_inner(body, params, args, line, true)
18897 }
18898
18899 fn call_class_method_inner(
18900 &mut self,
18901 body: &Block,
18902 params: &[SubSigParam],
18903 args: Vec<PerlValue>,
18904 line: usize,
18905 is_static: bool,
18906 ) -> ExecResult {
18907 self.scope_push_hook();
18908 self.scope.declare_array("_", args.clone());
18909 if !is_static {
18910 if let Some(self_val) = args.first() {
18912 self.scope.declare_scalar("self", self_val.clone());
18913 }
18914 }
18915 self.scope.set_closure_args(&args);
18917 let user_args: Vec<PerlValue> = if is_static {
18919 args.clone()
18920 } else {
18921 args.iter().skip(1).cloned().collect()
18922 };
18923 self.apply_params_to_argv(params, &user_args, line)?;
18924 let result = self.exec_block_no_scope(body);
18925 self.scope_pop_hook();
18926 match result {
18927 Ok(v) => Ok(v),
18928 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
18929 Err(e) => Err(e),
18930 }
18931 }
18932
18933 fn apply_params_to_argv(
18935 &mut self,
18936 params: &[SubSigParam],
18937 argv: &[PerlValue],
18938 line: usize,
18939 ) -> PerlResult<()> {
18940 let mut i = 0;
18941 for param in params {
18942 match param {
18943 SubSigParam::Scalar(name, ty_opt, default) => {
18944 let v = if i < argv.len() {
18945 argv[i].clone()
18946 } else if let Some(default_expr) = default {
18947 match self.eval_expr(default_expr) {
18948 Ok(v) => v,
18949 Err(FlowOrError::Error(e)) => return Err(e),
18950 Err(FlowOrError::Flow(_)) => {
18951 return Err(PerlError::runtime(
18952 "unexpected control flow in parameter default",
18953 line,
18954 ))
18955 }
18956 }
18957 } else {
18958 PerlValue::UNDEF
18959 };
18960 i += 1;
18961 if let Some(ty) = ty_opt {
18962 ty.check_value(&v).map_err(|msg| {
18963 PerlError::type_error(
18964 format!("method parameter ${}: {}", name, msg),
18965 line,
18966 )
18967 })?;
18968 }
18969 let n = self.english_scalar_name(name);
18970 self.scope.declare_scalar(n, v);
18971 }
18972 SubSigParam::Array(name, default) => {
18973 let rest: Vec<PerlValue> = if i < argv.len() {
18974 let r = argv[i..].to_vec();
18975 i = argv.len();
18976 r
18977 } else if let Some(default_expr) = default {
18978 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
18979 Ok(v) => v,
18980 Err(FlowOrError::Error(e)) => return Err(e),
18981 Err(FlowOrError::Flow(_)) => {
18982 return Err(PerlError::runtime(
18983 "unexpected control flow in parameter default",
18984 line,
18985 ))
18986 }
18987 };
18988 val.to_list()
18989 } else {
18990 vec![]
18991 };
18992 let aname = self.stash_array_name_for_package(name);
18993 self.scope.declare_array(&aname, rest);
18994 }
18995 SubSigParam::Hash(name, default) => {
18996 let rest: Vec<PerlValue> = if i < argv.len() {
18997 let r = argv[i..].to_vec();
18998 i = argv.len();
18999 r
19000 } else if let Some(default_expr) = default {
19001 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19002 Ok(v) => v,
19003 Err(FlowOrError::Error(e)) => return Err(e),
19004 Err(FlowOrError::Flow(_)) => {
19005 return Err(PerlError::runtime(
19006 "unexpected control flow in parameter default",
19007 line,
19008 ))
19009 }
19010 };
19011 val.to_list()
19012 } else {
19013 vec![]
19014 };
19015 let mut map = IndexMap::new();
19016 let mut j = 0;
19017 while j + 1 < rest.len() {
19018 map.insert(rest[j].to_string(), rest[j + 1].clone());
19019 j += 2;
19020 }
19021 self.scope.declare_hash(name, map);
19022 }
19023 SubSigParam::ArrayDestruct(elems) => {
19024 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
19025 i += 1;
19026 let Some(arr) = self.match_subject_as_array(&arg) else {
19027 return Err(PerlError::runtime(
19028 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
19029 line,
19030 ));
19031 };
19032 let binds = self
19033 .match_array_pattern_elems(&arr, elems, line)
19034 .map_err(|e| match e {
19035 FlowOrError::Error(stryke) => stryke,
19036 FlowOrError::Flow(_) => {
19037 PerlError::runtime("unexpected flow in method array destruct", line)
19038 }
19039 })?;
19040 let Some(binds) = binds else {
19041 return Err(PerlError::runtime(
19042 format!(
19043 "method parameter: array destructure failed at position {}",
19044 i
19045 ),
19046 line,
19047 ));
19048 };
19049 for b in binds {
19050 match b {
19051 PatternBinding::Scalar(name, v) => {
19052 let n = self.english_scalar_name(&name);
19053 self.scope.declare_scalar(n, v);
19054 }
19055 PatternBinding::Array(name, elems) => {
19056 self.scope.declare_array(&name, elems);
19057 }
19058 }
19059 }
19060 }
19061 SubSigParam::HashDestruct(pairs) => {
19062 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
19063 i += 1;
19064 let map = self.hash_for_signature_destruct(&arg, line)?;
19065 for (key, varname) in pairs {
19066 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
19067 let n = self.english_scalar_name(varname);
19068 self.scope.declare_scalar(n, v);
19069 }
19070 }
19071 }
19072 }
19073 Ok(())
19074 }
19075
19076 fn builtin_new(&mut self, class: &str, args: Vec<PerlValue>, line: usize) -> ExecResult {
19077 if class == "Set" {
19078 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
19079 }
19080 if let Some(def) = self.struct_defs.get(class).cloned() {
19081 let mut provided = Vec::new();
19082 let mut i = 1;
19083 while i + 1 < args.len() {
19084 let k = args[i].to_string();
19085 let v = args[i + 1].clone();
19086 provided.push((k, v));
19087 i += 2;
19088 }
19089 let mut defaults = Vec::with_capacity(def.fields.len());
19090 for field in &def.fields {
19091 if let Some(ref expr) = field.default {
19092 let val = self.eval_expr(expr)?;
19093 defaults.push(Some(val));
19094 } else {
19095 defaults.push(None);
19096 }
19097 }
19098 return Ok(crate::native_data::struct_new_with_defaults(
19099 &def, &provided, &defaults, line,
19100 )?);
19101 }
19102 if let Some(def) = self.class_defs.get(class).cloned() {
19111 let user_args: Vec<PerlValue> = args.into_iter().skip(1).collect();
19112 return self.class_construct(&def, user_args, line);
19113 }
19114 let mut map = IndexMap::new();
19116 let mut i = 1; while i + 1 < args.len() {
19118 let k = args[i].to_string();
19119 let v = args[i + 1].clone();
19120 map.insert(k, v);
19121 i += 2;
19122 }
19123 Ok(PerlValue::blessed(Arc::new(
19124 crate::value::BlessedRef::new_blessed(class.to_string(), PerlValue::hash(map)),
19125 )))
19126 }
19127
19128 fn exec_print(
19129 &mut self,
19130 handle: Option<&str>,
19131 args: &[Expr],
19132 newline: bool,
19133 line: usize,
19134 ) -> ExecResult {
19135 if newline && (self.feature_bits & FEAT_SAY) == 0 {
19136 return Err(PerlError::runtime(
19137 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
19138 line,
19139 )
19140 .into());
19141 }
19142 let mut output = String::new();
19143 if args.is_empty() {
19144 let topic = self.scope.get_scalar("_").clone();
19146 let s = self.stringify_value(topic, line)?;
19147 output.push_str(&s);
19148 } else {
19149 for (i, a) in args.iter().enumerate() {
19152 if i > 0 {
19153 output.push_str(&self.ofs);
19154 }
19155 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
19156 for item in val.to_list() {
19157 let s = self.stringify_value(item, line)?;
19158 output.push_str(&s);
19159 }
19160 }
19161 }
19162 if newline {
19163 output.push('\n');
19164 }
19165 output.push_str(&self.ors);
19166
19167 let handle_name =
19168 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
19169 self.write_formatted_print(handle_name.as_str(), &output, line)?;
19170 Ok(PerlValue::integer(1))
19171 }
19172
19173 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
19174 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
19175 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
19177 (s, &[])
19178 } else {
19179 (self.eval_expr(&args[0])?.to_string(), &args[1..])
19180 };
19181 let mut arg_vals = Vec::new();
19185 for a in rest {
19186 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
19187 if let Some(items) = v.as_array_vec() {
19188 arg_vals.extend(items);
19189 } else {
19190 arg_vals.push(v);
19191 }
19192 }
19193 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
19194 let handle_name =
19195 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
19196 match handle_name.as_str() {
19197 "STDOUT" => {
19198 if !self.suppress_stdout {
19199 print!("{}", output);
19200 if self.output_autoflush {
19201 let _ = io::stdout().flush();
19202 }
19203 }
19204 }
19205 "STDERR" => {
19206 eprint!("{}", output);
19207 let _ = io::stderr().flush();
19208 }
19209 name => {
19210 if let Some(writer) = self.output_handles.get_mut(name) {
19211 let _ = writer.write_all(output.as_bytes());
19212 if self.output_autoflush {
19213 let _ = writer.flush();
19214 }
19215 }
19216 }
19217 }
19218 Ok(PerlValue::integer(1))
19219 }
19220
19221 pub(crate) fn eval_substr_expr(
19223 &mut self,
19224 string: &Expr,
19225 offset: &Expr,
19226 length: Option<&Expr>,
19227 replacement: Option<&Expr>,
19228 _line: usize,
19229 ) -> Result<PerlValue, FlowOrError> {
19230 let s = self.eval_expr(string)?.to_string();
19231 let off = self.eval_expr(offset)?.to_int();
19232 let start = if off < 0 {
19233 (s.len() as i64 + off).max(0) as usize
19234 } else {
19235 off as usize
19236 };
19237 let len = if let Some(l) = length {
19238 let len_val = self.eval_expr(l)?.to_int();
19239 if len_val < 0 {
19240 let remaining = s.len().saturating_sub(start) as i64;
19242 (remaining + len_val).max(0) as usize
19243 } else {
19244 len_val as usize
19245 }
19246 } else {
19247 s.len().saturating_sub(start)
19248 };
19249 let end = start.saturating_add(len).min(s.len());
19250 let result = s.get(start..end).unwrap_or("").to_string();
19251 if let Some(rep) = replacement {
19252 let rep_s = self.eval_expr(rep)?.to_string();
19253 let mut new_s = String::new();
19254 new_s.push_str(&s[..start]);
19255 new_s.push_str(&rep_s);
19256 new_s.push_str(&s[end..]);
19257 self.assign_value(string, PerlValue::string(new_s))?;
19258 }
19259 Ok(PerlValue::string(result))
19260 }
19261
19262 pub(crate) fn eval_push_expr(
19263 &mut self,
19264 array: &Expr,
19265 values: &[Expr],
19266 line: usize,
19267 ) -> Result<PerlValue, FlowOrError> {
19268 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19269 for v in values {
19270 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19271 self.push_array_deref_value(aref.clone(), val, line)?;
19272 }
19273 let len = self.array_deref_len(aref, line)?;
19274 return Ok(PerlValue::integer(len));
19275 }
19276 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19277 if self.scope.is_array_frozen(&arr_name) {
19278 return Err(PerlError::runtime(
19279 format!("Modification of a frozen value: @{}", arr_name),
19280 line,
19281 )
19282 .into());
19283 }
19284 for v in values {
19285 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19286 if let Some(items) = val.as_array_vec() {
19287 for item in items {
19288 self.scope
19289 .push_to_array(&arr_name, item)
19290 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19291 }
19292 } else {
19293 self.scope
19294 .push_to_array(&arr_name, val)
19295 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19296 }
19297 }
19298 let len = self.scope.array_len(&arr_name);
19299 Ok(PerlValue::integer(len as i64))
19300 }
19301
19302 pub(crate) fn eval_pop_expr(
19303 &mut self,
19304 array: &Expr,
19305 line: usize,
19306 ) -> Result<PerlValue, FlowOrError> {
19307 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19308 return self.pop_array_deref(aref, line);
19309 }
19310 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19311 self.scope
19312 .pop_from_array(&arr_name)
19313 .map_err(|e| FlowOrError::Error(e.at_line(line)))
19314 }
19315
19316 pub(crate) fn eval_shift_expr(
19317 &mut self,
19318 array: &Expr,
19319 line: usize,
19320 ) -> Result<PerlValue, FlowOrError> {
19321 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19322 return self.shift_array_deref(aref, line);
19323 }
19324 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19325 self.scope
19326 .shift_from_array(&arr_name)
19327 .map_err(|e| FlowOrError::Error(e.at_line(line)))
19328 }
19329
19330 pub(crate) fn eval_unshift_expr(
19331 &mut self,
19332 array: &Expr,
19333 values: &[Expr],
19334 line: usize,
19335 ) -> Result<PerlValue, FlowOrError> {
19336 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19337 let mut vals = Vec::new();
19338 for v in values {
19339 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19340 if let Some(items) = val.as_array_vec() {
19341 vals.extend(items);
19342 } else {
19343 vals.push(val);
19344 }
19345 }
19346 let len = self.unshift_array_deref_multi(aref, vals, line)?;
19347 return Ok(PerlValue::integer(len));
19348 }
19349 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19350 let mut vals = Vec::new();
19351 for v in values {
19352 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19353 if let Some(items) = val.as_array_vec() {
19354 vals.extend(items);
19355 } else {
19356 vals.push(val);
19357 }
19358 }
19359 let arr = self
19360 .scope
19361 .get_array_mut(&arr_name)
19362 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19363 for (i, v) in vals.into_iter().enumerate() {
19364 arr.insert(i, v);
19365 }
19366 let len = arr.len();
19367 Ok(PerlValue::integer(len as i64))
19368 }
19369
19370 pub(crate) fn push_array_deref_value(
19372 &mut self,
19373 arr_ref: PerlValue,
19374 val: PerlValue,
19375 line: usize,
19376 ) -> Result<(), FlowOrError> {
19377 let val = self.scope.resolve_container_binding_ref(val);
19380 if let Some(r) = arr_ref.as_array_ref() {
19381 let mut w = r.write();
19382 if let Some(items) = val.as_array_vec() {
19383 w.extend(items.iter().cloned());
19384 } else {
19385 w.push(val);
19386 }
19387 return Ok(());
19388 }
19389 if let Some(name) = arr_ref.as_array_binding_name() {
19390 if let Some(items) = val.as_array_vec() {
19391 for item in items {
19392 self.scope
19393 .push_to_array(&name, item)
19394 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19395 }
19396 } else {
19397 self.scope
19398 .push_to_array(&name, val)
19399 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19400 }
19401 return Ok(());
19402 }
19403 if let Some(s) = arr_ref.as_str() {
19404 if self.strict_refs {
19405 return Err(PerlError::runtime(
19406 format!(
19407 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19408 s
19409 ),
19410 line,
19411 )
19412 .into());
19413 }
19414 let name = s.to_string();
19415 if let Some(items) = val.as_array_vec() {
19416 for item in items {
19417 self.scope
19418 .push_to_array(&name, item)
19419 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19420 }
19421 } else {
19422 self.scope
19423 .push_to_array(&name, val)
19424 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19425 }
19426 return Ok(());
19427 }
19428 Err(PerlError::runtime("push argument is not an ARRAY reference", line).into())
19429 }
19430
19431 pub(crate) fn array_deref_len(
19432 &self,
19433 arr_ref: PerlValue,
19434 line: usize,
19435 ) -> Result<i64, FlowOrError> {
19436 if let Some(r) = arr_ref.as_array_ref() {
19437 return Ok(r.read().len() as i64);
19438 }
19439 if let Some(name) = arr_ref.as_array_binding_name() {
19440 return Ok(self.scope.array_len(&name) as i64);
19441 }
19442 if let Some(s) = arr_ref.as_str() {
19443 if self.strict_refs {
19444 return Err(PerlError::runtime(
19445 format!(
19446 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19447 s
19448 ),
19449 line,
19450 )
19451 .into());
19452 }
19453 return Ok(self.scope.array_len(&s) as i64);
19454 }
19455 Err(PerlError::runtime("argument is not an ARRAY reference", line).into())
19456 }
19457
19458 pub(crate) fn pop_array_deref(
19459 &mut self,
19460 arr_ref: PerlValue,
19461 line: usize,
19462 ) -> Result<PerlValue, FlowOrError> {
19463 if let Some(r) = arr_ref.as_array_ref() {
19464 let mut w = r.write();
19465 return Ok(w.pop().unwrap_or(PerlValue::UNDEF));
19466 }
19467 if let Some(name) = arr_ref.as_array_binding_name() {
19468 return self
19469 .scope
19470 .pop_from_array(&name)
19471 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19472 }
19473 if let Some(s) = arr_ref.as_str() {
19474 if self.strict_refs {
19475 return Err(PerlError::runtime(
19476 format!(
19477 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19478 s
19479 ),
19480 line,
19481 )
19482 .into());
19483 }
19484 return self
19485 .scope
19486 .pop_from_array(&s)
19487 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19488 }
19489 Err(PerlError::runtime("pop argument is not an ARRAY reference", line).into())
19490 }
19491
19492 pub(crate) fn shift_array_deref(
19493 &mut self,
19494 arr_ref: PerlValue,
19495 line: usize,
19496 ) -> Result<PerlValue, FlowOrError> {
19497 if let Some(r) = arr_ref.as_array_ref() {
19498 let mut w = r.write();
19499 return Ok(if w.is_empty() {
19500 PerlValue::UNDEF
19501 } else {
19502 w.remove(0)
19503 });
19504 }
19505 if let Some(name) = arr_ref.as_array_binding_name() {
19506 return self
19507 .scope
19508 .shift_from_array(&name)
19509 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19510 }
19511 if let Some(s) = arr_ref.as_str() {
19512 if self.strict_refs {
19513 return Err(PerlError::runtime(
19514 format!(
19515 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19516 s
19517 ),
19518 line,
19519 )
19520 .into());
19521 }
19522 return self
19523 .scope
19524 .shift_from_array(&s)
19525 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19526 }
19527 Err(PerlError::runtime("shift argument is not an ARRAY reference", line).into())
19528 }
19529
19530 pub(crate) fn unshift_array_deref_multi(
19531 &mut self,
19532 arr_ref: PerlValue,
19533 vals: Vec<PerlValue>,
19534 line: usize,
19535 ) -> Result<i64, FlowOrError> {
19536 let mut flat: Vec<PerlValue> = Vec::new();
19537 for v in vals {
19538 if let Some(items) = v.as_array_vec() {
19539 flat.extend(items);
19540 } else {
19541 flat.push(v);
19542 }
19543 }
19544 if let Some(r) = arr_ref.as_array_ref() {
19545 let mut w = r.write();
19546 for (i, v) in flat.into_iter().enumerate() {
19547 w.insert(i, v);
19548 }
19549 return Ok(w.len() as i64);
19550 }
19551 if let Some(name) = arr_ref.as_array_binding_name() {
19552 let arr = self
19553 .scope
19554 .get_array_mut(&name)
19555 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19556 for (i, v) in flat.into_iter().enumerate() {
19557 arr.insert(i, v);
19558 }
19559 return Ok(arr.len() as i64);
19560 }
19561 if let Some(s) = arr_ref.as_str() {
19562 if self.strict_refs {
19563 return Err(PerlError::runtime(
19564 format!(
19565 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19566 s
19567 ),
19568 line,
19569 )
19570 .into());
19571 }
19572 let name = s.to_string();
19573 let arr = self
19574 .scope
19575 .get_array_mut(&name)
19576 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19577 for (i, v) in flat.into_iter().enumerate() {
19578 arr.insert(i, v);
19579 }
19580 return Ok(arr.len() as i64);
19581 }
19582 Err(PerlError::runtime("unshift argument is not an ARRAY reference", line).into())
19583 }
19584
19585 pub(crate) fn splice_array_deref(
19588 &mut self,
19589 aref: PerlValue,
19590 offset_val: PerlValue,
19591 length_val: PerlValue,
19592 rep_vals: Vec<PerlValue>,
19593 line: usize,
19594 ) -> Result<PerlValue, FlowOrError> {
19595 let ctx = self.wantarray_kind;
19596 if let Some(r) = aref.as_array_ref() {
19597 let arr_len = r.read().len();
19598 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
19599 let mut w = r.write();
19600 let removed: Vec<PerlValue> = w.drain(off..end).collect();
19601 for (i, v) in rep_vals.into_iter().enumerate() {
19602 w.insert(off + i, v);
19603 }
19604 return Ok(match ctx {
19605 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
19606 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
19607 });
19608 }
19609 if let Some(name) = aref.as_array_binding_name() {
19610 let arr_len = self.scope.array_len(&name);
19611 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
19612 let removed = self
19613 .scope
19614 .splice_in_place(&name, off, end, rep_vals)
19615 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19616 return Ok(match ctx {
19617 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
19618 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
19619 });
19620 }
19621 if let Some(s) = aref.as_str() {
19622 if self.strict_refs {
19623 return Err(PerlError::runtime(
19624 format!(
19625 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19626 s
19627 ),
19628 line,
19629 )
19630 .into());
19631 }
19632 let arr_len = self.scope.array_len(&s);
19633 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
19634 let removed = self
19635 .scope
19636 .splice_in_place(&s, off, end, rep_vals)
19637 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19638 return Ok(match ctx {
19639 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
19640 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
19641 });
19642 }
19643 Err(PerlError::runtime("splice argument is not an ARRAY reference", line).into())
19644 }
19645
19646 pub(crate) fn eval_splice_expr(
19647 &mut self,
19648 array: &Expr,
19649 offset: Option<&Expr>,
19650 length: Option<&Expr>,
19651 replacement: &[Expr],
19652 ctx: WantarrayCtx,
19653 line: usize,
19654 ) -> Result<PerlValue, FlowOrError> {
19655 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19656 let offset_val = if let Some(o) = offset {
19657 self.eval_expr(o)?
19658 } else {
19659 PerlValue::integer(0)
19660 };
19661 let length_val = if let Some(l) = length {
19662 self.eval_expr(l)?
19663 } else {
19664 PerlValue::UNDEF
19665 };
19666 let mut rep_vals = Vec::new();
19667 for r in replacement {
19668 rep_vals.push(self.eval_expr(r)?);
19669 }
19670 let saved = self.wantarray_kind;
19671 self.wantarray_kind = ctx;
19672 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
19673 self.wantarray_kind = saved;
19674 return out;
19675 }
19676 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19677 let arr_len = self.scope.array_len(&arr_name);
19678 let offset_val = if let Some(o) = offset {
19679 self.eval_expr(o)?
19680 } else {
19681 PerlValue::integer(0)
19682 };
19683 let length_val = if let Some(l) = length {
19684 self.eval_expr(l)?
19685 } else {
19686 PerlValue::UNDEF
19687 };
19688 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
19689 let mut rep_vals = Vec::new();
19690 for r in replacement {
19691 rep_vals.push(self.eval_expr(r)?);
19692 }
19693 let removed = self
19694 .scope
19695 .splice_in_place(&arr_name, off, end, rep_vals)
19696 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19697 Ok(match ctx {
19698 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
19699 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
19700 })
19701 }
19702
19703 pub(crate) fn keys_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
19705 if let Some(h) = val.as_hash_map() {
19706 Ok(PerlValue::array(
19707 h.keys().map(|k| PerlValue::string(k.clone())).collect(),
19708 ))
19709 } else if let Some(r) = val.as_hash_ref() {
19710 Ok(PerlValue::array(
19711 r.read()
19712 .keys()
19713 .map(|k| PerlValue::string(k.clone()))
19714 .collect(),
19715 ))
19716 } else {
19717 Err(PerlError::runtime("keys requires hash", line).into())
19718 }
19719 }
19720
19721 pub(crate) fn eval_keys_expr(
19722 &mut self,
19723 expr: &Expr,
19724 line: usize,
19725 ) -> Result<PerlValue, FlowOrError> {
19726 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
19729 Self::keys_from_value(val, line)
19730 }
19731
19732 pub(crate) fn values_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
19734 if let Some(h) = val.as_hash_map() {
19735 Ok(PerlValue::array(h.values().cloned().collect()))
19736 } else if let Some(r) = val.as_hash_ref() {
19737 Ok(PerlValue::array(r.read().values().cloned().collect()))
19738 } else {
19739 Err(PerlError::runtime("values requires hash", line).into())
19740 }
19741 }
19742
19743 pub(crate) fn eval_values_expr(
19744 &mut self,
19745 expr: &Expr,
19746 line: usize,
19747 ) -> Result<PerlValue, FlowOrError> {
19748 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
19749 Self::values_from_value(val, line)
19750 }
19751
19752 pub(crate) fn eval_delete_operand(
19753 &mut self,
19754 expr: &Expr,
19755 line: usize,
19756 ) -> Result<PerlValue, FlowOrError> {
19757 match &expr.kind {
19758 ExprKind::HashElement { hash, key } => {
19759 let k = self.eval_expr(key)?.to_string();
19760 self.touch_env_hash(hash);
19761 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
19762 let class = obj
19763 .as_blessed_ref()
19764 .map(|b| b.class.clone())
19765 .unwrap_or_default();
19766 let full = format!("{}::DELETE", class);
19767 if let Some(sub) = self.subs.get(&full).cloned() {
19768 return self.call_sub(
19769 &sub,
19770 vec![obj, PerlValue::string(k)],
19771 WantarrayCtx::Scalar,
19772 line,
19773 );
19774 }
19775 }
19776 self.scope
19777 .delete_hash_element(hash, &k)
19778 .map_err(|e| FlowOrError::Error(e.at_line(line)))
19779 }
19780 ExprKind::ArrayElement { array, index } => {
19781 self.check_strict_array_var(array, line)?;
19782 let idx = self.eval_expr(index)?.to_int();
19783 let aname = self.stash_array_name_for_package(array);
19784 self.scope
19785 .delete_array_element(&aname, idx)
19786 .map_err(|e| FlowOrError::Error(e.at_line(line)))
19787 }
19788 ExprKind::ArrowDeref {
19789 expr: inner,
19790 index,
19791 kind: DerefKind::Hash,
19792 } => {
19793 let k = self.eval_expr(index)?.to_string();
19794 let container = self.eval_expr(inner)?;
19795 self.delete_arrow_hash_element(container, &k, line)
19796 .map_err(Into::into)
19797 }
19798 ExprKind::ArrowDeref {
19799 expr: inner,
19800 index,
19801 kind: DerefKind::Array,
19802 } => {
19803 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
19804 return Err(PerlError::runtime(
19805 "delete on array element needs scalar subscript",
19806 line,
19807 )
19808 .into());
19809 }
19810 let container = self.eval_expr(inner)?;
19811 let idx = self.eval_expr(index)?.to_int();
19812 self.delete_arrow_array_element(container, idx, line)
19813 .map_err(Into::into)
19814 }
19815 _ => Err(PerlError::runtime("delete requires hash or array element", line).into()),
19816 }
19817 }
19818
19819 fn eval_expr_exists_mode(&mut self, expr: &Expr) -> Result<PerlValue, FlowOrError> {
19825 match &expr.kind {
19826 ExprKind::ArrowDeref {
19827 expr: inner,
19828 index,
19829 kind: DerefKind::Hash,
19830 } => {
19831 let inner_val = self.eval_expr_exists_mode(inner)?;
19832 if inner_val.is_undef() {
19833 return Ok(PerlValue::UNDEF);
19834 }
19835 if let Some(r) = inner_val.as_hash_ref() {
19836 let k = self.eval_expr(index)?.to_string();
19837 return Ok(r.read().get(&k).cloned().unwrap_or(PerlValue::UNDEF));
19838 }
19839 if let Some(b) = inner_val.as_blessed_ref() {
19840 let data = b.data.read();
19841 if let Some(r) = data.as_hash_ref() {
19842 let k = self.eval_expr(index)?.to_string();
19843 return Ok(r.read().get(&k).cloned().unwrap_or(PerlValue::UNDEF));
19844 }
19845 }
19846 if let Some(s) = inner_val.as_struct_inst() {
19850 let k = self.eval_expr(index)?.to_string();
19851 if let Some(idx) = s.def.field_index(&k) {
19852 return Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF));
19853 }
19854 return Ok(PerlValue::UNDEF);
19855 }
19856 if let Some(c) = inner_val.as_class_inst() {
19857 let k = self.eval_expr(index)?.to_string();
19858 if let Some(idx) = c.def.field_index(&k) {
19859 return Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF));
19860 }
19861 return Ok(PerlValue::UNDEF);
19862 }
19863 Ok(PerlValue::UNDEF)
19864 }
19865 ExprKind::ArrowDeref {
19866 expr: inner,
19867 index,
19868 kind: DerefKind::Array,
19869 } => {
19870 let inner_val = self.eval_expr_exists_mode(inner)?;
19871 if inner_val.is_undef() {
19872 return Ok(PerlValue::UNDEF);
19873 }
19874 if let Some(r) = inner_val.as_array_ref() {
19875 let idx = self.eval_expr(index)?.to_int();
19876 let arr = r.read();
19877 let i = if idx < 0 {
19878 (arr.len() as i64 + idx).max(0) as usize
19879 } else {
19880 idx as usize
19881 };
19882 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
19883 }
19884 Ok(PerlValue::UNDEF)
19885 }
19886 _ => self.eval_expr(expr),
19887 }
19888 }
19889
19890 pub(crate) fn eval_exists_operand(
19891 &mut self,
19892 expr: &Expr,
19893 line: usize,
19894 ) -> Result<PerlValue, FlowOrError> {
19895 match &expr.kind {
19896 ExprKind::HashElement { hash, key } => {
19897 let k = self.eval_expr(key)?.to_string();
19898 self.touch_env_hash(hash);
19899 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
19900 let class = obj
19901 .as_blessed_ref()
19902 .map(|b| b.class.clone())
19903 .unwrap_or_default();
19904 let full = format!("{}::EXISTS", class);
19905 if let Some(sub) = self.subs.get(&full).cloned() {
19906 return self.call_sub(
19907 &sub,
19908 vec![obj, PerlValue::string(k)],
19909 WantarrayCtx::Scalar,
19910 line,
19911 );
19912 }
19913 }
19914 Ok(PerlValue::integer(
19915 if self.scope.exists_hash_element(hash, &k) {
19916 1
19917 } else {
19918 0
19919 },
19920 ))
19921 }
19922 ExprKind::ArrayElement { array, index } => {
19923 self.check_strict_array_var(array, line)?;
19924 let idx = self.eval_expr(index)?.to_int();
19925 let aname = self.stash_array_name_for_package(array);
19926 Ok(PerlValue::integer(
19927 if self.scope.exists_array_element(&aname, idx) {
19928 1
19929 } else {
19930 0
19931 },
19932 ))
19933 }
19934 ExprKind::ArrowDeref {
19935 expr: inner,
19936 index,
19937 kind: DerefKind::Hash,
19938 } => {
19939 let k = self.eval_expr(index)?.to_string();
19940 let container = match self.eval_expr_exists_mode(inner) {
19945 Ok(v) => v,
19946 Err(_) => return Ok(PerlValue::integer(0)),
19947 };
19948 if container.is_undef() {
19949 return Ok(PerlValue::integer(0));
19950 }
19951 let yes = self.exists_arrow_hash_element(container, &k, line)?;
19952 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
19953 }
19954 ExprKind::ArrowDeref {
19955 expr: inner,
19956 index,
19957 kind: DerefKind::Array,
19958 } => {
19959 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
19960 return Err(PerlError::runtime(
19961 "exists on array element needs scalar subscript",
19962 line,
19963 )
19964 .into());
19965 }
19966 let container = match self.eval_expr_exists_mode(inner) {
19967 Ok(v) => v,
19968 Err(_) => return Ok(PerlValue::integer(0)),
19969 };
19970 if container.is_undef() {
19971 return Ok(PerlValue::integer(0));
19972 }
19973 let idx = self.eval_expr(index)?.to_int();
19974 let yes = self.exists_arrow_array_element(container, idx, line)?;
19975 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
19976 }
19977 _ => Err(PerlError::runtime("exists requires hash or array element", line).into()),
19978 }
19979 }
19980
19981 pub(crate) fn eval_pmap_remote(
19989 &mut self,
19990 cluster_pv: PerlValue,
19991 list_pv: PerlValue,
19992 show_progress: bool,
19993 block: &Block,
19994 flat_outputs: bool,
19995 line: usize,
19996 ) -> Result<PerlValue, FlowOrError> {
19997 let Some(cluster) = cluster_pv.as_remote_cluster() else {
19998 return Err(PerlError::runtime("pmap_on: expected cluster(...) value", line).into());
19999 };
20000 let items = list_pv.to_list();
20001 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20002 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
20003 return Err(PerlError::runtime(
20004 "pmap_on: mysync/atomic capture is not supported for remote workers",
20005 line,
20006 )
20007 .into());
20008 }
20009 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
20010 .map_err(|e| PerlError::runtime(e, line))?;
20011 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
20012 let block_src = crate::fmt::format_block(block);
20013 let item_jsons =
20014 crate::cluster::perl_items_to_json(&items).map_err(|e| PerlError::runtime(e, line))?;
20015
20016 let pmap_progress = PmapProgress::new(show_progress, items.len());
20019 let result_values =
20020 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
20021 .map_err(|e| PerlError::runtime(format!("pmap_on remote: {e}"), line))?;
20022 for _ in 0..result_values.len() {
20023 pmap_progress.tick();
20024 }
20025 pmap_progress.finish();
20026
20027 if flat_outputs {
20028 let flattened: Vec<PerlValue> = result_values
20029 .into_iter()
20030 .flat_map(|v| v.map_flatten_outputs(true))
20031 .collect();
20032 Ok(PerlValue::array(flattened))
20033 } else {
20034 Ok(PerlValue::array(result_values))
20035 }
20036 }
20037
20038 pub(crate) fn eval_par_lines_expr(
20040 &mut self,
20041 path: &Expr,
20042 callback: &Expr,
20043 progress: Option<&Expr>,
20044 line: usize,
20045 ) -> Result<PerlValue, FlowOrError> {
20046 let show_progress = progress
20047 .map(|p| self.eval_expr(p))
20048 .transpose()?
20049 .map(|v| v.is_true())
20050 .unwrap_or(false);
20051 let path_s = self.eval_expr(path)?.to_string();
20052 let cb_val = self.eval_expr(callback)?;
20053 let sub = if let Some(s) = cb_val.as_code_ref() {
20054 s
20055 } else {
20056 return Err(PerlError::runtime(
20057 "par_lines: second argument must be a code reference",
20058 line,
20059 )
20060 .into());
20061 };
20062 let subs = self.subs.clone();
20063 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20064 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
20065 FlowOrError::Error(PerlError::runtime(format!("par_lines: {}", e), line))
20066 })?;
20067 let mmap = unsafe {
20068 memmap2::Mmap::map(&file).map_err(|e| {
20069 FlowOrError::Error(PerlError::runtime(format!("par_lines: mmap: {}", e), line))
20070 })?
20071 };
20072 let data: &[u8] = &mmap;
20073 if data.is_empty() {
20074 return Ok(PerlValue::UNDEF);
20075 }
20076 let line_total = crate::par_lines::line_count_bytes(data);
20077 let pmap_progress = PmapProgress::new(show_progress, line_total);
20078 if self.num_threads == 0 {
20079 self.num_threads = rayon::current_num_threads();
20080 }
20081 let num_chunks = self.num_threads.saturating_mul(8).max(1);
20082 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
20083 chunks.into_par_iter().try_for_each(|(start, end)| {
20084 let slice = &data[start..end];
20085 let mut s = 0usize;
20086 while s < slice.len() {
20087 let e = slice[s..]
20088 .iter()
20089 .position(|&b| b == b'\n')
20090 .map(|p| s + p)
20091 .unwrap_or(slice.len());
20092 let line_bytes = &slice[s..e];
20093 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
20094 let mut local_interp = VMHelper::new();
20095 local_interp.subs = subs.clone();
20096 local_interp.scope.restore_capture(&scope_capture);
20097 local_interp
20098 .scope
20099 .restore_atomics(&atomic_arrays, &atomic_hashes);
20100 local_interp.enable_parallel_guard();
20101 local_interp.scope.set_topic(PerlValue::string(line_str));
20102 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
20103 Ok(_) => {}
20104 Err(e) => return Err(e),
20105 }
20106 pmap_progress.tick();
20107 if e >= slice.len() {
20108 break;
20109 }
20110 s = e + 1;
20111 }
20112 Ok(())
20113 })?;
20114 pmap_progress.finish();
20115 Ok(PerlValue::UNDEF)
20116 }
20117
20118 pub(crate) fn eval_par_walk_expr(
20120 &mut self,
20121 path: &Expr,
20122 callback: &Expr,
20123 progress: Option<&Expr>,
20124 line: usize,
20125 ) -> Result<PerlValue, FlowOrError> {
20126 let show_progress = progress
20127 .map(|p| self.eval_expr(p))
20128 .transpose()?
20129 .map(|v| v.is_true())
20130 .unwrap_or(false);
20131 let path_val = self.eval_expr(path)?;
20132 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
20133 arr.into_iter()
20134 .map(|v| PathBuf::from(v.to_string()))
20135 .collect()
20136 } else {
20137 vec![PathBuf::from(path_val.to_string())]
20138 };
20139 let cb_val = self.eval_expr(callback)?;
20140 let sub = if let Some(s) = cb_val.as_code_ref() {
20141 s
20142 } else {
20143 return Err(PerlError::runtime(
20144 "par_walk: second argument must be a code reference",
20145 line,
20146 )
20147 .into());
20148 };
20149 let subs = self.subs.clone();
20150 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20151
20152 if show_progress {
20153 let paths = crate::par_walk::collect_paths(&roots);
20154 let pmap_progress = PmapProgress::new(true, paths.len());
20155 paths.into_par_iter().try_for_each(|p| {
20156 let s = p.to_string_lossy().into_owned();
20157 let mut local_interp = VMHelper::new();
20158 local_interp.subs = subs.clone();
20159 local_interp.scope.restore_capture(&scope_capture);
20160 local_interp
20161 .scope
20162 .restore_atomics(&atomic_arrays, &atomic_hashes);
20163 local_interp.enable_parallel_guard();
20164 local_interp.scope.set_topic(PerlValue::string(s));
20165 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
20166 Ok(_) => {}
20167 Err(e) => return Err(e),
20168 }
20169 pmap_progress.tick();
20170 Ok(())
20171 })?;
20172 pmap_progress.finish();
20173 } else {
20174 for r in &roots {
20175 par_walk_recursive(
20176 r.as_path(),
20177 &sub,
20178 &subs,
20179 &scope_capture,
20180 &atomic_arrays,
20181 &atomic_hashes,
20182 line,
20183 )?;
20184 }
20185 }
20186 Ok(PerlValue::UNDEF)
20187 }
20188
20189 pub(crate) fn builtin_par_sed(
20191 &mut self,
20192 args: &[PerlValue],
20193 line: usize,
20194 has_progress: bool,
20195 ) -> PerlResult<PerlValue> {
20196 let show_progress = if has_progress {
20197 args.last().map(|v| v.is_true()).unwrap_or(false)
20198 } else {
20199 false
20200 };
20201 let slice = if has_progress {
20202 &args[..args.len().saturating_sub(1)]
20203 } else {
20204 args
20205 };
20206 if slice.len() < 3 {
20207 return Err(PerlError::runtime(
20208 "par_sed: need pattern, replacement, and at least one file path",
20209 line,
20210 ));
20211 }
20212 let pat_val = &slice[0];
20213 let repl = slice[1].to_string();
20214 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
20215
20216 let re = if let Some(rx) = pat_val.as_regex() {
20217 rx
20218 } else {
20219 let pattern = pat_val.to_string();
20220 match self.compile_regex(&pattern, "g", line) {
20221 Ok(r) => r,
20222 Err(FlowOrError::Error(e)) => return Err(e),
20223 Err(FlowOrError::Flow(f)) => {
20224 return Err(PerlError::runtime(format!("par_sed: {:?}", f), line))
20225 }
20226 }
20227 };
20228
20229 let pmap = PmapProgress::new(show_progress, files.len());
20230 let touched = AtomicUsize::new(0);
20231 files.par_iter().try_for_each(|path| {
20232 let content = read_file_text_perl_compat(path)
20233 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
20234 let new_s = re.replace_all(&content, &repl);
20235 if new_s != content {
20236 std::fs::write(path, new_s.as_bytes())
20237 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
20238 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
20239 }
20240 pmap.tick();
20241 Ok(())
20242 })?;
20243 pmap.finish();
20244 Ok(PerlValue::integer(
20245 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
20246 ))
20247 }
20248
20249 pub(crate) fn eval_pwatch_expr(
20251 &mut self,
20252 path: &Expr,
20253 callback: &Expr,
20254 line: usize,
20255 ) -> Result<PerlValue, FlowOrError> {
20256 let pattern_s = self.eval_expr(path)?.to_string();
20257 let cb_val = self.eval_expr(callback)?;
20258 let sub = if let Some(s) = cb_val.as_code_ref() {
20259 s
20260 } else {
20261 return Err(PerlError::runtime(
20262 "pwatch: second argument must be a code reference",
20263 line,
20264 )
20265 .into());
20266 };
20267 let subs = self.subs.clone();
20268 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20269 crate::pwatch::run_pwatch(
20270 &pattern_s,
20271 sub,
20272 subs,
20273 scope_capture,
20274 atomic_arrays,
20275 atomic_hashes,
20276 line,
20277 )
20278 .map_err(FlowOrError::Error)
20279 }
20280
20281 fn interpolate_replacement_string(&self, replacement: &str) -> String {
20283 let mut out = String::with_capacity(replacement.len());
20284 let chars: Vec<char> = replacement.chars().collect();
20285 let mut i = 0;
20286 while i < chars.len() {
20287 if chars[i] == '\\' && i + 1 < chars.len() {
20288 out.push(chars[i]);
20289 out.push(chars[i + 1]);
20290 i += 2;
20291 continue;
20292 }
20293 if chars[i] == '$' && i + 1 < chars.len() {
20294 let start = i;
20295 i += 1;
20296 if chars[i].is_ascii_digit() {
20297 out.push('$');
20298 while i < chars.len() && chars[i].is_ascii_digit() {
20299 out.push(chars[i]);
20300 i += 1;
20301 }
20302 continue;
20303 }
20304 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
20305 out.push('$');
20306 out.push(chars[i]);
20307 i += 1;
20308 continue;
20309 }
20310 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
20311 out.push('$');
20312 continue;
20313 }
20314 let mut name = String::new();
20315 if chars[i] == '{' {
20316 i += 1;
20317 while i < chars.len() && chars[i] != '}' {
20318 name.push(chars[i]);
20319 i += 1;
20320 }
20321 if i < chars.len() {
20322 i += 1;
20323 }
20324 } else {
20325 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
20326 name.push(chars[i]);
20327 i += 1;
20328 }
20329 }
20330 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
20331 let val = self.scope.get_scalar(&name);
20332 out.push_str(&val.to_string());
20333 } else if !name.is_empty() {
20334 out.push_str(&replacement[start..i]);
20335 } else {
20336 out.push('$');
20337 }
20338 continue;
20339 }
20340 out.push(chars[i]);
20341 i += 1;
20342 }
20343 out
20344 }
20345
20346 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
20348 let mut out = String::with_capacity(pattern.len());
20349 let chars: Vec<char> = pattern.chars().collect();
20350 let mut i = 0;
20351 while i < chars.len() {
20352 if chars[i] == '\\' && i + 1 < chars.len() {
20353 out.push(chars[i]);
20355 out.push(chars[i + 1]);
20356 i += 2;
20357 continue;
20358 }
20359 if chars[i] == '$' && i + 1 < chars.len() {
20360 i += 1;
20361 if i >= chars.len()
20363 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
20364 {
20365 out.push('$');
20366 continue;
20367 }
20368 let mut name = String::new();
20369 if chars[i] == '{' {
20370 i += 1;
20371 while i < chars.len() && chars[i] != '}' {
20372 name.push(chars[i]);
20373 i += 1;
20374 }
20375 if i < chars.len() {
20376 i += 1;
20377 } } else {
20379 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
20380 name.push(chars[i]);
20381 i += 1;
20382 }
20383 }
20384 if !name.is_empty() {
20385 let val = self.scope.get_scalar(&name);
20386 out.push_str(&val.to_string());
20387 } else {
20388 out.push('$');
20389 }
20390 continue;
20391 }
20392 out.push(chars[i]);
20393 i += 1;
20394 }
20395 out
20396 }
20397
20398 pub(crate) fn compile_regex(
20399 &mut self,
20400 pattern: &str,
20401 flags: &str,
20402 line: usize,
20403 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
20404 let pattern = if pattern.contains('$') || pattern.contains('@') {
20406 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
20407 } else {
20408 std::borrow::Cow::Borrowed(pattern)
20409 };
20410 let pattern = pattern.as_ref();
20411 let multiline = self.multiline_match;
20414 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
20415 if lp == pattern && lf == flags && *lm == multiline {
20416 return Ok(lr.clone());
20417 }
20418 }
20419 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
20421 if let Some(cached) = self.regex_cache.get(&key) {
20422 self.regex_last = Some((
20423 pattern.to_string(),
20424 flags.to_string(),
20425 multiline,
20426 cached.clone(),
20427 ));
20428 return Ok(cached.clone());
20429 }
20430 let expanded = expand_perl_regex_quotemeta(pattern);
20431 let expanded = expand_perl_regex_octal_escapes(&expanded);
20432 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
20433 let mut re_str = String::new();
20434 if flags.contains('i') {
20435 re_str.push_str("(?i)");
20436 }
20437 if flags.contains('s') {
20438 re_str.push_str("(?s)");
20439 }
20440 if flags.contains('m') {
20441 re_str.push_str("(?m)");
20442 }
20443 if flags.contains('x') {
20444 re_str.push_str("(?x)");
20445 }
20446 if multiline {
20448 re_str.push_str("(?s)");
20449 }
20450 re_str.push_str(&expanded);
20451 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
20452 FlowOrError::Error(PerlError::runtime(
20453 format!("Invalid regex /{}/: {}", pattern, e),
20454 line,
20455 ))
20456 })?;
20457 let arc = re;
20458 self.regex_last = Some((
20459 pattern.to_string(),
20460 flags.to_string(),
20461 multiline,
20462 arc.clone(),
20463 ));
20464 self.regex_cache.insert(key, arc.clone());
20465 Ok(arc)
20466 }
20467
20468 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
20470 if self.last_readline_handle.is_empty() {
20471 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
20472 }
20473 let n = *self
20474 .handle_line_numbers
20475 .get(&self.last_readline_handle)
20476 .unwrap_or(&0);
20477 if n <= 0 {
20478 return None;
20479 }
20480 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
20481 {
20482 return Some(("<>".to_string(), n));
20483 }
20484 if self.last_readline_handle == "STDIN" {
20485 return Some((self.last_stdin_die_bracket.clone(), n));
20486 }
20487 Some((format!("<{}>", self.last_readline_handle), n))
20488 }
20489
20490 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
20492 let mut s = format!(" at {} line {}", self.file, source_line);
20493 if let Some((bracket, n)) = self.die_warn_io_annotation() {
20494 s.push_str(&format!(", {} line {}.", bracket, n));
20495 } else {
20496 s.push('.');
20497 }
20498 s
20499 }
20500
20501 pub fn process_line(
20506 &mut self,
20507 line_str: &str,
20508 _program: &Program,
20509 is_last_input_line: bool,
20510 ) -> PerlResult<Option<String>> {
20511 let chunk = self
20512 .line_mode_chunk
20513 .as_ref()
20514 .expect("process_line called without compiled chunk — execute() must run first")
20515 .clone();
20516 crate::run_line_body(&chunk, self, line_str, is_last_input_line)
20517 }
20518}
20519
20520fn both_non_numeric_strings_iv(a: &PerlValue, b: &PerlValue) -> bool {
20524 if !a.is_string_like() || !b.is_string_like() {
20525 return false;
20526 }
20527 let sa = a.to_string();
20528 let sb = b.to_string();
20529 let looks = |s: &str| {
20530 let t = s.trim();
20531 !t.is_empty() && t.parse::<f64>().is_ok()
20532 };
20533 !looks(&sa) && !looks(&sb)
20534}
20535
20536fn par_walk_invoke_entry(
20537 path: &Path,
20538 sub: &Arc<PerlSub>,
20539 subs: &HashMap<String, Arc<PerlSub>>,
20540 scope_capture: &[(String, PerlValue)],
20541 atomic_arrays: &[(String, crate::scope::AtomicArray)],
20542 atomic_hashes: &[(String, crate::scope::AtomicHash)],
20543 line: usize,
20544) -> Result<(), FlowOrError> {
20545 let s = path.to_string_lossy().into_owned();
20546 let mut local_interp = VMHelper::new();
20547 local_interp.subs = subs.clone();
20548 local_interp.scope.restore_capture(scope_capture);
20549 local_interp
20550 .scope
20551 .restore_atomics(atomic_arrays, atomic_hashes);
20552 local_interp.enable_parallel_guard();
20553 local_interp.scope.set_topic(PerlValue::string(s));
20554 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
20555 Ok(())
20556}
20557
20558fn par_walk_recursive(
20559 path: &Path,
20560 sub: &Arc<PerlSub>,
20561 subs: &HashMap<String, Arc<PerlSub>>,
20562 scope_capture: &[(String, PerlValue)],
20563 atomic_arrays: &[(String, crate::scope::AtomicArray)],
20564 atomic_hashes: &[(String, crate::scope::AtomicHash)],
20565 line: usize,
20566) -> Result<(), FlowOrError> {
20567 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
20568 return par_walk_invoke_entry(
20569 path,
20570 sub,
20571 subs,
20572 scope_capture,
20573 atomic_arrays,
20574 atomic_hashes,
20575 line,
20576 );
20577 }
20578 if !path.is_dir() {
20579 return Ok(());
20580 }
20581 par_walk_invoke_entry(
20582 path,
20583 sub,
20584 subs,
20585 scope_capture,
20586 atomic_arrays,
20587 atomic_hashes,
20588 line,
20589 )?;
20590 let read = match std::fs::read_dir(path) {
20591 Ok(r) => r,
20592 Err(_) => return Ok(()),
20593 };
20594 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
20595 entries.par_iter().try_for_each(|e| {
20596 par_walk_recursive(
20597 &e.path(),
20598 sub,
20599 subs,
20600 scope_capture,
20601 atomic_arrays,
20602 atomic_hashes,
20603 line,
20604 )
20605 })?;
20606 Ok(())
20607}
20608
20609fn par_chunk_value(v: &PerlValue, n_threads: usize) -> Vec<PerlValue> {
20637 let n = n_threads.max(1);
20638 if let Some(s) = v.as_str() {
20640 let bytes = s.as_bytes();
20641 if bytes.len() < 16_384 || n < 2 {
20642 return vec![PerlValue::string(s)];
20643 }
20644 let target = bytes.len().div_ceil(n);
20645 let mut splits = vec![0usize];
20646 let mut cursor = target;
20647 while cursor < bytes.len() {
20648 while cursor < bytes.len() && (bytes[cursor] & 0xC0) == 0x80 {
20650 cursor += 1;
20651 }
20652 splits.push(cursor);
20653 cursor += target;
20654 }
20655 splits.push(bytes.len());
20656 return splits
20657 .windows(2)
20658 .map(|w| {
20659 let chunk = std::str::from_utf8(&bytes[w[0]..w[1]]).unwrap_or("");
20660 PerlValue::string(chunk.to_string())
20661 })
20662 .collect();
20663 }
20664 if let Some(arr) = v.as_array_vec() {
20666 if arr.len() < 32 || n < 2 {
20667 return vec![PerlValue::array(arr)];
20668 }
20669 let target = arr.len().div_ceil(n);
20670 let mut chunks = Vec::with_capacity(n);
20671 for slice in arr.chunks(target) {
20672 chunks.push(PerlValue::array(slice.to_vec()));
20673 }
20674 return chunks;
20675 }
20676 if let Some(arr_ref) = v.as_array_ref() {
20677 let arr = arr_ref.read().clone();
20678 if arr.len() < 32 || n < 2 {
20679 return vec![PerlValue::array(arr)];
20680 }
20681 let target = arr.len().div_ceil(n);
20682 let mut chunks = Vec::with_capacity(n);
20683 for slice in arr.chunks(target) {
20684 chunks.push(PerlValue::array(slice.to_vec()));
20685 }
20686 return chunks;
20687 }
20688 vec![v.clone()]
20690}
20691
20692fn par_reduce_auto_merge(chunks: Vec<PerlValue>) -> PerlValue {
20702 if chunks.is_empty() {
20703 return PerlValue::UNDEF;
20704 }
20705 let first = &chunks[0];
20706 if let Some(_h) = first.as_hash_ref() {
20708 let mut out: indexmap::IndexMap<String, f64> = indexmap::IndexMap::new();
20709 for chunk in &chunks {
20710 if let Some(hr) = chunk.as_hash_ref() {
20711 for (k, v) in hr.read().iter() {
20712 *out.entry(k.clone()).or_insert(0.0) += v.to_number();
20713 }
20714 }
20715 }
20716 let mut indexmap_out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
20719 for (k, v) in out {
20720 let pv = if v == v.trunc() && v.abs() < 1e15 {
20721 PerlValue::integer(v as i64)
20722 } else {
20723 PerlValue::float(v)
20724 };
20725 indexmap_out.insert(k, pv);
20726 }
20727 return PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(indexmap_out)));
20728 }
20729 if first.is_integer_like() || first.is_float_like() {
20731 let s: f64 = chunks.iter().map(|v| v.to_number()).sum();
20732 if s == s.trunc() && s.abs() < 1e15 {
20733 return PerlValue::integer(s as i64);
20734 }
20735 return PerlValue::float(s);
20736 }
20737 if first.as_array_vec().is_some() || first.as_array_ref().is_some() {
20739 let mut out = Vec::new();
20740 for v in &chunks {
20741 out.extend(v.map_flatten_outputs(true));
20742 }
20743 return PerlValue::array(out);
20744 }
20745 if first.is_string_like() {
20747 let mut out = String::new();
20748 for v in &chunks {
20749 out.push_str(&v.to_string());
20750 }
20751 return PerlValue::string(out);
20752 }
20753 PerlValue::array(chunks)
20755}
20756
20757fn perl_magic_str_inc(s: &str) -> Option<String> {
20760 if s.is_empty() {
20761 return Some("1".to_string());
20762 }
20763 let bytes = s.as_bytes();
20764 let mut i = 0;
20765 while i < bytes.len() && bytes[i].is_ascii_alphabetic() {
20766 i += 1;
20767 }
20768 let letters_end = i;
20769 while i < bytes.len() && bytes[i].is_ascii_digit() {
20770 i += 1;
20771 }
20772 if i != bytes.len() {
20773 return None;
20774 }
20775 if letters_end == 0 {
20776 return None;
20778 }
20779
20780 let mut result: Vec<u8> = bytes.to_vec();
20781 let mut carry = true;
20782 let mut idx = result.len();
20783
20784 while carry && idx > letters_end {
20786 idx -= 1;
20787 if result[idx] == b'9' {
20788 result[idx] = b'0';
20789 } else {
20791 result[idx] += 1;
20792 carry = false;
20793 }
20794 }
20795
20796 while carry && idx > 0 {
20798 idx -= 1;
20799 let c = result[idx];
20800 if c == b'z' {
20801 result[idx] = b'a';
20802 } else if c == b'Z' {
20803 result[idx] = b'A';
20804 } else {
20805 result[idx] += 1;
20806 carry = false;
20807 }
20808 }
20809
20810 if carry {
20812 let prepend = if bytes[0].is_ascii_uppercase() {
20813 b'A'
20814 } else {
20815 b'a'
20816 };
20817 let mut grown = Vec::with_capacity(result.len() + 1);
20818 grown.push(prepend);
20819 grown.extend_from_slice(&result);
20820 return String::from_utf8(grown).ok();
20821 }
20822
20823 String::from_utf8(result).ok()
20824}
20825
20826pub(crate) fn perl_inc(v: &PerlValue) -> PerlValue {
20830 if let Some(s) = v.as_str() {
20831 if let Some(new_s) = perl_magic_str_inc(&s) {
20832 return PerlValue::string(new_s);
20833 }
20834 }
20835 PerlValue::integer(v.to_int() + 1)
20836}
20837
20838fn perl_exponent_form(rust_repr: &str, upper: bool) -> String {
20839 let marker = if upper { 'E' } else { 'e' };
20840 if let Some(pos) = rust_repr.find(marker) {
20841 let (mantissa, after) = rust_repr.split_at(pos);
20842 let exp_part = &after[1..]; let (sign, digits) = match exp_part.chars().next() {
20844 Some('+') => ("+", &exp_part[1..]),
20845 Some('-') => ("-", &exp_part[1..]),
20846 _ => ("+", exp_part),
20847 };
20848 let padded = if digits.len() < 2 {
20849 format!("0{}", digits)
20850 } else {
20851 digits.to_string()
20852 };
20853 return format!("{}{}{}{}", mantissa, marker, sign, padded);
20854 }
20855 rust_repr.to_string()
20856}
20857
20858fn perl_hex_float(n: f64, upper: bool) -> String {
20862 if n.is_nan() {
20863 return if upper { "NAN" } else { "nan" }.to_string();
20864 }
20865 if n.is_infinite() {
20866 let sign = if n.is_sign_negative() { "-" } else { "" };
20867 let body = if upper { "INF" } else { "inf" };
20868 return format!("{}{}", sign, body);
20869 }
20870 let prefix = if upper { "0X" } else { "0x" };
20871 let p_letter = if upper { 'P' } else { 'p' };
20872 let bits = n.to_bits();
20873 let sign_bit = bits >> 63;
20874 let exp_bits = (bits >> 52) & 0x7FF;
20875 let mant_bits = bits & 0x000F_FFFF_FFFF_FFFF;
20876 let sign_str = if sign_bit == 1 { "-" } else { "" };
20877 if exp_bits == 0 && mant_bits == 0 {
20878 return format!("{}{}{}{}{}", sign_str, prefix, "0", p_letter, "+0");
20879 }
20880 let (lead_digit, exp_unbiased): (u64, i32) = if exp_bits == 0 {
20881 (0, -1022)
20883 } else {
20884 (1, (exp_bits as i32) - 1023)
20885 };
20886 let exp_sign = if exp_unbiased >= 0 { "+" } else { "-" };
20887 let exp_abs = exp_unbiased.unsigned_abs();
20888 if mant_bits == 0 {
20889 return format!(
20890 "{}{}{}{}{}{}",
20891 sign_str, prefix, lead_digit, p_letter, exp_sign, exp_abs
20892 );
20893 }
20894 let mant_hex = format!("{:013x}", mant_bits);
20896 let trimmed = mant_hex.trim_end_matches('0');
20897 let mant_str = if upper {
20898 trimmed.to_uppercase()
20899 } else {
20900 trimmed.to_string()
20901 };
20902 format!(
20903 "{}{}{}.{}{}{}{}",
20904 sign_str, prefix, lead_digit, mant_str, p_letter, exp_sign, exp_abs
20905 )
20906}
20907
20908fn perl_g_form(n: f64, prec: usize, upper: bool) -> String {
20911 let prec = prec.max(1);
20912 if !n.is_finite() {
20913 return if upper {
20914 format!("{}", n).to_uppercase()
20915 } else {
20916 format!("{}", n)
20917 };
20918 }
20919 let abs = n.abs();
20921 let x = if abs == 0.0 {
20922 0i32
20923 } else {
20924 abs.log10().floor() as i32
20925 };
20926 let use_e = x < -4 || x >= prec as i32;
20928 let formatted = if use_e {
20931 let raw = format!("{:.*e}", prec - 1, n);
20932 perl_exponent_form(&raw, false)
20933 } else {
20934 let f_prec = (prec as i32 - 1 - x).max(0) as usize;
20935 format!("{:.*}", f_prec, n)
20936 };
20937 let (mant, exp) = if let Some(pos) = formatted.find('e') {
20940 (formatted[..pos].to_string(), formatted[pos..].to_string())
20941 } else {
20942 (formatted.clone(), String::new())
20943 };
20944 let trimmed = if mant.contains('.') {
20945 let t = mant.trim_end_matches('0');
20946 let t = t.trim_end_matches('.');
20947 t.to_string()
20948 } else {
20949 mant
20950 };
20951 let combined = format!("{}{}", trimmed, exp);
20952 if upper {
20953 combined.replace('e', "E")
20954 } else {
20955 combined
20956 }
20957}
20958
20959pub(crate) fn perl_sprintf_format_full<F>(
20964 fmt: &str,
20965 args: &[PerlValue],
20966 string_for_s: &mut F,
20967) -> Result<(String, Vec<(PerlValue, i64)>), FlowOrError>
20968where
20969 F: FnMut(&PerlValue) -> Result<String, FlowOrError>,
20970{
20971 let mut pending_n: Vec<(PerlValue, i64)> = Vec::new();
20972 let mut result = String::new();
20973 let mut arg_idx = 0;
20974 let chars: Vec<char> = fmt.chars().collect();
20975 let mut i = 0;
20976
20977 let take_arg_int = |args: &[PerlValue], idx: &mut usize| -> i64 {
20979 let v = args.get(*idx).cloned().unwrap_or(PerlValue::UNDEF);
20980 *idx += 1;
20981 v.to_int()
20982 };
20983
20984 while i < chars.len() {
20985 if chars[i] == '%' {
20986 i += 1;
20987 if i >= chars.len() {
20988 break;
20989 }
20990 if chars[i] == '%' {
20991 result.push('%');
20992 i += 1;
20993 continue;
20994 }
20995
20996 let mut positional: Option<usize> = None;
21001 {
21002 let saved = i;
21003 let mut digits = String::new();
21004 let mut j = i;
21005 while j < chars.len() && chars[j].is_ascii_digit() {
21006 digits.push(chars[j]);
21007 j += 1;
21008 }
21009 if j < chars.len() && chars[j] == '$' && !digits.is_empty() {
21010 if let Ok(n) = digits.parse::<usize>() {
21011 if n >= 1 {
21012 positional = Some(n - 1);
21013 i = j + 1; }
21015 }
21016 }
21017 if positional.is_none() {
21018 i = saved;
21019 }
21020 }
21021
21022 let mut flags = String::new();
21024 while i < chars.len() && "-+ #0".contains(chars[i]) {
21025 flags.push(chars[i]);
21026 i += 1;
21027 }
21028 let mut vector_sep: Option<String> = None;
21032 if i < chars.len() && chars[i] == 'v' {
21033 vector_sep = Some(".".to_string());
21034 i += 1;
21035 } else if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == 'v' {
21036 let sep_arg = args.get(arg_idx).cloned().unwrap_or(PerlValue::UNDEF);
21037 arg_idx += 1;
21038 vector_sep = Some(sep_arg.to_string());
21039 i += 2;
21040 }
21041 let mut width = String::new();
21043 let mut left_align = flags.contains('-');
21044 if i < chars.len() && chars[i] == '*' {
21045 let n = take_arg_int(args, &mut arg_idx);
21046 if n < 0 {
21047 left_align = true;
21049 width = (-n).to_string();
21050 } else {
21051 width = n.to_string();
21052 }
21053 i += 1;
21054 } else {
21055 while i < chars.len() && chars[i].is_ascii_digit() {
21056 width.push(chars[i]);
21057 i += 1;
21058 }
21059 }
21060 let mut precision = String::new();
21062 if i < chars.len() && chars[i] == '.' {
21063 i += 1;
21064 if i < chars.len() && chars[i] == '*' {
21065 let n = take_arg_int(args, &mut arg_idx);
21066 precision = n.max(0).to_string();
21067 i += 1;
21068 } else {
21069 while i < chars.len() && chars[i].is_ascii_digit() {
21070 precision.push(chars[i]);
21071 i += 1;
21072 }
21073 if precision.is_empty() {
21075 precision = "0".to_string();
21076 }
21077 }
21078 }
21079 if i >= chars.len() {
21080 break;
21081 }
21082 let spec = chars[i];
21083 i += 1;
21084
21085 let arg = if let Some(idx) = positional {
21090 args.get(idx).cloned().unwrap_or(PerlValue::UNDEF)
21091 } else {
21092 let v = args.get(arg_idx).cloned().unwrap_or(PerlValue::UNDEF);
21093 arg_idx += 1;
21094 v
21095 };
21096
21097 let w: usize = width.parse().unwrap_or(0);
21098 let p: usize = precision.parse().unwrap_or(6);
21099
21100 let zero_pad = flags.contains('0') && !left_align;
21101 let plus = flags.contains('+');
21102 let space = flags.contains(' ');
21103 let hash = flags.contains('#');
21104
21105 let pad_align = |body: &str, width: usize, left: bool, zero: bool| -> String {
21109 if width == 0 || body.len() >= width {
21110 return body.to_string();
21111 }
21112 if zero && !left {
21113 if let Some(rest) = body.strip_prefix('-') {
21114 return format!("-{:0>width$}", rest, width = width - 1);
21115 }
21116 if let Some(rest) = body.strip_prefix('+') {
21117 return format!("+{:0>width$}", rest, width = width - 1);
21118 }
21119 return format!("{:0>width$}", body, width = width);
21120 }
21121 if left {
21122 format!("{:<width$}", body, width = width)
21123 } else {
21124 format!("{:>width$}", body, width = width)
21125 }
21126 };
21127
21128 let format_int_for_vector = |n: i64, spec: char| -> String {
21132 match spec {
21133 'd' | 'i' => format!("{}", n),
21134 'u' => format!("{}", n as u64),
21135 'x' => {
21136 if hash && n != 0 {
21137 format!("0x{:x}", n)
21138 } else {
21139 format!("{:x}", n)
21140 }
21141 }
21142 'X' => {
21143 if hash && n != 0 {
21144 format!("0X{:X}", n)
21145 } else {
21146 format!("{:X}", n)
21147 }
21148 }
21149 'o' => {
21150 if hash && n != 0 {
21151 format!("0{:o}", n)
21152 } else {
21153 format!("{:o}", n)
21154 }
21155 }
21156 'b' => {
21157 if hash && n != 0 {
21158 format!("0b{:b}", n)
21159 } else {
21160 format!("{:b}", n)
21161 }
21162 }
21163 'c' => char::from_u32(n as u32)
21164 .map(|c| c.to_string())
21165 .unwrap_or_default(),
21166 _ => format!("{}", n),
21167 }
21168 };
21169
21170 if let Some(ref sep) = vector_sep {
21174 let s = arg.to_string();
21175 let parts: Vec<String> = s
21176 .bytes()
21177 .map(|b| format_int_for_vector(b as i64, spec))
21178 .collect();
21179 let body = parts.join(sep);
21180 let final_body = if width.is_empty() {
21181 body
21182 } else if left_align {
21183 format!("{:<width$}", body, width = w)
21184 } else {
21185 format!("{:>width$}", body, width = w)
21186 };
21187 result.push_str(&final_body);
21188 continue;
21189 }
21190
21191 let formatted = match spec {
21192 'd' | 'i' => {
21193 let v = arg.to_int();
21194 let body = if plus && v >= 0 {
21195 format!("+{}", v)
21196 } else if space && v >= 0 {
21197 format!(" {}", v)
21198 } else {
21199 format!("{}", v)
21200 };
21201 pad_align(&body, w, left_align, zero_pad)
21202 }
21203 'u' => {
21204 let v = arg.to_int() as u64;
21205 pad_align(&format!("{}", v), w, left_align, zero_pad)
21206 }
21207 'f' => {
21208 let n = arg.to_number();
21209 let body = if plus && n.is_sign_positive() {
21210 format!("+{:.*}", p, n)
21211 } else if space && n.is_sign_positive() {
21212 format!(" {:.*}", p, n)
21213 } else {
21214 format!("{:.*}", p, n)
21215 };
21216 pad_align(&body, w, left_align, zero_pad)
21217 }
21218 'e' => {
21219 let n = arg.to_number();
21220 let raw = format!("{:.*e}", p, n);
21221 let body0 = perl_exponent_form(&raw, false);
21222 let body = if plus && n.is_sign_positive() {
21223 format!("+{}", body0)
21224 } else if space && n.is_sign_positive() {
21225 format!(" {}", body0)
21226 } else {
21227 body0
21228 };
21229 pad_align(&body, w, left_align, zero_pad)
21230 }
21231 'E' => {
21232 let n = arg.to_number();
21233 let raw = format!("{:.*E}", p, n);
21234 let body0 = perl_exponent_form(&raw, true);
21235 let body = if plus && n.is_sign_positive() {
21236 format!("+{}", body0)
21237 } else if space && n.is_sign_positive() {
21238 format!(" {}", body0)
21239 } else {
21240 body0
21241 };
21242 pad_align(&body, w, left_align, zero_pad)
21243 }
21244 'g' => {
21245 let n = arg.to_number();
21246 let prec_g = if precision.is_empty() { 6 } else { p };
21248 let body0 = perl_g_form(n, prec_g, false);
21249 let body = if plus && n.is_sign_positive() {
21250 format!("+{}", body0)
21251 } else if space && n.is_sign_positive() {
21252 format!(" {}", body0)
21253 } else {
21254 body0
21255 };
21256 pad_align(&body, w, left_align, zero_pad)
21257 }
21258 'G' => {
21259 let n = arg.to_number();
21260 let prec_g = if precision.is_empty() { 6 } else { p };
21261 let body0 = perl_g_form(n, prec_g, true);
21262 let body = if plus && n.is_sign_positive() {
21263 format!("+{}", body0)
21264 } else if space && n.is_sign_positive() {
21265 format!(" {}", body0)
21266 } else {
21267 body0
21268 };
21269 pad_align(&body, w, left_align, zero_pad)
21270 }
21271 's' => {
21272 let s = string_for_s(&arg)?;
21273 let body = if !precision.is_empty() {
21274 s.chars().take(p).collect::<String>()
21275 } else {
21276 s
21277 };
21278 if left_align {
21279 format!("{:<width$}", body, width = w)
21280 } else {
21281 format!("{:>width$}", body, width = w)
21282 }
21283 }
21284 'x' => {
21285 let v = arg.to_int();
21286 let body = if hash && v != 0 {
21287 format!("0x{:x}", v)
21288 } else {
21289 format!("{:x}", v)
21290 };
21291 pad_align(&body, w, left_align, zero_pad)
21292 }
21293 'X' => {
21294 let v = arg.to_int();
21295 let body = if hash && v != 0 {
21296 format!("0X{:X}", v)
21297 } else {
21298 format!("{:X}", v)
21299 };
21300 pad_align(&body, w, left_align, zero_pad)
21301 }
21302 'o' => {
21303 let v = arg.to_int();
21304 let body = if hash && v != 0 {
21305 format!("0{:o}", v)
21306 } else {
21307 format!("{:o}", v)
21308 };
21309 pad_align(&body, w, left_align, zero_pad)
21310 }
21311 'b' => {
21312 let v = arg.to_int();
21313 let body = if hash && v != 0 {
21314 format!("0b{:b}", v)
21315 } else {
21316 format!("{:b}", v)
21317 };
21318 pad_align(&body, w, left_align, zero_pad)
21319 }
21320 'c' => char::from_u32(arg.to_int() as u32)
21321 .map(|c| c.to_string())
21322 .unwrap_or_default(),
21323 'a' | 'A' => {
21324 let upper = spec == 'A';
21325 let body0 = perl_hex_float(arg.to_number(), upper);
21326 let body = if plus && !body0.starts_with('-') {
21327 format!("+{}", body0)
21328 } else if space && !body0.starts_with('-') {
21329 format!(" {}", body0)
21330 } else {
21331 body0
21332 };
21333 pad_align(&body, w, left_align, zero_pad)
21334 }
21335 'p' => {
21336 pad_align("0x...", w, left_align, false)
21340 }
21341 'n' => {
21342 pending_n.push((arg.clone(), result.len() as i64));
21350 String::new()
21351 }
21352 _ => arg.to_string(),
21353 };
21354
21355 result.push_str(&formatted);
21356 } else {
21357 result.push(chars[i]);
21358 i += 1;
21359 }
21360 }
21361 Ok((result, pending_n))
21362}
21363
21364#[cfg(test)]
21365mod regex_expand_tests {
21366 use super::VMHelper;
21367
21368 #[test]
21369 fn compile_regex_quotemeta_qe_matches_literal() {
21370 let mut i = VMHelper::new();
21371 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
21372 assert!(re.is_match("a.c"));
21373 assert!(!re.is_match("abc"));
21374 }
21375
21376 #[test]
21379 fn compile_regex_char_class_leading_close_bracket_is_literal() {
21380 let mut i = VMHelper::new();
21381 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
21382 assert!(re.is_match("$"));
21383 assert!(re.is_match("]"));
21384 assert!(!re.is_match("x"));
21385 }
21386}
21387
21388#[cfg(test)]
21389mod special_scalar_name_tests {
21390 use super::VMHelper;
21391
21392 #[test]
21393 fn special_scalar_name_for_get_matches_magic_globals() {
21394 assert!(VMHelper::is_special_scalar_name_for_get("0"));
21395 assert!(VMHelper::is_special_scalar_name_for_get("!"));
21396 assert!(VMHelper::is_special_scalar_name_for_get("^W"));
21397 assert!(VMHelper::is_special_scalar_name_for_get("^O"));
21398 assert!(VMHelper::is_special_scalar_name_for_get("^MATCH"));
21399 assert!(VMHelper::is_special_scalar_name_for_get("<"));
21400 assert!(VMHelper::is_special_scalar_name_for_get("?"));
21401 assert!(VMHelper::is_special_scalar_name_for_get("|"));
21402 assert!(VMHelper::is_special_scalar_name_for_get("^UNICODE"));
21403 assert!(VMHelper::is_special_scalar_name_for_get("\""));
21404 assert!(!VMHelper::is_special_scalar_name_for_get("foo"));
21405 assert!(!VMHelper::is_special_scalar_name_for_get("plainvar"));
21406 }
21407
21408 #[test]
21409 fn special_scalar_name_for_set_matches_set_special_var_arms() {
21410 assert!(VMHelper::is_special_scalar_name_for_set("0"));
21411 assert!(VMHelper::is_special_scalar_name_for_set("^D"));
21412 assert!(VMHelper::is_special_scalar_name_for_set("^H"));
21413 assert!(VMHelper::is_special_scalar_name_for_set("^WARNING_BITS"));
21414 assert!(VMHelper::is_special_scalar_name_for_set("ARGV"));
21415 assert!(VMHelper::is_special_scalar_name_for_set("|"));
21416 assert!(VMHelper::is_special_scalar_name_for_set("?"));
21417 assert!(VMHelper::is_special_scalar_name_for_set("^UNICODE"));
21418 assert!(VMHelper::is_special_scalar_name_for_set("."));
21419 assert!(!VMHelper::is_special_scalar_name_for_set("foo"));
21420 assert!(!VMHelper::is_special_scalar_name_for_set("__PACKAGE__"));
21421 }
21422
21423 #[test]
21424 fn caret_and_id_specials_roundtrip_get() {
21425 let i = VMHelper::new();
21426 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
21427 assert_eq!(
21428 i.get_special_var("^V").to_string(),
21429 format!("v{}", env!("CARGO_PKG_VERSION"))
21430 );
21431 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
21432 assert!(i.get_special_var("^T").to_int() >= 0);
21433 #[cfg(unix)]
21434 {
21435 assert!(i.get_special_var("<").to_int() >= 0);
21436 }
21437 }
21438
21439 #[test]
21440 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
21441 let mut i = VMHelper::new();
21442 i.last_readline_handle.clear();
21443 i.line_number = 3;
21444 i.prepare_flip_flop_vm_slots(1);
21445 assert_eq!(
21446 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
21447 1
21448 );
21449 assert!(i.flip_flop_active[0]);
21450 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
21451 assert_eq!(
21453 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
21454 1
21455 );
21456 assert!(i.flip_flop_active[0]);
21457 }
21458
21459 #[test]
21460 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
21461 let mut i = VMHelper::new();
21462 i.last_readline_handle.clear();
21463 i.line_number = 2;
21464 i.prepare_flip_flop_vm_slots(1);
21465 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
21466 assert!(i.flip_flop_active[0]);
21467 i.line_number = 3;
21468 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
21469 assert!(!i.flip_flop_active[0]);
21470 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
21471 }
21472}