1use std::cell::Cell;
2use std::cmp::Ordering;
3use std::collections::{HashMap, HashSet, VecDeque};
4use std::fs::File;
5use std::io::{self, BufRead, BufReader, Cursor, Read, Write as IoWrite};
6#[cfg(unix)]
7use std::os::unix::process::ExitStatusExt;
8use std::path::{Path, PathBuf};
9use std::process::{Child, Command, Stdio};
10use std::sync::atomic::AtomicUsize;
11use std::sync::Arc;
12use std::sync::{Barrier, OnceLock};
13use std::time::{Duration, Instant};
14
15use indexmap::IndexMap;
16use parking_lot::{Mutex, RwLock};
17use rand::rngs::StdRng;
18use rand::{Rng, SeedableRng};
19use rayon::prelude::*;
20
21use caseless::default_case_fold_str;
22
23use crate::ast::*;
24use crate::builtins::PerlSocket;
25use crate::crypt_util::perl_crypt;
26use crate::error::{ErrorKind, PerlError, PerlResult};
27use crate::mro::linearize_c3;
28use crate::perl_decode::decode_utf8_or_latin1;
29use crate::perl_fs::read_file_text_perl_compat;
30use crate::perl_regex::{perl_quotemeta, PerlCaptures, PerlCompiledRegex};
31use crate::pmap_progress::{FanProgress, PmapProgress};
32use crate::profiler::Profiler;
33use crate::scope::Scope;
34use crate::sort_fast::{detect_sort_block_fast, sort_magic_cmp};
35use crate::value::{
36 perl_list_range_expand, CaptureResult, PerlAsyncTask, PerlBarrier, PerlDataFrame,
37 PerlGenerator, PerlHeap, PerlPpool, PerlSub, PerlValue, PipelineInner, PipelineOp,
38 RemoteCluster,
39};
40
41pub(crate) fn preduce_init_merge_maps(
44 mut acc: IndexMap<String, PerlValue>,
45 b: IndexMap<String, PerlValue>,
46) -> PerlValue {
47 for (k, v2) in b {
48 acc.entry(k)
49 .and_modify(|v1| *v1 = PerlValue::float(v1.to_number() + v2.to_number()))
50 .or_insert(v2);
51 }
52 PerlValue::hash_ref(Arc::new(RwLock::new(acc)))
53}
54
55#[inline]
57fn splice_compute_range(
58 arr_len: usize,
59 offset_val: &PerlValue,
60 length_val: &PerlValue,
61) -> (usize, usize) {
62 let off_i = offset_val.to_int();
63 let off = if off_i < 0 {
64 arr_len.saturating_sub((-off_i) as usize)
65 } else {
66 (off_i as usize).min(arr_len)
67 };
68 let rest = arr_len.saturating_sub(off);
69 let take = if length_val.is_undef() {
70 rest
71 } else {
72 let l = length_val.to_int();
73 if l < 0 {
74 rest.saturating_sub((-l) as usize)
75 } else {
76 (l as usize).min(rest)
77 }
78 };
79 let end = (off + take).min(arr_len);
80 (off, end)
81}
82
83pub(crate) fn merge_preduce_init_partials(
86 a: PerlValue,
87 b: PerlValue,
88 block: &Block,
89 subs: &HashMap<String, Arc<PerlSub>>,
90 scope_capture: &[(String, PerlValue)],
91) -> PerlValue {
92 if let (Some(m1), Some(m2)) = (a.as_hash_map(), b.as_hash_map()) {
93 return preduce_init_merge_maps(m1, m2);
94 }
95 if let (Some(r1), Some(r2)) = (a.as_hash_ref(), b.as_hash_ref()) {
96 let m1 = r1.read().clone();
97 let m2 = r2.read().clone();
98 return preduce_init_merge_maps(m1, m2);
99 }
100 if let Some(m1) = a.as_hash_map() {
101 if let Some(r2) = b.as_hash_ref() {
102 let m2 = r2.read().clone();
103 return preduce_init_merge_maps(m1, m2);
104 }
105 }
106 if let Some(r1) = a.as_hash_ref() {
107 if let Some(m2) = b.as_hash_map() {
108 let m1 = r1.read().clone();
109 return preduce_init_merge_maps(m1, m2);
110 }
111 }
112 let mut local_interp = Interpreter::new();
113 local_interp.subs = subs.clone();
114 local_interp.scope.restore_capture(scope_capture);
115 local_interp.enable_parallel_guard();
116 local_interp
117 .scope
118 .declare_array("_", vec![a.clone(), b.clone()]);
119 let _ = local_interp.scope.set_scalar("a", a.clone());
120 let _ = local_interp.scope.set_scalar("b", b.clone());
121 let _ = local_interp.scope.set_scalar("_0", a);
122 let _ = local_interp.scope.set_scalar("_1", b);
123 match local_interp.exec_block(block) {
124 Ok(val) => val,
125 Err(_) => PerlValue::UNDEF,
126 }
127}
128
129pub(crate) fn preduce_init_fold_identity(init: &PerlValue) -> PerlValue {
132 if let Some(m) = init.as_hash_map() {
133 return PerlValue::hash(m.clone());
134 }
135 if let Some(r) = init.as_hash_ref() {
136 return PerlValue::hash_ref(Arc::new(RwLock::new(r.read().clone())));
137 }
138 init.clone()
139}
140
141pub(crate) fn fold_preduce_init_step(
142 subs: &HashMap<String, Arc<PerlSub>>,
143 scope_capture: &[(String, PerlValue)],
144 block: &Block,
145 acc: PerlValue,
146 item: PerlValue,
147) -> PerlValue {
148 let mut local_interp = Interpreter::new();
149 local_interp.subs = subs.clone();
150 local_interp.scope.restore_capture(scope_capture);
151 local_interp.enable_parallel_guard();
152 local_interp
153 .scope
154 .declare_array("_", vec![acc.clone(), item.clone()]);
155 let _ = local_interp.scope.set_scalar("a", acc.clone());
156 let _ = local_interp.scope.set_scalar("b", item.clone());
157 let _ = local_interp.scope.set_scalar("_0", acc);
158 let _ = local_interp.scope.set_scalar("_1", item);
159 match local_interp.exec_block(block) {
160 Ok(val) => val,
161 Err(_) => PerlValue::UNDEF,
162 }
163}
164
165pub const FEAT_SAY: u64 = 1 << 0;
167pub const FEAT_STATE: u64 = 1 << 1;
169pub const FEAT_SWITCH: u64 = 1 << 2;
171pub const FEAT_UNICODE_STRINGS: u64 = 1 << 3;
173
174#[derive(Debug)]
176pub(crate) enum Flow {
177 Return(PerlValue),
178 Last(Option<String>),
179 Next(Option<String>),
180 Redo(Option<String>),
181 Yield(PerlValue),
182 GotoSub(String),
184}
185
186pub(crate) type ExecResult = Result<PerlValue, FlowOrError>;
187
188#[derive(Debug)]
189pub(crate) enum FlowOrError {
190 Flow(Flow),
191 Error(PerlError),
192}
193
194impl From<PerlError> for FlowOrError {
195 fn from(e: PerlError) -> Self {
196 FlowOrError::Error(e)
197 }
198}
199
200impl From<Flow> for FlowOrError {
201 fn from(f: Flow) -> Self {
202 FlowOrError::Flow(f)
203 }
204}
205
206enum PatternBinding {
208 Scalar(String, PerlValue),
209 Array(String, Vec<PerlValue>),
210}
211
212pub fn perl_bracket_version() -> f64 {
215 const PERL_EMUL_MINOR: u32 = 38;
216 const PERL_EMUL_PATCH: u32 = 0;
217 5.0 + (PERL_EMUL_MINOR as f64) / 1000.0 + (PERL_EMUL_PATCH as f64) / 1_000_000.0
218}
219
220#[inline]
222fn fast_rng_seed() -> u64 {
223 let local: u8 = 0;
224 let addr = &local as *const u8 as u64;
225 (std::process::id() as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ addr
226}
227
228fn cached_executable_path() -> String {
230 static CACHED: OnceLock<String> = OnceLock::new();
231 CACHED
232 .get_or_init(|| {
233 std::env::current_exe()
234 .map(|p| p.to_string_lossy().into_owned())
235 .unwrap_or_else(|_| "stryke".to_string())
236 })
237 .clone()
238}
239
240fn build_term_hash() -> IndexMap<String, PerlValue> {
241 let mut m = IndexMap::new();
242 m.insert(
243 "TERM".into(),
244 PerlValue::string(std::env::var("TERM").unwrap_or_default()),
245 );
246 m.insert(
247 "COLORTERM".into(),
248 PerlValue::string(std::env::var("COLORTERM").unwrap_or_default()),
249 );
250
251 let (rows, cols) = term_size();
252 m.insert("rows".into(), PerlValue::integer(rows));
253 m.insert("cols".into(), PerlValue::integer(cols));
254
255 #[cfg(unix)]
256 let is_tty = unsafe { libc::isatty(1) != 0 };
257 #[cfg(not(unix))]
258 let is_tty = false;
259 m.insert(
260 "is_tty".into(),
261 PerlValue::integer(if is_tty { 1 } else { 0 }),
262 );
263
264 m
265}
266
267fn term_size() -> (i64, i64) {
268 #[cfg(unix)]
269 {
270 unsafe {
271 let mut ws: libc::winsize = std::mem::zeroed();
272 if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 {
273 return (ws.ws_row as i64, ws.ws_col as i64);
274 }
275 }
276 }
277 let rows = std::env::var("LINES")
278 .ok()
279 .and_then(|s| s.parse().ok())
280 .unwrap_or(24);
281 let cols = std::env::var("COLUMNS")
282 .ok()
283 .and_then(|s| s.parse().ok())
284 .unwrap_or(80);
285 (rows, cols)
286}
287
288#[cfg(unix)]
289fn build_uname_hash() -> IndexMap<String, PerlValue> {
290 fn uts_field(slice: &[libc::c_char]) -> String {
291 let n = slice.iter().take_while(|&&c| c != 0).count();
292 let bytes: Vec<u8> = slice[..n].iter().map(|&c| c as u8).collect();
293 String::from_utf8_lossy(&bytes).into_owned()
294 }
295 let mut m = IndexMap::new();
296 let mut uts: libc::utsname = unsafe { std::mem::zeroed() };
297 if unsafe { libc::uname(&mut uts) } == 0 {
298 m.insert(
299 "sysname".into(),
300 PerlValue::string(uts_field(uts.sysname.as_slice())),
301 );
302 m.insert(
303 "nodename".into(),
304 PerlValue::string(uts_field(uts.nodename.as_slice())),
305 );
306 m.insert(
307 "release".into(),
308 PerlValue::string(uts_field(uts.release.as_slice())),
309 );
310 m.insert(
311 "version".into(),
312 PerlValue::string(uts_field(uts.version.as_slice())),
313 );
314 m.insert(
315 "machine".into(),
316 PerlValue::string(uts_field(uts.machine.as_slice())),
317 );
318 }
319 m
320}
321
322#[cfg(unix)]
323fn build_limits_hash() -> IndexMap<String, PerlValue> {
324 use libc::{getrlimit, rlimit, RLIM_INFINITY};
325 #[cfg(target_os = "linux")]
326 type RlimitResource = libc::__rlimit_resource_t;
327 #[cfg(not(target_os = "linux"))]
328 type RlimitResource = libc::c_int;
329 fn get_limit(resource: RlimitResource) -> (i64, i64) {
330 let mut rlim = rlimit {
331 rlim_cur: 0,
332 rlim_max: 0,
333 };
334 if unsafe { getrlimit(resource, &mut rlim) } == 0 {
335 let cur = if rlim.rlim_cur == RLIM_INFINITY {
336 -1
337 } else {
338 rlim.rlim_cur as i64
339 };
340 let max = if rlim.rlim_max == RLIM_INFINITY {
341 -1
342 } else {
343 rlim.rlim_max as i64
344 };
345 (cur, max)
346 } else {
347 (-1, -1)
348 }
349 }
350 let mut m = IndexMap::new();
351 let (cur, max) = get_limit(libc::RLIMIT_NOFILE);
352 m.insert("nofile".into(), PerlValue::integer(cur));
353 m.insert("nofile_max".into(), PerlValue::integer(max));
354 let (cur, max) = get_limit(libc::RLIMIT_STACK);
355 m.insert("stack".into(), PerlValue::integer(cur));
356 m.insert("stack_max".into(), PerlValue::integer(max));
357 let (cur, max) = get_limit(libc::RLIMIT_AS);
358 m.insert("as".into(), PerlValue::integer(cur));
359 m.insert("as_max".into(), PerlValue::integer(max));
360 let (cur, max) = get_limit(libc::RLIMIT_DATA);
361 m.insert("data".into(), PerlValue::integer(cur));
362 m.insert("data_max".into(), PerlValue::integer(max));
363 let (cur, max) = get_limit(libc::RLIMIT_FSIZE);
364 m.insert("fsize".into(), PerlValue::integer(cur));
365 m.insert("fsize_max".into(), PerlValue::integer(max));
366 let (cur, max) = get_limit(libc::RLIMIT_CORE);
367 m.insert("core".into(), PerlValue::integer(cur));
368 m.insert("core_max".into(), PerlValue::integer(max));
369 let (cur, max) = get_limit(libc::RLIMIT_CPU);
370 m.insert("cpu".into(), PerlValue::integer(cur));
371 m.insert("cpu_max".into(), PerlValue::integer(max));
372 let (cur, max) = get_limit(libc::RLIMIT_NPROC);
373 m.insert("nproc".into(), PerlValue::integer(cur));
374 m.insert("nproc_max".into(), PerlValue::integer(max));
375 #[cfg(target_os = "linux")]
376 {
377 let (cur, max) = get_limit(libc::RLIMIT_MEMLOCK);
378 m.insert("memlock".into(), PerlValue::integer(cur));
379 m.insert("memlock_max".into(), PerlValue::integer(max));
380 }
381 m
382}
383
384#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
386pub(crate) enum WantarrayCtx {
387 #[default]
388 Scalar,
389 List,
390 Void,
391}
392
393impl WantarrayCtx {
394 #[inline]
395 pub(crate) fn from_byte(b: u8) -> Self {
396 match b {
397 1 => Self::List,
398 2 => Self::Void,
399 _ => Self::Scalar,
400 }
401 }
402
403 #[inline]
404 pub(crate) fn as_byte(self) -> u8 {
405 match self {
406 Self::Scalar => 0,
407 Self::List => 1,
408 Self::Void => 2,
409 }
410 }
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
415pub(crate) enum LogLevelFilter {
416 Trace,
417 Debug,
418 Info,
419 Warn,
420 Error,
421}
422
423impl LogLevelFilter {
424 pub(crate) fn parse(s: &str) -> Option<Self> {
425 match s.trim().to_ascii_lowercase().as_str() {
426 "trace" => Some(Self::Trace),
427 "debug" => Some(Self::Debug),
428 "info" => Some(Self::Info),
429 "warn" | "warning" => Some(Self::Warn),
430 "error" => Some(Self::Error),
431 _ => None,
432 }
433 }
434
435 pub(crate) fn as_str(self) -> &'static str {
436 match self {
437 Self::Trace => "trace",
438 Self::Debug => "debug",
439 Self::Info => "info",
440 Self::Warn => "warn",
441 Self::Error => "error",
442 }
443 }
444}
445
446fn arrow_deref_array_assign_rhs_list_ctx(index: &Expr) -> bool {
448 match &index.kind {
449 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => true,
450 ExprKind::QW(ws) => ws.len() > 1,
451 ExprKind::List(el) => {
452 if el.len() > 1 {
453 true
454 } else if el.len() == 1 {
455 arrow_deref_array_assign_rhs_list_ctx(&el[0])
456 } else {
457 false
458 }
459 }
460 _ => false,
461 }
462}
463
464pub(crate) fn assign_rhs_wantarray(target: &Expr) -> WantarrayCtx {
467 match &target.kind {
468 ExprKind::ArrayVar(_) | ExprKind::HashVar(_) => WantarrayCtx::List,
469 ExprKind::ScalarVar(_) | ExprKind::ArrayElement { .. } | ExprKind::HashElement { .. } => {
470 WantarrayCtx::Scalar
471 }
472 ExprKind::Deref { kind, .. } => match kind {
473 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
474 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
475 },
476 ExprKind::ArrowDeref {
477 index,
478 kind: DerefKind::Array,
479 ..
480 } => {
481 if arrow_deref_array_assign_rhs_list_ctx(index) {
482 WantarrayCtx::List
483 } else {
484 WantarrayCtx::Scalar
485 }
486 }
487 ExprKind::ArrowDeref {
488 kind: DerefKind::Hash,
489 ..
490 }
491 | ExprKind::ArrowDeref {
492 kind: DerefKind::Call,
493 ..
494 } => WantarrayCtx::Scalar,
495 ExprKind::HashSliceDeref { .. } | ExprKind::HashSlice { .. } => WantarrayCtx::List,
496 ExprKind::ArraySlice { indices, .. } => {
497 if indices.len() > 1 {
498 WantarrayCtx::List
499 } else if indices.len() == 1 {
500 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
501 WantarrayCtx::List
502 } else {
503 WantarrayCtx::Scalar
504 }
505 } else {
506 WantarrayCtx::Scalar
507 }
508 }
509 ExprKind::AnonymousListSlice { indices, .. } => {
510 if indices.len() > 1 {
511 WantarrayCtx::List
512 } else if indices.len() == 1 {
513 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
514 WantarrayCtx::List
515 } else {
516 WantarrayCtx::Scalar
517 }
518 } else {
519 WantarrayCtx::Scalar
520 }
521 }
522 ExprKind::Typeglob(_) | ExprKind::TypeglobExpr(_) => WantarrayCtx::Scalar,
523 ExprKind::List(_) => WantarrayCtx::List,
524 _ => WantarrayCtx::Scalar,
525 }
526}
527
528#[derive(Clone)]
533pub(crate) struct RegexMatchMemo {
534 pub pattern: String,
535 pub flags: String,
536 pub multiline: bool,
537 pub haystack: String,
538 pub result: PerlValue,
539}
540
541#[derive(Clone, Copy, Default)]
543struct FlipFlopTreeState {
544 active: bool,
545 exclusive_left_line: Option<i64>,
549}
550
551#[derive(Clone)]
553pub(crate) struct IoSharedFile(pub Arc<Mutex<File>>);
554
555impl Read for IoSharedFile {
556 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
557 self.0.lock().read(buf)
558 }
559}
560
561pub(crate) struct IoSharedFileWrite(pub Arc<Mutex<File>>);
562
563impl IoWrite for IoSharedFileWrite {
564 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
565 self.0.lock().write(buf)
566 }
567
568 fn flush(&mut self) -> io::Result<()> {
569 self.0.lock().flush()
570 }
571}
572
573pub struct Interpreter {
574 pub scope: Scope,
575 pub(crate) subs: HashMap<String, Arc<PerlSub>>,
576 pub(crate) file: String,
577 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
579 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
580 pub ofs: String,
582 pub ors: String,
584 pub irs: Option<String>,
587 pub errno: String,
589 pub errno_code: i32,
591 pub eval_error: String,
593 pub eval_error_code: i32,
595 pub eval_error_value: Option<PerlValue>,
597 pub argv: Vec<String>,
599 pub env: IndexMap<String, PerlValue>,
601 pub env_materialized: bool,
603 pub program_name: String,
605 pub line_number: i64,
607 pub last_readline_handle: String,
609 pub(crate) last_stdin_die_bracket: String,
611 pub handle_line_numbers: HashMap<String, i64>,
613 pub(crate) flip_flop_active: Vec<bool>,
617 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
620 pub(crate) flip_flop_sequence: Vec<i64>,
624 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
628 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
630 pub sigint_pending_caret: Cell<bool>,
632 pub auto_split: bool,
634 pub field_separator: Option<String>,
636 begin_blocks: Vec<Block>,
638 unit_check_blocks: Vec<Block>,
640 check_blocks: Vec<Block>,
642 init_blocks: Vec<Block>,
644 end_blocks: Vec<Block>,
646 pub warnings: bool,
648 pub output_autoflush: bool,
650 pub default_print_handle: String,
652 pub suppress_stdout: bool,
654 pub child_exit_status: i64,
656 pub last_match: String,
658 pub prematch: String,
660 pub postmatch: String,
662 pub last_paren_match: String,
664 pub list_separator: String,
666 pub script_start_time: i64,
668 pub compile_hints: i64,
670 pub warning_bits: i64,
672 pub global_phase: String,
674 pub subscript_sep: String,
676 pub inplace_edit: String,
679 pub debug_flags: i64,
681 pub perl_debug_flags: i64,
683 pub eval_nesting: u32,
685 pub argv_current_file: String,
687 pub(crate) diamond_next_idx: usize,
689 pub(crate) diamond_reader: Option<BufReader<File>>,
691 pub strict_refs: bool,
693 pub strict_subs: bool,
694 pub strict_vars: bool,
695 pub utf8_pragma: bool,
697 pub open_pragma_utf8: bool,
699 pub feature_bits: u64,
701 pub num_threads: usize,
703 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
705 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
708 regex_match_memo: Option<RegexMatchMemo>,
717 regex_capture_scope_fresh: bool,
721 pub(crate) regex_pos: HashMap<String, Option<usize>>,
723 pub(crate) state_vars: HashMap<String, PerlValue>,
725 pub(crate) state_bindings_stack: Vec<Vec<(String, String)>>,
727 pub(crate) rand_rng: StdRng,
729 pub(crate) dir_handles: HashMap<String, DirHandleState>,
731 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
733 pub(crate) pipe_children: HashMap<String, Child>,
735 pub(crate) socket_handles: HashMap<String, PerlSocket>,
737 pub(crate) wantarray_kind: WantarrayCtx,
739 pub struct_defs: HashMap<String, Arc<StructDef>>,
741 pub enum_defs: HashMap<String, Arc<EnumDef>>,
743 pub class_defs: HashMap<String, Arc<ClassDef>>,
745 pub trait_defs: HashMap<String, Arc<TraitDef>>,
747 pub profiler: Option<Profiler>,
750 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
752 pub(crate) virtual_modules: HashMap<String, String>,
754 pub(crate) tied_hashes: HashMap<String, PerlValue>,
756 pub(crate) tied_scalars: HashMap<String, PerlValue>,
758 pub(crate) tied_arrays: HashMap<String, PerlValue>,
760 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
762 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
764 pub(crate) special_caret_scalars: HashMap<String, PerlValue>,
766 pub format_page_number: i64,
768 pub format_lines_per_page: i64,
770 pub format_lines_left: i64,
772 pub format_line_break_chars: String,
774 pub format_top_name: String,
776 pub accumulator_format: String,
778 pub max_system_fd: i64,
780 pub emergency_memory: String,
782 pub last_subpattern_name: String,
784 pub inc_hook_index: i64,
786 pub multiline_match: bool,
788 pub executable_path: String,
790 pub formfeed_string: String,
792 pub(crate) glob_handle_alias: HashMap<String, String>,
794 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
796 pub(crate) special_var_restore_frames: Vec<Vec<(String, PerlValue)>>,
801 pub(crate) reflection_hashes_ready: bool,
805 pub(crate) english_enabled: bool,
806 pub(crate) english_no_match_vars: bool,
808 pub(crate) english_match_vars_ever_enabled: bool,
812 english_lexical_scalars: Vec<HashSet<String>>,
814 our_lexical_scalars: Vec<HashSet<String>>,
816 pub vm_jit_enabled: bool,
819 pub disasm_bytecode: bool,
821 pub cached_chunk: Option<crate::bytecode::Chunk>,
825 pub sqlite_cache_script_path: Option<std::path::PathBuf>,
827 pub(crate) in_generator: bool,
829 pub line_mode_skip_main: bool,
831 pub line_mode_chunk: Option<crate::bytecode::Chunk>,
834 pub(crate) line_mode_eof_pending: bool,
838 pub line_mode_stdin_pending: VecDeque<String>,
841 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
843 pub(crate) log_level_override: Option<LogLevelFilter>,
845 pub(crate) current_sub_stack: Vec<Arc<PerlSub>>,
848 pub debugger: Option<crate::debugger::Debugger>,
850 pub(crate) debug_call_stack: Vec<(String, usize)>,
852}
853
854#[derive(Debug, Clone, Default)]
856pub struct ReplCompletionSnapshot {
857 pub subs: Vec<String>,
858 pub blessed_scalars: HashMap<String, String>,
859 pub isa_for_class: HashMap<String, Vec<String>>,
860}
861
862impl ReplCompletionSnapshot {
863 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
865 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
866 let mro = linearize_c3(class, &parents, 0);
867 let mut names = HashSet::new();
868 for pkg in &mro {
869 if pkg == "UNIVERSAL" {
870 continue;
871 }
872 let prefix = format!("{}::", pkg);
873 for k in &self.subs {
874 if k.starts_with(&prefix) {
875 let rest = &k[prefix.len()..];
876 if !rest.contains("::") {
877 names.insert(rest.to_string());
878 }
879 }
880 }
881 }
882 for k in &self.subs {
883 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
884 if !rest.contains("::") {
885 names.insert(rest.to_string());
886 }
887 }
888 }
889 let mut v: Vec<String> = names.into_iter().collect();
890 v.sort();
891 v
892 }
893}
894
895fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
896 let left = left.trim_end();
897 if left.is_empty() {
898 return None;
899 }
900 if let Some(i) = left.rfind('$') {
901 let name = left[i + 1..].trim();
902 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
903 return state.blessed_scalars.get(name).cloned();
904 }
905 }
906 let tok = left.split_whitespace().last()?;
907 if tok.contains("::") {
908 return Some(tok.to_string());
909 }
910 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
911 return Some(tok.to_string());
912 }
913 None
914}
915
916pub fn repl_arrow_method_completions(
918 state: &ReplCompletionSnapshot,
919 line: &str,
920 pos: usize,
921) -> Option<(usize, Vec<String>)> {
922 let pos = pos.min(line.len());
923 let before = &line[..pos];
924 let arrow_idx = before.rfind("->")?;
925 let after_arrow = &before[arrow_idx + 2..];
926 let rest = after_arrow.trim_start();
927 let ws_len = after_arrow.len() - rest.len();
928 let method_start = arrow_idx + 2 + ws_len;
929 let method_prefix = &line[method_start..pos];
930 if !method_prefix
931 .chars()
932 .all(|c| c.is_alphanumeric() || c == '_')
933 {
934 return None;
935 }
936 let left = line[..arrow_idx].trim_end();
937 let class = repl_resolve_class_for_arrow(state, left)?;
938 let mut methods = state.methods_for_class(&class);
939 methods.retain(|m| m.starts_with(method_prefix));
940 Some((method_start, methods))
941}
942
943#[derive(Debug, Clone, Default)]
945pub(crate) struct ModuleExportLists {
946 pub export: Vec<String>,
948 pub export_ok: Vec<String>,
950}
951
952fn piped_shell_command(cmd: &str) -> Command {
954 if cfg!(windows) {
955 let mut c = Command::new("cmd");
956 c.arg("/C").arg(cmd);
957 c
958 } else {
959 let mut c = Command::new("sh");
960 c.arg("-c").arg(cmd);
961 c
962 }
963}
964
965fn expand_perl_regex_octal_escapes(pat: &str) -> String {
972 let mut out = String::with_capacity(pat.len());
973 let mut it = pat.chars().peekable();
974 while let Some(c) = it.next() {
975 if c == '\\' {
976 if let Some(&'0') = it.peek() {
977 let mut oct = String::new();
979 while oct.len() < 3 {
980 if let Some(&d) = it.peek() {
981 if ('0'..='7').contains(&d) {
982 oct.push(d);
983 it.next();
984 } else {
985 break;
986 }
987 } else {
988 break;
989 }
990 }
991 if let Ok(val) = u8::from_str_radix(&oct, 8) {
992 out.push_str(&format!("\\x{:02x}", val));
993 } else {
994 out.push('\\');
995 out.push_str(&oct);
996 }
997 continue;
998 }
999 }
1000 out.push(c);
1001 }
1002 out
1003}
1004
1005fn expand_perl_regex_quotemeta(pat: &str) -> String {
1006 let mut out = String::with_capacity(pat.len().saturating_mul(2));
1007 let mut it = pat.chars().peekable();
1008 let mut in_q = false;
1009 while let Some(c) = it.next() {
1010 if in_q {
1011 if c == '\\' && it.peek() == Some(&'E') {
1012 it.next();
1013 in_q = false;
1014 continue;
1015 }
1016 out.push_str(&perl_quotemeta(&c.to_string()));
1017 continue;
1018 }
1019 if c == '\\' && it.peek() == Some(&'Q') {
1020 it.next();
1021 in_q = true;
1022 continue;
1023 }
1024 out.push(c);
1025 }
1026 out
1027}
1028
1029pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
1035 let mut out = String::with_capacity(replacement.len() + 8);
1036 let mut it = replacement.chars().peekable();
1037 while let Some(c) = it.next() {
1038 if c == '\\' {
1039 match it.peek() {
1040 Some(&d) if d.is_ascii_digit() => {
1041 it.next();
1042 out.push_str("${");
1043 out.push(d);
1044 while let Some(&d2) = it.peek() {
1045 if !d2.is_ascii_digit() {
1046 break;
1047 }
1048 it.next();
1049 out.push(d2);
1050 }
1051 out.push('}');
1052 }
1053 Some(&'\\') => {
1054 it.next();
1055 out.push('\\');
1056 }
1057 _ => out.push('\\'),
1058 }
1059 } else if c == '$' {
1060 match it.peek() {
1061 Some(&d) if d.is_ascii_digit() => {
1062 it.next();
1063 out.push_str("${");
1064 out.push(d);
1065 while let Some(&d2) = it.peek() {
1066 if !d2.is_ascii_digit() {
1067 break;
1068 }
1069 it.next();
1070 out.push(d2);
1071 }
1072 out.push('}');
1073 }
1074 Some(&'{') => {
1075 out.push('$');
1077 }
1078 _ => out.push('$'),
1079 }
1080 } else {
1081 out.push(c);
1082 }
1083 }
1084 out
1085}
1086
1087fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
1090 debug_assert_eq!(chars.get(i), Some(&'['));
1091 out.push('[');
1092 i += 1;
1093 if i < chars.len() && chars[i] == '^' {
1094 out.push('^');
1095 i += 1;
1096 }
1097 if i >= chars.len() {
1098 return i;
1099 }
1100 if chars[i] == ']' {
1104 if i + 1 < chars.len() && chars[i + 1] == ']' {
1105 out.push(']');
1107 i += 1;
1108 } else {
1109 let mut scan = i + 1;
1110 let mut found_closing = false;
1111 while scan < chars.len() {
1112 if chars[scan] == '\\' && scan + 1 < chars.len() {
1113 scan += 2;
1114 continue;
1115 }
1116 if chars[scan] == ']' {
1117 found_closing = true;
1118 break;
1119 }
1120 scan += 1;
1121 }
1122 if found_closing {
1123 out.push(']');
1124 i += 1;
1125 } else {
1126 out.push(']');
1127 return i + 1;
1128 }
1129 }
1130 }
1131 while i < chars.len() && chars[i] != ']' {
1132 if chars[i] == '\\' && i + 1 < chars.len() {
1133 out.push(chars[i]);
1134 out.push(chars[i + 1]);
1135 i += 2;
1136 continue;
1137 }
1138 out.push(chars[i]);
1139 i += 1;
1140 }
1141 if i < chars.len() {
1142 out.push(']');
1143 i += 1;
1144 }
1145 i
1146}
1147
1148fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1153 if multiline_flag {
1154 return pat.to_string();
1155 }
1156 let chars: Vec<char> = pat.chars().collect();
1157 let mut out = String::with_capacity(pat.len().saturating_add(16));
1158 let mut i = 0usize;
1159 while i < chars.len() {
1160 let c = chars[i];
1161 if c == '\\' && i + 1 < chars.len() {
1162 out.push(c);
1163 out.push(chars[i + 1]);
1164 i += 2;
1165 continue;
1166 }
1167 if c == '[' {
1168 i = copy_regex_char_class(&chars, i, &mut out);
1169 continue;
1170 }
1171 if c == '$' {
1172 if let Some(&next) = chars.get(i + 1) {
1173 if next.is_ascii_digit() {
1174 out.push(c);
1175 i += 1;
1176 continue;
1177 }
1178 if next == '{' {
1179 out.push(c);
1180 i += 1;
1181 continue;
1182 }
1183 if next.is_ascii_alphanumeric() || next == '_' {
1184 out.push(c);
1185 i += 1;
1186 continue;
1187 }
1188 }
1189 out.push_str("(?=\\n?\\z)");
1190 i += 1;
1191 continue;
1192 }
1193 out.push(c);
1194 i += 1;
1195 }
1196 out
1197}
1198
1199#[derive(Debug, Clone)]
1201pub(crate) struct DirHandleState {
1202 pub entries: Vec<String>,
1203 pub pos: usize,
1204}
1205
1206pub(crate) fn perl_osname() -> String {
1208 match std::env::consts::OS {
1209 "linux" => "linux".to_string(),
1210 "macos" => "darwin".to_string(),
1211 "windows" => "MSWin32".to_string(),
1212 other => other.to_string(),
1213 }
1214}
1215
1216fn perl_version_v_string() -> String {
1217 format!("v{}", env!("CARGO_PKG_VERSION"))
1218}
1219
1220fn extended_os_error_string() -> String {
1221 std::io::Error::last_os_error().to_string()
1222}
1223
1224#[cfg(unix)]
1225fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1226 unsafe {
1227 (
1228 libc::getuid() as i64,
1229 libc::geteuid() as i64,
1230 libc::getgid() as i64,
1231 libc::getegid() as i64,
1232 )
1233 }
1234}
1235
1236#[cfg(not(unix))]
1237fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1238 (0, 0, 0, 0)
1239}
1240
1241fn unix_id_for_special(name: &str) -> i64 {
1242 let (r, e, _, _) = unix_real_effective_ids();
1243 match name {
1244 "<" => r,
1245 ">" => e,
1246 _ => 0,
1247 }
1248}
1249
1250#[cfg(unix)]
1251fn unix_group_list_string(primary: libc::gid_t) -> String {
1252 let mut buf = vec![0 as libc::gid_t; 256];
1253 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1254 if n <= 0 {
1255 return format!("{}", primary);
1256 }
1257 let mut parts = vec![format!("{}", primary)];
1258 for g in buf.iter().take(n as usize) {
1259 parts.push(format!("{}", g));
1260 }
1261 parts.join(" ")
1262}
1263
1264#[cfg(unix)]
1266fn unix_group_list_for_special(name: &str) -> String {
1267 let (_, _, gid, egid) = unix_real_effective_ids();
1268 match name {
1269 "(" => unix_group_list_string(gid as libc::gid_t),
1270 ")" => unix_group_list_string(egid as libc::gid_t),
1271 _ => String::new(),
1272 }
1273}
1274
1275#[cfg(not(unix))]
1276fn unix_group_list_for_special(_name: &str) -> String {
1277 String::new()
1278}
1279
1280#[cfg(unix)]
1283fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1284 use libc::{getpwuid_r, getuid};
1285 use std::ffi::CStr;
1286 use std::os::unix::ffi::OsStringExt;
1287 let uid = unsafe { getuid() };
1288 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1289 let mut result: *mut libc::passwd = std::ptr::null_mut();
1290 let mut buf = vec![0u8; 16_384];
1291 let rc = unsafe {
1292 getpwuid_r(
1293 uid,
1294 &mut pw,
1295 buf.as_mut_ptr().cast::<libc::c_char>(),
1296 buf.len(),
1297 &mut result,
1298 )
1299 };
1300 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1301 return None;
1302 }
1303 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1304 if bytes.is_empty() {
1305 return None;
1306 }
1307 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1308}
1309
1310#[cfg(unix)]
1312fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1313 use libc::getpwnam_r;
1314 use std::ffi::{CStr, CString};
1315 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1316 let bytes = login.as_bytes();
1317 if bytes.is_empty() || bytes.contains(&0) {
1318 return None;
1319 }
1320 let cname = CString::new(bytes).ok()?;
1321 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1322 let mut result: *mut libc::passwd = std::ptr::null_mut();
1323 let mut buf = vec![0u8; 16_384];
1324 let rc = unsafe {
1325 getpwnam_r(
1326 cname.as_ptr(),
1327 &mut pw,
1328 buf.as_mut_ptr().cast::<libc::c_char>(),
1329 buf.len(),
1330 &mut result,
1331 )
1332 };
1333 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1334 return None;
1335 }
1336 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1337 if dir_bytes.is_empty() {
1338 return None;
1339 }
1340 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1341}
1342
1343impl Default for Interpreter {
1344 fn default() -> Self {
1345 Self::new()
1346 }
1347}
1348
1349#[derive(Clone, Copy)]
1351pub(crate) enum CaptureAllMode {
1352 Empty,
1354 Append,
1356 Skip,
1358}
1359
1360impl Interpreter {
1361 pub fn new() -> Self {
1362 let mut scope = Scope::new();
1363 scope.declare_array("INC", vec![PerlValue::string(".".to_string())]);
1364 scope.declare_hash("INC", IndexMap::new());
1365 scope.declare_array("ARGV", vec![]);
1366 scope.declare_array("_", vec![]);
1367
1368 let path_vec: Vec<PerlValue> = std::env::var("PATH")
1370 .unwrap_or_default()
1371 .split(if cfg!(windows) { ';' } else { ':' })
1372 .filter(|s| !s.is_empty())
1373 .map(|p| PerlValue::string(p.to_string()))
1374 .collect();
1375 scope.declare_array_frozen("path", path_vec.clone(), true);
1376 scope.declare_array_frozen("p", path_vec, true);
1377
1378 let fpath_vec: Vec<PerlValue> = std::env::var("FPATH")
1380 .unwrap_or_default()
1381 .split(':')
1382 .filter(|s| !s.is_empty())
1383 .map(|p| PerlValue::string(p.to_string()))
1384 .collect();
1385 scope.declare_array_frozen("fpath", fpath_vec.clone(), true);
1386 scope.declare_array_frozen("f", fpath_vec, true);
1387 scope.declare_hash("ENV", IndexMap::new());
1388 scope.declare_hash("SIG", IndexMap::new());
1389
1390 let term_map = build_term_hash();
1392 scope.declare_hash_global_frozen("term", term_map);
1393
1394 #[cfg(unix)]
1396 {
1397 let uname_map = build_uname_hash();
1398 scope.declare_hash_global_frozen("uname", uname_map);
1399 }
1400 #[cfg(not(unix))]
1401 {
1402 scope.declare_hash_global_frozen("uname", IndexMap::new());
1403 }
1404
1405 #[cfg(unix)]
1407 {
1408 let limits_map = build_limits_hash();
1409 scope.declare_hash_global_frozen("limits", limits_map);
1410 }
1411 #[cfg(not(unix))]
1412 {
1413 scope.declare_hash_global_frozen("limits", IndexMap::new());
1414 }
1415
1416 scope.declare_scalar(
1435 "stryke::VERSION",
1436 PerlValue::string(env!("CARGO_PKG_VERSION").to_string()),
1437 );
1438 scope.declare_array("-", vec![]);
1439 scope.declare_array("+", vec![]);
1440 scope.declare_array("^CAPTURE", vec![]);
1441 scope.declare_array("^CAPTURE_ALL", vec![]);
1442 scope.declare_hash("^HOOK", IndexMap::new());
1443 scope.declare_scalar("~", PerlValue::string("STDOUT".to_string()));
1444
1445 let script_start_time = std::time::SystemTime::now()
1446 .duration_since(std::time::UNIX_EPOCH)
1447 .map(|d| d.as_secs() as i64)
1448 .unwrap_or(0);
1449
1450 let executable_path = cached_executable_path();
1451
1452 let mut special_caret_scalars: HashMap<String, PerlValue> = HashMap::new();
1453 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1454 special_caret_scalars.insert(format!("^{}", name), PerlValue::UNDEF);
1455 }
1456
1457 let mut s = Self {
1458 scope,
1459 subs: HashMap::new(),
1460 struct_defs: HashMap::new(),
1461 enum_defs: HashMap::new(),
1462 class_defs: HashMap::new(),
1463 trait_defs: HashMap::new(),
1464 file: "-e".to_string(),
1465 output_handles: HashMap::new(),
1466 input_handles: HashMap::new(),
1467 ofs: String::new(),
1468 ors: String::new(),
1469 irs: Some("\n".to_string()),
1470 errno: String::new(),
1471 errno_code: 0,
1472 eval_error: String::new(),
1473 eval_error_code: 0,
1474 eval_error_value: None,
1475 argv: Vec::new(),
1476 env: IndexMap::new(),
1477 env_materialized: false,
1478 program_name: "stryke".to_string(),
1479 line_number: 0,
1480 last_readline_handle: String::new(),
1481 last_stdin_die_bracket: "<STDIN>".to_string(),
1482 handle_line_numbers: HashMap::new(),
1483 flip_flop_active: Vec::new(),
1484 flip_flop_exclusive_left_line: Vec::new(),
1485 flip_flop_sequence: Vec::new(),
1486 flip_flop_last_dot: Vec::new(),
1487 flip_flop_tree: HashMap::new(),
1488 sigint_pending_caret: Cell::new(false),
1489 auto_split: false,
1490 field_separator: None,
1491 begin_blocks: Vec::new(),
1492 unit_check_blocks: Vec::new(),
1493 check_blocks: Vec::new(),
1494 init_blocks: Vec::new(),
1495 end_blocks: Vec::new(),
1496 warnings: false,
1497 output_autoflush: false,
1498 default_print_handle: "STDOUT".to_string(),
1499 suppress_stdout: false,
1500 child_exit_status: 0,
1501 last_match: String::new(),
1502 prematch: String::new(),
1503 postmatch: String::new(),
1504 last_paren_match: String::new(),
1505 list_separator: " ".to_string(),
1506 script_start_time,
1507 compile_hints: 0,
1508 warning_bits: 0,
1509 global_phase: "RUN".to_string(),
1510 subscript_sep: "\x1c".to_string(),
1511 inplace_edit: String::new(),
1512 debug_flags: 0,
1513 perl_debug_flags: 0,
1514 eval_nesting: 0,
1515 argv_current_file: String::new(),
1516 diamond_next_idx: 0,
1517 diamond_reader: None,
1518 strict_refs: false,
1519 strict_subs: false,
1520 strict_vars: false,
1521 utf8_pragma: false,
1522 open_pragma_utf8: false,
1523 feature_bits: FEAT_SAY,
1525 num_threads: 0, regex_cache: HashMap::new(),
1527 regex_last: None,
1528 regex_match_memo: None,
1529 regex_capture_scope_fresh: false,
1530 regex_pos: HashMap::new(),
1531 state_vars: HashMap::new(),
1532 state_bindings_stack: Vec::new(),
1533 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1534 dir_handles: HashMap::new(),
1535 io_file_slots: HashMap::new(),
1536 pipe_children: HashMap::new(),
1537 socket_handles: HashMap::new(),
1538 wantarray_kind: WantarrayCtx::Scalar,
1539 profiler: None,
1540 module_export_lists: HashMap::new(),
1541 virtual_modules: HashMap::new(),
1542 tied_hashes: HashMap::new(),
1543 tied_scalars: HashMap::new(),
1544 tied_arrays: HashMap::new(),
1545 overload_table: HashMap::new(),
1546 format_templates: HashMap::new(),
1547 special_caret_scalars,
1548 format_page_number: 0,
1549 format_lines_per_page: 60,
1550 format_lines_left: 0,
1551 format_line_break_chars: "\n".to_string(),
1552 format_top_name: String::new(),
1553 accumulator_format: String::new(),
1554 max_system_fd: 2,
1555 emergency_memory: String::new(),
1556 last_subpattern_name: String::new(),
1557 inc_hook_index: 0,
1558 multiline_match: false,
1559 executable_path,
1560 formfeed_string: "\x0c".to_string(),
1561 glob_handle_alias: HashMap::new(),
1562 glob_restore_frames: vec![Vec::new()],
1563 special_var_restore_frames: vec![Vec::new()],
1564 reflection_hashes_ready: false,
1565 english_enabled: false,
1566 english_no_match_vars: false,
1567 english_match_vars_ever_enabled: false,
1568 english_lexical_scalars: vec![HashSet::new()],
1569 our_lexical_scalars: vec![HashSet::new()],
1570 vm_jit_enabled: !matches!(
1571 std::env::var("STRYKE_NO_JIT"),
1572 Ok(v)
1573 if v == "1"
1574 || v.eq_ignore_ascii_case("true")
1575 || v.eq_ignore_ascii_case("yes")
1576 ),
1577 disasm_bytecode: false,
1578 cached_chunk: None,
1579 sqlite_cache_script_path: None,
1580 in_generator: false,
1581 line_mode_skip_main: false,
1582 line_mode_chunk: None,
1583 line_mode_eof_pending: false,
1584 line_mode_stdin_pending: VecDeque::new(),
1585 rate_limit_slots: Vec::new(),
1586 log_level_override: None,
1587 current_sub_stack: Vec::new(),
1588 debugger: None,
1589 debug_call_stack: Vec::new(),
1590 };
1591 s.install_overload_pragma_stubs();
1592 s
1593 }
1594
1595 pub(crate) fn ensure_reflection_hashes(&mut self) {
1599 if self.reflection_hashes_ready {
1600 return;
1601 }
1602 self.reflection_hashes_ready = true;
1603 let builtins_map = crate::builtins::builtins_hash_map();
1604 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1605 let extensions_map = crate::builtins::extensions_hash_map();
1606 let aliases_map = crate::builtins::aliases_hash_map();
1607 let descriptions_map = crate::builtins::descriptions_hash_map();
1608 let categories_map = crate::builtins::categories_hash_map();
1609 let primaries_map = crate::builtins::primaries_hash_map();
1610 let all_map = crate::builtins::all_hash_map();
1611 self.scope
1612 .declare_hash_global_frozen("stryke::builtins", builtins_map.clone());
1613 self.scope
1614 .declare_hash_global_frozen("stryke::perl_compats", perl_compats_map.clone());
1615 self.scope
1616 .declare_hash_global_frozen("stryke::extensions", extensions_map.clone());
1617 self.scope
1618 .declare_hash_global_frozen("stryke::aliases", aliases_map.clone());
1619 self.scope
1620 .declare_hash_global_frozen("stryke::descriptions", descriptions_map.clone());
1621 self.scope
1622 .declare_hash_global_frozen("stryke::categories", categories_map.clone());
1623 self.scope
1624 .declare_hash_global_frozen("stryke::primaries", primaries_map.clone());
1625 self.scope
1626 .declare_hash_global_frozen("stryke::all", all_map.clone());
1627 for (name, val) in [
1630 ("b", builtins_map),
1631 ("pc", perl_compats_map),
1632 ("e", extensions_map),
1633 ("a", aliases_map),
1634 ("d", descriptions_map),
1635 ("c", categories_map),
1636 ("p", primaries_map),
1637 ("all", all_map),
1638 ] {
1639 if !self.scope.any_frame_has_hash(name) {
1640 self.scope.declare_hash_global_frozen(name, val);
1641 }
1642 }
1643 }
1644
1645 fn install_overload_pragma_stubs(&mut self) {
1649 let empty: Block = vec![];
1650 for key in ["overload::import", "overload::unimport"] {
1651 let name = key.to_string();
1652 self.subs.insert(
1653 name.clone(),
1654 Arc::new(PerlSub {
1655 name,
1656 params: vec![],
1657 body: empty.clone(),
1658 prototype: None,
1659 closure_env: None,
1660 fib_like: None,
1661 }),
1662 );
1663 }
1664 }
1665
1666 pub fn line_mode_worker_clone(&self) -> Interpreter {
1669 Interpreter {
1670 scope: self.scope.clone(),
1671 subs: self.subs.clone(),
1672 struct_defs: self.struct_defs.clone(),
1673 enum_defs: self.enum_defs.clone(),
1674 class_defs: self.class_defs.clone(),
1675 trait_defs: self.trait_defs.clone(),
1676 file: self.file.clone(),
1677 output_handles: HashMap::new(),
1678 input_handles: HashMap::new(),
1679 ofs: self.ofs.clone(),
1680 ors: self.ors.clone(),
1681 irs: self.irs.clone(),
1682 errno: self.errno.clone(),
1683 errno_code: self.errno_code,
1684 eval_error: self.eval_error.clone(),
1685 eval_error_code: self.eval_error_code,
1686 eval_error_value: self.eval_error_value.clone(),
1687 argv: self.argv.clone(),
1688 env: self.env.clone(),
1689 env_materialized: self.env_materialized,
1690 program_name: self.program_name.clone(),
1691 line_number: 0,
1692 last_readline_handle: String::new(),
1693 last_stdin_die_bracket: "<STDIN>".to_string(),
1694 handle_line_numbers: HashMap::new(),
1695 flip_flop_active: Vec::new(),
1696 flip_flop_exclusive_left_line: Vec::new(),
1697 flip_flop_sequence: Vec::new(),
1698 flip_flop_last_dot: Vec::new(),
1699 flip_flop_tree: HashMap::new(),
1700 sigint_pending_caret: Cell::new(false),
1701 auto_split: self.auto_split,
1702 field_separator: self.field_separator.clone(),
1703 begin_blocks: self.begin_blocks.clone(),
1704 unit_check_blocks: self.unit_check_blocks.clone(),
1705 check_blocks: self.check_blocks.clone(),
1706 init_blocks: self.init_blocks.clone(),
1707 end_blocks: self.end_blocks.clone(),
1708 warnings: self.warnings,
1709 output_autoflush: self.output_autoflush,
1710 default_print_handle: self.default_print_handle.clone(),
1711 suppress_stdout: self.suppress_stdout,
1712 child_exit_status: self.child_exit_status,
1713 last_match: self.last_match.clone(),
1714 prematch: self.prematch.clone(),
1715 postmatch: self.postmatch.clone(),
1716 last_paren_match: self.last_paren_match.clone(),
1717 list_separator: self.list_separator.clone(),
1718 script_start_time: self.script_start_time,
1719 compile_hints: self.compile_hints,
1720 warning_bits: self.warning_bits,
1721 global_phase: self.global_phase.clone(),
1722 subscript_sep: self.subscript_sep.clone(),
1723 inplace_edit: self.inplace_edit.clone(),
1724 debug_flags: self.debug_flags,
1725 perl_debug_flags: self.perl_debug_flags,
1726 eval_nesting: self.eval_nesting,
1727 argv_current_file: String::new(),
1728 diamond_next_idx: 0,
1729 diamond_reader: None,
1730 strict_refs: self.strict_refs,
1731 strict_subs: self.strict_subs,
1732 strict_vars: self.strict_vars,
1733 utf8_pragma: self.utf8_pragma,
1734 open_pragma_utf8: self.open_pragma_utf8,
1735 feature_bits: self.feature_bits,
1736 num_threads: 0,
1737 regex_cache: self.regex_cache.clone(),
1738 regex_last: self.regex_last.clone(),
1739 regex_match_memo: self.regex_match_memo.clone(),
1740 regex_capture_scope_fresh: false,
1741 regex_pos: self.regex_pos.clone(),
1742 state_vars: self.state_vars.clone(),
1743 state_bindings_stack: Vec::new(),
1744 rand_rng: self.rand_rng.clone(),
1745 dir_handles: HashMap::new(),
1746 io_file_slots: HashMap::new(),
1747 pipe_children: HashMap::new(),
1748 socket_handles: HashMap::new(),
1749 wantarray_kind: self.wantarray_kind,
1750 profiler: None,
1751 module_export_lists: self.module_export_lists.clone(),
1752 virtual_modules: self.virtual_modules.clone(),
1753 tied_hashes: self.tied_hashes.clone(),
1754 tied_scalars: self.tied_scalars.clone(),
1755 tied_arrays: self.tied_arrays.clone(),
1756 overload_table: self.overload_table.clone(),
1757 format_templates: self.format_templates.clone(),
1758 special_caret_scalars: self.special_caret_scalars.clone(),
1759 format_page_number: self.format_page_number,
1760 format_lines_per_page: self.format_lines_per_page,
1761 format_lines_left: self.format_lines_left,
1762 format_line_break_chars: self.format_line_break_chars.clone(),
1763 format_top_name: self.format_top_name.clone(),
1764 accumulator_format: self.accumulator_format.clone(),
1765 max_system_fd: self.max_system_fd,
1766 emergency_memory: self.emergency_memory.clone(),
1767 last_subpattern_name: self.last_subpattern_name.clone(),
1768 inc_hook_index: self.inc_hook_index,
1769 multiline_match: self.multiline_match,
1770 executable_path: self.executable_path.clone(),
1771 formfeed_string: self.formfeed_string.clone(),
1772 glob_handle_alias: self.glob_handle_alias.clone(),
1773 glob_restore_frames: self.glob_restore_frames.clone(),
1774 special_var_restore_frames: self.special_var_restore_frames.clone(),
1775 reflection_hashes_ready: self.reflection_hashes_ready,
1776 english_enabled: self.english_enabled,
1777 english_no_match_vars: self.english_no_match_vars,
1778 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1779 english_lexical_scalars: self.english_lexical_scalars.clone(),
1780 our_lexical_scalars: self.our_lexical_scalars.clone(),
1781 vm_jit_enabled: self.vm_jit_enabled,
1782 disasm_bytecode: self.disasm_bytecode,
1783 cached_chunk: None,
1785 sqlite_cache_script_path: None,
1786 in_generator: false,
1787 line_mode_skip_main: false,
1788 line_mode_chunk: self.line_mode_chunk.clone(),
1789 line_mode_eof_pending: false,
1790 line_mode_stdin_pending: VecDeque::new(),
1791 rate_limit_slots: Vec::new(),
1792 log_level_override: self.log_level_override,
1793 current_sub_stack: Vec::new(),
1794 debugger: None,
1795 debug_call_stack: Vec::new(),
1796 }
1797 }
1798
1799 pub(crate) fn parallel_thread_count(&mut self) -> usize {
1801 if self.num_threads == 0 {
1802 self.num_threads = rayon::current_num_threads();
1803 }
1804 self.num_threads
1805 }
1806
1807 pub(crate) fn eval_par_list_call(
1809 &mut self,
1810 name: &str,
1811 args: &[PerlValue],
1812 ctx: WantarrayCtx,
1813 line: usize,
1814 ) -> PerlResult<PerlValue> {
1815 match name {
1816 "puniq" => {
1817 let (list_src, show_prog) = match args.len() {
1818 0 => return Err(PerlError::runtime("puniq: expected LIST", line)),
1819 1 => (&args[0], false),
1820 2 => (&args[0], args[1].is_true()),
1821 _ => {
1822 return Err(PerlError::runtime(
1823 "puniq: expected LIST [, progress => EXPR]",
1824 line,
1825 ));
1826 }
1827 };
1828 let list = list_src.to_list();
1829 let n_threads = self.parallel_thread_count();
1830 let pmap_progress = PmapProgress::new(show_prog, list.len());
1831 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
1832 pmap_progress.finish();
1833 if ctx == WantarrayCtx::List {
1834 Ok(PerlValue::array(out))
1835 } else {
1836 Ok(PerlValue::integer(out.len() as i64))
1837 }
1838 }
1839 "pfirst" => {
1840 let (code_val, list_src, show_prog) = match args.len() {
1841 2 => (&args[0], &args[1], false),
1842 3 => (&args[0], &args[1], args[2].is_true()),
1843 _ => {
1844 return Err(PerlError::runtime(
1845 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
1846 line,
1847 ));
1848 }
1849 };
1850 let Some(sub) = code_val.as_code_ref() else {
1851 return Err(PerlError::runtime(
1852 "pfirst: first argument must be a code reference",
1853 line,
1854 ));
1855 };
1856 let sub = sub.clone();
1857 let list = list_src.to_list();
1858 if list.is_empty() {
1859 return Ok(PerlValue::UNDEF);
1860 }
1861 let pmap_progress = PmapProgress::new(show_prog, list.len());
1862 let subs = self.subs.clone();
1863 let (scope_capture, atomic_arrays, atomic_hashes) =
1864 self.scope.capture_with_atomics();
1865 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
1866 let mut local_interp = Interpreter::new();
1867 local_interp.subs = subs.clone();
1868 local_interp.scope.restore_capture(&scope_capture);
1869 local_interp
1870 .scope
1871 .restore_atomics(&atomic_arrays, &atomic_hashes);
1872 local_interp.enable_parallel_guard();
1873 local_interp.scope.set_topic(item);
1874 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1875 Ok(v) => v.is_true(),
1876 Err(_) => false,
1877 }
1878 });
1879 pmap_progress.finish();
1880 Ok(out.unwrap_or(PerlValue::UNDEF))
1881 }
1882 "pany" => {
1883 let (code_val, list_src, show_prog) = match args.len() {
1884 2 => (&args[0], &args[1], false),
1885 3 => (&args[0], &args[1], args[2].is_true()),
1886 _ => {
1887 return Err(PerlError::runtime(
1888 "pany: expected BLOCK, LIST [, progress => EXPR]",
1889 line,
1890 ));
1891 }
1892 };
1893 let Some(sub) = code_val.as_code_ref() else {
1894 return Err(PerlError::runtime(
1895 "pany: first argument must be a code reference",
1896 line,
1897 ));
1898 };
1899 let sub = sub.clone();
1900 let list = list_src.to_list();
1901 let pmap_progress = PmapProgress::new(show_prog, list.len());
1902 let subs = self.subs.clone();
1903 let (scope_capture, atomic_arrays, atomic_hashes) =
1904 self.scope.capture_with_atomics();
1905 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
1906 let mut local_interp = Interpreter::new();
1907 local_interp.subs = subs.clone();
1908 local_interp.scope.restore_capture(&scope_capture);
1909 local_interp
1910 .scope
1911 .restore_atomics(&atomic_arrays, &atomic_hashes);
1912 local_interp.enable_parallel_guard();
1913 local_interp.scope.set_topic(item);
1914 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1915 Ok(v) => v.is_true(),
1916 Err(_) => false,
1917 }
1918 });
1919 pmap_progress.finish();
1920 Ok(PerlValue::integer(if b { 1 } else { 0 }))
1921 }
1922 _ => Err(PerlError::runtime(
1923 format!("internal: unknown par_list builtin {name}"),
1924 line,
1925 )),
1926 }
1927 }
1928
1929 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
1930 #[cfg(unix)]
1931 if let Some(sig) = s.signal() {
1932 return sig as i64 & 0x7f;
1933 }
1934 let code = s.code().unwrap_or(0) as i64;
1935 code << 8
1936 }
1937
1938 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
1939 self.child_exit_status = self.encode_exit_status(s);
1940 }
1941
1942 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
1944 self.errno = e.to_string();
1945 self.errno_code = e.raw_os_error().unwrap_or(0);
1946 }
1947
1948 pub(crate) fn ssh_builtin_execute(&mut self, args: &[PerlValue]) -> PerlResult<PerlValue> {
1959 use std::process::Command;
1960 let mut cmd = Command::new("ssh");
1961 #[cfg(unix)]
1962 {
1963 use libc::geteuid;
1964 let home_for_ssh = if unsafe { geteuid() } == 0 {
1965 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
1966 } else {
1967 None
1968 };
1969 if let Some(h) = home_for_ssh {
1970 cmd.env("HOME", h);
1971 } else if std::env::var_os("HOME").is_none() {
1972 if let Some(h) = pw_home_dir_for_current_uid() {
1973 cmd.env("HOME", h);
1974 }
1975 }
1976 }
1977 for a in args {
1978 cmd.arg(a.to_string());
1979 }
1980 match cmd.status() {
1981 Ok(s) => {
1982 self.record_child_exit_status(s);
1983 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
1984 }
1985 Err(e) => {
1986 self.apply_io_error_to_errno(&e);
1987 Ok(PerlValue::integer(-1))
1988 }
1989 }
1990 }
1991
1992 pub(crate) fn set_eval_error(&mut self, msg: String) {
1994 self.eval_error = msg;
1995 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
1996 self.eval_error_value = None;
1997 }
1998
1999 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &PerlError) {
2000 self.eval_error = e.to_string();
2001 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2002 self.eval_error_value = e.die_value.clone();
2003 }
2004
2005 pub(crate) fn clear_eval_error(&mut self) {
2006 self.eval_error = String::new();
2007 self.eval_error_code = 0;
2008 self.eval_error_value = None;
2009 }
2010
2011 fn bump_line_for_handle(&mut self, handle_key: &str) {
2013 self.last_readline_handle = handle_key.to_string();
2014 *self
2015 .handle_line_numbers
2016 .entry(handle_key.to_string())
2017 .or_insert(0) += 1;
2018 }
2019
2020 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
2022 if name.starts_with('^') {
2023 return name.to_string();
2024 }
2025 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
2026 let pkg = self.current_package();
2027 if !pkg.is_empty() && pkg != "main" {
2028 return format!("{}::{}", pkg, name);
2029 }
2030 }
2031 name.to_string()
2032 }
2033
2034 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
2036 if name.contains("::") {
2037 return name.to_string();
2038 }
2039 let pkg = self.current_package();
2040 if pkg.is_empty() || pkg == "main" {
2041 format!("main::{}", name)
2042 } else {
2043 format!("{}::{}", pkg, name)
2044 }
2045 }
2046
2047 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
2049 if name.contains("::") {
2050 return name.to_string();
2051 }
2052 for (lex, our) in self
2053 .english_lexical_scalars
2054 .iter()
2055 .zip(self.our_lexical_scalars.iter())
2056 .rev()
2057 {
2058 if lex.contains(name) {
2059 if our.contains(name) {
2060 return self.stash_scalar_name_for_package(name);
2061 }
2062 return name.to_string();
2063 }
2064 }
2065 name.to_string()
2066 }
2067
2068 pub(crate) fn tie_execute(
2070 &mut self,
2071 target_kind: u8,
2072 target_name: &str,
2073 class_and_args: Vec<PerlValue>,
2074 line: usize,
2075 ) -> PerlResult<PerlValue> {
2076 let mut it = class_and_args.into_iter();
2077 let class = it.next().unwrap_or(PerlValue::UNDEF);
2078 let pkg = class.to_string();
2079 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
2080 let tie_ctor = match target_kind {
2081 0 => "TIESCALAR",
2082 1 => "TIEARRAY",
2083 2 => "TIEHASH",
2084 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
2085 };
2086 let tie_fn = format!("{}::{}", pkg, tie_ctor);
2087 let sub = self
2088 .subs
2089 .get(&tie_fn)
2090 .cloned()
2091 .ok_or_else(|| PerlError::runtime(format!("tie: cannot find &{}", tie_fn), line))?;
2092 let mut call_args = vec![PerlValue::string(pkg.clone())];
2093 call_args.extend(it);
2094 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
2095 Ok(v) => v,
2096 Err(FlowOrError::Flow(_)) => PerlValue::UNDEF,
2097 Err(FlowOrError::Error(e)) => return Err(e),
2098 };
2099 match target_kind {
2100 0 => {
2101 self.tied_scalars.insert(target_name.to_string(), obj);
2102 }
2103 1 => {
2104 let key = self.stash_array_name_for_package(target_name);
2105 self.tied_arrays.insert(key, obj);
2106 }
2107 2 => {
2108 self.tied_hashes.insert(target_name.to_string(), obj);
2109 }
2110 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
2111 }
2112 Ok(PerlValue::UNDEF)
2113 }
2114
2115 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
2117 let key = format!("{}::ISA", class);
2118 self.scope
2119 .get_array(&key)
2120 .into_iter()
2121 .map(|v| v.to_string())
2122 .collect()
2123 }
2124
2125 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
2126 let p = |c: &str| self.parents_of_class(c);
2127 linearize_c3(class, &p, 0)
2128 }
2129
2130 pub(crate) fn resolve_method_full_name(
2132 &self,
2133 invocant_class: &str,
2134 method: &str,
2135 super_mode: bool,
2136 ) -> Option<String> {
2137 let mro = self.mro_linearize(invocant_class);
2138 let start = if super_mode {
2142 mro.iter()
2143 .position(|p| p == invocant_class)
2144 .map(|i| i + 1)
2145 .unwrap_or(1)
2148 } else {
2149 0
2150 };
2151 for pkg in mro.iter().skip(start) {
2152 if pkg == "UNIVERSAL" {
2153 continue;
2154 }
2155 let fq = format!("{}::{}", pkg, method);
2156 if self.subs.contains_key(&fq) {
2157 return Some(fq);
2158 }
2159 }
2160 mro.iter()
2161 .skip(start)
2162 .find(|p| *p != "UNIVERSAL")
2163 .map(|pkg| format!("{}::{}", pkg, method))
2164 }
2165
2166 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
2167 if let Some(alias) = self.glob_handle_alias.get(name) {
2168 return alias.clone();
2169 }
2170 if let Some(var_name) = name.strip_prefix('$') {
2173 let val = self.scope.get_scalar(var_name);
2174 let s = val.to_string();
2175 if !s.is_empty() {
2176 return self.resolve_io_handle_name(&s);
2177 }
2178 }
2179 name.to_string()
2180 }
2181
2182 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2184 if name.contains("::") {
2185 name.to_string()
2186 } else {
2187 self.qualify_sub_key(name)
2188 }
2189 }
2190
2191 pub(crate) fn copy_typeglob_slots(
2193 &mut self,
2194 lhs: &str,
2195 rhs: &str,
2196 line: usize,
2197 ) -> PerlResult<()> {
2198 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2199 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2200 match self.subs.get(&rhs_sub).cloned() {
2201 Some(s) => {
2202 self.subs.insert(lhs_sub, s);
2203 }
2204 None => {
2205 self.subs.remove(&lhs_sub);
2206 }
2207 }
2208 let sv = self.scope.get_scalar(rhs);
2209 self.scope
2210 .set_scalar(lhs, sv.clone())
2211 .map_err(|e| e.at_line(line))?;
2212 let lhs_an = self.stash_array_name_for_package(lhs);
2213 let rhs_an = self.stash_array_name_for_package(rhs);
2214 let av = self.scope.get_array(&rhs_an);
2215 self.scope
2216 .set_array(&lhs_an, av.clone())
2217 .map_err(|e| e.at_line(line))?;
2218 let hv = self.scope.get_hash(rhs);
2219 self.scope
2220 .set_hash(lhs, hv.clone())
2221 .map_err(|e| e.at_line(line))?;
2222 match self.glob_handle_alias.get(rhs).cloned() {
2223 Some(t) => {
2224 self.glob_handle_alias.insert(lhs.to_string(), t);
2225 }
2226 None => {
2227 self.glob_handle_alias.remove(lhs);
2228 }
2229 }
2230 Ok(())
2231 }
2232
2233 pub(crate) fn install_format_decl(
2235 &mut self,
2236 basename: &str,
2237 lines: &[String],
2238 line: usize,
2239 ) -> PerlResult<()> {
2240 let pkg = self.current_package();
2241 let key = format!("{}::{}", pkg, basename);
2242 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2243 self.format_templates.insert(key, Arc::new(tmpl));
2244 Ok(())
2245 }
2246
2247 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2249 let pkg = self.current_package();
2250 let ent = self.overload_table.entry(pkg).or_default();
2251 for (k, v) in pairs {
2252 ent.insert(k.clone(), v.clone());
2253 }
2254 }
2255
2256 pub(crate) fn local_declare_typeglob(
2259 &mut self,
2260 lhs: &str,
2261 rhs: Option<&str>,
2262 line: usize,
2263 ) -> PerlResult<()> {
2264 let old = self.glob_handle_alias.remove(lhs);
2265 let Some(frame) = self.glob_restore_frames.last_mut() else {
2266 return Err(PerlError::runtime(
2267 "internal: no glob restore frame for local *GLOB",
2268 line,
2269 ));
2270 };
2271 frame.push((lhs.to_string(), old));
2272 if let Some(r) = rhs {
2273 self.glob_handle_alias
2274 .insert(lhs.to_string(), r.to_string());
2275 }
2276 Ok(())
2277 }
2278
2279 pub(crate) fn scope_push_hook(&mut self) {
2280 self.scope.push_frame();
2281 self.glob_restore_frames.push(Vec::new());
2282 self.special_var_restore_frames.push(Vec::new());
2283 self.english_lexical_scalars.push(HashSet::new());
2284 self.our_lexical_scalars.push(HashSet::new());
2285 self.state_bindings_stack.push(Vec::new());
2286 }
2287
2288 #[inline]
2289 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2290 if let Some(s) = self.english_lexical_scalars.last_mut() {
2291 s.insert(name.to_string());
2292 }
2293 }
2294
2295 #[inline]
2296 fn note_our_scalar(&mut self, bare_name: &str) {
2297 if let Some(s) = self.our_lexical_scalars.last_mut() {
2298 s.insert(bare_name.to_string());
2299 }
2300 }
2301
2302 pub(crate) fn scope_pop_hook(&mut self) {
2303 if !self.scope.can_pop_frame() {
2304 return;
2305 }
2306 let defers = self.scope.take_defers();
2310 for coderef in defers {
2311 if let Some(sub) = coderef.as_code_ref() {
2312 let saved_wa = self.wantarray_kind;
2316 self.wantarray_kind = WantarrayCtx::Void;
2317 let _ = self.exec_block_no_scope(&sub.body);
2318 self.wantarray_kind = saved_wa;
2319 }
2320 }
2321 if let Some(bindings) = self.state_bindings_stack.pop() {
2323 for (var_name, state_key) in &bindings {
2324 let val = self.scope.get_scalar(var_name).clone();
2325 self.state_vars.insert(state_key.clone(), val);
2326 }
2327 }
2328 if let Some(entries) = self.special_var_restore_frames.pop() {
2331 for (name, old) in entries.into_iter().rev() {
2332 let _ = self.set_special_var(&name, &old);
2333 }
2334 }
2335 if let Some(entries) = self.glob_restore_frames.pop() {
2336 for (name, old) in entries.into_iter().rev() {
2337 match old {
2338 Some(s) => {
2339 self.glob_handle_alias.insert(name, s);
2340 }
2341 None => {
2342 self.glob_handle_alias.remove(&name);
2343 }
2344 }
2345 }
2346 }
2347 self.scope.pop_frame();
2348 let _ = self.english_lexical_scalars.pop();
2349 let _ = self.our_lexical_scalars.pop();
2350 }
2351
2352 #[inline]
2355 pub(crate) fn enable_parallel_guard(&mut self) {
2356 self.scope.set_parallel_guard(true);
2357 }
2358
2359 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2361 self.begin_blocks.clear();
2362 self.unit_check_blocks.clear();
2363 self.check_blocks.clear();
2364 self.init_blocks.clear();
2365 self.end_blocks.clear();
2366 }
2367
2368 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2374 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2375 self.scope_pop_hook();
2376 }
2377 }
2378
2379 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> PerlResult<()> {
2386 self.touch_env_hash("SIG");
2387 let v = self.scope.get_hash_element("SIG", sig);
2388 if v.is_undef() {
2389 return Self::default_sig_action(sig);
2390 }
2391 if let Some(s) = v.as_str() {
2392 if s == "IGNORE" {
2393 return Ok(());
2394 }
2395 if s == "DEFAULT" {
2396 return Self::default_sig_action(sig);
2397 }
2398 }
2399 if let Some(sub) = v.as_code_ref() {
2400 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2401 Ok(_) => Ok(()),
2402 Err(FlowOrError::Flow(_)) => Ok(()),
2403 Err(FlowOrError::Error(e)) => Err(e),
2404 }
2405 } else {
2406 Self::default_sig_action(sig)
2407 }
2408 }
2409
2410 #[inline]
2412 fn default_sig_action(sig: &str) -> PerlResult<()> {
2413 match sig {
2414 "INT" => std::process::exit(130),
2416 "TERM" => std::process::exit(143),
2417 "ALRM" => std::process::exit(142),
2418 "CHLD" => Ok(()),
2420 _ => Ok(()),
2421 }
2422 }
2423
2424 pub fn materialize_env_if_needed(&mut self) {
2427 if self.env_materialized {
2428 return;
2429 }
2430 self.env = std::env::vars()
2431 .map(|(k, v)| (k, PerlValue::string(v)))
2432 .collect();
2433 self.scope
2434 .set_hash("ENV", self.env.clone())
2435 .expect("set %ENV");
2436 self.env_materialized = true;
2437 }
2438
2439 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2441 self.materialize_env_if_needed();
2442 if let Some(x) = self.log_level_override {
2443 return x;
2444 }
2445 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2446 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2447 }
2448
2449 pub(crate) fn no_color_effective(&mut self) -> bool {
2451 self.materialize_env_if_needed();
2452 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2453 if v.is_undef() {
2454 return false;
2455 }
2456 !v.to_string().is_empty()
2457 }
2458
2459 #[inline]
2460 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2461 if hash_name == "ENV" {
2462 self.materialize_env_if_needed();
2463 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2464 match hash_name {
2465 "b"
2466 | "pc"
2467 | "e"
2468 | "a"
2469 | "d"
2470 | "c"
2471 | "p"
2472 | "all"
2473 | "stryke::builtins"
2474 | "stryke::perl_compats"
2475 | "stryke::extensions"
2476 | "stryke::aliases"
2477 | "stryke::descriptions"
2478 | "stryke::categories"
2479 | "stryke::primaries"
2480 | "stryke::all" => {
2481 self.ensure_reflection_hashes();
2482 }
2483 _ => {}
2484 }
2485 }
2486 }
2487
2488 pub(crate) fn exists_arrow_hash_element(
2490 &self,
2491 container: PerlValue,
2492 key: &str,
2493 line: usize,
2494 ) -> PerlResult<bool> {
2495 if let Some(r) = container.as_hash_ref() {
2496 return Ok(r.read().contains_key(key));
2497 }
2498 if let Some(b) = container.as_blessed_ref() {
2499 let data = b.data.read();
2500 if let Some(r) = data.as_hash_ref() {
2501 return Ok(r.read().contains_key(key));
2502 }
2503 if let Some(hm) = data.as_hash_map() {
2504 return Ok(hm.contains_key(key));
2505 }
2506 return Err(PerlError::runtime(
2507 "exists argument is not a HASH reference",
2508 line,
2509 ));
2510 }
2511 Err(PerlError::runtime(
2512 "exists argument is not a HASH reference",
2513 line,
2514 ))
2515 }
2516
2517 pub(crate) fn delete_arrow_hash_element(
2519 &self,
2520 container: PerlValue,
2521 key: &str,
2522 line: usize,
2523 ) -> PerlResult<PerlValue> {
2524 if let Some(r) = container.as_hash_ref() {
2525 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2526 }
2527 if let Some(b) = container.as_blessed_ref() {
2528 let mut data = b.data.write();
2529 if let Some(r) = data.as_hash_ref() {
2530 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2531 }
2532 if let Some(mut map) = data.as_hash_map() {
2533 let v = map.shift_remove(key).unwrap_or(PerlValue::UNDEF);
2534 *data = PerlValue::hash(map);
2535 return Ok(v);
2536 }
2537 return Err(PerlError::runtime(
2538 "delete argument is not a HASH reference",
2539 line,
2540 ));
2541 }
2542 Err(PerlError::runtime(
2543 "delete argument is not a HASH reference",
2544 line,
2545 ))
2546 }
2547
2548 pub(crate) fn exists_arrow_array_element(
2550 &self,
2551 container: PerlValue,
2552 idx: i64,
2553 line: usize,
2554 ) -> PerlResult<bool> {
2555 if let Some(a) = container.as_array_ref() {
2556 let arr = a.read();
2557 let i = if idx < 0 {
2558 (arr.len() as i64 + idx) as usize
2559 } else {
2560 idx as usize
2561 };
2562 return Ok(i < arr.len());
2563 }
2564 Err(PerlError::runtime(
2565 "exists argument is not an ARRAY reference",
2566 line,
2567 ))
2568 }
2569
2570 pub(crate) fn delete_arrow_array_element(
2572 &self,
2573 container: PerlValue,
2574 idx: i64,
2575 line: usize,
2576 ) -> PerlResult<PerlValue> {
2577 if let Some(a) = container.as_array_ref() {
2578 let mut arr = a.write();
2579 let i = if idx < 0 {
2580 (arr.len() as i64 + idx) as usize
2581 } else {
2582 idx as usize
2583 };
2584 if i >= arr.len() {
2585 return Ok(PerlValue::UNDEF);
2586 }
2587 let old = arr.get(i).cloned().unwrap_or(PerlValue::UNDEF);
2588 arr[i] = PerlValue::UNDEF;
2589 return Ok(old);
2590 }
2591 Err(PerlError::runtime(
2592 "delete argument is not an ARRAY reference",
2593 line,
2594 ))
2595 }
2596
2597 pub(crate) fn inc_directories(&self) -> Vec<String> {
2599 let mut v: Vec<String> = self
2600 .scope
2601 .get_array("INC")
2602 .into_iter()
2603 .map(|x| x.to_string())
2604 .filter(|s| !s.is_empty())
2605 .collect();
2606 if v.is_empty() {
2607 v.push(".".to_string());
2608 }
2609 v
2610 }
2611
2612 #[inline]
2613 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
2614 matches!(
2615 name,
2616 "_" | "0"
2617 | "!"
2618 | "@"
2619 | "/"
2620 | "\\"
2621 | ","
2622 | "."
2623 | "__PACKAGE__"
2624 | "$$"
2625 | "|"
2626 | "?"
2627 | "\""
2628 | "&"
2629 | "`"
2630 | "'"
2631 | "+"
2632 | "<"
2633 | ">"
2634 | "("
2635 | ")"
2636 | "]"
2637 | ";"
2638 | "ARGV"
2639 | "%"
2640 | "="
2641 | "-"
2642 | ":"
2643 | "*"
2644 | "INC"
2645 ) || name.chars().all(|c| c.is_ascii_digit())
2646 || name.starts_with('^')
2647 || (name.starts_with('#') && name.len() > 1)
2648 }
2649
2650 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2651 if !self.strict_vars
2652 || Self::strict_scalar_exempt(name)
2653 || name.contains("::")
2654 || self.scope.scalar_binding_exists(name)
2655 {
2656 return Ok(());
2657 }
2658 Err(PerlError::runtime(
2659 format!(
2660 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
2661 name, name
2662 ),
2663 line,
2664 )
2665 .into())
2666 }
2667
2668 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2669 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
2670 return Ok(());
2671 }
2672 Err(PerlError::runtime(
2673 format!(
2674 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
2675 name, name
2676 ),
2677 line,
2678 )
2679 .into())
2680 }
2681
2682 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2683 if !self.strict_vars
2685 || name.contains("::")
2686 || self.scope.hash_binding_exists(name)
2687 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
2688 {
2689 return Ok(());
2690 }
2691 Err(PerlError::runtime(
2692 format!(
2693 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
2694 name, name
2695 ),
2696 line,
2697 )
2698 .into())
2699 }
2700
2701 fn looks_like_version_only(spec: &str) -> bool {
2702 let t = spec.trim();
2703 !t.is_empty()
2704 && !t.contains('/')
2705 && !t.contains('\\')
2706 && !t.contains("::")
2707 && t.chars()
2708 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
2709 && t.chars().any(|c| c.is_ascii_digit())
2710 }
2711
2712 fn module_spec_to_relpath(spec: &str) -> String {
2713 let t = spec.trim();
2714 if t.contains("::") {
2715 format!("{}.pm", t.replace("::", "/"))
2716 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
2717 t.replace('\\', "/")
2718 } else {
2719 format!("{}.pm", t)
2720 }
2721 }
2722
2723 fn try_resolve_via_lockfile(relpath: &str) -> Option<std::path::PathBuf> {
2730 let cwd = std::env::current_dir().ok()?;
2731 let project_root = crate::pkg::commands::find_project_root(&cwd)?;
2732
2733 let stem = relpath
2736 .strip_suffix(".pm")
2737 .or_else(|| relpath.strip_suffix(".pl"))
2738 .or_else(|| relpath.strip_suffix(".stk"))
2739 .unwrap_or(relpath);
2740 let logical = stem.replace('/', "::");
2741
2742 match crate::pkg::commands::resolve_module(&project_root, &logical) {
2743 Ok(found) => found,
2744 Err(_) => None,
2745 }
2746 }
2747
2748 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
2753 if name.contains("::") {
2754 return name.to_string();
2755 }
2756 let pkg = self.current_package();
2757 if pkg.is_empty() || pkg == "main" {
2758 name.to_string()
2759 } else {
2760 format!("{}::{}", pkg, name)
2761 }
2762 }
2763
2764 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
2766 let mut msg = format!("Undefined subroutine &{}", name);
2767 if self.strict_subs {
2768 msg.push_str(
2769 " (strict subs: declare the sub or use a fully qualified name before calling)",
2770 );
2771 }
2772 msg
2773 }
2774
2775 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
2777 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
2778 if self.strict_subs {
2779 msg.push_str(
2780 " (strict subs: declare the sub or use a fully qualified name before calling)",
2781 );
2782 }
2783 msg
2784 }
2785
2786 fn import_alias_key(&self, short: &str) -> String {
2788 self.qualify_sub_key(short)
2789 }
2790
2791 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
2793 if imports.len() == 1 {
2794 match &imports[0].kind {
2795 ExprKind::QW(ws) => return ws.is_empty(),
2796 ExprKind::List(xs) => return xs.is_empty(),
2798 _ => {}
2799 }
2800 }
2801 false
2802 }
2803
2804 fn apply_module_import(
2806 &mut self,
2807 module: &str,
2808 imports: &[Expr],
2809 line: usize,
2810 ) -> PerlResult<()> {
2811 if imports.is_empty() {
2812 return self.import_all_from_module(module, line);
2813 }
2814 if Self::is_explicit_empty_import_list(imports) {
2815 return Ok(());
2816 }
2817 let names = Self::pragma_import_strings(imports, line)?;
2818 if names.is_empty() {
2819 return Ok(());
2820 }
2821 for name in names {
2822 self.import_one_symbol(module, &name, line)?;
2823 }
2824 Ok(())
2825 }
2826
2827 fn import_all_from_module(&mut self, module: &str, line: usize) -> PerlResult<()> {
2828 if let Some(lists) = self.module_export_lists.get(module) {
2829 let export: Vec<String> = lists.export.clone();
2830 for short in export {
2831 self.import_named_sub(module, &short, line)?;
2832 }
2833 return Ok(());
2834 }
2835 let prefix = format!("{}::", module);
2837 let keys: Vec<String> = self
2838 .subs
2839 .keys()
2840 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
2841 .cloned()
2842 .collect();
2843 for k in keys {
2844 let short = k[prefix.len()..].to_string();
2845 if let Some(sub) = self.subs.get(&k).cloned() {
2846 let alias = self.import_alias_key(&short);
2847 self.subs.insert(alias, sub);
2848 }
2849 }
2850 Ok(())
2851 }
2852
2853 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> PerlResult<()> {
2855 let qual = format!("{}::{}", module, short);
2856 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
2857 PerlError::runtime(
2858 format!(
2859 "`{}` is not defined in module `{}` (expected `{}`)",
2860 short, module, qual
2861 ),
2862 line,
2863 )
2864 })?;
2865 let alias = self.import_alias_key(short);
2866 self.subs.insert(alias, sub);
2867 Ok(())
2868 }
2869
2870 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> PerlResult<()> {
2871 if let Some(lists) = self.module_export_lists.get(module) {
2872 let allowed: HashSet<&str> = lists
2873 .export
2874 .iter()
2875 .map(|s| s.as_str())
2876 .chain(lists.export_ok.iter().map(|s| s.as_str()))
2877 .collect();
2878 if !allowed.contains(export) {
2879 return Err(PerlError::runtime(
2880 format!(
2881 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
2882 export, module
2883 ),
2884 line,
2885 ));
2886 }
2887 }
2888 self.import_named_sub(module, export, line)
2889 }
2890
2891 fn record_exporter_our_array_name(&mut self, name: &str, items: &[PerlValue]) {
2893 if name != "EXPORT" && name != "EXPORT_OK" {
2894 return;
2895 }
2896 let pkg = self.current_package();
2897 if pkg.is_empty() || pkg == "main" {
2898 return;
2899 }
2900 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
2901 let ent = self.module_export_lists.entry(pkg).or_default();
2902 if name == "EXPORT" {
2903 ent.export = names;
2904 } else {
2905 ent.export_ok = names;
2906 }
2907 }
2908
2909 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
2913 let key = self.qualify_sub_key(name);
2914 let Some(sub) = self.subs.get(&key).cloned() else {
2915 return;
2916 };
2917 let captured = self.scope.capture();
2918 let closure_env = if captured.is_empty() {
2919 None
2920 } else {
2921 Some(captured)
2922 };
2923 let mut new_sub = (*sub).clone();
2924 new_sub.closure_env = closure_env;
2925 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
2926 self.subs.insert(key, Arc::new(new_sub));
2927 }
2928
2929 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<PerlSub>> {
2930 if let Some(s) = self.subs.get(name) {
2931 return Some(s.clone());
2932 }
2933 if !name.contains("::") {
2934 let pkg = self.current_package();
2936 if !pkg.is_empty() && pkg != "main" {
2937 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
2938 q.push_str(&pkg);
2939 q.push_str("::");
2940 q.push_str(name);
2941 return self.subs.get(&q).cloned();
2942 }
2943 return None;
2944 }
2945 if let Some(rest) = name.strip_prefix("main::") {
2950 if !rest.contains("::") {
2951 return self.subs.get(rest).cloned();
2952 }
2953 }
2954 None
2955 }
2956
2957 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
2960 if let Some(first) = imports.first() {
2961 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
2962 return &imports[1..];
2963 }
2964 }
2965 imports
2966 }
2967
2968 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> PerlResult<Vec<String>> {
2970 let mut out = Vec::new();
2971 for e in imports {
2972 match &e.kind {
2973 ExprKind::String(s) => out.push(s.clone()),
2974 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
2975 ExprKind::Integer(n) => out.push(n.to_string()),
2976 ExprKind::InterpolatedString(parts) => {
2979 let mut s = String::new();
2980 for p in parts {
2981 match p {
2982 StringPart::Literal(l) => s.push_str(l),
2983 StringPart::ScalarVar(v) => {
2984 s.push('$');
2985 s.push_str(v);
2986 }
2987 StringPart::ArrayVar(v) => {
2988 s.push('@');
2989 s.push_str(v);
2990 }
2991 _ => {
2992 return Err(PerlError::runtime(
2993 "pragma import must be a compile-time string, qw(), or integer",
2994 e.line.max(default_line),
2995 ));
2996 }
2997 }
2998 }
2999 out.push(s);
3000 }
3001 _ => {
3002 return Err(PerlError::runtime(
3003 "pragma import must be a compile-time string, qw(), or integer",
3004 e.line.max(default_line),
3005 ));
3006 }
3007 }
3008 }
3009 Ok(out)
3010 }
3011
3012 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3013 if imports.is_empty() {
3014 self.strict_refs = true;
3015 self.strict_subs = true;
3016 self.strict_vars = true;
3017 return Ok(());
3018 }
3019 let names = Self::pragma_import_strings(imports, line)?;
3020 for name in names {
3021 match name.as_str() {
3022 "refs" => self.strict_refs = true,
3023 "subs" => self.strict_subs = true,
3024 "vars" => self.strict_vars = true,
3025 _ => {
3026 return Err(PerlError::runtime(
3027 format!("Unknown strict mode `{}`", name),
3028 line,
3029 ));
3030 }
3031 }
3032 }
3033 Ok(())
3034 }
3035
3036 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3037 if imports.is_empty() {
3038 self.strict_refs = false;
3039 self.strict_subs = false;
3040 self.strict_vars = false;
3041 return Ok(());
3042 }
3043 let names = Self::pragma_import_strings(imports, line)?;
3044 for name in names {
3045 match name.as_str() {
3046 "refs" => self.strict_refs = false,
3047 "subs" => self.strict_subs = false,
3048 "vars" => self.strict_vars = false,
3049 _ => {
3050 return Err(PerlError::runtime(
3051 format!("Unknown strict mode `{}`", name),
3052 line,
3053 ));
3054 }
3055 }
3056 }
3057 Ok(())
3058 }
3059
3060 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3061 let items = Self::pragma_import_strings(imports, line)?;
3062 if items.is_empty() {
3063 return Err(PerlError::runtime(
3064 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
3065 line,
3066 ));
3067 }
3068 for item in items {
3069 let s = item.trim();
3070 if let Some(rest) = s.strip_prefix(':') {
3071 self.apply_feature_bundle(rest, line)?;
3072 } else {
3073 self.apply_feature_name(s, true, line)?;
3074 }
3075 }
3076 Ok(())
3077 }
3078
3079 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3080 if imports.is_empty() {
3081 self.feature_bits = 0;
3082 return Ok(());
3083 }
3084 let items = Self::pragma_import_strings(imports, line)?;
3085 for item in items {
3086 let s = item.trim();
3087 if let Some(rest) = s.strip_prefix(':') {
3088 self.clear_feature_bundle(rest);
3089 } else {
3090 self.apply_feature_name(s, false, line)?;
3091 }
3092 }
3093 Ok(())
3094 }
3095
3096 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> PerlResult<()> {
3097 let key = v.trim();
3098 match key {
3099 "5.10" | "5.010" | "5.10.0" => {
3100 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3101 }
3102 "5.12" | "5.012" | "5.12.0" => {
3103 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3104 }
3105 _ => {
3106 return Err(PerlError::runtime(
3107 format!("unsupported feature bundle :{}", key),
3108 line,
3109 ));
3110 }
3111 }
3112 Ok(())
3113 }
3114
3115 fn clear_feature_bundle(&mut self, v: &str) {
3116 let key = v.trim();
3117 if matches!(
3118 key,
3119 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
3120 ) {
3121 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
3122 }
3123 }
3124
3125 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> PerlResult<()> {
3126 let bit = match name {
3127 "say" => FEAT_SAY,
3128 "state" => FEAT_STATE,
3129 "switch" => FEAT_SWITCH,
3130 "unicode_strings" => FEAT_UNICODE_STRINGS,
3131 "postderef"
3135 | "postderef_qq"
3136 | "evalbytes"
3137 | "current_sub"
3138 | "fc"
3139 | "lexical_subs"
3140 | "signatures"
3141 | "refaliasing"
3142 | "bitwise"
3143 | "isa"
3144 | "indirect"
3145 | "multidimensional"
3146 | "bareword_filehandles"
3147 | "try"
3148 | "defer"
3149 | "extra_paired_delimiters"
3150 | "module_true"
3151 | "class"
3152 | "array_base" => return Ok(()),
3153 _ => {
3154 return Err(PerlError::runtime(
3155 format!("unknown feature `{}`", name),
3156 line,
3157 ));
3158 }
3159 };
3160 if enable {
3161 self.feature_bits |= bit;
3162 } else {
3163 self.feature_bits &= !bit;
3164 }
3165 Ok(())
3166 }
3167
3168 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> PerlResult<PerlValue> {
3170 let t = spec.trim();
3171 if t.is_empty() {
3172 return Err(PerlError::runtime("require: empty argument", line));
3173 }
3174 match t {
3175 "strict" => {
3176 self.apply_use_strict(&[], line)?;
3177 return Ok(PerlValue::integer(1));
3178 }
3179 "utf8" => {
3180 self.utf8_pragma = true;
3181 return Ok(PerlValue::integer(1));
3182 }
3183 "feature" | "v5" => {
3184 return Ok(PerlValue::integer(1));
3185 }
3186 "warnings" => {
3187 self.warnings = true;
3188 return Ok(PerlValue::integer(1));
3189 }
3190 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
3191 return Ok(PerlValue::integer(1));
3192 }
3193 _ => {}
3194 }
3195 let p = Path::new(t);
3196 if p.is_absolute() {
3197 return self.require_absolute_path(p, line);
3198 }
3199 if t.starts_with("./") || t.starts_with("../") {
3200 return self.require_relative_path(p, line);
3201 }
3202 if Self::looks_like_version_only(t) {
3203 return Ok(PerlValue::integer(1));
3204 }
3205 let relpath = Self::module_spec_to_relpath(t);
3206 self.require_from_inc(&relpath, line)
3207 }
3208
3209 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> PerlResult<()> {
3211 let v = self.scope.get_hash_element("^HOOK", key);
3212 if v.is_undef() {
3213 return Ok(());
3214 }
3215 let Some(sub) = v.as_code_ref() else {
3216 return Ok(());
3217 };
3218 let r = self.call_sub(
3219 sub.as_ref(),
3220 vec![PerlValue::string(path.to_string())],
3221 WantarrayCtx::Scalar,
3222 line,
3223 );
3224 match r {
3225 Ok(_) => Ok(()),
3226 Err(FlowOrError::Error(e)) => Err(e),
3227 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3228 Err(FlowOrError::Flow(other)) => Err(PerlError::runtime(
3229 format!(
3230 "require hook {:?} returned unexpected control flow: {:?}",
3231 key, other
3232 ),
3233 line,
3234 )),
3235 }
3236 }
3237
3238 fn require_absolute_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3239 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3240 let key = canon.to_string_lossy().into_owned();
3241 if self.scope.exists_hash_element("INC", &key) {
3242 return Ok(PerlValue::integer(1));
3243 }
3244 self.invoke_require_hook("require__before", &key, line)?;
3245 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3246 PerlError::runtime(
3247 format!("Can't open {} for reading: {}", canon.display(), e),
3248 line,
3249 )
3250 })?;
3251 let code = crate::data_section::strip_perl_end_marker(&code);
3252 self.scope
3253 .set_hash_element("INC", &key, PerlValue::string(key.clone()))?;
3254 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3255 let r = crate::parse_and_run_module_in_file(code, self, &key);
3256 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3257 r?;
3258 self.invoke_require_hook("require__after", &key, line)?;
3259 Ok(PerlValue::integer(1))
3260 }
3261
3262 fn require_relative_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3263 if !path.exists() {
3264 return Err(PerlError::runtime(
3265 format!(
3266 "Can't locate {} (relative path does not exist)",
3267 path.display()
3268 ),
3269 line,
3270 ));
3271 }
3272 self.require_absolute_path(path, line)
3273 }
3274
3275 fn require_from_inc(&mut self, relpath: &str, line: usize) -> PerlResult<PerlValue> {
3276 if self.scope.exists_hash_element("INC", relpath) {
3277 return Ok(PerlValue::integer(1));
3278 }
3279 self.invoke_require_hook("require__before", relpath, line)?;
3280
3281 if let Some(found) = Self::try_resolve_via_lockfile(relpath) {
3287 let code = read_file_text_perl_compat(&found).map_err(|e| {
3288 PerlError::runtime(
3289 format!("Can't open {} for reading: {}", found.display(), e),
3290 line,
3291 )
3292 })?;
3293 let code = crate::data_section::strip_perl_end_marker(&code);
3294 let abs = found.canonicalize().unwrap_or(found);
3295 let abs_s = abs.to_string_lossy().into_owned();
3296 self.scope
3297 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3298 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3299 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3300 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3301 r?;
3302 self.invoke_require_hook("require__after", relpath, line)?;
3303 return Ok(PerlValue::integer(1));
3304 }
3305
3306 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3308 let code = crate::data_section::strip_perl_end_marker(&code);
3309 self.scope.set_hash_element(
3310 "INC",
3311 relpath,
3312 PerlValue::string(format!("(virtual)/{}", relpath)),
3313 )?;
3314 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3315 let r = crate::parse_and_run_module_in_file(code, self, relpath);
3316 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3317 r?;
3318 self.invoke_require_hook("require__after", relpath, line)?;
3319 return Ok(PerlValue::integer(1));
3320 }
3321
3322 for dir in self.inc_directories() {
3323 let full = Path::new(&dir).join(relpath);
3324 if full.is_file() {
3325 let code = read_file_text_perl_compat(&full).map_err(|e| {
3326 PerlError::runtime(
3327 format!("Can't open {} for reading: {}", full.display(), e),
3328 line,
3329 )
3330 })?;
3331 let code = crate::data_section::strip_perl_end_marker(&code);
3332 let abs = full.canonicalize().unwrap_or(full);
3333 let abs_s = abs.to_string_lossy().into_owned();
3334 self.scope
3335 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3336 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3337 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3338 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3339 r?;
3340 self.invoke_require_hook("require__after", relpath, line)?;
3341 return Ok(PerlValue::integer(1));
3342 }
3343 }
3344 Err(PerlError::runtime(
3345 format!(
3346 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3347 relpath
3348 ),
3349 line,
3350 ))
3351 }
3352
3353 pub fn register_virtual_module(&mut self, path: String, source: String) {
3355 self.virtual_modules.insert(path, source);
3356 }
3357
3358 pub(crate) fn exec_use_stmt(
3360 &mut self,
3361 module: &str,
3362 imports: &[Expr],
3363 line: usize,
3364 ) -> PerlResult<()> {
3365 match module {
3366 "strict" => self.apply_use_strict(imports, line),
3367 "utf8" => {
3368 if !imports.is_empty() {
3369 return Err(PerlError::runtime("use utf8 takes no arguments", line));
3370 }
3371 self.utf8_pragma = true;
3372 Ok(())
3373 }
3374 "feature" => self.apply_use_feature(imports, line),
3375 "v5" => Ok(()),
3376 "warnings" => {
3377 self.warnings = true;
3378 Ok(())
3379 }
3380 "English" => {
3381 self.english_enabled = true;
3382 let args = Self::pragma_import_strings(imports, line)?;
3383 let no_match = args.iter().any(|a| a == "-no_match_vars");
3384 if !no_match {
3388 self.english_match_vars_ever_enabled = true;
3389 }
3390 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3391 Ok(())
3392 }
3393 "Env" => self.apply_use_env(imports, line),
3394 "open" => self.apply_use_open(imports, line),
3395 "constant" => self.apply_use_constant(imports, line),
3396 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3397 _ => {
3398 self.require_execute(module, line)?;
3399 let imports = Self::imports_after_leading_use_version(imports);
3400 self.apply_module_import(module, imports, line)?;
3401 Ok(())
3402 }
3403 }
3404 }
3405
3406 pub(crate) fn exec_no_stmt(
3408 &mut self,
3409 module: &str,
3410 imports: &[Expr],
3411 line: usize,
3412 ) -> PerlResult<()> {
3413 match module {
3414 "strict" => self.apply_no_strict(imports, line),
3415 "utf8" => {
3416 if !imports.is_empty() {
3417 return Err(PerlError::runtime("no utf8 takes no arguments", line));
3418 }
3419 self.utf8_pragma = false;
3420 Ok(())
3421 }
3422 "feature" => self.apply_no_feature(imports, line),
3423 "v5" => Ok(()),
3424 "warnings" => {
3425 self.warnings = false;
3426 Ok(())
3427 }
3428 "English" => {
3429 self.english_enabled = false;
3430 if !self.english_match_vars_ever_enabled {
3433 self.english_no_match_vars = false;
3434 }
3435 Ok(())
3436 }
3437 "open" => {
3438 self.open_pragma_utf8 = false;
3439 Ok(())
3440 }
3441 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3442 _ => Ok(()),
3443 }
3444 }
3445
3446 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3448 let names = Self::pragma_import_strings(imports, line)?;
3449 for n in names {
3450 let key = n.trim_start_matches('@');
3451 if key.eq_ignore_ascii_case("PATH") {
3452 let path_env = std::env::var("PATH").unwrap_or_default();
3453 let path_vec: Vec<PerlValue> = std::env::split_paths(&path_env)
3454 .map(|p| PerlValue::string(p.to_string_lossy().into_owned()))
3455 .collect();
3456 let aname = self.stash_array_name_for_package("PATH");
3457 self.scope.declare_array(&aname, path_vec);
3458 }
3459 }
3460 Ok(())
3461 }
3462
3463 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3465 let items = Self::pragma_import_strings(imports, line)?;
3466 for item in items {
3467 let s = item.trim();
3468 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3469 self.open_pragma_utf8 = true;
3470 continue;
3471 }
3472 if let Some(rest) = s.strip_prefix(":encoding(") {
3473 if let Some(inner) = rest.strip_suffix(')') {
3474 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3475 self.open_pragma_utf8 = true;
3476 }
3477 }
3478 }
3479 }
3480 Ok(())
3481 }
3482
3483 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3485 if imports.is_empty() {
3486 return Ok(());
3487 }
3488 if imports.len() == 1 {
3490 match &imports[0].kind {
3491 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
3492 _ => {}
3493 }
3494 }
3495 for imp in imports {
3496 match &imp.kind {
3497 ExprKind::List(items) => {
3498 if items.len() % 2 != 0 {
3499 return Err(PerlError::runtime(
3500 format!(
3501 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
3502 items.len()
3503 ),
3504 line,
3505 ));
3506 }
3507 let mut i = 0;
3508 while i < items.len() {
3509 let name = match &items[i].kind {
3510 ExprKind::String(s) => s.clone(),
3511 _ => {
3512 return Err(PerlError::runtime(
3513 "use constant: constant name must be a string literal",
3514 line,
3515 ));
3516 }
3517 };
3518 let val = match self.eval_expr(&items[i + 1]) {
3519 Ok(v) => v,
3520 Err(FlowOrError::Error(e)) => return Err(e),
3521 Err(FlowOrError::Flow(_)) => {
3522 return Err(PerlError::runtime(
3523 "use constant: unexpected control flow in initializer",
3524 line,
3525 ));
3526 }
3527 };
3528 self.install_constant_sub(&name, &val, line)?;
3529 i += 2;
3530 }
3531 }
3532 _ => {
3533 return Err(PerlError::runtime(
3534 "use constant: expected list of NAME => VALUE pairs",
3535 line,
3536 ));
3537 }
3538 }
3539 }
3540 Ok(())
3541 }
3542
3543 fn install_constant_sub(&mut self, name: &str, val: &PerlValue, line: usize) -> PerlResult<()> {
3544 let key = self.qualify_sub_key(name);
3545 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
3546 let body = vec![Statement {
3547 label: None,
3548 kind: StmtKind::Return(Some(ret_expr)),
3549 line,
3550 }];
3551 self.subs.insert(
3552 key.clone(),
3553 Arc::new(PerlSub {
3554 name: key,
3555 params: vec![],
3556 body,
3557 prototype: None,
3558 closure_env: None,
3559 fib_like: None,
3560 }),
3561 );
3562 Ok(())
3563 }
3564
3565 fn perl_value_to_const_literal_expr(&self, v: &PerlValue, line: usize) -> PerlResult<Expr> {
3567 if v.is_undef() {
3568 return Ok(Expr {
3569 kind: ExprKind::Undef,
3570 line,
3571 });
3572 }
3573 if let Some(n) = v.as_integer() {
3574 return Ok(Expr {
3575 kind: ExprKind::Integer(n),
3576 line,
3577 });
3578 }
3579 if let Some(f) = v.as_float() {
3580 return Ok(Expr {
3581 kind: ExprKind::Float(f),
3582 line,
3583 });
3584 }
3585 if let Some(s) = v.as_str() {
3586 return Ok(Expr {
3587 kind: ExprKind::String(s),
3588 line,
3589 });
3590 }
3591 if let Some(arr) = v.as_array_vec() {
3592 let mut elems = Vec::with_capacity(arr.len());
3593 for e in &arr {
3594 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3595 }
3596 return Ok(Expr {
3597 kind: ExprKind::ArrayRef(elems),
3598 line,
3599 });
3600 }
3601 if let Some(h) = v.as_hash_map() {
3602 let mut pairs = Vec::with_capacity(h.len());
3603 for (k, vv) in h.iter() {
3604 pairs.push((
3605 Expr {
3606 kind: ExprKind::String(k.clone()),
3607 line,
3608 },
3609 self.perl_value_to_const_literal_expr(vv, line)?,
3610 ));
3611 }
3612 return Ok(Expr {
3613 kind: ExprKind::HashRef(pairs),
3614 line,
3615 });
3616 }
3617 if let Some(aref) = v.as_array_ref() {
3618 let arr = aref.read();
3619 let mut elems = Vec::with_capacity(arr.len());
3620 for e in arr.iter() {
3621 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3622 }
3623 return Ok(Expr {
3624 kind: ExprKind::ArrayRef(elems),
3625 line,
3626 });
3627 }
3628 if let Some(href) = v.as_hash_ref() {
3629 let h = href.read();
3630 let mut pairs = Vec::with_capacity(h.len());
3631 for (k, vv) in h.iter() {
3632 pairs.push((
3633 Expr {
3634 kind: ExprKind::String(k.clone()),
3635 line,
3636 },
3637 self.perl_value_to_const_literal_expr(vv, line)?,
3638 ));
3639 }
3640 return Ok(Expr {
3641 kind: ExprKind::HashRef(pairs),
3642 line,
3643 });
3644 }
3645 Err(PerlError::runtime(
3646 format!("use constant: unsupported value type ({v:?})"),
3647 line,
3648 ))
3649 }
3650
3651 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> PerlResult<()> {
3653 for stmt in &program.statements {
3654 match &stmt.kind {
3655 StmtKind::Package { name } => {
3656 let _ = self
3657 .scope
3658 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
3659 }
3660 StmtKind::SubDecl {
3661 name,
3662 params,
3663 body,
3664 prototype,
3665 } => {
3666 let key = self.qualify_sub_key(name);
3667 let mut sub = PerlSub {
3668 name: name.clone(),
3669 params: params.clone(),
3670 body: body.clone(),
3671 closure_env: None,
3672 prototype: prototype.clone(),
3673 fib_like: None,
3674 };
3675 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
3676 self.subs.insert(key, Arc::new(sub));
3677 }
3678 StmtKind::UsePerlVersion { .. } => {}
3679 StmtKind::Use { module, imports } => {
3680 self.exec_use_stmt(module, imports, stmt.line)?;
3681 }
3682 StmtKind::UseOverload { pairs } => {
3683 self.install_use_overload_pairs(pairs);
3684 }
3685 StmtKind::FormatDecl { name, lines } => {
3686 self.install_format_decl(name, lines, stmt.line)?;
3687 }
3688 StmtKind::No { module, imports } => {
3689 self.exec_no_stmt(module, imports, stmt.line)?;
3690 }
3691 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
3692 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
3693 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
3694 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
3695 StmtKind::End(block) => self.end_blocks.push(block.clone()),
3696 _ => {}
3697 }
3698 }
3699 Ok(())
3700 }
3701
3702 pub fn install_data_handle(&mut self, data: Vec<u8>) {
3704 self.input_handles.insert(
3705 "DATA".to_string(),
3706 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
3707 );
3708 }
3709
3710 pub(crate) fn open_builtin_execute(
3716 &mut self,
3717 handle_name: String,
3718 mode_s: String,
3719 file_opt: Option<String>,
3720 line: usize,
3721 ) -> PerlResult<PerlValue> {
3722 let (actual_mode, path) = if let Some(f) = file_opt {
3727 (mode_s, f)
3728 } else {
3729 let trimmed = mode_s.trim();
3730 if let Some(rest) = trimmed.strip_prefix('|') {
3731 ("|-".to_string(), rest.trim_start().to_string())
3732 } else if trimmed.ends_with('|') {
3733 let mut cmd = trimmed.to_string();
3734 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
3736 } else if let Some(rest) = trimmed.strip_prefix(">>") {
3737 (">>".to_string(), rest.trim().to_string())
3738 } else if let Some(rest) = trimmed.strip_prefix('>') {
3739 (">".to_string(), rest.trim().to_string())
3740 } else if let Some(rest) = trimmed.strip_prefix('<') {
3741 ("<".to_string(), rest.trim().to_string())
3742 } else {
3743 ("<".to_string(), trimmed.to_string())
3744 }
3745 };
3746 let handle_return = handle_name.clone();
3747 match actual_mode.as_str() {
3748 "-|" => {
3749 let mut cmd = piped_shell_command(&path);
3750 cmd.stdout(Stdio::piped());
3751 let mut child = cmd.spawn().map_err(|e| {
3752 self.apply_io_error_to_errno(&e);
3753 PerlError::runtime(format!("Can't open pipe from command: {}", e), line)
3754 })?;
3755 let stdout = child
3756 .stdout
3757 .take()
3758 .ok_or_else(|| PerlError::runtime("pipe: child has no stdout", line))?;
3759 self.input_handles
3760 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
3761 self.pipe_children.insert(handle_name, child);
3762 }
3763 "|-" => {
3764 let mut cmd = piped_shell_command(&path);
3765 cmd.stdin(Stdio::piped());
3766 let mut child = cmd.spawn().map_err(|e| {
3767 self.apply_io_error_to_errno(&e);
3768 PerlError::runtime(format!("Can't open pipe to command: {}", e), line)
3769 })?;
3770 let stdin = child
3771 .stdin
3772 .take()
3773 .ok_or_else(|| PerlError::runtime("pipe: child has no stdin", line))?;
3774 self.output_handles
3775 .insert(handle_name.clone(), Box::new(stdin));
3776 self.pipe_children.insert(handle_name, child);
3777 }
3778 "<" => {
3779 let file = std::fs::File::open(&path).map_err(|e| {
3780 self.apply_io_error_to_errno(&e);
3781 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3782 })?;
3783 let shared = Arc::new(Mutex::new(file));
3784 self.io_file_slots
3785 .insert(handle_name.clone(), Arc::clone(&shared));
3786 self.input_handles.insert(
3787 handle_name.clone(),
3788 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
3789 );
3790 }
3791 ">" => {
3792 let file = std::fs::File::create(&path).map_err(|e| {
3793 self.apply_io_error_to_errno(&e);
3794 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3795 })?;
3796 let shared = Arc::new(Mutex::new(file));
3797 self.io_file_slots
3798 .insert(handle_name.clone(), Arc::clone(&shared));
3799 self.output_handles.insert(
3800 handle_name.clone(),
3801 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3802 );
3803 }
3804 ">>" => {
3805 let file = std::fs::OpenOptions::new()
3806 .append(true)
3807 .create(true)
3808 .open(&path)
3809 .map_err(|e| {
3810 self.apply_io_error_to_errno(&e);
3811 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3812 })?;
3813 let shared = Arc::new(Mutex::new(file));
3814 self.io_file_slots
3815 .insert(handle_name.clone(), Arc::clone(&shared));
3816 self.output_handles.insert(
3817 handle_name.clone(),
3818 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3819 );
3820 }
3821 _ => {
3822 return Err(PerlError::runtime(
3823 format!("Unknown open mode '{}'", actual_mode),
3824 line,
3825 ));
3826 }
3827 }
3828 Ok(PerlValue::io_handle(handle_return))
3829 }
3830
3831 pub(crate) fn eval_chunk_by_builtin(
3835 &mut self,
3836 key_spec: &Expr,
3837 list_expr: &Expr,
3838 ctx: WantarrayCtx,
3839 line: usize,
3840 ) -> ExecResult {
3841 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
3842 let chunks = match &key_spec.kind {
3843 ExprKind::CodeRef { .. } => {
3844 let cr = self.eval_expr(key_spec)?;
3845 let Some(sub) = cr.as_code_ref() else {
3846 return Err(PerlError::runtime(
3847 "group_by/chunk_by: first argument must be { BLOCK }",
3848 line,
3849 )
3850 .into());
3851 };
3852 let sub = sub.clone();
3853 let mut chunks: Vec<PerlValue> = Vec::new();
3854 let mut run: Vec<PerlValue> = Vec::new();
3855 let mut prev_key: Option<PerlValue> = None;
3856 for item in list {
3857 self.scope.set_topic(item.clone());
3858 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
3859 Ok(k) => k,
3860 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
3861 Err(FlowOrError::Flow(Flow::Return(v))) => v,
3862 Err(_) => PerlValue::UNDEF,
3863 };
3864 match &prev_key {
3865 None => {
3866 run.push(item);
3867 prev_key = Some(key);
3868 }
3869 Some(pk) => {
3870 if key.str_eq(pk) {
3871 run.push(item);
3872 } else {
3873 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3874 std::mem::take(&mut run),
3875 ))));
3876 run.push(item);
3877 prev_key = Some(key);
3878 }
3879 }
3880 }
3881 }
3882 if !run.is_empty() {
3883 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3884 }
3885 chunks
3886 }
3887 _ => {
3888 let mut chunks: Vec<PerlValue> = Vec::new();
3889 let mut run: Vec<PerlValue> = Vec::new();
3890 let mut prev_key: Option<PerlValue> = None;
3891 for item in list {
3892 self.scope.set_topic(item.clone());
3893 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
3894 match &prev_key {
3895 None => {
3896 run.push(item);
3897 prev_key = Some(key);
3898 }
3899 Some(pk) => {
3900 if key.str_eq(pk) {
3901 run.push(item);
3902 } else {
3903 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3904 std::mem::take(&mut run),
3905 ))));
3906 run.push(item);
3907 prev_key = Some(key);
3908 }
3909 }
3910 }
3911 }
3912 if !run.is_empty() {
3913 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3914 }
3915 chunks
3916 }
3917 };
3918 Ok(match ctx {
3919 WantarrayCtx::List => PerlValue::array(chunks),
3920 WantarrayCtx::Scalar => PerlValue::integer(chunks.len() as i64),
3921 WantarrayCtx::Void => PerlValue::UNDEF,
3922 })
3923 }
3924
3925 pub(crate) fn list_higher_order_block_builtin(
3927 &mut self,
3928 name: &str,
3929 args: &[PerlValue],
3930 line: usize,
3931 ) -> PerlResult<PerlValue> {
3932 match self.list_higher_order_block_builtin_exec(name, args, line) {
3933 Ok(v) => Ok(v),
3934 Err(FlowOrError::Error(e)) => Err(e),
3935 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
3936 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
3937 format!("{name}: unsupported control flow in block"),
3938 line,
3939 )),
3940 }
3941 }
3942
3943 fn list_higher_order_block_builtin_exec(
3944 &mut self,
3945 name: &str,
3946 args: &[PerlValue],
3947 line: usize,
3948 ) -> ExecResult {
3949 if args.is_empty() {
3950 return Err(
3951 PerlError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
3952 );
3953 }
3954 let Some(sub) = args[0].as_code_ref() else {
3955 return Err(PerlError::runtime(
3956 format!("{name}: first argument must be {{ BLOCK }}"),
3957 line,
3958 )
3959 .into());
3960 };
3961 let sub = sub.clone();
3962 let items: Vec<PerlValue> = args[1..].to_vec();
3963 if matches!(name, "tap" | "peek") && items.len() == 1 {
3964 if let Some(p) = items[0].as_pipeline() {
3965 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
3966 return Ok(PerlValue::pipeline(Arc::clone(&p)));
3967 }
3968 let v = &items[0];
3969 if v.is_iterator() || v.as_array_vec().is_some() {
3970 let source = crate::map_stream::into_pull_iter(v.clone());
3971 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
3972 return Ok(PerlValue::iterator(Arc::new(
3973 crate::map_stream::TapIterator::new(
3974 source,
3975 sub,
3976 self.subs.clone(),
3977 capture,
3978 atomic_arrays,
3979 atomic_hashes,
3980 ),
3981 )));
3982 }
3983 }
3984 let wa = self.wantarray_kind;
3989 match name {
3990 "take_while" => {
3991 let mut out = Vec::new();
3992 for item in items {
3993 self.scope_push_hook();
3994 self.scope.set_topic(item.clone());
3995 let pred = self.exec_block(&sub.body)?;
3996 self.scope_pop_hook();
3997 if !pred.is_true() {
3998 break;
3999 }
4000 out.push(item);
4001 }
4002 Ok(match wa {
4003 WantarrayCtx::List => PerlValue::array(out),
4004 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4005 WantarrayCtx::Void => PerlValue::UNDEF,
4006 })
4007 }
4008 "drop_while" | "skip_while" => {
4009 let mut i = 0usize;
4010 while i < items.len() {
4011 self.scope_push_hook();
4012 self.scope.set_topic(items[i].clone());
4013 let pred = self.exec_block(&sub.body)?;
4014 self.scope_pop_hook();
4015 if !pred.is_true() {
4016 break;
4017 }
4018 i += 1;
4019 }
4020 let rest = items[i..].to_vec();
4021 Ok(match wa {
4022 WantarrayCtx::List => PerlValue::array(rest),
4023 WantarrayCtx::Scalar => PerlValue::integer(rest.len() as i64),
4024 WantarrayCtx::Void => PerlValue::UNDEF,
4025 })
4026 }
4027 "reject" => {
4028 let mut out = Vec::new();
4029 for item in items {
4030 self.scope_push_hook();
4031 self.scope.set_topic(item.clone());
4032 let pred = self.exec_block(&sub.body)?;
4033 self.scope_pop_hook();
4034 if !pred.is_true() {
4035 out.push(item);
4036 }
4037 }
4038 Ok(match wa {
4039 WantarrayCtx::List => PerlValue::array(out),
4040 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4041 WantarrayCtx::Void => PerlValue::UNDEF,
4042 })
4043 }
4044 "tap" | "peek" => {
4045 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
4046 Ok(match wa {
4047 WantarrayCtx::List => PerlValue::array(items),
4048 WantarrayCtx::Scalar => PerlValue::integer(items.len() as i64),
4049 WantarrayCtx::Void => PerlValue::UNDEF,
4050 })
4051 }
4052 "partition" => {
4053 let mut yes = Vec::new();
4054 let mut no = Vec::new();
4055 for item in items {
4056 self.scope.set_topic(item.clone());
4057 let pred = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4058 if pred.is_true() {
4059 yes.push(item);
4060 } else {
4061 no.push(item);
4062 }
4063 }
4064 let yes_ref = PerlValue::array_ref(Arc::new(RwLock::new(yes)));
4065 let no_ref = PerlValue::array_ref(Arc::new(RwLock::new(no)));
4066 Ok(match wa {
4067 WantarrayCtx::List => PerlValue::array(vec![yes_ref, no_ref]),
4068 WantarrayCtx::Scalar => PerlValue::integer(2),
4069 WantarrayCtx::Void => PerlValue::UNDEF,
4070 })
4071 }
4072 "min_by" => {
4073 let mut best: Option<(PerlValue, PerlValue)> = None;
4074 for item in items {
4075 self.scope.set_topic(item.clone());
4076 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4077 best = Some(match best {
4078 None => (item, key),
4079 Some((bv, bk)) => {
4080 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
4081 (item, key)
4082 } else {
4083 (bv, bk)
4084 }
4085 }
4086 });
4087 }
4088 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
4089 }
4090 "max_by" => {
4091 let mut best: Option<(PerlValue, PerlValue)> = None;
4092 for item in items {
4093 self.scope.set_topic(item.clone());
4094 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4095 best = Some(match best {
4096 None => (item, key),
4097 Some((bv, bk)) => {
4098 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
4099 (item, key)
4100 } else {
4101 (bv, bk)
4102 }
4103 }
4104 });
4105 }
4106 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
4107 }
4108 "zip_with" => {
4109 let flat: Vec<PerlValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
4112 let refs: Vec<Vec<PerlValue>> = flat
4113 .iter()
4114 .map(|el| {
4115 if let Some(ar) = el.as_array_ref() {
4116 ar.read().clone()
4117 } else if let Some(name) = el.as_array_binding_name() {
4118 self.scope.get_array(&name)
4119 } else {
4120 vec![el.clone()]
4121 }
4122 })
4123 .collect();
4124 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
4125 let mut out = Vec::with_capacity(max_len);
4126 for i in 0..max_len {
4127 let pair: Vec<PerlValue> = refs
4128 .iter()
4129 .map(|l| l.get(i).cloned().unwrap_or(PerlValue::UNDEF))
4130 .collect();
4131 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
4132 out.push(result);
4133 }
4134 Ok(match wa {
4135 WantarrayCtx::List => PerlValue::array(out),
4136 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
4137 WantarrayCtx::Void => PerlValue::UNDEF,
4138 })
4139 }
4140 "count_by" => {
4141 let mut counts = indexmap::IndexMap::new();
4142 for item in items {
4143 self.scope.set_topic(item.clone());
4144 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4145 let k = key.to_string();
4146 let entry = counts.entry(k).or_insert(PerlValue::integer(0));
4147 *entry = PerlValue::integer(entry.to_int() + 1);
4148 }
4149 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(counts))))
4150 }
4151 _ => Err(PerlError::runtime(
4152 format!("internal: unknown list block builtin `{name}`"),
4153 line,
4154 )
4155 .into()),
4156 }
4157 }
4158
4159 pub(crate) fn builtin_rmdir_execute(
4161 &mut self,
4162 args: &[PerlValue],
4163 _line: usize,
4164 ) -> PerlResult<PerlValue> {
4165 let mut count = 0i64;
4166 for a in args {
4167 let p = a.to_string();
4168 if p.is_empty() {
4169 continue;
4170 }
4171 if std::fs::remove_dir(&p).is_ok() {
4172 count += 1;
4173 }
4174 }
4175 Ok(PerlValue::integer(count))
4176 }
4177
4178 pub(crate) fn builtin_touch_execute(
4180 &mut self,
4181 args: &[PerlValue],
4182 _line: usize,
4183 ) -> PerlResult<PerlValue> {
4184 let paths: Vec<String> = args.iter().map(|v| v.to_string()).collect();
4185 Ok(PerlValue::integer(crate::perl_fs::touch_paths(&paths)))
4186 }
4187
4188 pub(crate) fn builtin_utime_execute(
4190 &mut self,
4191 args: &[PerlValue],
4192 line: usize,
4193 ) -> PerlResult<PerlValue> {
4194 if args.len() < 3 {
4195 return Err(PerlError::runtime(
4196 "utime requires at least three arguments (atime, mtime, files...)",
4197 line,
4198 ));
4199 }
4200 let at = args[0].to_int();
4201 let mt = args[1].to_int();
4202 let paths: Vec<String> = args.iter().skip(2).map(|v| v.to_string()).collect();
4203 let n = crate::perl_fs::utime_paths(at, mt, &paths);
4204 #[cfg(not(unix))]
4205 if !paths.is_empty() && n == 0 {
4206 return Err(PerlError::runtime(
4207 "utime is not supported on this platform",
4208 line,
4209 ));
4210 }
4211 Ok(PerlValue::integer(n))
4212 }
4213
4214 pub(crate) fn builtin_umask_execute(
4216 &mut self,
4217 args: &[PerlValue],
4218 line: usize,
4219 ) -> PerlResult<PerlValue> {
4220 #[cfg(unix)]
4221 {
4222 let _ = line;
4223 if args.is_empty() {
4224 let cur = unsafe { libc::umask(0) };
4225 unsafe { libc::umask(cur) };
4226 return Ok(PerlValue::integer(cur as i64));
4227 }
4228 let new_m = args[0].to_int() as libc::mode_t;
4229 let old = unsafe { libc::umask(new_m) };
4230 Ok(PerlValue::integer(old as i64))
4231 }
4232 #[cfg(not(unix))]
4233 {
4234 let _ = args;
4235 Err(PerlError::runtime(
4236 "umask is not supported on this platform",
4237 line,
4238 ))
4239 }
4240 }
4241
4242 pub(crate) fn builtin_getcwd_execute(
4244 &mut self,
4245 args: &[PerlValue],
4246 line: usize,
4247 ) -> PerlResult<PerlValue> {
4248 if !args.is_empty() {
4249 return Err(PerlError::runtime("getcwd takes no arguments", line));
4250 }
4251 match std::env::current_dir() {
4252 Ok(p) => Ok(PerlValue::string(p.to_string_lossy().into_owned())),
4253 Err(e) => {
4254 self.apply_io_error_to_errno(&e);
4255 Ok(PerlValue::UNDEF)
4256 }
4257 }
4258 }
4259
4260 pub(crate) fn builtin_realpath_execute(
4262 &mut self,
4263 args: &[PerlValue],
4264 line: usize,
4265 ) -> PerlResult<PerlValue> {
4266 let path = args
4267 .first()
4268 .ok_or_else(|| PerlError::runtime("realpath: need path", line))?
4269 .to_string();
4270 if path.is_empty() {
4271 return Err(PerlError::runtime("realpath: need path", line));
4272 }
4273 match crate::perl_fs::realpath_resolved(&path) {
4274 Ok(s) => Ok(PerlValue::string(s)),
4275 Err(e) => {
4276 self.apply_io_error_to_errno(&e);
4277 Ok(PerlValue::UNDEF)
4278 }
4279 }
4280 }
4281
4282 pub(crate) fn builtin_pipe_execute(
4284 &mut self,
4285 args: &[PerlValue],
4286 line: usize,
4287 ) -> PerlResult<PerlValue> {
4288 if args.len() != 2 {
4289 return Err(PerlError::runtime(
4290 "pipe requires exactly two arguments",
4291 line,
4292 ));
4293 }
4294 #[cfg(unix)]
4295 {
4296 use std::fs::File;
4297 use std::os::unix::io::FromRawFd;
4298
4299 let read_name = args[0].to_string();
4300 let write_name = args[1].to_string();
4301 if read_name.is_empty() || write_name.is_empty() {
4302 return Err(PerlError::runtime("pipe: invalid handle name", line));
4303 }
4304 let mut fds = [0i32; 2];
4305 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
4306 let e = std::io::Error::last_os_error();
4307 self.apply_io_error_to_errno(&e);
4308 return Ok(PerlValue::integer(0));
4309 }
4310 let read_file = unsafe { File::from_raw_fd(fds[0]) };
4311 let write_file = unsafe { File::from_raw_fd(fds[1]) };
4312
4313 let read_shared = Arc::new(Mutex::new(read_file));
4314 let write_shared = Arc::new(Mutex::new(write_file));
4315
4316 self.close_builtin_execute(read_name.clone()).ok();
4317 self.close_builtin_execute(write_name.clone()).ok();
4318
4319 self.io_file_slots
4320 .insert(read_name.clone(), Arc::clone(&read_shared));
4321 self.input_handles.insert(
4322 read_name,
4323 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
4324 );
4325
4326 self.io_file_slots
4327 .insert(write_name.clone(), Arc::clone(&write_shared));
4328 self.output_handles
4329 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
4330
4331 Ok(PerlValue::integer(1))
4332 }
4333 #[cfg(not(unix))]
4334 {
4335 let _ = args;
4336 Err(PerlError::runtime(
4337 "pipe is not supported on this platform",
4338 line,
4339 ))
4340 }
4341 }
4342
4343 pub(crate) fn close_builtin_execute(&mut self, name: String) -> PerlResult<PerlValue> {
4344 self.output_handles.remove(&name);
4345 self.input_handles.remove(&name);
4346 self.io_file_slots.remove(&name);
4347 if let Some(mut child) = self.pipe_children.remove(&name) {
4348 if let Ok(st) = child.wait() {
4349 self.record_child_exit_status(st);
4350 }
4351 }
4352 Ok(PerlValue::integer(1))
4353 }
4354
4355 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
4356 self.input_handles.contains_key(name)
4357 }
4358
4359 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
4363 self.line_mode_eof_pending
4364 }
4365
4366 pub(crate) fn eof_builtin_execute(
4370 &self,
4371 args: &[PerlValue],
4372 line: usize,
4373 ) -> PerlResult<PerlValue> {
4374 match args.len() {
4375 0 => Ok(PerlValue::integer(if self.eof_without_arg_is_true() {
4376 1
4377 } else {
4378 0
4379 })),
4380 1 => {
4381 let name = args[0].to_string();
4382 let at_eof = !self.has_input_handle(&name);
4383 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
4384 }
4385 _ => Err(PerlError::runtime("eof: too many arguments", line)),
4386 }
4387 }
4388
4389 pub(crate) fn study_return_value(s: &str) -> PerlValue {
4392 if s.is_empty() {
4393 PerlValue::string(String::new())
4394 } else {
4395 PerlValue::integer(1)
4396 }
4397 }
4398
4399 pub(crate) fn readline_builtin_execute(
4400 &mut self,
4401 handle: Option<&str>,
4402 ) -> PerlResult<PerlValue> {
4403 if handle.is_none() {
4405 let argv = self.scope.get_array("ARGV");
4406 if !argv.is_empty() {
4407 loop {
4408 if self.diamond_reader.is_none() {
4409 while self.diamond_next_idx < argv.len() {
4410 let path = argv[self.diamond_next_idx].to_string();
4411 self.diamond_next_idx += 1;
4412 match File::open(&path) {
4413 Ok(f) => {
4414 self.argv_current_file = path;
4415 self.diamond_reader = Some(BufReader::new(f));
4416 break;
4417 }
4418 Err(e) => {
4419 self.apply_io_error_to_errno(&e);
4420 }
4421 }
4422 }
4423 if self.diamond_reader.is_none() {
4424 return Ok(PerlValue::UNDEF);
4425 }
4426 }
4427 let mut line_str = String::new();
4428 let read_result: Result<usize, io::Error> =
4429 if let Some(reader) = self.diamond_reader.as_mut() {
4430 if self.open_pragma_utf8 {
4431 let mut buf = Vec::new();
4432 reader.read_until(b'\n', &mut buf).inspect(|n| {
4433 if *n > 0 {
4434 line_str = String::from_utf8_lossy(&buf).into_owned();
4435 }
4436 })
4437 } else {
4438 let mut buf = Vec::new();
4439 match reader.read_until(b'\n', &mut buf) {
4440 Ok(n) => {
4441 if n > 0 {
4442 line_str =
4443 crate::perl_decode::decode_utf8_or_latin1_read_until(
4444 &buf,
4445 );
4446 }
4447 Ok(n)
4448 }
4449 Err(e) => Err(e),
4450 }
4451 }
4452 } else {
4453 unreachable!()
4454 };
4455 match read_result {
4456 Ok(0) => {
4457 self.diamond_reader = None;
4458 continue;
4459 }
4460 Ok(_) => {
4461 self.bump_line_for_handle(&self.argv_current_file.clone());
4462 return Ok(PerlValue::string(line_str));
4463 }
4464 Err(e) => {
4465 self.apply_io_error_to_errno(&e);
4466 self.diamond_reader = None;
4467 continue;
4468 }
4469 }
4470 }
4471 } else {
4472 self.argv_current_file.clear();
4473 }
4474 }
4475
4476 let handle_name = handle.unwrap_or("STDIN");
4477 let mut line_str = String::new();
4478 if handle_name == "STDIN" {
4479 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
4480 self.last_stdin_die_bracket = if handle.is_none() {
4481 "<>".to_string()
4482 } else {
4483 "<STDIN>".to_string()
4484 };
4485 self.bump_line_for_handle("STDIN");
4486 return Ok(PerlValue::string(queued));
4487 }
4488 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4489 let mut buf = Vec::new();
4490 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
4491 if *n > 0 {
4492 line_str = String::from_utf8_lossy(&buf).into_owned();
4493 }
4494 })
4495 } else {
4496 let mut buf = Vec::new();
4497 let mut lock = io::stdin().lock();
4498 match lock.read_until(b'\n', &mut buf) {
4499 Ok(n) => {
4500 if n > 0 {
4501 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4502 }
4503 Ok(n)
4504 }
4505 Err(e) => Err(e),
4506 }
4507 };
4508 match r {
4509 Ok(0) => Ok(PerlValue::UNDEF),
4510 Ok(_) => {
4511 self.last_stdin_die_bracket = if handle.is_none() {
4512 "<>".to_string()
4513 } else {
4514 "<STDIN>".to_string()
4515 };
4516 self.bump_line_for_handle("STDIN");
4517 Ok(PerlValue::string(line_str))
4518 }
4519 Err(e) => {
4520 self.apply_io_error_to_errno(&e);
4521 Ok(PerlValue::UNDEF)
4522 }
4523 }
4524 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
4525 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4526 let mut buf = Vec::new();
4527 reader.read_until(b'\n', &mut buf).inspect(|n| {
4528 if *n > 0 {
4529 line_str = String::from_utf8_lossy(&buf).into_owned();
4530 }
4531 })
4532 } else {
4533 let mut buf = Vec::new();
4534 match reader.read_until(b'\n', &mut buf) {
4535 Ok(n) => {
4536 if n > 0 {
4537 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4538 }
4539 Ok(n)
4540 }
4541 Err(e) => Err(e),
4542 }
4543 };
4544 match r {
4545 Ok(0) => Ok(PerlValue::UNDEF),
4546 Ok(_) => {
4547 self.bump_line_for_handle(handle_name);
4548 Ok(PerlValue::string(line_str))
4549 }
4550 Err(e) => {
4551 self.apply_io_error_to_errno(&e);
4552 Ok(PerlValue::UNDEF)
4553 }
4554 }
4555 } else {
4556 Ok(PerlValue::UNDEF)
4557 }
4558 }
4559
4560 pub(crate) fn readline_builtin_execute_list(
4562 &mut self,
4563 handle: Option<&str>,
4564 ) -> PerlResult<PerlValue> {
4565 let mut lines = Vec::new();
4566 loop {
4567 let v = self.readline_builtin_execute(handle)?;
4568 if v.is_undef() {
4569 break;
4570 }
4571 lines.push(v);
4572 }
4573 Ok(PerlValue::array(lines))
4574 }
4575
4576 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> PerlValue {
4577 match std::fs::read_dir(path) {
4578 Ok(rd) => {
4579 let entries: Vec<String> = rd
4580 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
4581 .collect();
4582 self.dir_handles
4583 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
4584 PerlValue::integer(1)
4585 }
4586 Err(e) => {
4587 self.apply_io_error_to_errno(&e);
4588 PerlValue::integer(0)
4589 }
4590 }
4591 }
4592
4593 pub(crate) fn readdir_handle(&mut self, handle: &str) -> PerlValue {
4594 if let Some(dh) = self.dir_handles.get_mut(handle) {
4595 if dh.pos < dh.entries.len() {
4596 let s = dh.entries[dh.pos].clone();
4597 dh.pos += 1;
4598 PerlValue::string(s)
4599 } else {
4600 PerlValue::UNDEF
4601 }
4602 } else {
4603 PerlValue::UNDEF
4604 }
4605 }
4606
4607 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> PerlValue {
4609 if let Some(dh) = self.dir_handles.get_mut(handle) {
4610 let rest: Vec<PerlValue> = dh.entries[dh.pos..]
4611 .iter()
4612 .cloned()
4613 .map(PerlValue::string)
4614 .collect();
4615 dh.pos = dh.entries.len();
4616 PerlValue::array(rest)
4617 } else {
4618 PerlValue::array(Vec::new())
4619 }
4620 }
4621
4622 pub(crate) fn closedir_handle(&mut self, handle: &str) -> PerlValue {
4623 PerlValue::integer(if self.dir_handles.remove(handle).is_some() {
4624 1
4625 } else {
4626 0
4627 })
4628 }
4629
4630 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> PerlValue {
4631 if let Some(dh) = self.dir_handles.get_mut(handle) {
4632 dh.pos = 0;
4633 PerlValue::integer(1)
4634 } else {
4635 PerlValue::integer(0)
4636 }
4637 }
4638
4639 pub(crate) fn telldir_handle(&mut self, handle: &str) -> PerlValue {
4640 self.dir_handles
4641 .get(handle)
4642 .map(|dh| PerlValue::integer(dh.pos as i64))
4643 .unwrap_or(PerlValue::UNDEF)
4644 }
4645
4646 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> PerlValue {
4647 if let Some(dh) = self.dir_handles.get_mut(handle) {
4648 dh.pos = pos.min(dh.entries.len());
4649 PerlValue::integer(1)
4650 } else {
4651 PerlValue::integer(0)
4652 }
4653 }
4654
4655 #[inline]
4660 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
4661 crate::special_vars::is_regex_match_scalar_name(name)
4662 }
4663
4664 #[inline]
4668 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
4669 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
4670 self.regex_capture_scope_fresh = false;
4671 }
4672 }
4673
4674 pub(crate) fn apply_regex_captures(
4675 &mut self,
4676 haystack: &str,
4677 offset: usize,
4678 re: &PerlCompiledRegex,
4679 caps: &PerlCaptures<'_>,
4680 capture_all: CaptureAllMode,
4681 ) -> Result<(), FlowOrError> {
4682 let m0 = caps.get(0).expect("regex capture 0");
4683 let s0 = offset + m0.start;
4684 let e0 = offset + m0.end;
4685 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
4686 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
4687 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
4688 let mut last_paren = String::new();
4689 for i in 1..caps.len() {
4690 if let Some(m) = caps.get(i) {
4691 last_paren = m.text.to_string();
4692 }
4693 }
4694 self.last_paren_match = last_paren;
4695 self.last_subpattern_name = String::new();
4696 for n in re.capture_names().flatten() {
4697 if caps.name(n).is_some() {
4698 self.last_subpattern_name = n.to_string();
4699 }
4700 }
4701 self.scope
4702 .set_scalar("&", PerlValue::string(self.last_match.clone()))?;
4703 self.scope
4704 .set_scalar("`", PerlValue::string(self.prematch.clone()))?;
4705 self.scope
4706 .set_scalar("'", PerlValue::string(self.postmatch.clone()))?;
4707 self.scope
4708 .set_scalar("+", PerlValue::string(self.last_paren_match.clone()))?;
4709 for i in 1..caps.len() {
4710 if let Some(m) = caps.get(i) {
4711 self.scope
4712 .set_scalar(&i.to_string(), PerlValue::string(m.text.to_string()))?;
4713 }
4714 }
4715 let mut start_arr = vec![PerlValue::integer(s0 as i64)];
4716 let mut end_arr = vec![PerlValue::integer(e0 as i64)];
4717 for i in 1..caps.len() {
4718 if let Some(m) = caps.get(i) {
4719 start_arr.push(PerlValue::integer((offset + m.start) as i64));
4720 end_arr.push(PerlValue::integer((offset + m.end) as i64));
4721 } else {
4722 start_arr.push(PerlValue::integer(-1));
4723 end_arr.push(PerlValue::integer(-1));
4724 }
4725 }
4726 self.scope.set_array("-", start_arr)?;
4727 self.scope.set_array("+", end_arr)?;
4728 let mut named = IndexMap::new();
4729 for name in re.capture_names().flatten() {
4730 if let Some(m) = caps.name(name) {
4731 named.insert(name.to_string(), PerlValue::string(m.text.to_string()));
4732 }
4733 }
4734 self.scope.set_hash("+", named.clone())?;
4735 let mut named_minus = IndexMap::new();
4737 for (name, val) in &named {
4738 named_minus.insert(
4739 name.clone(),
4740 PerlValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
4741 );
4742 }
4743 self.scope.set_hash("-", named_minus)?;
4744 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
4745 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
4746 match capture_all {
4747 CaptureAllMode::Empty => {
4748 self.scope.set_array("^CAPTURE_ALL", vec![])?;
4749 }
4750 CaptureAllMode::Append => {
4751 let mut rows = self.scope.get_array("^CAPTURE_ALL");
4752 rows.push(PerlValue::array(cap_flat));
4753 self.scope.set_array("^CAPTURE_ALL", rows)?;
4754 }
4755 CaptureAllMode::Skip => {}
4756 }
4757 Ok(())
4758 }
4759
4760 pub(crate) fn clear_flip_flop_state(&mut self) {
4761 self.flip_flop_active.clear();
4762 self.flip_flop_exclusive_left_line.clear();
4763 self.flip_flop_sequence.clear();
4764 self.flip_flop_last_dot.clear();
4765 self.flip_flop_tree.clear();
4766 }
4767
4768 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
4769 self.flip_flop_active.resize(slots as usize, false);
4770 self.flip_flop_active.fill(false);
4771 self.flip_flop_exclusive_left_line
4772 .resize(slots as usize, None);
4773 self.flip_flop_exclusive_left_line.fill(None);
4774 self.flip_flop_sequence.resize(slots as usize, 0);
4775 self.flip_flop_sequence.fill(0);
4776 self.flip_flop_last_dot.resize(slots as usize, None);
4777 self.flip_flop_last_dot.fill(None);
4778 }
4779
4780 #[inline]
4784 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
4785 if self.last_readline_handle.is_empty() {
4786 self.line_number
4787 } else {
4788 *self
4789 .handle_line_numbers
4790 .get(&self.last_readline_handle)
4791 .unwrap_or(&0)
4792 }
4793 }
4794
4795 pub(crate) fn scalar_flip_flop_eval(
4804 &mut self,
4805 left: i64,
4806 right: i64,
4807 slot: usize,
4808 exclusive: bool,
4809 ) -> PerlResult<PerlValue> {
4810 if self.flip_flop_active.len() <= slot {
4811 self.flip_flop_active.resize(slot + 1, false);
4812 }
4813 if self.flip_flop_exclusive_left_line.len() <= slot {
4814 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4815 }
4816 if self.flip_flop_sequence.len() <= slot {
4817 self.flip_flop_sequence.resize(slot + 1, 0);
4818 }
4819 if self.flip_flop_last_dot.len() <= slot {
4820 self.flip_flop_last_dot.resize(slot + 1, None);
4821 }
4822 let dot = self.scalar_flipflop_dot_line();
4823 let active = &mut self.flip_flop_active[slot];
4824 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4825 let seq = &mut self.flip_flop_sequence[slot];
4826 let last_dot = &mut self.flip_flop_last_dot[slot];
4827 if !*active {
4828 if dot == left {
4829 *active = true;
4830 *seq = 1;
4831 *last_dot = Some(dot);
4832 if exclusive {
4833 *excl_left = Some(dot);
4834 } else {
4835 *excl_left = None;
4836 if dot == right {
4837 *active = false;
4838 return Ok(PerlValue::string(format!("{}E0", *seq)));
4839 }
4840 }
4841 return Ok(PerlValue::string(seq.to_string()));
4842 }
4843 *last_dot = Some(dot);
4844 return Ok(PerlValue::string(String::new()));
4845 }
4846 if *last_dot != Some(dot) {
4849 *seq += 1;
4850 *last_dot = Some(dot);
4851 }
4852 let cur_seq = *seq;
4853 if let Some(ll) = *excl_left {
4854 if dot == right && dot > ll {
4855 *active = false;
4856 *excl_left = None;
4857 *seq = 0;
4858 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4859 }
4860 } else if dot == right {
4861 *active = false;
4862 *seq = 0;
4863 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4864 }
4865 Ok(PerlValue::string(cur_seq.to_string()))
4866 }
4867
4868 fn regex_flip_flop_transition(
4869 active: &mut bool,
4870 excl_left: &mut Option<i64>,
4871 exclusive: bool,
4872 dot: i64,
4873 left_m: bool,
4874 right_m: bool,
4875 ) -> i64 {
4876 if !*active {
4877 if left_m {
4878 *active = true;
4879 if exclusive {
4880 *excl_left = Some(dot);
4881 } else {
4882 *excl_left = None;
4883 if right_m {
4884 *active = false;
4885 }
4886 }
4887 return 1;
4888 }
4889 return 0;
4890 }
4891 if let Some(ll) = *excl_left {
4892 if right_m && dot > ll {
4893 *active = false;
4894 *excl_left = None;
4895 }
4896 } else if right_m {
4897 *active = false;
4898 }
4899 1
4900 }
4901
4902 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
4907 &mut self,
4908 left_pat: &str,
4909 left_flags: &str,
4910 right_pat: &str,
4911 right_flags: &str,
4912 slot: usize,
4913 exclusive: bool,
4914 line: usize,
4915 ) -> PerlResult<PerlValue> {
4916 let dot = self.scalar_flipflop_dot_line();
4917 let subject = self.scope.get_scalar("_").to_string();
4918 let left_re = self
4919 .compile_regex(left_pat, left_flags, line)
4920 .map_err(|e| match e {
4921 FlowOrError::Error(err) => err,
4922 FlowOrError::Flow(_) => {
4923 PerlError::runtime("unexpected flow in regex flip-flop", line)
4924 }
4925 })?;
4926 let right_re = self
4927 .compile_regex(right_pat, right_flags, line)
4928 .map_err(|e| match e {
4929 FlowOrError::Error(err) => err,
4930 FlowOrError::Flow(_) => {
4931 PerlError::runtime("unexpected flow in regex flip-flop", line)
4932 }
4933 })?;
4934 let left_m = left_re.is_match(&subject);
4935 let right_m = right_re.is_match(&subject);
4936 if self.flip_flop_active.len() <= slot {
4937 self.flip_flop_active.resize(slot + 1, false);
4938 }
4939 if self.flip_flop_exclusive_left_line.len() <= slot {
4940 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4941 }
4942 let active = &mut self.flip_flop_active[slot];
4943 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4944 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4945 active, excl_left, exclusive, dot, left_m, right_m,
4946 )))
4947 }
4948
4949 pub(crate) fn regex_flip_flop_eval_dynamic_right(
4951 &mut self,
4952 left_pat: &str,
4953 left_flags: &str,
4954 slot: usize,
4955 exclusive: bool,
4956 line: usize,
4957 right_m: bool,
4958 ) -> PerlResult<PerlValue> {
4959 let dot = self.scalar_flipflop_dot_line();
4960 let subject = self.scope.get_scalar("_").to_string();
4961 let left_re = self
4962 .compile_regex(left_pat, left_flags, line)
4963 .map_err(|e| match e {
4964 FlowOrError::Error(err) => err,
4965 FlowOrError::Flow(_) => {
4966 PerlError::runtime("unexpected flow in regex flip-flop", line)
4967 }
4968 })?;
4969 let left_m = left_re.is_match(&subject);
4970 if self.flip_flop_active.len() <= slot {
4971 self.flip_flop_active.resize(slot + 1, false);
4972 }
4973 if self.flip_flop_exclusive_left_line.len() <= slot {
4974 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4975 }
4976 let active = &mut self.flip_flop_active[slot];
4977 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4978 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4979 active, excl_left, exclusive, dot, left_m, right_m,
4980 )))
4981 }
4982
4983 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
4985 &mut self,
4986 left_pat: &str,
4987 left_flags: &str,
4988 slot: usize,
4989 exclusive: bool,
4990 line: usize,
4991 rhs_line: i64,
4992 ) -> PerlResult<PerlValue> {
4993 let dot = self.scalar_flipflop_dot_line();
4994 let subject = self.scope.get_scalar("_").to_string();
4995 let left_re = self
4996 .compile_regex(left_pat, left_flags, line)
4997 .map_err(|e| match e {
4998 FlowOrError::Error(err) => err,
4999 FlowOrError::Flow(_) => {
5000 PerlError::runtime("unexpected flow in regex flip-flop", line)
5001 }
5002 })?;
5003 let left_m = left_re.is_match(&subject);
5004 let right_m = dot == rhs_line;
5005 if self.flip_flop_active.len() <= slot {
5006 self.flip_flop_active.resize(slot + 1, false);
5007 }
5008 if self.flip_flop_exclusive_left_line.len() <= slot {
5009 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5010 }
5011 let active = &mut self.flip_flop_active[slot];
5012 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5013 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5014 active, excl_left, exclusive, dot, left_m, right_m,
5015 )))
5016 }
5017
5018 pub(crate) fn regex_eof_flip_flop_eval(
5023 &mut self,
5024 left_pat: &str,
5025 left_flags: &str,
5026 slot: usize,
5027 exclusive: bool,
5028 line: usize,
5029 ) -> PerlResult<PerlValue> {
5030 let dot = self.scalar_flipflop_dot_line();
5031 let subject = self.scope.get_scalar("_").to_string();
5032 let left_re = self
5033 .compile_regex(left_pat, left_flags, line)
5034 .map_err(|e| match e {
5035 FlowOrError::Error(err) => err,
5036 FlowOrError::Flow(_) => {
5037 PerlError::runtime("unexpected flow in regex/eof flip-flop", line)
5038 }
5039 })?;
5040 let left_m = left_re.is_match(&subject);
5041 let right_m = self.eof_without_arg_is_true();
5042 if self.flip_flop_active.len() <= slot {
5043 self.flip_flop_active.resize(slot + 1, false);
5044 }
5045 if self.flip_flop_exclusive_left_line.len() <= slot {
5046 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5047 }
5048 let active = &mut self.flip_flop_active[slot];
5049 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5050 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
5051 active, excl_left, exclusive, dot, left_m, right_m,
5052 )))
5053 }
5054
5055 pub(crate) fn builtin_read_into(
5059 &mut self,
5060 fh_val: PerlValue,
5061 var_name: &str,
5062 length: usize,
5063 line: usize,
5064 ) -> ExecResult {
5065 use std::io::Read;
5066 let fh = fh_val
5067 .as_io_handle_name()
5068 .unwrap_or_else(|| fh_val.to_string());
5069 let mut buf = vec![0u8; length];
5070 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
5071 slot.lock().read(&mut buf).unwrap_or(0)
5072 } else if fh == "STDIN" {
5073 std::io::stdin().read(&mut buf).unwrap_or(0)
5074 } else {
5075 return Err(PerlError::runtime(format!("read: unopened handle {}", fh), line).into());
5076 };
5077 buf.truncate(n);
5078 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
5079 let _ = self.scope.set_scalar(var_name, PerlValue::string(read_str));
5080 Ok(PerlValue::integer(n as i64))
5081 }
5082
5083 pub(crate) fn chomp_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
5084 let mut s = val.to_string();
5085 let removed = if s.ends_with('\n') {
5086 s.pop();
5087 1i64
5088 } else {
5089 0i64
5090 };
5091 self.assign_value(target, PerlValue::string(s))?;
5092 Ok(PerlValue::integer(removed))
5093 }
5094
5095 pub(crate) fn chop_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
5097 let mut s = val.to_string();
5098 let chopped = s
5099 .pop()
5100 .map(|c| PerlValue::string(c.to_string()))
5101 .unwrap_or(PerlValue::UNDEF);
5102 self.assign_value(target, PerlValue::string(s))?;
5103 Ok(chopped)
5104 }
5105
5106 pub(crate) fn regex_match_execute(
5108 &mut self,
5109 s: String,
5110 pattern: &str,
5111 flags: &str,
5112 scalar_g: bool,
5113 pos_key: &str,
5114 line: usize,
5115 ) -> ExecResult {
5116 if !flags.contains('g') && !scalar_g {
5124 let memo_hit = {
5125 if let Some(ref mem) = self.regex_match_memo {
5126 mem.pattern == pattern
5127 && mem.flags == flags
5128 && mem.multiline == self.multiline_match
5129 && mem.haystack == s
5130 } else {
5131 false
5132 }
5133 };
5134 if memo_hit {
5135 if self.regex_capture_scope_fresh {
5136 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
5137 }
5138 let (memo_s, memo_result) = {
5141 let mem = self.regex_match_memo.as_ref().expect("memo");
5142 (mem.haystack.clone(), mem.result.clone())
5143 };
5144 let re = self.compile_regex(pattern, flags, line)?;
5145 if let Some(caps) = re.captures(&memo_s) {
5146 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
5147 }
5148 self.regex_capture_scope_fresh = true;
5149 return Ok(memo_result);
5150 }
5151 }
5152 let re = self.compile_regex(pattern, flags, line)?;
5153 if flags.contains('g') && scalar_g {
5154 let key = pos_key.to_string();
5155 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
5156 if start == 0 {
5157 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5158 }
5159 if start > s.len() {
5160 self.regex_pos.insert(key, None);
5161 return Ok(PerlValue::integer(0));
5162 }
5163 let sub = s.get(start..).unwrap_or("");
5164 if let Some(caps) = re.captures(sub) {
5165 let overall = caps.get(0).expect("capture 0");
5166 let abs_end = start + overall.end;
5167 self.regex_pos.insert(key, Some(abs_end));
5168 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
5169 Ok(PerlValue::integer(1))
5170 } else {
5171 self.regex_pos.insert(key, None);
5172 Ok(PerlValue::integer(0))
5173 }
5174 } else if flags.contains('g') {
5175 let mut rows = Vec::new();
5176 let mut last_caps: Option<PerlCaptures<'_>> = None;
5177 for caps in re.captures_iter(&s) {
5178 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5179 &caps,
5180 )));
5181 last_caps = Some(caps);
5182 }
5183 self.scope.set_array("^CAPTURE_ALL", rows)?;
5184 let matches: Vec<PerlValue> = match &*re {
5185 PerlCompiledRegex::Rust(r) => r
5186 .find_iter(&s)
5187 .map(|m| PerlValue::string(m.as_str().to_string()))
5188 .collect(),
5189 PerlCompiledRegex::Fancy(r) => r
5190 .find_iter(&s)
5191 .filter_map(|m| m.ok())
5192 .map(|m| PerlValue::string(m.as_str().to_string()))
5193 .collect(),
5194 PerlCompiledRegex::Pcre2(r) => r
5195 .find_iter(s.as_bytes())
5196 .filter_map(|m| m.ok())
5197 .map(|m| {
5198 let t = s.get(m.start()..m.end()).unwrap_or("");
5199 PerlValue::string(t.to_string())
5200 })
5201 .collect(),
5202 };
5203 if matches.is_empty() {
5204 Ok(PerlValue::integer(0))
5205 } else {
5206 if let Some(caps) = last_caps {
5207 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
5208 }
5209 Ok(PerlValue::array(matches))
5210 }
5211 } else if let Some(caps) = re.captures(&s) {
5212 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
5213 let result = PerlValue::integer(1);
5214 self.regex_match_memo = Some(RegexMatchMemo {
5215 pattern: pattern.to_string(),
5216 flags: flags.to_string(),
5217 multiline: self.multiline_match,
5218 haystack: s,
5219 result: result.clone(),
5220 });
5221 self.regex_capture_scope_fresh = true;
5222 Ok(result)
5223 } else {
5224 let result = PerlValue::integer(0);
5225 self.regex_match_memo = Some(RegexMatchMemo {
5227 pattern: pattern.to_string(),
5228 flags: flags.to_string(),
5229 multiline: self.multiline_match,
5230 haystack: s,
5231 result: result.clone(),
5232 });
5233 Ok(result)
5236 }
5237 }
5238
5239 pub(crate) fn expand_env_braces_in_subst(
5243 &mut self,
5244 raw: &str,
5245 line: usize,
5246 ) -> PerlResult<String> {
5247 self.materialize_env_if_needed();
5248 let mut out = String::new();
5249 let mut rest = raw;
5250 while let Some(idx) = rest.find("$ENV{") {
5251 out.push_str(&rest[..idx]);
5252 let after = &rest[idx + 5..];
5253 let end = after
5254 .find('}')
5255 .ok_or_else(|| PerlError::runtime("Unclosed $ENV{...} in s///", line))?;
5256 let key = &after[..end];
5257 let val = self.scope.get_hash_element("ENV", key);
5258 out.push_str(&val.to_string());
5259 rest = &after[end + 1..];
5260 }
5261 out.push_str(rest);
5262 Ok(out)
5263 }
5264
5265 pub(crate) fn regex_subst_execute(
5271 &mut self,
5272 s: String,
5273 pattern: &str,
5274 replacement: &str,
5275 flags: &str,
5276 target: &Expr,
5277 line: usize,
5278 ) -> ExecResult {
5279 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
5280 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
5281 let re = self.compile_regex(&pattern, &re_flags, line)?;
5282 if flags.contains('e') {
5283 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
5284 }
5285 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
5286 let replacement = self.interpolate_replacement_string(&replacement);
5287 let replacement = normalize_replacement_backrefs(&replacement);
5288 let last_caps = if flags.contains('g') {
5289 let mut rows = Vec::new();
5290 let mut last = None;
5291 for caps in re.captures_iter(&s) {
5292 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5293 &caps,
5294 )));
5295 last = Some(caps);
5296 }
5297 self.scope.set_array("^CAPTURE_ALL", rows)?;
5298 last
5299 } else {
5300 re.captures(&s)
5301 };
5302 if let Some(caps) = last_caps {
5303 let mode = if flags.contains('g') {
5304 CaptureAllMode::Skip
5305 } else {
5306 CaptureAllMode::Empty
5307 };
5308 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
5309 }
5310 let (new_s, count) = if flags.contains('g') {
5311 let count = re.find_iter_count(&s);
5312 (re.replace_all(&s, replacement.as_str()), count)
5313 } else {
5314 let count = if re.is_match(&s) { 1 } else { 0 };
5315 (re.replace(&s, replacement.as_str()), count)
5316 };
5317 if flags.contains('r') {
5318 Ok(PerlValue::string(new_s))
5320 } else {
5321 self.assign_value(target, PerlValue::string(new_s))?;
5322 Ok(PerlValue::integer(count as i64))
5323 }
5324 }
5325
5326 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
5329 let prep_source = |raw: &str| -> String {
5330 let mut code = raw.trim().to_string();
5331 if !code.ends_with(';') {
5332 code.push(';');
5333 }
5334 code
5335 };
5336 let mut cur = prep_source(replacement);
5337 let mut last = PerlValue::UNDEF;
5338 for round in 0..e_count {
5339 last = crate::parse_and_run_string(&cur, self)?;
5340 if round + 1 < e_count {
5341 cur = prep_source(&last.to_string());
5342 }
5343 }
5344 Ok(last)
5345 }
5346
5347 fn regex_subst_execute_eval(
5348 &mut self,
5349 s: String,
5350 re: &PerlCompiledRegex,
5351 replacement: &str,
5352 flags: &str,
5353 target: &Expr,
5354 line: usize,
5355 ) -> ExecResult {
5356 let e_count = flags.chars().filter(|c| *c == 'e').count();
5357 if e_count == 0 {
5358 return Err(PerlError::runtime("s///e: internal error (no e flag)", line).into());
5359 }
5360
5361 if flags.contains('g') {
5362 let mut rows = Vec::new();
5363 let mut out = String::new();
5364 let mut last = 0usize;
5365 let mut count = 0usize;
5366 for caps in re.captures_iter(&s) {
5367 let m0 = caps.get(0).expect("regex capture 0");
5368 out.push_str(&s[last..m0.start]);
5369 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5370 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5371 out.push_str(&repl_val.to_string());
5372 last = m0.end;
5373 count += 1;
5374 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5375 &caps,
5376 )));
5377 }
5378 self.scope.set_array("^CAPTURE_ALL", rows)?;
5379 out.push_str(&s[last..]);
5380 if flags.contains('r') {
5381 return Ok(PerlValue::string(out));
5382 }
5383 self.assign_value(target, PerlValue::string(out))?;
5384 return Ok(PerlValue::integer(count as i64));
5385 }
5386 if let Some(caps) = re.captures(&s) {
5387 let m0 = caps.get(0).expect("regex capture 0");
5388 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5389 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5390 let mut out = String::new();
5391 out.push_str(&s[..m0.start]);
5392 out.push_str(&repl_val.to_string());
5393 out.push_str(&s[m0.end..]);
5394 if flags.contains('r') {
5395 return Ok(PerlValue::string(out));
5396 }
5397 self.assign_value(target, PerlValue::string(out))?;
5398 return Ok(PerlValue::integer(1));
5399 }
5400 if flags.contains('r') {
5401 return Ok(PerlValue::string(s));
5402 }
5403 self.assign_value(target, PerlValue::string(s))?;
5404 Ok(PerlValue::integer(0))
5405 }
5406
5407 pub(crate) fn regex_transliterate_execute(
5409 &mut self,
5410 s: String,
5411 from: &str,
5412 to: &str,
5413 flags: &str,
5414 target: &Expr,
5415 line: usize,
5416 ) -> ExecResult {
5417 let _ = line;
5418 let from_chars = Self::tr_expand_ranges(from);
5419 let to_chars = Self::tr_expand_ranges(to);
5420 let delete_mode = flags.contains('d');
5421 let mut count = 0i64;
5422 let new_s: String = s
5423 .chars()
5424 .filter_map(|c| {
5425 if let Some(pos) = from_chars.iter().position(|&fc| fc == c) {
5426 count += 1;
5427 if delete_mode {
5428 if pos < to_chars.len() {
5430 Some(to_chars[pos])
5431 } else {
5432 None }
5434 } else {
5435 Some(to_chars.get(pos).or(to_chars.last()).copied().unwrap_or(c))
5437 }
5438 } else {
5439 Some(c)
5440 }
5441 })
5442 .collect();
5443 if flags.contains('r') {
5444 Ok(PerlValue::string(new_s))
5446 } else {
5447 self.assign_value(target, PerlValue::string(new_s))?;
5448 Ok(PerlValue::integer(count))
5449 }
5450 }
5451
5452 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
5455 let raw: Vec<char> = spec.chars().collect();
5456 let mut out = Vec::with_capacity(raw.len());
5457 let mut i = 0;
5458 while i < raw.len() {
5459 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
5460 let start = raw[i] as u32;
5461 let end = raw[i + 2] as u32;
5462 for code in start..=end {
5463 if let Some(c) = char::from_u32(code) {
5464 out.push(c);
5465 }
5466 }
5467 i += 3;
5468 } else {
5469 out.push(raw[i]);
5470 i += 1;
5471 }
5472 }
5473 out
5474 }
5475
5476 pub(crate) fn splice_builtin_execute(
5478 &mut self,
5479 args: &[PerlValue],
5480 line: usize,
5481 ) -> PerlResult<PerlValue> {
5482 if args.is_empty() {
5483 return Err(PerlError::runtime("splice: missing array", line));
5484 }
5485 let arr_name = args[0].to_string();
5486 let arr_len = self.scope.array_len(&arr_name);
5487 let offset_val = args
5488 .get(1)
5489 .cloned()
5490 .unwrap_or_else(|| PerlValue::integer(0));
5491 let length_val = match args.get(2) {
5492 None => PerlValue::UNDEF,
5493 Some(v) => v.clone(),
5494 };
5495 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
5496 let rep_vals: Vec<PerlValue> = args.iter().skip(3).cloned().collect();
5497 let arr = self.scope.get_array_mut(&arr_name)?;
5498 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
5499 for (i, v) in rep_vals.into_iter().enumerate() {
5500 arr.insert(off + i, v);
5501 }
5502 Ok(match self.wantarray_kind {
5503 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
5504 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
5505 })
5506 }
5507
5508 pub(crate) fn unshift_builtin_execute(
5510 &mut self,
5511 args: &[PerlValue],
5512 line: usize,
5513 ) -> PerlResult<PerlValue> {
5514 if args.is_empty() {
5515 return Err(PerlError::runtime("unshift: missing array", line));
5516 }
5517 let arr_name = args[0].to_string();
5518 let mut flat_vals: Vec<PerlValue> = Vec::new();
5519 for a in args.iter().skip(1) {
5520 if let Some(items) = a.as_array_vec() {
5521 flat_vals.extend(items);
5522 } else {
5523 flat_vals.push(a.clone());
5524 }
5525 }
5526 let arr = self.scope.get_array_mut(&arr_name)?;
5527 for (i, v) in flat_vals.into_iter().enumerate() {
5528 arr.insert(i, v);
5529 }
5530 Ok(PerlValue::integer(arr.len() as i64))
5531 }
5532
5533 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
5536 if upper == 0.0 {
5537 self.rand_rng.gen_range(0.0..1.0)
5538 } else if upper > 0.0 {
5539 self.rand_rng.gen_range(0.0..upper)
5540 } else {
5541 self.rand_rng.gen_range(upper..0.0)
5542 }
5543 }
5544
5545 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
5547 let n = if let Some(s) = seed {
5548 s as i64
5549 } else {
5550 std::time::SystemTime::now()
5551 .duration_since(std::time::UNIX_EPOCH)
5552 .map(|d| d.as_secs() as i64)
5553 .unwrap_or(1)
5554 };
5555 let mag = n.unsigned_abs();
5556 self.rand_rng = StdRng::seed_from_u64(mag);
5557 n.abs()
5558 }
5559
5560 pub fn set_file(&mut self, file: &str) {
5561 self.file = file.to_string();
5562 }
5563
5564 pub fn repl_completion_names(&self) -> Vec<String> {
5566 let mut v = self.scope.repl_binding_names();
5567 v.extend(self.subs.keys().cloned());
5568 v.sort();
5569 v.dedup();
5570 v
5571 }
5572
5573 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
5575 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
5576 subs.sort();
5577 let mut classes: HashSet<String> = HashSet::new();
5578 for k in &subs {
5579 if let Some((pkg, rest)) = k.split_once("::") {
5580 if !rest.contains("::") {
5581 classes.insert(pkg.to_string());
5582 }
5583 }
5584 }
5585 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
5586 for bn in self.scope.repl_binding_names() {
5587 if let Some(r) = bn.strip_prefix('$') {
5588 let v = self.scope.get_scalar(r);
5589 if let Some(b) = v.as_blessed_ref() {
5590 blessed_scalars.insert(r.to_string(), b.class.clone());
5591 classes.insert(b.class.clone());
5592 }
5593 }
5594 }
5595 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
5596 for c in classes {
5597 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
5598 }
5599 ReplCompletionSnapshot {
5600 subs,
5601 blessed_scalars,
5602 isa_for_class,
5603 }
5604 }
5605
5606 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
5607 if n == 0 {
5608 return Err(FlowOrError::Error(PerlError::runtime(
5609 "bench: iteration count must be positive",
5610 line,
5611 )));
5612 }
5613 let mut samples = Vec::with_capacity(n);
5614 for _ in 0..n {
5615 let start = std::time::Instant::now();
5616 self.exec_block(body)?;
5617 samples.push(start.elapsed().as_secs_f64() * 1000.0);
5618 }
5619 let mut sorted = samples.clone();
5620 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
5621 let min_ms = sorted[0];
5622 let mean = samples.iter().sum::<f64>() / n as f64;
5623 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
5624 .saturating_sub(1)
5625 .min(n - 1);
5626 let p99_ms = sorted[p99_idx];
5627 Ok(PerlValue::string(format!(
5628 "bench: n={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
5629 n, min_ms, mean, p99_ms
5630 )))
5631 }
5632
5633 pub fn execute(&mut self, program: &Program) -> PerlResult<PerlValue> {
5634 if self.line_mode_skip_main {
5636 crate::compile_and_run_prelude(program, self)?;
5637 return Ok(PerlValue::UNDEF);
5638 }
5639 crate::try_vm_execute(program, self)
5640 .expect("VM compilation must succeed — all execution is VM-only")
5641 }
5642
5643 pub fn run_end_blocks(&mut self) -> PerlResult<()> {
5645 self.global_phase = "END".to_string();
5646 let ends = std::mem::take(&mut self.end_blocks);
5647 for block in &ends {
5648 self.exec_block(block).map_err(|e| match e {
5649 FlowOrError::Error(e) => e,
5650 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in END", 0),
5651 })?;
5652 }
5653 Ok(())
5654 }
5655
5656 pub fn run_global_teardown(&mut self) -> PerlResult<()> {
5659 self.global_phase = "DESTRUCT".to_string();
5660 self.drain_pending_destroys(0)
5661 }
5662
5663 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> PerlResult<()> {
5665 loop {
5666 let batch = crate::pending_destroy::take_queue();
5667 if batch.is_empty() {
5668 break;
5669 }
5670 for (class, payload) in batch {
5671 let fq = format!("{}::DESTROY", class);
5672 let Some(sub) = self.subs.get(&fq).cloned() else {
5673 continue;
5674 };
5675 let inv = PerlValue::blessed(Arc::new(
5676 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
5677 ));
5678 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
5679 Ok(_) => {}
5680 Err(FlowOrError::Error(e)) => return Err(e),
5681 Err(FlowOrError::Flow(Flow::Return(_))) => {}
5682 Err(FlowOrError::Flow(other)) => {
5683 return Err(PerlError::runtime(
5684 format!("DESTROY: unexpected control flow ({other:?})"),
5685 line,
5686 ));
5687 }
5688 }
5689 }
5690 }
5691 Ok(())
5692 }
5693
5694 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
5695 self.exec_block_with_tail(block, WantarrayCtx::Void)
5696 }
5697
5698 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5701 let uses_goto = block
5702 .iter()
5703 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
5704 if uses_goto {
5705 self.scope_push_hook();
5706 let r = self.exec_block_with_goto_tail(block, tail);
5707 self.scope_pop_hook();
5708 r
5709 } else {
5710 self.scope_push_hook();
5711 let result = self.exec_block_no_scope_with_tail(block, tail);
5712 self.scope_pop_hook();
5713 result
5714 }
5715 }
5716
5717 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5718 let mut map: HashMap<String, usize> = HashMap::new();
5719 for (i, s) in block.iter().enumerate() {
5720 if let Some(l) = &s.label {
5721 map.insert(l.clone(), i);
5722 }
5723 }
5724 let mut pc = 0usize;
5725 let mut last = PerlValue::UNDEF;
5726 let last_idx = block.len().saturating_sub(1);
5727 while pc < block.len() {
5728 if let StmtKind::Goto { target } = &block[pc].kind {
5729 let line = block[pc].line;
5730 let name = self.eval_expr(target)?.to_string();
5731 pc = *map.get(&name).ok_or_else(|| {
5732 FlowOrError::Error(PerlError::runtime(
5733 format!("goto: unknown label {}", name),
5734 line,
5735 ))
5736 })?;
5737 continue;
5738 }
5739 let v = if pc == last_idx {
5740 match &block[pc].kind {
5741 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
5742 _ => self.exec_statement(&block[pc])?,
5743 }
5744 } else {
5745 self.exec_statement(&block[pc])?
5746 };
5747 last = v;
5748 pc += 1;
5749 }
5750 Ok(last)
5751 }
5752
5753 #[inline]
5756 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
5757 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
5758 }
5759
5760 pub(crate) fn exec_block_no_scope_with_tail(
5761 &mut self,
5762 block: &Block,
5763 tail: WantarrayCtx,
5764 ) -> ExecResult {
5765 if block.is_empty() {
5766 return Ok(PerlValue::UNDEF);
5767 }
5768 let last_i = block.len() - 1;
5769 for (i, stmt) in block.iter().enumerate() {
5770 if i < last_i {
5771 self.exec_statement(stmt)?;
5772 } else {
5773 return match &stmt.kind {
5774 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
5775 _ => self.exec_statement(stmt),
5776 };
5777 }
5778 }
5779 Ok(PerlValue::UNDEF)
5780 }
5781
5782 pub(crate) fn spawn_async_block(&self, block: &Block) -> PerlValue {
5784 use parking_lot::Mutex as ParkMutex;
5785
5786 let block = block.clone();
5787 let subs = self.subs.clone();
5788 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5789 let result = Arc::new(ParkMutex::new(None));
5790 let join = Arc::new(ParkMutex::new(None));
5791 let result2 = result.clone();
5792 let h = std::thread::spawn(move || {
5793 let mut interp = Interpreter::new();
5794 interp.subs = subs;
5795 interp.scope.restore_capture(&scalars);
5796 interp.scope.restore_atomics(&aar, &ahash);
5797 interp.enable_parallel_guard();
5798 let r = match interp.exec_block(&block) {
5799 Ok(v) => Ok(v),
5800 Err(FlowOrError::Error(e)) => Err(e),
5801 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5802 Err(PerlError::runtime("yield inside async/spawn block", 0))
5803 }
5804 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5805 };
5806 *result2.lock() = Some(r);
5807 });
5808 *join.lock() = Some(h);
5809 PerlValue::async_task(Arc::new(PerlAsyncTask { result, join }))
5810 }
5811
5812 pub(crate) fn eval_timeout_block(
5814 &mut self,
5815 body: &Block,
5816 secs: f64,
5817 line: usize,
5818 ) -> ExecResult {
5819 use std::sync::mpsc::channel;
5820 use std::time::Duration;
5821
5822 let block = body.clone();
5823 let subs = self.subs.clone();
5824 let struct_defs = self.struct_defs.clone();
5825 let enum_defs = self.enum_defs.clone();
5826 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5827 self.materialize_env_if_needed();
5828 let env = self.env.clone();
5829 let argv = self.argv.clone();
5830 let inc = self.scope.get_array("INC");
5831 let (tx, rx) = channel::<PerlResult<PerlValue>>();
5832 let _handle = std::thread::spawn(move || {
5833 let mut interp = Interpreter::new();
5834 interp.subs = subs;
5835 interp.struct_defs = struct_defs;
5836 interp.enum_defs = enum_defs;
5837 interp.env = env.clone();
5838 interp.argv = argv.clone();
5839 interp.scope.declare_array(
5840 "ARGV",
5841 argv.iter().map(|s| PerlValue::string(s.clone())).collect(),
5842 );
5843 for (k, v) in env {
5844 interp
5845 .scope
5846 .set_hash_element("ENV", &k, v)
5847 .expect("set ENV in timeout thread");
5848 }
5849 interp.scope.declare_array("INC", inc);
5850 interp.scope.restore_capture(&scalars);
5851 interp.scope.restore_atomics(&aar, &ahash);
5852 interp.enable_parallel_guard();
5853 let out: PerlResult<PerlValue> = match interp.exec_block(&block) {
5854 Ok(v) => Ok(v),
5855 Err(FlowOrError::Error(e)) => Err(e),
5856 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5857 Err(PerlError::runtime("yield inside eval_timeout block", 0))
5858 }
5859 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5860 };
5861 let _ = tx.send(out);
5862 });
5863 let dur = Duration::from_secs_f64(secs.max(0.0));
5864 match rx.recv_timeout(dur) {
5865 Ok(Ok(v)) => Ok(v),
5866 Ok(Err(e)) => Err(FlowOrError::Error(e)),
5867 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(PerlError::runtime(
5868 format!(
5869 "eval_timeout: exceeded {} second(s) (worker continues in background)",
5870 secs
5871 ),
5872 line,
5873 )
5874 .into()),
5875 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(PerlError::runtime(
5876 "eval_timeout: worker thread panicked or disconnected",
5877 line,
5878 )
5879 .into()),
5880 }
5881 }
5882
5883 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
5884 let mut last = PerlValue::UNDEF;
5885 for stmt in body {
5886 match &stmt.kind {
5887 StmtKind::When { cond, body: wb } => {
5888 if self.when_matches(cond)? {
5889 return self.exec_block_smart(wb);
5890 }
5891 }
5892 StmtKind::DefaultCase { body: db } => {
5893 return self.exec_block_smart(db);
5894 }
5895 _ => {
5896 last = self.exec_statement(stmt)?;
5897 }
5898 }
5899 }
5900 Ok(last)
5901 }
5902
5903 pub(crate) fn exec_given_with_topic_value(
5905 &mut self,
5906 topic: PerlValue,
5907 body: &Block,
5908 ) -> ExecResult {
5909 self.scope_push_hook();
5910 self.scope.declare_scalar("_", topic);
5911 self.english_note_lexical_scalar("_");
5912 let r = self.exec_given_body(body);
5913 self.scope_pop_hook();
5914 r
5915 }
5916
5917 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
5918 let t = self.eval_expr(topic)?;
5919 self.exec_given_with_topic_value(t, body)
5920 }
5921
5922 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5924 let topic = self.scope.get_scalar("_");
5925 let line = cond.line;
5926 match &cond.kind {
5927 ExprKind::Regex(pattern, flags) => {
5928 let re = self.compile_regex(pattern, flags, line)?;
5929 let s = topic.to_string();
5930 Ok(re.is_match(&s))
5931 }
5932 ExprKind::String(s) => Ok(topic.to_string() == *s),
5933 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
5934 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
5935 _ => {
5936 let c = self.eval_expr(cond)?;
5937 Ok(self.smartmatch_when(&topic, &c))
5938 }
5939 }
5940 }
5941
5942 fn smartmatch_when(&self, topic: &PerlValue, c: &PerlValue) -> bool {
5943 if let Some(re) = c.as_regex() {
5944 return re.is_match(&topic.to_string());
5945 }
5946 topic.to_string() == c.to_string()
5947 }
5948
5949 pub(crate) fn eval_boolean_rvalue_condition(
5951 &mut self,
5952 cond: &Expr,
5953 ) -> Result<bool, FlowOrError> {
5954 match &cond.kind {
5955 ExprKind::Regex(pattern, flags) => {
5956 let topic = self.scope.get_scalar("_");
5957 let line = cond.line;
5958 let s = topic.to_string();
5959 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
5960 Ok(v.is_true())
5961 }
5962 ExprKind::ReadLine(_) => {
5964 let v = self.eval_expr(cond)?;
5965 self.scope.set_topic(v.clone());
5966 Ok(!v.is_undef())
5967 }
5968 _ => {
5969 let v = self.eval_expr(cond)?;
5970 Ok(v.is_true())
5971 }
5972 }
5973 }
5974
5975 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5977 self.eval_boolean_rvalue_condition(cond)
5978 }
5979
5980 pub(crate) fn eval_algebraic_match(
5981 &mut self,
5982 subject: &Expr,
5983 arms: &[MatchArm],
5984 line: usize,
5985 ) -> ExecResult {
5986 let val = self.eval_algebraic_match_subject(subject, line)?;
5987 self.eval_algebraic_match_with_subject_value(val, arms, line)
5988 }
5989
5990 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
5992 match &subject.kind {
5993 ExprKind::ArrayVar(name) => {
5994 self.check_strict_array_var(name, line)?;
5995 let aname = self.stash_array_name_for_package(name);
5996 Ok(PerlValue::array_binding_ref(aname))
5997 }
5998 ExprKind::HashVar(name) => {
5999 self.check_strict_hash_var(name, line)?;
6000 self.touch_env_hash(name);
6001 Ok(PerlValue::hash_binding_ref(name.clone()))
6002 }
6003 _ => self.eval_expr(subject),
6004 }
6005 }
6006
6007 pub(crate) fn eval_algebraic_match_with_subject_value(
6009 &mut self,
6010 val: PerlValue,
6011 arms: &[MatchArm],
6012 line: usize,
6013 ) -> ExecResult {
6014 if let Some(e) = val.as_enum_inst() {
6016 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
6017 if !has_catchall {
6018 let covered: Vec<String> = arms
6019 .iter()
6020 .filter_map(|a| {
6021 if let MatchPattern::Value(expr) = &a.pattern {
6022 if let ExprKind::FuncCall { name, .. } = &expr.kind {
6023 return name.rsplit_once("::").map(|(_, v)| v.to_string());
6024 }
6025 }
6026 None
6027 })
6028 .collect();
6029 let missing: Vec<&str> = e
6030 .def
6031 .variants
6032 .iter()
6033 .filter(|v| !covered.contains(&v.name))
6034 .map(|v| v.name.as_str())
6035 .collect();
6036 if !missing.is_empty() {
6037 return Err(PerlError::runtime(
6038 format!(
6039 "non-exhaustive match on enum `{}`: missing variant(s) {}",
6040 e.def.name,
6041 missing.join(", ")
6042 ),
6043 line,
6044 )
6045 .into());
6046 }
6047 }
6048 }
6049 for arm in arms {
6050 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
6051 let re = self.compile_regex(pattern, flags, line)?;
6052 let s = val.to_string();
6053 if let Some(caps) = re.captures(&s) {
6054 self.scope_push_hook();
6055 self.scope.declare_scalar("_", val.clone());
6056 self.english_note_lexical_scalar("_");
6057 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
6058 let guard_ok = if let Some(g) = &arm.guard {
6059 self.eval_expr(g)?.is_true()
6060 } else {
6061 true
6062 };
6063 if !guard_ok {
6064 self.scope_pop_hook();
6065 continue;
6066 }
6067 let out = self.eval_expr(&arm.body);
6068 self.scope_pop_hook();
6069 return out;
6070 }
6071 continue;
6072 }
6073 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
6074 self.scope_push_hook();
6075 self.scope.declare_scalar("_", val.clone());
6076 self.english_note_lexical_scalar("_");
6077 for b in bindings {
6078 match b {
6079 PatternBinding::Scalar(name, v) => {
6080 self.scope.declare_scalar(&name, v);
6081 self.english_note_lexical_scalar(&name);
6082 }
6083 PatternBinding::Array(name, elems) => {
6084 self.scope.declare_array(&name, elems);
6085 }
6086 }
6087 }
6088 let guard_ok = if let Some(g) = &arm.guard {
6089 self.eval_expr(g)?.is_true()
6090 } else {
6091 true
6092 };
6093 if !guard_ok {
6094 self.scope_pop_hook();
6095 continue;
6096 }
6097 let out = self.eval_expr(&arm.body);
6098 self.scope_pop_hook();
6099 return out;
6100 }
6101 }
6102 Err(PerlError::runtime(
6103 "match: no arm matched the value (add a `_` catch-all)",
6104 line,
6105 )
6106 .into())
6107 }
6108
6109 fn parse_duration_seconds(pv: &PerlValue) -> Option<f64> {
6110 let s = pv.to_string();
6111 let s = s.trim();
6112 if let Some(rest) = s.strip_suffix("ms") {
6113 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
6114 }
6115 if let Some(rest) = s.strip_suffix('s') {
6116 return rest.trim().parse::<f64>().ok();
6117 }
6118 if let Some(rest) = s.strip_suffix('m') {
6119 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
6120 }
6121 s.parse::<f64>().ok()
6122 }
6123
6124 fn eval_retry_block(
6125 &mut self,
6126 body: &Block,
6127 times: &Expr,
6128 backoff: RetryBackoff,
6129 _line: usize,
6130 ) -> ExecResult {
6131 let max = self.eval_expr(times)?.to_int().max(1) as usize;
6132 let base_ms: u64 = 10;
6133 let mut attempt = 0usize;
6134 loop {
6135 attempt += 1;
6136 match self.exec_block(body) {
6137 Ok(v) => return Ok(v),
6138 Err(FlowOrError::Error(e)) => {
6139 if attempt >= max {
6140 return Err(FlowOrError::Error(e));
6141 }
6142 let delay_ms = match backoff {
6143 RetryBackoff::None => 0,
6144 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
6145 RetryBackoff::Exponential => {
6146 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
6147 }
6148 };
6149 if delay_ms > 0 {
6150 std::thread::sleep(Duration::from_millis(delay_ms));
6151 }
6152 }
6153 Err(e) => return Err(e),
6154 }
6155 }
6156 }
6157
6158 fn eval_rate_limit_block(
6159 &mut self,
6160 slot: u32,
6161 max: &Expr,
6162 window: &Expr,
6163 body: &Block,
6164 _line: usize,
6165 ) -> ExecResult {
6166 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
6167 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
6168 .filter(|s| *s > 0.0)
6169 .unwrap_or(1.0);
6170 let window_d = Duration::from_secs_f64(window_sec);
6171 let slot = slot as usize;
6172 while self.rate_limit_slots.len() <= slot {
6173 self.rate_limit_slots.push(VecDeque::new());
6174 }
6175 {
6176 let dq = &mut self.rate_limit_slots[slot];
6177 loop {
6178 let now = Instant::now();
6179 while let Some(t0) = dq.front().copied() {
6180 if now.duration_since(t0) >= window_d {
6181 dq.pop_front();
6182 } else {
6183 break;
6184 }
6185 }
6186 if dq.len() < max_n || max_n == 0 {
6187 break;
6188 }
6189 let t0 = dq.front().copied().unwrap();
6190 let wait = window_d.saturating_sub(now.duration_since(t0));
6191 if wait.is_zero() {
6192 dq.pop_front();
6193 continue;
6194 }
6195 std::thread::sleep(wait);
6196 }
6197 dq.push_back(Instant::now());
6198 }
6199 self.exec_block(body)
6200 }
6201
6202 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
6203 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
6204 .filter(|s| *s > 0.0)
6205 .unwrap_or(1.0);
6206 loop {
6207 match self.exec_block(body) {
6208 Ok(_) => {}
6209 Err(e) => return Err(e),
6210 }
6211 std::thread::sleep(Duration::from_secs_f64(sec));
6212 }
6213 }
6214
6215 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> PerlResult<PerlValue> {
6217 let pair = |value: PerlValue, more: i64| {
6218 PerlValue::array_ref(Arc::new(RwLock::new(vec![value, PerlValue::integer(more)])))
6219 };
6220 let mut exhausted = gen.exhausted.lock();
6221 if *exhausted {
6222 return Ok(pair(PerlValue::UNDEF, 0));
6223 }
6224 let mut pc = gen.pc.lock();
6225 let mut scope_started = gen.scope_started.lock();
6226 if *pc >= gen.block.len() {
6227 if *scope_started {
6228 self.scope_pop_hook();
6229 *scope_started = false;
6230 }
6231 *exhausted = true;
6232 return Ok(pair(PerlValue::UNDEF, 0));
6233 }
6234 if !*scope_started {
6235 self.scope_push_hook();
6236 *scope_started = true;
6237 }
6238 self.in_generator = true;
6239 while *pc < gen.block.len() {
6240 let stmt = &gen.block[*pc];
6241 match self.exec_statement(stmt) {
6242 Ok(_) => {
6243 *pc += 1;
6244 }
6245 Err(FlowOrError::Flow(Flow::Yield(v))) => {
6246 *pc += 1;
6247 self.in_generator = false;
6248 if *scope_started {
6251 self.scope_pop_hook();
6252 *scope_started = false;
6253 }
6254 return Ok(pair(v, 1));
6255 }
6256 Err(e) => {
6257 self.in_generator = false;
6258 if *scope_started {
6259 self.scope_pop_hook();
6260 *scope_started = false;
6261 }
6262 return Err(match e {
6263 FlowOrError::Error(ee) => ee,
6264 FlowOrError::Flow(Flow::Yield(_)) => {
6265 unreachable!("yield handled above")
6266 }
6267 FlowOrError::Flow(flow) => PerlError::runtime(
6268 format!("unexpected control flow in generator: {:?}", flow),
6269 0,
6270 ),
6271 });
6272 }
6273 }
6274 }
6275 self.in_generator = false;
6276 if *scope_started {
6277 self.scope_pop_hook();
6278 *scope_started = false;
6279 }
6280 *exhausted = true;
6281 Ok(pair(PerlValue::UNDEF, 0))
6282 }
6283
6284 fn match_pattern_try(
6285 &mut self,
6286 subject: &PerlValue,
6287 pattern: &MatchPattern,
6288 line: usize,
6289 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6290 match pattern {
6291 MatchPattern::Any => Ok(Some(vec![])),
6292 MatchPattern::Regex { .. } => {
6293 unreachable!("regex arms are handled in eval_algebraic_match")
6294 }
6295 MatchPattern::Value(expr) => {
6296 if self.match_pattern_value_alternation(subject, expr, line)? {
6297 Ok(Some(vec![]))
6298 } else {
6299 Ok(None)
6300 }
6301 }
6302 MatchPattern::Array(elems) => {
6303 let Some(arr) = self.match_subject_as_array(subject) else {
6304 return Ok(None);
6305 };
6306 self.match_array_pattern_elems(&arr, elems, line)
6307 }
6308 MatchPattern::Hash(pairs) => {
6309 let Some(h) = self.match_subject_as_hash(subject) else {
6310 return Ok(None);
6311 };
6312 self.match_hash_pattern_pairs(&h, pairs, line)
6313 }
6314 MatchPattern::OptionSome(name) => {
6315 let Some(arr) = self.match_subject_as_array(subject) else {
6316 return Ok(None);
6317 };
6318 if arr.len() < 2 {
6319 return Ok(None);
6320 }
6321 if !arr[1].is_true() {
6322 return Ok(None);
6323 }
6324 Ok(Some(vec![PatternBinding::Scalar(
6325 name.clone(),
6326 arr[0].clone(),
6327 )]))
6328 }
6329 }
6330 }
6331
6332 fn match_pattern_value_alternation(
6335 &mut self,
6336 subject: &PerlValue,
6337 expr: &Expr,
6338 _line: usize,
6339 ) -> Result<bool, FlowOrError> {
6340 if let ExprKind::BinOp {
6341 left,
6342 op: BinOp::BitOr,
6343 right,
6344 } = &expr.kind
6345 {
6346 if self.match_pattern_value_alternation(subject, left, _line)? {
6347 return Ok(true);
6348 }
6349 return self.match_pattern_value_alternation(subject, right, _line);
6350 }
6351 let pv = self.eval_expr(expr)?;
6352 Ok(self.smartmatch_when(subject, &pv))
6353 }
6354
6355 fn match_subject_as_array(&self, v: &PerlValue) -> Option<Vec<PerlValue>> {
6357 if let Some(a) = v.as_array_vec() {
6358 return Some(a);
6359 }
6360 if let Some(r) = v.as_array_ref() {
6361 return Some(r.read().clone());
6362 }
6363 if let Some(name) = v.as_array_binding_name() {
6364 return Some(self.scope.get_array(&name));
6365 }
6366 None
6367 }
6368
6369 fn match_subject_as_hash(&mut self, v: &PerlValue) -> Option<IndexMap<String, PerlValue>> {
6370 if let Some(h) = v.as_hash_map() {
6371 return Some(h);
6372 }
6373 if let Some(r) = v.as_hash_ref() {
6374 return Some(r.read().clone());
6375 }
6376 if let Some(name) = v.as_hash_binding_name() {
6377 self.touch_env_hash(&name);
6378 return Some(self.scope.get_hash(&name));
6379 }
6380 None
6381 }
6382
6383 pub(crate) fn hash_slice_deref_values(
6386 &mut self,
6387 container: &PerlValue,
6388 key_values: &[PerlValue],
6389 line: usize,
6390 ) -> Result<PerlValue, FlowOrError> {
6391 let h = if let Some(m) = self.match_subject_as_hash(container) {
6392 m
6393 } else {
6394 return Err(PerlError::runtime(
6395 "Hash slice dereference needs a hash or hash reference value",
6396 line,
6397 )
6398 .into());
6399 };
6400 let mut result = Vec::new();
6401 for kv in key_values {
6402 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6403 vv.iter().map(|x| x.to_string()).collect()
6404 } else {
6405 vec![kv.to_string()]
6406 };
6407 for k in key_strings {
6408 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6409 }
6410 }
6411 Ok(PerlValue::array(result))
6412 }
6413
6414 pub(crate) fn assign_hash_slice_one_key(
6417 &mut self,
6418 container: PerlValue,
6419 key: &str,
6420 val: PerlValue,
6421 line: usize,
6422 ) -> Result<PerlValue, FlowOrError> {
6423 if let Some(r) = container.as_hash_ref() {
6424 r.write().insert(key.to_string(), val);
6425 return Ok(PerlValue::UNDEF);
6426 }
6427 if let Some(name) = container.as_hash_binding_name() {
6428 self.touch_env_hash(&name);
6429 self.scope
6430 .set_hash_element(&name, key, val)
6431 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6432 return Ok(PerlValue::UNDEF);
6433 }
6434 if let Some(s) = container.as_str() {
6435 self.touch_env_hash(&s);
6436 if self.strict_refs {
6437 return Err(PerlError::runtime(
6438 format!(
6439 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6440 s
6441 ),
6442 line,
6443 )
6444 .into());
6445 }
6446 self.scope
6447 .set_hash_element(&s, key, val)
6448 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6449 return Ok(PerlValue::UNDEF);
6450 }
6451 Err(PerlError::runtime(
6452 "Hash slice assignment needs a hash or hash reference value",
6453 line,
6454 )
6455 .into())
6456 }
6457
6458 pub(crate) fn assign_named_hash_slice(
6461 &mut self,
6462 hash: &str,
6463 key_values: Vec<PerlValue>,
6464 val: PerlValue,
6465 line: usize,
6466 ) -> Result<PerlValue, FlowOrError> {
6467 self.touch_env_hash(hash);
6468 let mut ks: Vec<String> = Vec::new();
6469 for kv in key_values {
6470 if let Some(vv) = kv.as_array_vec() {
6471 ks.extend(vv.iter().map(|x| x.to_string()));
6472 } else {
6473 ks.push(kv.to_string());
6474 }
6475 }
6476 if ks.is_empty() {
6477 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6478 }
6479 let items = val.to_list();
6480 for (i, k) in ks.iter().enumerate() {
6481 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6482 self.scope
6483 .set_hash_element(hash, k, v)
6484 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6485 }
6486 Ok(PerlValue::UNDEF)
6487 }
6488
6489 pub(crate) fn assign_hash_slice_deref(
6491 &mut self,
6492 container: PerlValue,
6493 key_values: Vec<PerlValue>,
6494 val: PerlValue,
6495 line: usize,
6496 ) -> Result<PerlValue, FlowOrError> {
6497 let mut ks: Vec<String> = Vec::new();
6498 for kv in key_values {
6499 if let Some(vv) = kv.as_array_vec() {
6500 ks.extend(vv.iter().map(|x| x.to_string()));
6501 } else {
6502 ks.push(kv.to_string());
6503 }
6504 }
6505 if ks.is_empty() {
6506 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6507 }
6508 let items = val.to_list();
6509 if let Some(r) = container.as_hash_ref() {
6510 let mut h = r.write();
6511 for (i, k) in ks.iter().enumerate() {
6512 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6513 h.insert(k.clone(), v);
6514 }
6515 return Ok(PerlValue::UNDEF);
6516 }
6517 if let Some(name) = container.as_hash_binding_name() {
6518 self.touch_env_hash(&name);
6519 for (i, k) in ks.iter().enumerate() {
6520 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6521 self.scope
6522 .set_hash_element(&name, k, v)
6523 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6524 }
6525 return Ok(PerlValue::UNDEF);
6526 }
6527 if let Some(s) = container.as_str() {
6528 if self.strict_refs {
6529 return Err(PerlError::runtime(
6530 format!(
6531 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6532 s
6533 ),
6534 line,
6535 )
6536 .into());
6537 }
6538 self.touch_env_hash(&s);
6539 for (i, k) in ks.iter().enumerate() {
6540 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6541 self.scope
6542 .set_hash_element(&s, k, v)
6543 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6544 }
6545 return Ok(PerlValue::UNDEF);
6546 }
6547 Err(PerlError::runtime(
6548 "Hash slice assignment needs a hash or hash reference value",
6549 line,
6550 )
6551 .into())
6552 }
6553
6554 pub(crate) fn compound_assign_hash_slice_deref(
6557 &mut self,
6558 container: PerlValue,
6559 key_values: Vec<PerlValue>,
6560 op: BinOp,
6561 rhs: PerlValue,
6562 line: usize,
6563 ) -> Result<PerlValue, FlowOrError> {
6564 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6565 let last_old = old_list
6566 .to_list()
6567 .last()
6568 .cloned()
6569 .unwrap_or(PerlValue::UNDEF);
6570 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6571 let mut ks: Vec<String> = Vec::new();
6572 for kv in &key_values {
6573 if let Some(vv) = kv.as_array_vec() {
6574 ks.extend(vv.iter().map(|x| x.to_string()));
6575 } else {
6576 ks.push(kv.to_string());
6577 }
6578 }
6579 if ks.is_empty() {
6580 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6581 }
6582 let last_key = ks.last().expect("non-empty ks");
6583 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6584 Ok(new_val)
6585 }
6586
6587 pub(crate) fn hash_slice_deref_inc_dec(
6593 &mut self,
6594 container: PerlValue,
6595 key_values: Vec<PerlValue>,
6596 kind: u8,
6597 line: usize,
6598 ) -> Result<PerlValue, FlowOrError> {
6599 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6600 let last_old = old_list
6601 .to_list()
6602 .last()
6603 .cloned()
6604 .unwrap_or(PerlValue::UNDEF);
6605 let new_val = if kind & 1 == 0 {
6606 PerlValue::integer(last_old.to_int() + 1)
6607 } else {
6608 PerlValue::integer(last_old.to_int() - 1)
6609 };
6610 let mut ks: Vec<String> = Vec::new();
6611 for kv in &key_values {
6612 if let Some(vv) = kv.as_array_vec() {
6613 ks.extend(vv.iter().map(|x| x.to_string()));
6614 } else {
6615 ks.push(kv.to_string());
6616 }
6617 }
6618 let last_key = ks.last().ok_or_else(|| {
6619 PerlError::runtime("Hash slice increment needs at least one key", line)
6620 })?;
6621 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6622 Ok(if kind < 2 { new_val } else { last_old })
6623 }
6624
6625 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[PerlValue]) -> PerlValue {
6626 self.touch_env_hash(hash);
6627 let h = self.scope.get_hash(hash);
6628 let mut result = Vec::new();
6629 for kv in key_values {
6630 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6631 vv.iter().map(|x| x.to_string()).collect()
6632 } else {
6633 vec![kv.to_string()]
6634 };
6635 for k in key_strings {
6636 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6637 }
6638 }
6639 PerlValue::array(result)
6640 }
6641
6642 pub(crate) fn compound_assign_named_hash_slice(
6644 &mut self,
6645 hash: &str,
6646 key_values: Vec<PerlValue>,
6647 op: BinOp,
6648 rhs: PerlValue,
6649 line: usize,
6650 ) -> Result<PerlValue, FlowOrError> {
6651 let old_list = self.hash_slice_named_values(hash, &key_values);
6652 let last_old = old_list
6653 .to_list()
6654 .last()
6655 .cloned()
6656 .unwrap_or(PerlValue::UNDEF);
6657 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6658 let mut ks: Vec<String> = Vec::new();
6659 for kv in &key_values {
6660 if let Some(vv) = kv.as_array_vec() {
6661 ks.extend(vv.iter().map(|x| x.to_string()));
6662 } else {
6663 ks.push(kv.to_string());
6664 }
6665 }
6666 if ks.is_empty() {
6667 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6668 }
6669 let last_key = ks.last().expect("non-empty ks");
6670 let container = PerlValue::string(hash.to_string());
6671 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6672 Ok(new_val)
6673 }
6674
6675 pub(crate) fn named_hash_slice_inc_dec(
6677 &mut self,
6678 hash: &str,
6679 key_values: Vec<PerlValue>,
6680 kind: u8,
6681 line: usize,
6682 ) -> Result<PerlValue, FlowOrError> {
6683 let old_list = self.hash_slice_named_values(hash, &key_values);
6684 let last_old = old_list
6685 .to_list()
6686 .last()
6687 .cloned()
6688 .unwrap_or(PerlValue::UNDEF);
6689 let new_val = if kind & 1 == 0 {
6690 PerlValue::integer(last_old.to_int() + 1)
6691 } else {
6692 PerlValue::integer(last_old.to_int() - 1)
6693 };
6694 let mut ks: Vec<String> = Vec::new();
6695 for kv in &key_values {
6696 if let Some(vv) = kv.as_array_vec() {
6697 ks.extend(vv.iter().map(|x| x.to_string()));
6698 } else {
6699 ks.push(kv.to_string());
6700 }
6701 }
6702 let last_key = ks.last().ok_or_else(|| {
6703 PerlError::runtime("Hash slice increment needs at least one key", line)
6704 })?;
6705 let container = PerlValue::string(hash.to_string());
6706 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6707 Ok(if kind < 2 { new_val } else { last_old })
6708 }
6709
6710 fn match_array_pattern_elems(
6711 &mut self,
6712 arr: &[PerlValue],
6713 elems: &[MatchArrayElem],
6714 line: usize,
6715 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6716 let has_rest = elems
6717 .iter()
6718 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
6719 let mut binds: Vec<PatternBinding> = Vec::new();
6720 let mut idx = 0usize;
6721 for (i, elem) in elems.iter().enumerate() {
6722 match elem {
6723 MatchArrayElem::Rest => {
6724 if i != elems.len() - 1 {
6725 return Err(PerlError::runtime(
6726 "internal: `*` must be last in array match pattern",
6727 line,
6728 )
6729 .into());
6730 }
6731 return Ok(Some(binds));
6732 }
6733 MatchArrayElem::RestBind(name) => {
6734 if i != elems.len() - 1 {
6735 return Err(PerlError::runtime(
6736 "internal: `@name` rest bind must be last in array match pattern",
6737 line,
6738 )
6739 .into());
6740 }
6741 let tail = arr[idx..].to_vec();
6742 binds.push(PatternBinding::Array(name.clone(), tail));
6743 return Ok(Some(binds));
6744 }
6745 MatchArrayElem::CaptureScalar(name) => {
6746 if idx >= arr.len() {
6747 return Ok(None);
6748 }
6749 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
6750 idx += 1;
6751 }
6752 MatchArrayElem::Expr(e) => {
6753 if idx >= arr.len() {
6754 return Ok(None);
6755 }
6756 let expected = self.eval_expr(e)?;
6757 if !self.smartmatch_when(&arr[idx], &expected) {
6758 return Ok(None);
6759 }
6760 idx += 1;
6761 }
6762 }
6763 }
6764 if !has_rest && idx != arr.len() {
6765 return Ok(None);
6766 }
6767 Ok(Some(binds))
6768 }
6769
6770 fn match_hash_pattern_pairs(
6771 &mut self,
6772 h: &IndexMap<String, PerlValue>,
6773 pairs: &[MatchHashPair],
6774 _line: usize,
6775 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6776 let mut binds = Vec::new();
6777 for pair in pairs {
6778 match pair {
6779 MatchHashPair::KeyOnly { key } => {
6780 let ks = self.eval_expr(key)?.to_string();
6781 if !h.contains_key(&ks) {
6782 return Ok(None);
6783 }
6784 }
6785 MatchHashPair::Capture { key, name } => {
6786 let ks = self.eval_expr(key)?.to_string();
6787 let Some(v) = h.get(&ks) else {
6788 return Ok(None);
6789 };
6790 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
6791 }
6792 }
6793 }
6794 Ok(Some(binds))
6795 }
6796
6797 #[inline]
6799 fn block_needs_scope(block: &Block) -> bool {
6800 block.iter().any(|s| match &s.kind {
6801 StmtKind::My(_)
6802 | StmtKind::Our(_)
6803 | StmtKind::Local(_)
6804 | StmtKind::State(_)
6805 | StmtKind::LocalExpr { .. } => true,
6806 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
6807 _ => false,
6808 })
6809 }
6810
6811 #[inline]
6813 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
6814 if Self::block_needs_scope(block) {
6815 self.exec_block(block)
6816 } else {
6817 self.exec_block_no_scope(block)
6818 }
6819 }
6820
6821 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
6822 let t0 = self.profiler.is_some().then(std::time::Instant::now);
6823 let r = self.exec_statement_inner(stmt);
6824 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
6825 prof.on_line(&self.file, stmt.line, t0.elapsed());
6826 }
6827 r
6828 }
6829
6830 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
6831 if let Err(e) = crate::perl_signal::poll(self) {
6832 return Err(FlowOrError::Error(e));
6833 }
6834 if let Err(e) = self.drain_pending_destroys(stmt.line) {
6835 return Err(FlowOrError::Error(e));
6836 }
6837 match &stmt.kind {
6838 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
6839 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
6840 StmtKind::If {
6841 condition,
6842 body,
6843 elsifs,
6844 else_block,
6845 } => {
6846 if self.eval_boolean_rvalue_condition(condition)? {
6847 return self.exec_block(body);
6848 }
6849 for (c, b) in elsifs {
6850 if self.eval_boolean_rvalue_condition(c)? {
6851 return self.exec_block(b);
6852 }
6853 }
6854 if let Some(eb) = else_block {
6855 return self.exec_block(eb);
6856 }
6857 Ok(PerlValue::UNDEF)
6858 }
6859 StmtKind::Unless {
6860 condition,
6861 body,
6862 else_block,
6863 } => {
6864 if !self.eval_boolean_rvalue_condition(condition)? {
6865 return self.exec_block(body);
6866 }
6867 if let Some(eb) = else_block {
6868 return self.exec_block(eb);
6869 }
6870 Ok(PerlValue::UNDEF)
6871 }
6872 StmtKind::While {
6873 condition,
6874 body,
6875 label,
6876 continue_block,
6877 } => {
6878 'outer: loop {
6879 if !self.eval_boolean_rvalue_condition(condition)? {
6880 break;
6881 }
6882 'inner: loop {
6883 match self.exec_block_smart(body) {
6884 Ok(_) => break 'inner,
6885 Err(FlowOrError::Flow(Flow::Last(ref l)))
6886 if l == label || l.is_none() =>
6887 {
6888 break 'outer;
6889 }
6890 Err(FlowOrError::Flow(Flow::Next(ref l)))
6891 if l == label || l.is_none() =>
6892 {
6893 if let Some(cb) = continue_block {
6894 let _ = self.exec_block_smart(cb);
6895 }
6896 continue 'outer;
6897 }
6898 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6899 if l == label || l.is_none() =>
6900 {
6901 continue 'inner;
6902 }
6903 Err(e) => return Err(e),
6904 }
6905 }
6906 if let Some(cb) = continue_block {
6907 let _ = self.exec_block_smart(cb);
6908 }
6909 }
6910 Ok(PerlValue::UNDEF)
6911 }
6912 StmtKind::Until {
6913 condition,
6914 body,
6915 label,
6916 continue_block,
6917 } => {
6918 'outer: loop {
6919 if self.eval_boolean_rvalue_condition(condition)? {
6920 break;
6921 }
6922 'inner: loop {
6923 match self.exec_block(body) {
6924 Ok(_) => break 'inner,
6925 Err(FlowOrError::Flow(Flow::Last(ref l)))
6926 if l == label || l.is_none() =>
6927 {
6928 break 'outer;
6929 }
6930 Err(FlowOrError::Flow(Flow::Next(ref l)))
6931 if l == label || l.is_none() =>
6932 {
6933 if let Some(cb) = continue_block {
6934 let _ = self.exec_block_smart(cb);
6935 }
6936 continue 'outer;
6937 }
6938 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6939 if l == label || l.is_none() =>
6940 {
6941 continue 'inner;
6942 }
6943 Err(e) => return Err(e),
6944 }
6945 }
6946 if let Some(cb) = continue_block {
6947 let _ = self.exec_block_smart(cb);
6948 }
6949 }
6950 Ok(PerlValue::UNDEF)
6951 }
6952 StmtKind::DoWhile { body, condition } => {
6953 loop {
6954 self.exec_block(body)?;
6955 if !self.eval_boolean_rvalue_condition(condition)? {
6956 break;
6957 }
6958 }
6959 Ok(PerlValue::UNDEF)
6960 }
6961 StmtKind::For {
6962 init,
6963 condition,
6964 step,
6965 body,
6966 label,
6967 continue_block,
6968 } => {
6969 self.scope_push_hook();
6970 if let Some(init) = init {
6971 self.exec_statement(init)?;
6972 }
6973 'outer: loop {
6974 if let Some(cond) = condition {
6975 if !self.eval_boolean_rvalue_condition(cond)? {
6976 break;
6977 }
6978 }
6979 'inner: loop {
6980 match self.exec_block_smart(body) {
6981 Ok(_) => break 'inner,
6982 Err(FlowOrError::Flow(Flow::Last(ref l)))
6983 if l == label || l.is_none() =>
6984 {
6985 break 'outer;
6986 }
6987 Err(FlowOrError::Flow(Flow::Next(ref l)))
6988 if l == label || l.is_none() =>
6989 {
6990 if let Some(cb) = continue_block {
6991 let _ = self.exec_block_smart(cb);
6992 }
6993 if let Some(step) = step {
6994 self.eval_expr(step)?;
6995 }
6996 continue 'outer;
6997 }
6998 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6999 if l == label || l.is_none() =>
7000 {
7001 continue 'inner;
7002 }
7003 Err(e) => {
7004 self.scope_pop_hook();
7005 return Err(e);
7006 }
7007 }
7008 }
7009 if let Some(cb) = continue_block {
7010 let _ = self.exec_block_smart(cb);
7011 }
7012 if let Some(step) = step {
7013 self.eval_expr(step)?;
7014 }
7015 }
7016 self.scope_pop_hook();
7017 Ok(PerlValue::UNDEF)
7018 }
7019 StmtKind::Foreach {
7020 var,
7021 list,
7022 body,
7023 label,
7024 continue_block,
7025 } => {
7026 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
7027 let items = list_val.to_list();
7028 self.scope_push_hook();
7029 self.scope.declare_scalar(var, PerlValue::UNDEF);
7030 self.english_note_lexical_scalar(var);
7031 let mut i = 0usize;
7032 'outer: while i < items.len() {
7033 self.scope
7034 .set_scalar(var, items[i].clone())
7035 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
7036 'inner: loop {
7037 match self.exec_block_smart(body) {
7038 Ok(_) => break 'inner,
7039 Err(FlowOrError::Flow(Flow::Last(ref l)))
7040 if l == label || l.is_none() =>
7041 {
7042 break 'outer;
7043 }
7044 Err(FlowOrError::Flow(Flow::Next(ref l)))
7045 if l == label || l.is_none() =>
7046 {
7047 if let Some(cb) = continue_block {
7048 let _ = self.exec_block_smart(cb);
7049 }
7050 i += 1;
7051 continue 'outer;
7052 }
7053 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7054 if l == label || l.is_none() =>
7055 {
7056 continue 'inner;
7057 }
7058 Err(e) => {
7059 self.scope_pop_hook();
7060 return Err(e);
7061 }
7062 }
7063 }
7064 if let Some(cb) = continue_block {
7065 let _ = self.exec_block_smart(cb);
7066 }
7067 i += 1;
7068 }
7069 self.scope_pop_hook();
7070 Ok(PerlValue::UNDEF)
7071 }
7072 StmtKind::SubDecl {
7073 name,
7074 params,
7075 body,
7076 prototype,
7077 } => {
7078 let key = self.qualify_sub_key(name);
7079 let captured = self.scope.capture();
7080 let closure_env = if captured.is_empty() {
7081 None
7082 } else {
7083 Some(captured)
7084 };
7085 let mut sub = PerlSub {
7086 name: name.clone(),
7087 params: params.clone(),
7088 body: body.clone(),
7089 closure_env,
7090 prototype: prototype.clone(),
7091 fib_like: None,
7092 };
7093 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
7094 self.subs.insert(key, Arc::new(sub));
7095 Ok(PerlValue::UNDEF)
7096 }
7097 StmtKind::StructDecl { def } => {
7098 if self.struct_defs.contains_key(&def.name) {
7099 return Err(PerlError::runtime(
7100 format!("duplicate struct `{}`", def.name),
7101 stmt.line,
7102 )
7103 .into());
7104 }
7105 self.struct_defs
7106 .insert(def.name.clone(), Arc::new(def.clone()));
7107 Ok(PerlValue::UNDEF)
7108 }
7109 StmtKind::EnumDecl { def } => {
7110 if self.enum_defs.contains_key(&def.name) {
7111 return Err(PerlError::runtime(
7112 format!("duplicate enum `{}`", def.name),
7113 stmt.line,
7114 )
7115 .into());
7116 }
7117 self.enum_defs
7118 .insert(def.name.clone(), Arc::new(def.clone()));
7119 Ok(PerlValue::UNDEF)
7120 }
7121 StmtKind::ClassDecl { def } => {
7122 if self.class_defs.contains_key(&def.name) {
7123 return Err(PerlError::runtime(
7124 format!("duplicate class `{}`", def.name),
7125 stmt.line,
7126 )
7127 .into());
7128 }
7129 for parent_name in &def.extends {
7131 if let Some(parent_def) = self.class_defs.get(parent_name) {
7132 if parent_def.is_final {
7133 return Err(PerlError::runtime(
7134 format!("cannot extend final class `{}`", parent_name),
7135 stmt.line,
7136 )
7137 .into());
7138 }
7139 for m in &def.methods {
7141 if let Some(parent_method) = parent_def.method(&m.name) {
7142 if parent_method.is_final {
7143 return Err(PerlError::runtime(
7144 format!(
7145 "cannot override final method `{}` from class `{}`",
7146 m.name, parent_name
7147 ),
7148 stmt.line,
7149 )
7150 .into());
7151 }
7152 }
7153 }
7154 }
7155 }
7156 let mut def = def.clone();
7158 for trait_name in &def.implements.clone() {
7159 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
7160 for required in trait_def.required_methods() {
7161 let has_method = def.methods.iter().any(|m| m.name == required.name);
7162 if !has_method {
7163 return Err(PerlError::runtime(
7164 format!(
7165 "class `{}` implements trait `{}` but does not define required method `{}`",
7166 def.name, trait_name, required.name
7167 ),
7168 stmt.line,
7169 )
7170 .into());
7171 }
7172 }
7173 for tm in &trait_def.methods {
7175 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
7176 def.methods.push(tm.clone());
7177 }
7178 }
7179 }
7180 }
7181 if !def.is_abstract {
7184 for parent_name in &def.extends.clone() {
7185 if let Some(parent_def) = self.class_defs.get(parent_name) {
7186 if parent_def.is_abstract {
7187 for m in &parent_def.methods {
7188 if m.body.is_none()
7189 && !def.methods.iter().any(|dm| dm.name == m.name)
7190 {
7191 return Err(PerlError::runtime(
7192 format!(
7193 "class `{}` must implement abstract method `{}` from `{}`",
7194 def.name, m.name, parent_name
7195 ),
7196 stmt.line,
7197 )
7198 .into());
7199 }
7200 }
7201 }
7202 }
7203 }
7204 }
7205 for sf in &def.static_fields {
7207 let val = if let Some(ref expr) = sf.default {
7208 self.eval_expr(expr)?
7209 } else {
7210 PerlValue::UNDEF
7211 };
7212 let key = format!("{}::{}", def.name, sf.name);
7213 self.scope.declare_scalar(&key, val);
7214 }
7215 for m in &def.methods {
7217 if let Some(ref body) = m.body {
7218 let fq = format!("{}::{}", def.name, m.name);
7219 let sub = Arc::new(PerlSub {
7220 name: fq.clone(),
7221 params: m.params.clone(),
7222 body: body.clone(),
7223 closure_env: None,
7224 prototype: None,
7225 fib_like: None,
7226 });
7227 self.subs.insert(fq, sub);
7228 }
7229 }
7230 if !def.extends.is_empty() {
7232 let isa_key = format!("{}::ISA", def.name);
7233 let parents: Vec<PerlValue> = def
7234 .extends
7235 .iter()
7236 .map(|p| PerlValue::string(p.clone()))
7237 .collect();
7238 self.scope.declare_array(&isa_key, parents);
7239 }
7240 self.class_defs.insert(def.name.clone(), Arc::new(def));
7241 Ok(PerlValue::UNDEF)
7242 }
7243 StmtKind::TraitDecl { def } => {
7244 if self.trait_defs.contains_key(&def.name) {
7245 return Err(PerlError::runtime(
7246 format!("duplicate trait `{}`", def.name),
7247 stmt.line,
7248 )
7249 .into());
7250 }
7251 self.trait_defs
7252 .insert(def.name.clone(), Arc::new(def.clone()));
7253 Ok(PerlValue::UNDEF)
7254 }
7255 StmtKind::My(decls) | StmtKind::Our(decls) => {
7256 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
7257 if decls.len() > 1 && decls[0].initializer.is_some() {
7260 let val = self.eval_expr_ctx(
7261 decls[0].initializer.as_ref().unwrap(),
7262 WantarrayCtx::List,
7263 )?;
7264 let items = val.to_list();
7265 let mut idx = 0;
7266 for decl in decls {
7267 match decl.sigil {
7268 Sigil::Scalar => {
7269 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7270 let skey = if is_our {
7271 self.stash_scalar_name_for_package(&decl.name)
7272 } else {
7273 decl.name.clone()
7274 };
7275 self.scope.declare_scalar_frozen(
7276 &skey,
7277 v,
7278 decl.frozen,
7279 decl.type_annotation.clone(),
7280 )?;
7281 self.english_note_lexical_scalar(&decl.name);
7282 if is_our {
7283 self.note_our_scalar(&decl.name);
7284 }
7285 idx += 1;
7286 }
7287 Sigil::Array => {
7288 let rest: Vec<PerlValue> = items[idx..].to_vec();
7290 idx = items.len();
7291 if is_our {
7292 self.record_exporter_our_array_name(&decl.name, &rest);
7293 }
7294 let aname = self.stash_array_name_for_package(&decl.name);
7295 self.scope.declare_array(&aname, rest);
7296 }
7297 Sigil::Hash => {
7298 let rest: Vec<PerlValue> = items[idx..].to_vec();
7299 idx = items.len();
7300 let mut map = IndexMap::new();
7301 let mut i = 0;
7302 while i + 1 < rest.len() {
7303 map.insert(rest[i].to_string(), rest[i + 1].clone());
7304 i += 2;
7305 }
7306 self.scope.declare_hash(&decl.name, map);
7307 }
7308 Sigil::Typeglob => {
7309 return Err(PerlError::runtime(
7310 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
7311 stmt.line,
7312 )
7313 .into());
7314 }
7315 }
7316 }
7317 } else {
7318 for decl in decls {
7320 let compound_init = decl
7324 .initializer
7325 .as_ref()
7326 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
7327
7328 if compound_init {
7329 match decl.sigil {
7330 Sigil::Typeglob => {
7331 return Err(PerlError::runtime(
7332 "compound assignment on typeglob declaration is not supported",
7333 stmt.line,
7334 )
7335 .into());
7336 }
7337 Sigil::Scalar => {
7338 let skey = if is_our {
7339 self.stash_scalar_name_for_package(&decl.name)
7340 } else {
7341 decl.name.clone()
7342 };
7343 self.scope.declare_scalar_frozen(
7344 &skey,
7345 PerlValue::UNDEF,
7346 decl.frozen,
7347 decl.type_annotation.clone(),
7348 )?;
7349 self.english_note_lexical_scalar(&decl.name);
7350 if is_our {
7351 self.note_our_scalar(&decl.name);
7352 }
7353 let init = decl.initializer.as_ref().unwrap();
7354 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7355 }
7356 Sigil::Array => {
7357 let aname = self.stash_array_name_for_package(&decl.name);
7358 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
7359 let init = decl.initializer.as_ref().unwrap();
7360 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7361 if is_our {
7362 let items = self.scope.get_array(&aname);
7363 self.record_exporter_our_array_name(&decl.name, &items);
7364 }
7365 }
7366 Sigil::Hash => {
7367 self.scope.declare_hash_frozen(
7368 &decl.name,
7369 IndexMap::new(),
7370 decl.frozen,
7371 );
7372 let init = decl.initializer.as_ref().unwrap();
7373 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7374 }
7375 }
7376 continue;
7377 }
7378
7379 let val = if let Some(init) = &decl.initializer {
7380 let ctx = match decl.sigil {
7381 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7382 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7383 };
7384 self.eval_expr_ctx(init, ctx)?
7385 } else {
7386 PerlValue::UNDEF
7387 };
7388 match decl.sigil {
7389 Sigil::Typeglob => {
7390 return Err(PerlError::runtime(
7391 "`my *FH` / typeglob declaration is not supported",
7392 stmt.line,
7393 )
7394 .into());
7395 }
7396 Sigil::Scalar => {
7397 let skey = if is_our {
7398 self.stash_scalar_name_for_package(&decl.name)
7399 } else {
7400 decl.name.clone()
7401 };
7402 self.scope.declare_scalar_frozen(
7403 &skey,
7404 val,
7405 decl.frozen,
7406 decl.type_annotation.clone(),
7407 )?;
7408 self.english_note_lexical_scalar(&decl.name);
7409 if is_our {
7410 self.note_our_scalar(&decl.name);
7411 }
7412 }
7413 Sigil::Array => {
7414 let items = val.to_list();
7415 if is_our {
7416 self.record_exporter_our_array_name(&decl.name, &items);
7417 }
7418 let aname = self.stash_array_name_for_package(&decl.name);
7419 self.scope.declare_array_frozen(&aname, items, decl.frozen);
7420 }
7421 Sigil::Hash => {
7422 let items = val.to_list();
7423 let mut map = IndexMap::new();
7424 let mut i = 0;
7425 while i + 1 < items.len() {
7426 let k = items[i].to_string();
7427 let v = items[i + 1].clone();
7428 map.insert(k, v);
7429 i += 2;
7430 }
7431 self.scope.declare_hash_frozen(&decl.name, map, decl.frozen);
7432 }
7433 }
7434 }
7435 }
7436 Ok(PerlValue::UNDEF)
7437 }
7438 StmtKind::State(decls) => {
7439 for decl in decls {
7442 let state_key = format!("{}:{}", stmt.line, decl.name);
7443 match decl.sigil {
7444 Sigil::Scalar => {
7445 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
7446 self.scope.declare_scalar(&decl.name, prev);
7448 } else {
7449 let val = if let Some(init) = &decl.initializer {
7451 self.eval_expr(init)?
7452 } else {
7453 PerlValue::UNDEF
7454 };
7455 self.state_vars.insert(state_key.clone(), val.clone());
7456 self.scope.declare_scalar(&decl.name, val);
7457 }
7458 if let Some(frame) = self.state_bindings_stack.last_mut() {
7460 frame.push((decl.name.clone(), state_key));
7461 }
7462 }
7463 _ => {
7464 let val = if let Some(init) = &decl.initializer {
7466 self.eval_expr(init)?
7467 } else {
7468 PerlValue::UNDEF
7469 };
7470 match decl.sigil {
7471 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
7472 Sigil::Hash => {
7473 let items = val.to_list();
7474 let mut map = IndexMap::new();
7475 let mut i = 0;
7476 while i + 1 < items.len() {
7477 map.insert(items[i].to_string(), items[i + 1].clone());
7478 i += 2;
7479 }
7480 self.scope.declare_hash(&decl.name, map);
7481 }
7482 _ => {}
7483 }
7484 }
7485 }
7486 }
7487 Ok(PerlValue::UNDEF)
7488 }
7489 StmtKind::Local(decls) => {
7490 if decls.len() > 1 && decls[0].initializer.is_some() {
7491 let val = self.eval_expr_ctx(
7492 decls[0].initializer.as_ref().unwrap(),
7493 WantarrayCtx::List,
7494 )?;
7495 let items = val.to_list();
7496 let mut idx = 0;
7497 for decl in decls {
7498 match decl.sigil {
7499 Sigil::Scalar => {
7500 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7501 idx += 1;
7502 self.scope.local_set_scalar(&decl.name, v)?;
7503 }
7504 Sigil::Array => {
7505 let rest: Vec<PerlValue> = items[idx..].to_vec();
7506 idx = items.len();
7507 self.scope.local_set_array(&decl.name, rest)?;
7508 }
7509 Sigil::Hash => {
7510 let rest: Vec<PerlValue> = items[idx..].to_vec();
7511 idx = items.len();
7512 if decl.name == "ENV" {
7513 self.materialize_env_if_needed();
7514 }
7515 let mut map = IndexMap::new();
7516 let mut i = 0;
7517 while i + 1 < rest.len() {
7518 map.insert(rest[i].to_string(), rest[i + 1].clone());
7519 i += 2;
7520 }
7521 self.scope.local_set_hash(&decl.name, map)?;
7522 }
7523 Sigil::Typeglob => {
7524 return Err(PerlError::runtime(
7525 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
7526 stmt.line,
7527 )
7528 .into());
7529 }
7530 }
7531 }
7532 Ok(val)
7533 } else {
7534 let mut last_val = PerlValue::UNDEF;
7535 for decl in decls {
7536 let val = if let Some(init) = &decl.initializer {
7537 let ctx = match decl.sigil {
7538 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7539 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7540 };
7541 self.eval_expr_ctx(init, ctx)?
7542 } else {
7543 PerlValue::UNDEF
7544 };
7545 last_val = val.clone();
7546 match decl.sigil {
7547 Sigil::Typeglob => {
7548 let old = self.glob_handle_alias.remove(&decl.name);
7549 if let Some(frame) = self.glob_restore_frames.last_mut() {
7550 frame.push((decl.name.clone(), old));
7551 }
7552 if let Some(init) = &decl.initializer {
7553 if let ExprKind::Typeglob(rhs) = &init.kind {
7554 self.glob_handle_alias
7555 .insert(decl.name.clone(), rhs.clone());
7556 } else {
7557 return Err(PerlError::runtime(
7558 "local *GLOB = *OTHER — right side must be a typeglob",
7559 stmt.line,
7560 )
7561 .into());
7562 }
7563 }
7564 }
7565 Sigil::Scalar => {
7566 if Self::is_special_scalar_name_for_set(&decl.name) {
7572 let old = self.get_special_var(&decl.name);
7573 if let Some(frame) = self.special_var_restore_frames.last_mut()
7574 {
7575 frame.push((decl.name.clone(), old));
7576 }
7577 self.set_special_var(&decl.name, &val)
7578 .map_err(|e| e.at_line(stmt.line))?;
7579 }
7580 self.scope.local_set_scalar(&decl.name, val)?;
7581 }
7582 Sigil::Array => {
7583 self.scope.local_set_array(&decl.name, val.to_list())?;
7584 }
7585 Sigil::Hash => {
7586 if decl.name == "ENV" {
7587 self.materialize_env_if_needed();
7588 }
7589 let items = val.to_list();
7590 let mut map = IndexMap::new();
7591 let mut i = 0;
7592 while i + 1 < items.len() {
7593 let k = items[i].to_string();
7594 let v = items[i + 1].clone();
7595 map.insert(k, v);
7596 i += 2;
7597 }
7598 self.scope.local_set_hash(&decl.name, map)?;
7599 }
7600 }
7601 }
7602 Ok(last_val)
7603 }
7604 }
7605 StmtKind::LocalExpr {
7606 target,
7607 initializer,
7608 } => {
7609 let rhs_name = |init: &Expr| -> PerlResult<Option<String>> {
7610 match &init.kind {
7611 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
7612 _ => Err(PerlError::runtime(
7613 "local *GLOB = *OTHER — right side must be a typeglob",
7614 stmt.line,
7615 )),
7616 }
7617 };
7618 match &target.kind {
7619 ExprKind::Typeglob(name) => {
7620 let rhs = if let Some(init) = initializer {
7621 rhs_name(init)?
7622 } else {
7623 None
7624 };
7625 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
7626 return Ok(PerlValue::UNDEF);
7627 }
7628 ExprKind::Deref {
7629 expr,
7630 kind: Sigil::Typeglob,
7631 } => {
7632 let lhs = self.eval_expr(expr)?.to_string();
7633 let rhs = if let Some(init) = initializer {
7634 rhs_name(init)?
7635 } else {
7636 None
7637 };
7638 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7639 return Ok(PerlValue::UNDEF);
7640 }
7641 ExprKind::TypeglobExpr(e) => {
7642 let lhs = self.eval_expr(e)?.to_string();
7643 let rhs = if let Some(init) = initializer {
7644 rhs_name(init)?
7645 } else {
7646 None
7647 };
7648 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7649 return Ok(PerlValue::UNDEF);
7650 }
7651 _ => {}
7652 }
7653 let val = if let Some(init) = initializer {
7654 let ctx = match &target.kind {
7655 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
7656 _ => WantarrayCtx::Scalar,
7657 };
7658 self.eval_expr_ctx(init, ctx)?
7659 } else {
7660 PerlValue::UNDEF
7661 };
7662 match &target.kind {
7663 ExprKind::ScalarVar(name) => {
7664 if Self::is_special_scalar_name_for_set(name) {
7667 let old = self.get_special_var(name);
7668 if let Some(frame) = self.special_var_restore_frames.last_mut() {
7669 frame.push((name.clone(), old));
7670 }
7671 self.set_special_var(name, &val)
7672 .map_err(|e| e.at_line(stmt.line))?;
7673 }
7674 self.scope.local_set_scalar(name, val.clone())?;
7675 }
7676 ExprKind::ArrayVar(name) => {
7677 self.scope.local_set_array(name, val.to_list())?;
7678 }
7679 ExprKind::HashVar(name) => {
7680 if name == "ENV" {
7681 self.materialize_env_if_needed();
7682 }
7683 let items = val.to_list();
7684 let mut map = IndexMap::new();
7685 let mut i = 0;
7686 while i + 1 < items.len() {
7687 map.insert(items[i].to_string(), items[i + 1].clone());
7688 i += 2;
7689 }
7690 self.scope.local_set_hash(name, map)?;
7691 }
7692 ExprKind::HashElement { hash, key } => {
7693 let ks = self.eval_expr(key)?.to_string();
7694 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
7695 }
7696 ExprKind::ArrayElement { array, index } => {
7697 self.check_strict_array_var(array, stmt.line)?;
7698 let aname = self.stash_array_name_for_package(array);
7699 let idx = self.eval_expr(index)?.to_int();
7700 self.scope
7701 .local_set_array_element(&aname, idx, val.clone())?;
7702 }
7703 _ => {
7704 return Err(PerlError::runtime(
7705 format!(
7706 "local on this lvalue is not supported yet ({:?})",
7707 target.kind
7708 ),
7709 stmt.line,
7710 )
7711 .into());
7712 }
7713 }
7714 Ok(val)
7715 }
7716 StmtKind::MySync(decls) => {
7717 for decl in decls {
7718 let val = if let Some(init) = &decl.initializer {
7719 self.eval_expr(init)?
7720 } else {
7721 PerlValue::UNDEF
7722 };
7723 match decl.sigil {
7724 Sigil::Typeglob => {
7725 return Err(PerlError::runtime(
7726 "`mysync` does not support typeglob variables",
7727 stmt.line,
7728 )
7729 .into());
7730 }
7731 Sigil::Scalar => {
7732 let stored = if val.is_mysync_deque_or_heap() {
7735 val
7736 } else {
7737 PerlValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(val)))
7738 };
7739 self.scope.declare_scalar(&decl.name, stored);
7740 }
7741 Sigil::Array => {
7742 self.scope.declare_atomic_array(&decl.name, val.to_list());
7743 }
7744 Sigil::Hash => {
7745 let items = val.to_list();
7746 let mut map = IndexMap::new();
7747 let mut i = 0;
7748 while i + 1 < items.len() {
7749 map.insert(items[i].to_string(), items[i + 1].clone());
7750 i += 2;
7751 }
7752 self.scope.declare_atomic_hash(&decl.name, map);
7753 }
7754 }
7755 }
7756 Ok(PerlValue::UNDEF)
7757 }
7758 StmtKind::Package { name } => {
7759 let _ = self
7761 .scope
7762 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
7763 Ok(PerlValue::UNDEF)
7764 }
7765 StmtKind::UsePerlVersion { .. } => Ok(PerlValue::UNDEF),
7766 StmtKind::Use { .. } => {
7767 Ok(PerlValue::UNDEF)
7769 }
7770 StmtKind::UseOverload { pairs } => {
7771 self.install_use_overload_pairs(pairs);
7772 Ok(PerlValue::UNDEF)
7773 }
7774 StmtKind::No { .. } => {
7775 Ok(PerlValue::UNDEF)
7777 }
7778 StmtKind::Return(val) => {
7779 let v = if let Some(e) = val {
7780 self.eval_expr_ctx(e, self.wantarray_kind)?
7784 } else {
7785 PerlValue::UNDEF
7786 };
7787 Err(Flow::Return(v).into())
7788 }
7789 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
7790 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
7791 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
7792 StmtKind::Block(block) => self.exec_block(block),
7793 StmtKind::Begin(_)
7794 | StmtKind::UnitCheck(_)
7795 | StmtKind::Check(_)
7796 | StmtKind::Init(_)
7797 | StmtKind::End(_) => Ok(PerlValue::UNDEF),
7798 StmtKind::Empty => Ok(PerlValue::UNDEF),
7799 StmtKind::Goto { target } => {
7800 if let ExprKind::SubroutineRef(name) = &target.kind {
7802 return Err(Flow::GotoSub(name.clone()).into());
7803 }
7804 Err(PerlError::runtime("goto reached outside goto-aware block", stmt.line).into())
7805 }
7806 StmtKind::EvalTimeout { timeout, body } => {
7807 let secs = self.eval_expr(timeout)?.to_number();
7808 self.eval_timeout_block(body, secs, stmt.line)
7809 }
7810 StmtKind::Tie {
7811 target,
7812 class,
7813 args,
7814 } => {
7815 let kind = match &target {
7816 TieTarget::Scalar(_) => 0u8,
7817 TieTarget::Array(_) => 1u8,
7818 TieTarget::Hash(_) => 2u8,
7819 };
7820 let name = match &target {
7821 TieTarget::Scalar(s) => s.as_str(),
7822 TieTarget::Array(a) => a.as_str(),
7823 TieTarget::Hash(h) => h.as_str(),
7824 };
7825 let mut vals = vec![self.eval_expr(class)?];
7826 for a in args {
7827 vals.push(self.eval_expr(a)?);
7828 }
7829 self.tie_execute(kind, name, vals, stmt.line)
7830 .map_err(Into::into)
7831 }
7832 StmtKind::TryCatch {
7833 try_block,
7834 catch_var,
7835 catch_block,
7836 finally_block,
7837 } => match self.exec_block(try_block) {
7838 Ok(v) => {
7839 if let Some(fb) = finally_block {
7840 self.exec_block(fb)?;
7841 }
7842 Ok(v)
7843 }
7844 Err(FlowOrError::Error(e)) => {
7845 if matches!(e.kind, ErrorKind::Exit(_)) {
7846 return Err(FlowOrError::Error(e));
7847 }
7848 self.scope_push_hook();
7849 self.scope
7850 .declare_scalar(catch_var, PerlValue::string(e.to_string()));
7851 self.english_note_lexical_scalar(catch_var);
7852 let r = self.exec_block(catch_block);
7853 self.scope_pop_hook();
7854 if let Some(fb) = finally_block {
7855 self.exec_block(fb)?;
7856 }
7857 r
7858 }
7859 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
7860 },
7861 StmtKind::Given { topic, body } => self.exec_given(topic, body),
7862 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(PerlError::runtime(
7863 "when/default may only appear inside a given block",
7864 stmt.line,
7865 )
7866 .into()),
7867 StmtKind::FormatDecl { .. } => {
7868 Ok(PerlValue::UNDEF)
7870 }
7871 StmtKind::Continue(block) => self.exec_block_smart(block),
7872 }
7873 }
7874
7875 #[inline]
7876 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
7877 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
7878 }
7879
7880 pub(crate) fn scalar_compound_assign_scalar_target(
7884 &mut self,
7885 name: &str,
7886 op: BinOp,
7887 rhs: PerlValue,
7888 ) -> Result<PerlValue, PerlError> {
7889 if op == BinOp::Concat {
7890 return self.scope.scalar_concat_inplace(name, &rhs);
7891 }
7892 Ok(self
7893 .scope
7894 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs)))
7895 }
7896
7897 fn compound_scalar_binop(old: &PerlValue, op: BinOp, rhs: &PerlValue) -> PerlValue {
7898 match op {
7899 BinOp::Add => {
7900 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7901 PerlValue::integer(a.wrapping_add(b))
7902 } else {
7903 PerlValue::float(old.to_number() + rhs.to_number())
7904 }
7905 }
7906 BinOp::Sub => {
7907 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7908 PerlValue::integer(a.wrapping_sub(b))
7909 } else {
7910 PerlValue::float(old.to_number() - rhs.to_number())
7911 }
7912 }
7913 BinOp::Mul => {
7914 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7915 PerlValue::integer(a.wrapping_mul(b))
7916 } else {
7917 PerlValue::float(old.to_number() * rhs.to_number())
7918 }
7919 }
7920 BinOp::BitAnd => {
7921 if let Some(s) = crate::value::set_intersection(old, rhs) {
7922 s
7923 } else {
7924 PerlValue::integer(old.to_int() & rhs.to_int())
7925 }
7926 }
7927 BinOp::BitOr => {
7928 if let Some(s) = crate::value::set_union(old, rhs) {
7929 s
7930 } else {
7931 PerlValue::integer(old.to_int() | rhs.to_int())
7932 }
7933 }
7934 BinOp::BitXor => PerlValue::integer(old.to_int() ^ rhs.to_int()),
7935 BinOp::ShiftLeft => PerlValue::integer(old.to_int() << rhs.to_int()),
7936 BinOp::ShiftRight => PerlValue::integer(old.to_int() >> rhs.to_int()),
7937 BinOp::Div => PerlValue::float(old.to_number() / rhs.to_number()),
7938 BinOp::Mod => PerlValue::float(old.to_number() % rhs.to_number()),
7939 BinOp::Pow => PerlValue::float(old.to_number().powf(rhs.to_number())),
7940 BinOp::LogOr => {
7941 if old.is_true() {
7942 old.clone()
7943 } else {
7944 rhs.clone()
7945 }
7946 }
7947 BinOp::DefinedOr => {
7948 if !old.is_undef() {
7949 old.clone()
7950 } else {
7951 rhs.clone()
7952 }
7953 }
7954 BinOp::LogAnd => {
7955 if old.is_true() {
7956 rhs.clone()
7957 } else {
7958 old.clone()
7959 }
7960 }
7961 _ => PerlValue::float(old.to_number() + rhs.to_number()),
7962 }
7963 }
7964
7965 fn eval_hash_slice_key_components(
7969 &mut self,
7970 key_expr: &Expr,
7971 ) -> Result<Vec<String>, FlowOrError> {
7972 let v = if matches!(
7973 key_expr.kind,
7974 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
7975 ) {
7976 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
7977 } else {
7978 self.eval_expr(key_expr)?
7979 };
7980 if let Some(vv) = v.as_array_vec() {
7981 Ok(vv.iter().map(|x| x.to_string()).collect())
7982 } else {
7983 Ok(vec![v.to_string()])
7984 }
7985 }
7986
7987 pub(crate) fn symbolic_deref(
7989 &mut self,
7990 val: PerlValue,
7991 kind: Sigil,
7992 line: usize,
7993 ) -> ExecResult {
7994 match kind {
7995 Sigil::Scalar => {
7996 if let Some(name) = val.as_scalar_binding_name() {
7997 return Ok(self.get_special_var(&name));
7998 }
7999 if let Some(r) = val.as_scalar_ref() {
8000 return Ok(r.read().clone());
8001 }
8002 if let Some(r) = val.as_array_ref() {
8004 return Ok(PerlValue::array(r.read().clone()));
8005 }
8006 if let Some(name) = val.as_array_binding_name() {
8007 return Ok(PerlValue::array(self.scope.get_array(&name)));
8008 }
8009 if let Some(r) = val.as_hash_ref() {
8010 return Ok(PerlValue::hash(r.read().clone()));
8011 }
8012 if let Some(name) = val.as_hash_binding_name() {
8013 self.touch_env_hash(&name);
8014 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
8015 }
8016 if let Some(s) = val.as_str() {
8017 if self.strict_refs {
8018 return Err(PerlError::runtime(
8019 format!(
8020 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
8021 s
8022 ),
8023 line,
8024 )
8025 .into());
8026 }
8027 return Ok(self.get_special_var(&s));
8028 }
8029 Err(PerlError::runtime("Can't dereference non-reference as scalar", line).into())
8030 }
8031 Sigil::Array => {
8032 if let Some(r) = val.as_array_ref() {
8033 return Ok(PerlValue::array(r.read().clone()));
8034 }
8035 if let Some(name) = val.as_array_binding_name() {
8036 return Ok(PerlValue::array(self.scope.get_array(&name)));
8037 }
8038 if let Some(s) = val.as_str() {
8039 if self.strict_refs {
8040 return Err(PerlError::runtime(
8041 format!(
8042 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8043 s
8044 ),
8045 line,
8046 )
8047 .into());
8048 }
8049 return Ok(PerlValue::array(self.scope.get_array(&s)));
8050 }
8051 Err(PerlError::runtime("Can't dereference non-reference as array", line).into())
8052 }
8053 Sigil::Hash => {
8054 if let Some(r) = val.as_hash_ref() {
8055 return Ok(PerlValue::hash(r.read().clone()));
8056 }
8057 if let Some(name) = val.as_hash_binding_name() {
8058 self.touch_env_hash(&name);
8059 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
8060 }
8061 if let Some(s) = val.as_str() {
8062 if self.strict_refs {
8063 return Err(PerlError::runtime(
8064 format!(
8065 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8066 s
8067 ),
8068 line,
8069 )
8070 .into());
8071 }
8072 self.touch_env_hash(&s);
8073 return Ok(PerlValue::hash(self.scope.get_hash(&s)));
8074 }
8075 Err(PerlError::runtime("Can't dereference non-reference as hash", line).into())
8076 }
8077 Sigil::Typeglob => {
8078 if let Some(s) = val.as_str() {
8079 return Ok(PerlValue::string(self.resolve_io_handle_name(&s)));
8080 }
8081 Err(PerlError::runtime("Can't dereference non-reference as typeglob", line).into())
8082 }
8083 }
8084 }
8085
8086 #[inline]
8089 pub(crate) fn peel_array_ref_for_list_join(&self, v: PerlValue) -> PerlValue {
8090 if let Some(r) = v.as_array_ref() {
8091 return PerlValue::array(r.read().clone());
8092 }
8093 v
8094 }
8095
8096 pub(crate) fn make_array_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
8098 if let Some(a) = val.as_array_ref() {
8099 return Ok(PerlValue::array_ref(Arc::clone(&a)));
8100 }
8101 if let Some(name) = val.as_array_binding_name() {
8102 return Ok(PerlValue::array_binding_ref(name));
8103 }
8104 if let Some(s) = val.as_str() {
8105 if self.strict_refs {
8106 return Err(PerlError::runtime(
8107 format!(
8108 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8109 s
8110 ),
8111 line,
8112 )
8113 .into());
8114 }
8115 return Ok(PerlValue::array_binding_ref(s.to_string()));
8116 }
8117 if let Some(r) = val.as_scalar_ref() {
8118 let inner = r.read().clone();
8119 return self.make_array_ref_alias(inner, line);
8120 }
8121 Err(PerlError::runtime("Can't make array reference from value", line).into())
8122 }
8123
8124 pub(crate) fn make_hash_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
8126 if let Some(h) = val.as_hash_ref() {
8127 return Ok(PerlValue::hash_ref(Arc::clone(&h)));
8128 }
8129 if let Some(name) = val.as_hash_binding_name() {
8130 return Ok(PerlValue::hash_binding_ref(name));
8131 }
8132 if let Some(s) = val.as_str() {
8133 if self.strict_refs {
8134 return Err(PerlError::runtime(
8135 format!(
8136 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8137 s
8138 ),
8139 line,
8140 )
8141 .into());
8142 }
8143 return Ok(PerlValue::hash_binding_ref(s.to_string()));
8144 }
8145 if let Some(r) = val.as_scalar_ref() {
8146 let inner = r.read().clone();
8147 return self.make_hash_ref_alias(inner, line);
8148 }
8149 Err(PerlError::runtime("Can't make hash reference from value", line).into())
8150 }
8151
8152 pub(crate) fn process_case_escapes(s: &str) -> String {
8155 if !s.contains('\\') {
8157 return s.to_string();
8158 }
8159 let mut result = String::with_capacity(s.len());
8160 let mut chars = s.chars().peekable();
8161 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
8165 if c == '\\' {
8166 match chars.peek() {
8167 Some(&'U') => {
8168 chars.next();
8169 mode = Some('U');
8170 continue;
8171 }
8172 Some(&'L') => {
8173 chars.next();
8174 mode = Some('L');
8175 continue;
8176 }
8177 Some(&'Q') => {
8178 chars.next();
8179 mode = Some('Q');
8180 continue;
8181 }
8182 Some(&'E') => {
8183 chars.next();
8184 mode = None;
8185 next_char_mod = None;
8186 continue;
8187 }
8188 Some(&'u') => {
8189 chars.next();
8190 next_char_mod = Some('u');
8191 continue;
8192 }
8193 Some(&'l') => {
8194 chars.next();
8195 next_char_mod = Some('l');
8196 continue;
8197 }
8198 _ => {}
8199 }
8200 }
8201
8202 let ch = c;
8203
8204 if let Some(m) = next_char_mod.take() {
8206 let transformed = match m {
8207 'u' => ch.to_uppercase().next().unwrap_or(ch),
8208 'l' => ch.to_lowercase().next().unwrap_or(ch),
8209 _ => ch,
8210 };
8211 result.push(transformed);
8212 } else {
8213 match mode {
8215 Some('U') => {
8216 for uc in ch.to_uppercase() {
8217 result.push(uc);
8218 }
8219 }
8220 Some('L') => {
8221 for lc in ch.to_lowercase() {
8222 result.push(lc);
8223 }
8224 }
8225 Some('Q') => {
8226 if !ch.is_ascii_alphanumeric() && ch != '_' {
8227 result.push('\\');
8228 }
8229 result.push(ch);
8230 }
8231 None | Some(_) => {
8232 result.push(ch);
8233 }
8234 }
8235 }
8236 }
8237 result
8238 }
8239
8240 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
8241 let line = expr.line;
8242 match &expr.kind {
8243 ExprKind::Integer(n) => Ok(PerlValue::integer(*n)),
8244 ExprKind::Float(f) => Ok(PerlValue::float(*f)),
8245 ExprKind::String(s) => {
8246 let processed = Self::process_case_escapes(s);
8247 Ok(PerlValue::string(processed))
8248 }
8249 ExprKind::Bareword(s) => {
8250 if s == "__PACKAGE__" {
8251 return Ok(PerlValue::string(self.current_package()));
8252 }
8253 if let Some(sub) = self.resolve_sub_by_name(s) {
8254 return self.call_sub(&sub, vec![], ctx, line);
8255 }
8256 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
8258 return r.map_err(Into::into);
8259 }
8260 Ok(PerlValue::string(s.clone()))
8261 }
8262 ExprKind::Undef => Ok(PerlValue::UNDEF),
8263 ExprKind::MagicConst(MagicConstKind::File) => Ok(PerlValue::string(self.file.clone())),
8264 ExprKind::MagicConst(MagicConstKind::Line) => Ok(PerlValue::integer(expr.line as i64)),
8265 ExprKind::MagicConst(MagicConstKind::Sub) => {
8266 if let Some(sub) = self.current_sub_stack.last().cloned() {
8267 Ok(PerlValue::code_ref(sub))
8268 } else {
8269 Ok(PerlValue::UNDEF)
8270 }
8271 }
8272 ExprKind::Regex(pattern, flags) => {
8273 if ctx == WantarrayCtx::Void {
8274 let topic = self.scope.get_scalar("_");
8276 let s = topic.to_string();
8277 self.regex_match_execute(s, pattern, flags, false, "_", line)
8278 } else {
8279 let re = self.compile_regex(pattern, flags, line)?;
8280 Ok(PerlValue::regex(re, pattern.clone(), flags.clone()))
8281 }
8282 }
8283 ExprKind::QW(words) => Ok(PerlValue::array(
8284 words.iter().map(|w| PerlValue::string(w.clone())).collect(),
8285 )),
8286
8287 ExprKind::InterpolatedString(parts) => {
8289 let mut raw_result = String::new();
8290 for part in parts {
8291 match part {
8292 StringPart::Literal(s) => raw_result.push_str(s),
8293 StringPart::ScalarVar(name) => {
8294 self.check_strict_scalar_var(name, line)?;
8295 let val = self.get_special_var(name);
8296 let s = self.stringify_value(val, line)?;
8297 raw_result.push_str(&s);
8298 }
8299 StringPart::ArrayVar(name) => {
8300 self.check_strict_array_var(name, line)?;
8301 let aname = self.stash_array_name_for_package(name);
8302 let arr = self.scope.get_array(&aname);
8303 let mut parts = Vec::with_capacity(arr.len());
8304 for v in &arr {
8305 parts.push(self.stringify_value(v.clone(), line)?);
8306 }
8307 let sep = self.list_separator.clone();
8308 raw_result.push_str(&parts.join(&sep));
8309 }
8310 StringPart::Expr(e) => {
8311 if let ExprKind::ArraySlice { array, .. } = &e.kind {
8312 self.check_strict_array_var(array, line)?;
8313 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8314 let val = self.peel_array_ref_for_list_join(val);
8315 let list = val.to_list();
8316 let sep = self.list_separator.clone();
8317 let mut parts = Vec::with_capacity(list.len());
8318 for v in list {
8319 parts.push(self.stringify_value(v, line)?);
8320 }
8321 raw_result.push_str(&parts.join(&sep));
8322 } else if let ExprKind::Deref {
8323 kind: Sigil::Array, ..
8324 } = &e.kind
8325 {
8326 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8327 let val = self.peel_array_ref_for_list_join(val);
8328 let list = val.to_list();
8329 let sep = self.list_separator.clone();
8330 let mut parts = Vec::with_capacity(list.len());
8331 for v in list {
8332 parts.push(self.stringify_value(v, line)?);
8333 }
8334 raw_result.push_str(&parts.join(&sep));
8335 } else {
8336 let val = self.eval_expr(e)?;
8337 let s = self.stringify_value(val, line)?;
8338 raw_result.push_str(&s);
8339 }
8340 }
8341 }
8342 }
8343 let result = Self::process_case_escapes(&raw_result);
8344 Ok(PerlValue::string(result))
8345 }
8346
8347 ExprKind::ScalarVar(name) => {
8349 self.check_strict_scalar_var(name, line)?;
8350 let stor = self.tree_scalar_storage_name(name);
8351 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
8352 let class = obj
8353 .as_blessed_ref()
8354 .map(|b| b.class.clone())
8355 .unwrap_or_default();
8356 let full = format!("{}::FETCH", class);
8357 if let Some(sub) = self.subs.get(&full).cloned() {
8358 return self.call_sub(&sub, vec![obj], ctx, line);
8359 }
8360 }
8361 Ok(self.get_special_var(&stor))
8362 }
8363 ExprKind::ArrayVar(name) => {
8364 self.check_strict_array_var(name, line)?;
8365 let aname = self.stash_array_name_for_package(name);
8366 let arr = self.scope.get_array(&aname);
8367 if ctx == WantarrayCtx::List {
8368 Ok(PerlValue::array(arr))
8369 } else {
8370 Ok(PerlValue::integer(arr.len() as i64))
8371 }
8372 }
8373 ExprKind::HashVar(name) => {
8374 self.check_strict_hash_var(name, line)?;
8375 self.touch_env_hash(name);
8376 let h = self.scope.get_hash(name);
8377 let pv = PerlValue::hash(h);
8378 if ctx == WantarrayCtx::List {
8379 Ok(pv)
8380 } else {
8381 Ok(pv.scalar_context())
8382 }
8383 }
8384 ExprKind::Typeglob(name) => {
8385 let n = self.resolve_io_handle_name(name);
8386 Ok(PerlValue::string(n))
8387 }
8388 ExprKind::TypeglobExpr(e) => {
8389 let name = self.eval_expr(e)?.to_string();
8390 let n = self.resolve_io_handle_name(&name);
8391 Ok(PerlValue::string(n))
8392 }
8393 ExprKind::ArrayElement { array, index } => {
8394 self.check_strict_array_var(array, line)?;
8395 let idx = self.eval_expr(index)?.to_int();
8396 let aname = self.stash_array_name_for_package(array);
8397 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
8398 let class = obj
8399 .as_blessed_ref()
8400 .map(|b| b.class.clone())
8401 .unwrap_or_default();
8402 let full = format!("{}::FETCH", class);
8403 if let Some(sub) = self.subs.get(&full).cloned() {
8404 let arg_vals = vec![obj, PerlValue::integer(idx)];
8405 return self.call_sub(&sub, arg_vals, ctx, line);
8406 }
8407 }
8408 Ok(self.scope.get_array_element(&aname, idx))
8409 }
8410 ExprKind::HashElement { hash, key } => {
8411 self.check_strict_hash_var(hash, line)?;
8412 let k = self.eval_expr(key)?.to_string();
8413 self.touch_env_hash(hash);
8414 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
8415 let class = obj
8416 .as_blessed_ref()
8417 .map(|b| b.class.clone())
8418 .unwrap_or_default();
8419 let full = format!("{}::FETCH", class);
8420 if let Some(sub) = self.subs.get(&full).cloned() {
8421 let arg_vals = vec![obj, PerlValue::string(k)];
8422 return self.call_sub(&sub, arg_vals, ctx, line);
8423 }
8424 }
8425 Ok(self.scope.get_hash_element(hash, &k))
8426 }
8427 ExprKind::ArraySlice { array, indices } => {
8428 self.check_strict_array_var(array, line)?;
8429 let aname = self.stash_array_name_for_package(array);
8430 let flat = self.flatten_array_slice_index_specs(indices)?;
8431 let mut result = Vec::with_capacity(flat.len());
8432 for idx in flat {
8433 result.push(self.scope.get_array_element(&aname, idx));
8434 }
8435 Ok(PerlValue::array(result))
8436 }
8437 ExprKind::HashSlice { hash, keys } => {
8438 self.check_strict_hash_var(hash, line)?;
8439 self.touch_env_hash(hash);
8440 let mut result = Vec::new();
8441 for key_expr in keys {
8442 for k in self.eval_hash_slice_key_components(key_expr)? {
8443 result.push(self.scope.get_hash_element(hash, &k));
8444 }
8445 }
8446 Ok(PerlValue::array(result))
8447 }
8448 ExprKind::HashSliceDeref { container, keys } => {
8449 let hv = self.eval_expr(container)?;
8450 let mut key_vals = Vec::with_capacity(keys.len());
8451 for key_expr in keys {
8452 let v = if matches!(
8453 key_expr.kind,
8454 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
8455 ) {
8456 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8457 } else {
8458 self.eval_expr(key_expr)?
8459 };
8460 key_vals.push(v);
8461 }
8462 self.hash_slice_deref_values(&hv, &key_vals, line)
8463 }
8464 ExprKind::AnonymousListSlice { source, indices } => {
8465 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
8466 let items = list_val.to_list();
8467 let flat = self.flatten_array_slice_index_specs(indices)?;
8468 let mut out = Vec::with_capacity(flat.len());
8469 for idx in flat {
8470 let i = if idx < 0 {
8471 (items.len() as i64 + idx) as usize
8472 } else {
8473 idx as usize
8474 };
8475 out.push(items.get(i).cloned().unwrap_or(PerlValue::UNDEF));
8476 }
8477 let arr = PerlValue::array(out);
8478 if ctx != WantarrayCtx::List {
8479 let v = arr.to_list();
8480 Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF))
8481 } else {
8482 Ok(arr)
8483 }
8484 }
8485
8486 ExprKind::ScalarRef(inner) => match &inner.kind {
8488 ExprKind::ScalarVar(name) => Ok(PerlValue::scalar_binding_ref(name.clone())),
8489 ExprKind::ArrayVar(name) => {
8490 self.check_strict_array_var(name, line)?;
8491 let aname = self.stash_array_name_for_package(name);
8492 let arc = self.scope.promote_array_to_shared(&aname);
8495 Ok(PerlValue::array_ref(arc))
8496 }
8497 ExprKind::HashVar(name) => {
8498 self.check_strict_hash_var(name, line)?;
8499 let arc = self.scope.promote_hash_to_shared(name);
8500 Ok(PerlValue::hash_ref(arc))
8501 }
8502 ExprKind::Deref {
8503 expr: e,
8504 kind: Sigil::Array,
8505 } => {
8506 let v = self.eval_expr(e)?;
8507 self.make_array_ref_alias(v, line)
8508 }
8509 ExprKind::Deref {
8510 expr: e,
8511 kind: Sigil::Hash,
8512 } => {
8513 let v = self.eval_expr(e)?;
8514 self.make_hash_ref_alias(v, line)
8515 }
8516 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
8517 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8518 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8519 }
8520 ExprKind::HashSliceDeref { .. } => {
8521 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8522 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8523 }
8524 _ => {
8525 let val = self.eval_expr(inner)?;
8526 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8527 }
8528 },
8529 ExprKind::ArrayRef(elems) => {
8530 let mut arr = Vec::with_capacity(elems.len());
8534 for e in elems {
8535 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8536 let v = self.scope.resolve_container_binding_ref(v);
8537 if let Some(vec) = v.as_array_vec() {
8538 arr.extend(vec);
8539 } else {
8540 arr.push(v);
8541 }
8542 }
8543 Ok(PerlValue::array_ref(Arc::new(RwLock::new(arr))))
8544 }
8545 ExprKind::HashRef(pairs) => {
8546 let mut map = IndexMap::new();
8549 for (k, v) in pairs {
8550 let key_str = self.eval_expr(k)?.to_string();
8551 if key_str == "__HASH_SPREAD__" {
8552 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8554 let items = spread.to_list();
8555 let mut i = 0;
8556 while i + 1 < items.len() {
8557 map.insert(items[i].to_string(), items[i + 1].clone());
8558 i += 2;
8559 }
8560 } else {
8561 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8562 map.insert(key_str, val);
8563 }
8564 }
8565 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map))))
8566 }
8567 ExprKind::CodeRef { params, body } => {
8568 let captured = self.scope.capture();
8569 Ok(PerlValue::code_ref(Arc::new(PerlSub {
8570 name: "__ANON__".to_string(),
8571 params: params.clone(),
8572 body: body.clone(),
8573 closure_env: Some(captured),
8574 prototype: None,
8575 fib_like: None,
8576 })))
8577 }
8578 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
8579 ExprKind::SubroutineCodeRef(name) => {
8580 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
8581 PerlError::runtime(self.undefined_subroutine_resolve_message(name), line)
8582 })?;
8583 Ok(PerlValue::code_ref(sub))
8584 }
8585 ExprKind::DynamicSubCodeRef(expr) => {
8586 let name = self.eval_expr(expr)?.to_string();
8587 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
8588 PerlError::runtime(self.undefined_subroutine_resolve_message(&name), line)
8589 })?;
8590 Ok(PerlValue::code_ref(sub))
8591 }
8592 ExprKind::Deref { expr, kind } => {
8593 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
8594 let val = self.eval_expr(expr)?;
8595 let n = self.array_deref_len(val, line)?;
8596 return Ok(PerlValue::integer(n));
8597 }
8598 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
8599 let val = self.eval_expr(expr)?;
8600 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
8601 return Ok(h.scalar_context());
8602 }
8603 let val = self.eval_expr(expr)?;
8604 self.symbolic_deref(val, *kind, line)
8605 }
8606 ExprKind::ArrowDeref { expr, index, kind } => {
8607 match kind {
8608 DerefKind::Array => {
8609 let container = self.eval_arrow_array_base(expr, line)?;
8610 if let ExprKind::List(indices) = &index.kind {
8611 let mut out = Vec::with_capacity(indices.len());
8612 for ix in indices {
8613 let idx = self.eval_expr(ix)?.to_int();
8614 out.push(self.read_arrow_array_element(
8615 container.clone(),
8616 idx,
8617 line,
8618 )?);
8619 }
8620 let arr = PerlValue::array(out);
8621 if ctx != WantarrayCtx::List {
8622 let v = arr.to_list();
8623 return Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF));
8624 }
8625 return Ok(arr);
8626 }
8627 let idx = self.eval_expr(index)?.to_int();
8628 self.read_arrow_array_element(container, idx, line)
8629 }
8630 DerefKind::Hash => {
8631 let val = self.eval_arrow_hash_base(expr, line)?;
8632 let key = self.eval_expr(index)?.to_string();
8633 self.read_arrow_hash_element(val, key.as_str(), line)
8634 }
8635 DerefKind::Call => {
8636 let val = self.eval_expr(expr)?;
8638 if let ExprKind::List(ref arg_exprs) = index.kind {
8639 let mut args = Vec::new();
8640 for a in arg_exprs {
8641 args.push(self.eval_expr(a)?);
8642 }
8643 let callable = if let Some(inner) = val.as_scalar_ref() {
8645 inner.read().clone()
8646 } else {
8647 val
8648 };
8649 if let Some(sub) = callable.as_code_ref() {
8650 return self.call_sub(&sub, args, ctx, line);
8651 }
8652 Err(PerlError::runtime("Not a code reference", line).into())
8653 } else {
8654 Err(PerlError::runtime("Invalid call deref", line).into())
8655 }
8656 }
8657 }
8658 }
8659
8660 ExprKind::BinOp { left, op, right } => {
8662 match op {
8664 BinOp::BindMatch => {
8665 let lv = self.eval_expr(left)?;
8666 let rv = self.eval_expr(right)?;
8667 let s = lv.to_string();
8668 let pat = rv.to_string();
8669 return self.regex_match_execute(s, &pat, "", false, "_", line);
8670 }
8671 BinOp::BindNotMatch => {
8672 let lv = self.eval_expr(left)?;
8673 let rv = self.eval_expr(right)?;
8674 let s = lv.to_string();
8675 let pat = rv.to_string();
8676 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
8677 return Ok(PerlValue::integer(if m.is_true() { 0 } else { 1 }));
8678 }
8679 BinOp::LogAnd | BinOp::LogAndWord => {
8680 match &left.kind {
8681 ExprKind::Regex(_, _) => {
8682 if !self.eval_boolean_rvalue_condition(left)? {
8683 return Ok(PerlValue::string(String::new()));
8684 }
8685 }
8686 _ => {
8687 let lv = self.eval_expr(left)?;
8688 if !lv.is_true() {
8689 return Ok(lv);
8690 }
8691 }
8692 }
8693 return match &right.kind {
8694 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8695 if self.eval_boolean_rvalue_condition(right)? {
8696 1
8697 } else {
8698 0
8699 },
8700 )),
8701 _ => self.eval_expr(right),
8702 };
8703 }
8704 BinOp::LogOr | BinOp::LogOrWord => {
8705 match &left.kind {
8706 ExprKind::Regex(_, _) => {
8707 if self.eval_boolean_rvalue_condition(left)? {
8708 return Ok(PerlValue::integer(1));
8709 }
8710 }
8711 _ => {
8712 let lv = self.eval_expr(left)?;
8713 if lv.is_true() {
8714 return Ok(lv);
8715 }
8716 }
8717 }
8718 return match &right.kind {
8719 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8720 if self.eval_boolean_rvalue_condition(right)? {
8721 1
8722 } else {
8723 0
8724 },
8725 )),
8726 _ => self.eval_expr(right),
8727 };
8728 }
8729 BinOp::DefinedOr => {
8730 let lv = self.eval_expr(left)?;
8731 if !lv.is_undef() {
8732 return Ok(lv);
8733 }
8734 return self.eval_expr(right);
8735 }
8736 _ => {}
8737 }
8738 let lv = self.eval_expr(left)?;
8739 let rv = self.eval_expr(right)?;
8740 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
8741 return r;
8742 }
8743 self.eval_binop(*op, &lv, &rv, line)
8744 }
8745
8746 ExprKind::UnaryOp { op, expr } => match op {
8748 UnaryOp::PreIncrement => {
8749 if let ExprKind::ScalarVar(name) = &expr.kind {
8750 self.check_strict_scalar_var(name, line)?;
8751 let n = self.english_scalar_name(name);
8752 return Ok(self
8753 .scope
8754 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() + 1)));
8755 }
8756 if let ExprKind::Deref { kind, .. } = &expr.kind {
8757 if matches!(kind, Sigil::Array | Sigil::Hash) {
8758 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8759 *kind, true, true, line,
8760 ));
8761 }
8762 }
8763 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8764 let href = self.eval_expr(container)?;
8765 let mut key_vals = Vec::with_capacity(keys.len());
8766 for key_expr in keys {
8767 key_vals.push(self.eval_expr(key_expr)?);
8768 }
8769 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
8770 }
8771 if let ExprKind::ArrowDeref {
8772 expr: arr_expr,
8773 index,
8774 kind: DerefKind::Array,
8775 } = &expr.kind
8776 {
8777 if let ExprKind::List(indices) = &index.kind {
8778 let container = self.eval_arrow_array_base(arr_expr, line)?;
8779 let mut idxs = Vec::with_capacity(indices.len());
8780 for ix in indices {
8781 idxs.push(self.eval_expr(ix)?.to_int());
8782 }
8783 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
8784 }
8785 }
8786 let val = self.eval_expr(expr)?;
8787 let new_val = PerlValue::integer(val.to_int() + 1);
8788 self.assign_value(expr, new_val.clone())?;
8789 Ok(new_val)
8790 }
8791 UnaryOp::PreDecrement => {
8792 if let ExprKind::ScalarVar(name) = &expr.kind {
8793 self.check_strict_scalar_var(name, line)?;
8794 let n = self.english_scalar_name(name);
8795 return Ok(self
8796 .scope
8797 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() - 1)));
8798 }
8799 if let ExprKind::Deref { kind, .. } = &expr.kind {
8800 if matches!(kind, Sigil::Array | Sigil::Hash) {
8801 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8802 *kind, true, false, line,
8803 ));
8804 }
8805 }
8806 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8807 let href = self.eval_expr(container)?;
8808 let mut key_vals = Vec::with_capacity(keys.len());
8809 for key_expr in keys {
8810 key_vals.push(self.eval_expr(key_expr)?);
8811 }
8812 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
8813 }
8814 if let ExprKind::ArrowDeref {
8815 expr: arr_expr,
8816 index,
8817 kind: DerefKind::Array,
8818 } = &expr.kind
8819 {
8820 if let ExprKind::List(indices) = &index.kind {
8821 let container = self.eval_arrow_array_base(arr_expr, line)?;
8822 let mut idxs = Vec::with_capacity(indices.len());
8823 for ix in indices {
8824 idxs.push(self.eval_expr(ix)?.to_int());
8825 }
8826 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
8827 }
8828 }
8829 let val = self.eval_expr(expr)?;
8830 let new_val = PerlValue::integer(val.to_int() - 1);
8831 self.assign_value(expr, new_val.clone())?;
8832 Ok(new_val)
8833 }
8834 _ => {
8835 match op {
8836 UnaryOp::LogNot | UnaryOp::LogNotWord => {
8837 if let ExprKind::Regex(pattern, flags) = &expr.kind {
8838 let topic = self.scope.get_scalar("_");
8839 let rl = expr.line;
8840 let s = topic.to_string();
8841 let v =
8842 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
8843 return Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }));
8844 }
8845 }
8846 _ => {}
8847 }
8848 let val = self.eval_expr(expr)?;
8849 match op {
8850 UnaryOp::Negate => {
8851 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
8852 return r;
8853 }
8854 if let Some(n) = val.as_integer() {
8855 Ok(PerlValue::integer(-n))
8856 } else {
8857 Ok(PerlValue::float(-val.to_number()))
8858 }
8859 }
8860 UnaryOp::LogNot => {
8861 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8862 let pv = r?;
8863 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8864 }
8865 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8866 }
8867 UnaryOp::BitNot => Ok(PerlValue::integer(!val.to_int())),
8868 UnaryOp::LogNotWord => {
8869 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8870 let pv = r?;
8871 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8872 }
8873 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8874 }
8875 UnaryOp::Ref => {
8876 if let ExprKind::ScalarVar(name) = &expr.kind {
8877 return Ok(PerlValue::scalar_binding_ref(name.clone()));
8878 }
8879 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8880 }
8881 _ => unreachable!(),
8882 }
8883 }
8884 },
8885
8886 ExprKind::PostfixOp { expr, op } => {
8887 if let ExprKind::ScalarVar(name) = &expr.kind {
8890 self.check_strict_scalar_var(name, line)?;
8891 let n = self.english_scalar_name(name);
8892 let f: fn(&PerlValue) -> PerlValue = match op {
8893 PostfixOp::Increment => |v| PerlValue::integer(v.to_int() + 1),
8894 PostfixOp::Decrement => |v| PerlValue::integer(v.to_int() - 1),
8895 };
8896 return Ok(self.scope.atomic_mutate_post(n, f));
8897 }
8898 if let ExprKind::Deref { kind, .. } = &expr.kind {
8899 if matches!(kind, Sigil::Array | Sigil::Hash) {
8900 let is_inc = matches!(op, PostfixOp::Increment);
8901 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8902 *kind, false, is_inc, line,
8903 ));
8904 }
8905 }
8906 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8907 let href = self.eval_expr(container)?;
8908 let mut key_vals = Vec::with_capacity(keys.len());
8909 for key_expr in keys {
8910 key_vals.push(self.eval_expr(key_expr)?);
8911 }
8912 let kind_byte = match op {
8913 PostfixOp::Increment => 2u8,
8914 PostfixOp::Decrement => 3u8,
8915 };
8916 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
8917 }
8918 if let ExprKind::ArrowDeref {
8919 expr: arr_expr,
8920 index,
8921 kind: DerefKind::Array,
8922 } = &expr.kind
8923 {
8924 if let ExprKind::List(indices) = &index.kind {
8925 let container = self.eval_arrow_array_base(arr_expr, line)?;
8926 let mut idxs = Vec::with_capacity(indices.len());
8927 for ix in indices {
8928 idxs.push(self.eval_expr(ix)?.to_int());
8929 }
8930 let kind_byte = match op {
8931 PostfixOp::Increment => 2u8,
8932 PostfixOp::Decrement => 3u8,
8933 };
8934 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
8935 }
8936 }
8937 let val = self.eval_expr(expr)?;
8938 let old = val.clone();
8939 let new_val = match op {
8940 PostfixOp::Increment => PerlValue::integer(val.to_int() + 1),
8941 PostfixOp::Decrement => PerlValue::integer(val.to_int() - 1),
8942 };
8943 self.assign_value(expr, new_val)?;
8944 Ok(old)
8945 }
8946
8947 ExprKind::Assign { target, value } => {
8949 if let ExprKind::Typeglob(lhs) = &target.kind {
8950 if let ExprKind::Typeglob(rhs) = &value.kind {
8951 self.copy_typeglob_slots(lhs, rhs, line)?;
8952 return self.eval_expr(value);
8953 }
8954 }
8955 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
8956 self.assign_value(target, val.clone())?;
8957 Ok(val)
8958 }
8959 ExprKind::CompoundAssign { target, op, value } => {
8960 if let ExprKind::ScalarVar(name) = &target.kind {
8963 self.check_strict_scalar_var(name, line)?;
8964 let n = self.english_scalar_name(name);
8965 let op = *op;
8966 let rhs = match op {
8967 BinOp::LogOr => {
8968 let old = self.scope.get_scalar(n);
8969 if old.is_true() {
8970 return Ok(old);
8971 }
8972 self.eval_expr(value)?
8973 }
8974 BinOp::DefinedOr => {
8975 let old = self.scope.get_scalar(n);
8976 if !old.is_undef() {
8977 return Ok(old);
8978 }
8979 self.eval_expr(value)?
8980 }
8981 BinOp::LogAnd => {
8982 let old = self.scope.get_scalar(n);
8983 if !old.is_true() {
8984 return Ok(old);
8985 }
8986 self.eval_expr(value)?
8987 }
8988 _ => self.eval_expr(value)?,
8989 };
8990 return Ok(self.scalar_compound_assign_scalar_target(n, op, rhs)?);
8991 }
8992 let rhs = self.eval_expr(value)?;
8993 if let ExprKind::HashElement { hash, key } = &target.kind {
8995 self.check_strict_hash_var(hash, line)?;
8996 let k = self.eval_expr(key)?.to_string();
8997 let op = *op;
8998 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
8999 BinOp::Add => {
9000 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9001 PerlValue::integer(a.wrapping_add(b))
9002 } else {
9003 PerlValue::float(old.to_number() + rhs.to_number())
9004 }
9005 }
9006 BinOp::Sub => {
9007 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9008 PerlValue::integer(a.wrapping_sub(b))
9009 } else {
9010 PerlValue::float(old.to_number() - rhs.to_number())
9011 }
9012 }
9013 BinOp::Concat => {
9014 let mut s = old.to_string();
9015 rhs.append_to(&mut s);
9016 PerlValue::string(s)
9017 }
9018 _ => PerlValue::float(old.to_number() + rhs.to_number()),
9019 })?);
9020 }
9021 if let ExprKind::ArrayElement { array, index } = &target.kind {
9023 self.check_strict_array_var(array, line)?;
9024 let idx = self.eval_expr(index)?.to_int();
9025 let op = *op;
9026 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
9027 BinOp::Add => {
9028 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9029 PerlValue::integer(a.wrapping_add(b))
9030 } else {
9031 PerlValue::float(old.to_number() + rhs.to_number())
9032 }
9033 }
9034 _ => PerlValue::float(old.to_number() + rhs.to_number()),
9035 })?);
9036 }
9037 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
9038 let href = self.eval_expr(container)?;
9039 let mut key_vals = Vec::with_capacity(keys.len());
9040 for key_expr in keys {
9041 key_vals.push(self.eval_expr(key_expr)?);
9042 }
9043 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
9044 }
9045 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
9046 if let ExprKind::Deref {
9047 expr: inner,
9048 kind: Sigil::Array,
9049 } = &source.kind
9050 {
9051 let container = self.eval_arrow_array_base(inner, line)?;
9052 let idxs = self.flatten_array_slice_index_specs(indices)?;
9053 return self
9054 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
9055 }
9056 }
9057 if let ExprKind::ArrowDeref {
9058 expr: arr_expr,
9059 index,
9060 kind: DerefKind::Array,
9061 } = &target.kind
9062 {
9063 if let ExprKind::List(indices) = &index.kind {
9064 let container = self.eval_arrow_array_base(arr_expr, line)?;
9065 let mut idxs = Vec::with_capacity(indices.len());
9066 for ix in indices {
9067 idxs.push(self.eval_expr(ix)?.to_int());
9068 }
9069 return self
9070 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
9071 }
9072 }
9073 let old = self.eval_expr(target)?;
9074 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
9075 self.assign_value(target, new_val.clone())?;
9076 Ok(new_val)
9077 }
9078
9079 ExprKind::Ternary {
9081 condition,
9082 then_expr,
9083 else_expr,
9084 } => {
9085 if self.eval_boolean_rvalue_condition(condition)? {
9086 self.eval_expr(then_expr)
9087 } else {
9088 self.eval_expr(else_expr)
9089 }
9090 }
9091
9092 ExprKind::Range {
9094 from,
9095 to,
9096 exclusive,
9097 step,
9098 } => {
9099 if ctx == WantarrayCtx::List {
9100 let f = self.eval_expr(from)?;
9101 let t = self.eval_expr(to)?;
9102 if let Some(s) = step {
9103 let step_val = self.eval_expr(s)?.to_int();
9104 let from_i = f.to_int();
9105 let to_i = t.to_int();
9106 let list = if step_val == 0 {
9107 vec![]
9108 } else if step_val > 0 {
9109 (from_i..=to_i)
9110 .step_by(step_val as usize)
9111 .map(PerlValue::integer)
9112 .collect()
9113 } else {
9114 std::iter::successors(Some(from_i), |&x| {
9115 let next = x - step_val.abs();
9116 if next >= to_i {
9117 Some(next)
9118 } else {
9119 None
9120 }
9121 })
9122 .map(PerlValue::integer)
9123 .collect()
9124 };
9125 Ok(PerlValue::array(list))
9126 } else {
9127 let list = perl_list_range_expand(f, t);
9128 Ok(PerlValue::array(list))
9129 }
9130 } else {
9131 let key = std::ptr::from_ref(expr) as usize;
9132 match (&from.kind, &to.kind) {
9133 (
9134 ExprKind::Regex(left_pat, left_flags),
9135 ExprKind::Regex(right_pat, right_flags),
9136 ) => {
9137 let dot = self.scalar_flipflop_dot_line();
9138 let subject = self.scope.get_scalar("_").to_string();
9139 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9140 |e| match e {
9141 FlowOrError::Error(err) => err,
9142 FlowOrError::Flow(_) => PerlError::runtime(
9143 "unexpected flow in regex flip-flop",
9144 line,
9145 ),
9146 },
9147 )?;
9148 let right_re = self
9149 .compile_regex(right_pat, right_flags, line)
9150 .map_err(|e| match e {
9151 FlowOrError::Error(err) => err,
9152 FlowOrError::Flow(_) => PerlError::runtime(
9153 "unexpected flow in regex flip-flop",
9154 line,
9155 ),
9156 })?;
9157 let left_m = left_re.is_match(&subject);
9158 let right_m = right_re.is_match(&subject);
9159 let st = self.flip_flop_tree.entry(key).or_default();
9160 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9161 &mut st.active,
9162 &mut st.exclusive_left_line,
9163 *exclusive,
9164 dot,
9165 left_m,
9166 right_m,
9167 )))
9168 }
9169 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
9170 let dot = self.scalar_flipflop_dot_line();
9171 let subject = self.scope.get_scalar("_").to_string();
9172 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9173 |e| match e {
9174 FlowOrError::Error(err) => err,
9175 FlowOrError::Flow(_) => PerlError::runtime(
9176 "unexpected flow in regex/eof flip-flop",
9177 line,
9178 ),
9179 },
9180 )?;
9181 let left_m = left_re.is_match(&subject);
9182 let right_m = self.eof_without_arg_is_true();
9183 let st = self.flip_flop_tree.entry(key).or_default();
9184 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9185 &mut st.active,
9186 &mut st.exclusive_left_line,
9187 *exclusive,
9188 dot,
9189 left_m,
9190 right_m,
9191 )))
9192 }
9193 (
9194 ExprKind::Regex(left_pat, left_flags),
9195 ExprKind::Integer(_) | ExprKind::Float(_),
9196 ) => {
9197 let dot = self.scalar_flipflop_dot_line();
9198 let right = self.eval_expr(to)?.to_int();
9199 let subject = self.scope.get_scalar("_").to_string();
9200 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9201 |e| match e {
9202 FlowOrError::Error(err) => err,
9203 FlowOrError::Flow(_) => PerlError::runtime(
9204 "unexpected flow in regex flip-flop",
9205 line,
9206 ),
9207 },
9208 )?;
9209 let left_m = left_re.is_match(&subject);
9210 let right_m = dot == right;
9211 let st = self.flip_flop_tree.entry(key).or_default();
9212 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9213 &mut st.active,
9214 &mut st.exclusive_left_line,
9215 *exclusive,
9216 dot,
9217 left_m,
9218 right_m,
9219 )))
9220 }
9221 (ExprKind::Regex(left_pat, left_flags), _) => {
9222 if let ExprKind::Eof(Some(_)) = &to.kind {
9223 return Err(FlowOrError::Error(PerlError::runtime(
9224 "regex flip-flop with eof(HANDLE) is not supported",
9225 line,
9226 )));
9227 }
9228 let dot = self.scalar_flipflop_dot_line();
9229 let subject = self.scope.get_scalar("_").to_string();
9230 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9231 |e| match e {
9232 FlowOrError::Error(err) => err,
9233 FlowOrError::Flow(_) => PerlError::runtime(
9234 "unexpected flow in regex flip-flop",
9235 line,
9236 ),
9237 },
9238 )?;
9239 let left_m = left_re.is_match(&subject);
9240 let right_m = self.eval_boolean_rvalue_condition(to)?;
9241 let st = self.flip_flop_tree.entry(key).or_default();
9242 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9243 &mut st.active,
9244 &mut st.exclusive_left_line,
9245 *exclusive,
9246 dot,
9247 left_m,
9248 right_m,
9249 )))
9250 }
9251 _ => {
9252 let left = self.eval_expr(from)?.to_int();
9253 let right = self.eval_expr(to)?.to_int();
9254 let dot = self.scalar_flipflop_dot_line();
9255 let st = self.flip_flop_tree.entry(key).or_default();
9256 if !st.active {
9257 if dot == left {
9258 st.active = true;
9259 if *exclusive {
9260 st.exclusive_left_line = Some(dot);
9261 } else {
9262 st.exclusive_left_line = None;
9263 if dot == right {
9264 st.active = false;
9265 }
9266 }
9267 return Ok(PerlValue::integer(1));
9268 }
9269 return Ok(PerlValue::integer(0));
9270 }
9271 if let Some(ll) = st.exclusive_left_line {
9272 if dot == right && dot > ll {
9273 st.active = false;
9274 st.exclusive_left_line = None;
9275 }
9276 } else if dot == right {
9277 st.active = false;
9278 }
9279 Ok(PerlValue::integer(1))
9280 }
9281 }
9282 }
9283 }
9284
9285 ExprKind::SliceRange { from, to, step } => {
9291 let f = match from {
9292 Some(e) => self.eval_expr(e)?,
9293 None => {
9294 return Err(PerlError::runtime(
9295 "open-ended slice range cannot be evaluated outside slice subscript",
9296 line,
9297 )
9298 .into());
9299 }
9300 };
9301 let t = match to {
9302 Some(e) => self.eval_expr(e)?,
9303 None => {
9304 return Err(PerlError::runtime(
9305 "open-ended slice range cannot be evaluated outside slice subscript",
9306 line,
9307 )
9308 .into());
9309 }
9310 };
9311 let list = if let Some(s) = step {
9312 let sv = self.eval_expr(s)?;
9313 crate::value::perl_list_range_expand_stepped(f, t, sv)
9314 } else {
9315 perl_list_range_expand(f, t)
9316 };
9317 Ok(PerlValue::array(list))
9318 }
9319
9320 ExprKind::Repeat { expr, count } => {
9322 let val = self.eval_expr(expr)?;
9323 let n = self.eval_expr(count)?.to_int().max(0) as usize;
9324 if let Some(s) = val.as_str() {
9325 Ok(PerlValue::string(s.repeat(n)))
9326 } else if let Some(a) = val.as_array_vec() {
9327 let mut result = Vec::with_capacity(a.len() * n);
9328 for _ in 0..n {
9329 result.extend(a.iter().cloned());
9330 }
9331 Ok(PerlValue::array(result))
9332 } else {
9333 Ok(PerlValue::string(val.to_string().repeat(n)))
9334 }
9335 }
9336
9337 ExprKind::MyExpr { keyword, decls } => {
9342 let stmt_kind = match keyword.as_str() {
9345 "my" => StmtKind::My(decls.clone()),
9346 "our" => StmtKind::Our(decls.clone()),
9347 "state" => StmtKind::State(decls.clone()),
9348 "local" => StmtKind::Local(decls.clone()),
9349 _ => StmtKind::My(decls.clone()),
9350 };
9351 let stmt = Statement {
9352 label: None,
9353 kind: stmt_kind,
9354 line,
9355 };
9356 self.exec_statement(&stmt)?;
9357 let first = decls.first().ok_or_else(|| {
9361 FlowOrError::Error(PerlError::runtime("MyExpr: empty decl list", line))
9362 })?;
9363 Ok(match first.sigil {
9364 Sigil::Scalar => self.scope.get_scalar(&first.name),
9365 Sigil::Array => PerlValue::array(self.scope.get_array(&first.name)),
9366 Sigil::Hash => {
9367 let h = self.scope.get_hash(&first.name);
9368 let mut flat: Vec<PerlValue> = Vec::with_capacity(h.len() * 2);
9369 for (k, v) in h {
9370 flat.push(PerlValue::string(k));
9371 flat.push(v);
9372 }
9373 PerlValue::array(flat)
9374 }
9375 Sigil::Typeglob => PerlValue::UNDEF,
9376 })
9377 }
9378
9379 ExprKind::FuncCall { name, args } => {
9381 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
9384 if matches!(dispatch_name, "read") && args.len() >= 3 {
9386 let fh_val = self.eval_expr(&args[0])?;
9387 let fh = fh_val
9388 .as_io_handle_name()
9389 .unwrap_or_else(|| fh_val.to_string());
9390 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
9391 let offset = if args.len() > 3 {
9392 self.eval_expr(&args[3])?.to_int().max(0) as usize
9393 } else {
9394 0
9395 };
9396 let var_name = match &args[1].kind {
9398 ExprKind::ScalarVar(n) => n.clone(),
9399 _ => self.eval_expr(&args[1])?.to_string(),
9400 };
9401 let mut buf = vec![0u8; len];
9402 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
9403 slot.lock().read(&mut buf).unwrap_or(0)
9404 } else if fh == "STDIN" {
9405 std::io::stdin().read(&mut buf).unwrap_or(0)
9406 } else {
9407 return Err(PerlError::runtime(
9408 format!("read: unopened handle {}", fh),
9409 line,
9410 )
9411 .into());
9412 };
9413 buf.truncate(n);
9414 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
9415 if offset > 0 {
9416 let mut existing = self.scope.get_scalar(&var_name).to_string();
9417 while existing.len() < offset {
9418 existing.push('\0');
9419 }
9420 existing.push_str(&read_str);
9421 let _ = self
9422 .scope
9423 .set_scalar(&var_name, PerlValue::string(existing));
9424 } else {
9425 let _ = self
9426 .scope
9427 .set_scalar(&var_name, PerlValue::string(read_str));
9428 }
9429 return Ok(PerlValue::integer(n as i64));
9430 }
9431 if matches!(dispatch_name, "group_by" | "chunk_by") {
9432 if args.len() != 2 {
9433 return Err(PerlError::runtime(
9434 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
9435 line,
9436 )
9437 .into());
9438 }
9439 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
9440 }
9441 if matches!(dispatch_name, "puniq" | "pfirst" | "pany") {
9442 let mut arg_vals = Vec::with_capacity(args.len());
9443 for a in args {
9444 arg_vals.push(self.eval_expr(a)?);
9445 }
9446 let saved_wa = self.wantarray_kind;
9447 self.wantarray_kind = ctx;
9448 let r = self.eval_par_list_call(dispatch_name, &arg_vals, ctx, line);
9449 self.wantarray_kind = saved_wa;
9450 return r.map_err(Into::into);
9451 }
9452 let arg_vals = if matches!(dispatch_name, "any" | "all" | "none" | "first")
9453 || matches!(
9454 dispatch_name,
9455 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9456 )
9457 || matches!(
9458 dispatch_name,
9459 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
9460 ) {
9461 if args.len() != 2 {
9462 return Err(PerlError::runtime(
9463 format!("{}: expected BLOCK, LIST", name),
9464 line,
9465 )
9466 .into());
9467 }
9468 let cr = self.eval_expr(&args[0])?;
9469 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
9470 let mut v = vec![cr];
9471 v.extend(list_src.to_list());
9472 v
9473 } else if matches!(
9474 dispatch_name,
9475 "zip"
9476 | "zip_longest"
9477 | "zip_shortest"
9478 | "mesh"
9479 | "mesh_longest"
9480 | "mesh_shortest"
9481 ) {
9482 let mut v = Vec::with_capacity(args.len());
9483 for a in args {
9484 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9485 }
9486 v
9487 } else if matches!(
9488 dispatch_name,
9489 "uniq"
9490 | "distinct"
9491 | "uniqstr"
9492 | "uniqint"
9493 | "uniqnum"
9494 | "flatten"
9495 | "set"
9496 | "list_count"
9497 | "list_size"
9498 | "count"
9499 | "size"
9500 | "cnt"
9501 | "with_index"
9502 | "shuffle"
9503 | "sum"
9504 | "sum0"
9505 | "product"
9506 | "min"
9507 | "max"
9508 | "minstr"
9509 | "maxstr"
9510 | "mean"
9511 | "median"
9512 | "mode"
9513 | "stddev"
9514 | "variance"
9515 | "pairs"
9516 | "unpairs"
9517 | "pairkeys"
9518 | "pairvalues"
9519 ) {
9520 let mut list_out = Vec::new();
9524 if args.len() == 1 {
9525 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
9526 } else {
9527 for a in args {
9528 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
9529 }
9530 }
9531 list_out
9532 } else if matches!(dispatch_name, "take" | "head" | "tail" | "drop") {
9533 if args.is_empty() {
9534 return Err(PerlError::runtime(
9535 "take/head/tail/drop: need LIST..., N or unary N",
9536 line,
9537 )
9538 .into());
9539 }
9540 let mut arg_vals = Vec::with_capacity(args.len());
9541 if args.len() == 1 {
9542 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9544 } else {
9545 for a in &args[..args.len() - 1] {
9546 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9547 }
9548 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
9549 }
9550 arg_vals
9551 } else if matches!(dispatch_name, "chunked" | "windowed") {
9552 let mut list_out = Vec::new();
9553 match args.len() {
9554 0 => {
9555 return Err(PerlError::runtime(
9556 format!("{name}: expected (LIST, N) or unary N after |>"),
9557 line,
9558 )
9559 .into());
9560 }
9561 1 => {
9562 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9564 }
9565 2 => {
9566 list_out.extend(
9567 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
9568 );
9569 list_out.push(self.eval_expr(&args[1])?);
9570 }
9571 _ => {
9572 return Err(PerlError::runtime(
9573 format!(
9574 "{name}: expected exactly (LIST, N); use one list expression then size"
9575 ),
9576 line,
9577 )
9578 .into());
9579 }
9580 }
9581 list_out
9582 } else {
9583 let mut arg_vals = Vec::with_capacity(args.len());
9586 for a in args {
9587 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
9588 if let Some(items) = v.as_array_vec() {
9589 arg_vals.extend(items);
9590 } else {
9591 arg_vals.push(v);
9592 }
9593 }
9594 arg_vals
9595 };
9596 let saved_wa = self.wantarray_kind;
9598 self.wantarray_kind = ctx;
9599 if !crate::compat_mode() {
9602 if matches!(
9603 dispatch_name,
9604 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9605 ) {
9606 let r =
9607 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
9608 self.wantarray_kind = saved_wa;
9609 return r.map_err(Into::into);
9610 }
9611 if let Some(r) =
9612 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
9613 {
9614 self.wantarray_kind = saved_wa;
9615 return r.map_err(Into::into);
9616 }
9617 }
9618 if let Some(sub) = self.resolve_sub_by_name(name) {
9619 self.wantarray_kind = saved_wa;
9620 let args = self.with_topic_default_args(arg_vals);
9621 return self.call_sub(&sub, args, ctx, line);
9622 }
9623 if crate::compat_mode() {
9625 if matches!(
9626 dispatch_name,
9627 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9628 ) {
9629 let r =
9630 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
9631 self.wantarray_kind = saved_wa;
9632 return r.map_err(Into::into);
9633 }
9634 if let Some(r) =
9635 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
9636 {
9637 self.wantarray_kind = saved_wa;
9638 return r.map_err(Into::into);
9639 }
9640 }
9641 self.wantarray_kind = saved_wa;
9642 self.call_named_sub(name, arg_vals, line, ctx)
9643 }
9644 ExprKind::IndirectCall {
9645 target,
9646 args,
9647 ampersand: _,
9648 pass_caller_arglist,
9649 } => {
9650 let tval = self.eval_expr(target)?;
9651 let arg_vals = if *pass_caller_arglist {
9652 self.scope.get_array("_")
9653 } else {
9654 let mut v = Vec::with_capacity(args.len());
9655 for a in args {
9656 v.push(self.eval_expr(a)?);
9657 }
9658 v
9659 };
9660 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
9661 }
9662 ExprKind::MethodCall {
9663 object,
9664 method,
9665 args,
9666 super_call,
9667 } => {
9668 let obj = self.eval_expr(object)?;
9669 let mut arg_vals = vec![obj.clone()];
9670 for a in args {
9671 arg_vals.push(self.eval_expr(a)?);
9672 }
9673 if let Some(r) =
9674 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
9675 {
9676 return r.map_err(Into::into);
9677 }
9678 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
9679 return r.map_err(Into::into);
9680 }
9681 let class = if let Some(b) = obj.as_blessed_ref() {
9683 b.class.clone()
9684 } else if let Some(s) = obj.as_str() {
9685 s } else {
9687 return Err(PerlError::runtime("Can't call method on non-object", line).into());
9688 };
9689 if method == "VERSION" && !*super_call {
9690 if let Some(ver) = self.package_version_scalar(class.as_str())? {
9691 return Ok(ver);
9692 }
9693 }
9694 if !*super_call {
9696 match method.as_str() {
9697 "isa" => {
9698 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9699 let mro = self.mro_linearize(&class);
9700 let result = mro.iter().any(|c| c == &target);
9701 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9702 }
9703 "can" => {
9704 let target_method =
9705 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9706 let found = self
9707 .resolve_method_full_name(&class, &target_method, false)
9708 .and_then(|fq| self.subs.get(&fq))
9709 .is_some();
9710 if found {
9711 return Ok(PerlValue::code_ref(Arc::new(PerlSub {
9712 name: target_method,
9713 params: vec![],
9714 body: vec![],
9715 closure_env: None,
9716 prototype: None,
9717 fib_like: None,
9718 })));
9719 } else {
9720 return Ok(PerlValue::UNDEF);
9721 }
9722 }
9723 "DOES" => {
9724 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9725 let mro = self.mro_linearize(&class);
9726 let result = mro.iter().any(|c| c == &target);
9727 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9728 }
9729 _ => {}
9730 }
9731 }
9732 let full_name = self
9733 .resolve_method_full_name(&class, method, *super_call)
9734 .ok_or_else(|| {
9735 PerlError::runtime(
9736 format!(
9737 "Can't locate method \"{}\" for invocant \"{}\"",
9738 method, class
9739 ),
9740 line,
9741 )
9742 })?;
9743 if let Some(sub) = self.subs.get(&full_name).cloned() {
9744 self.call_sub(&sub, arg_vals, ctx, line)
9745 } else if method == "new" && !*super_call {
9746 self.builtin_new(&class, arg_vals, line)
9748 } else if let Some(r) =
9749 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
9750 {
9751 r
9752 } else {
9753 Err(PerlError::runtime(
9754 format!(
9755 "Can't locate method \"{}\" in package \"{}\"",
9756 method, class
9757 ),
9758 line,
9759 )
9760 .into())
9761 }
9762 }
9763
9764 ExprKind::Print { handle, args } => {
9766 self.exec_print(handle.as_deref(), args, false, line)
9767 }
9768 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
9769 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
9770 ExprKind::Die(args) => {
9771 if args.is_empty() {
9772 let current = self.scope.get_scalar("@");
9774 let msg = if current.is_undef() || current.to_string().is_empty() {
9775 let mut m = "Died".to_string();
9776 m.push_str(&self.die_warn_at_suffix(line));
9777 m.push('\n');
9778 m
9779 } else {
9780 current.to_string()
9781 };
9782 return Err(PerlError::die(msg, line).into());
9783 }
9784 if args.len() == 1 {
9786 let v = self.eval_expr(&args[0])?;
9787 if v.as_hash_ref().is_some()
9788 || v.as_blessed_ref().is_some()
9789 || v.as_array_ref().is_some()
9790 || v.as_code_ref().is_some()
9791 {
9792 let msg = v.to_string();
9793 return Err(PerlError::die_with_value(v, msg, line).into());
9794 }
9795 }
9796 let mut msg = String::new();
9797 for a in args {
9798 let v = self.eval_expr(a)?;
9799 msg.push_str(&v.to_string());
9800 }
9801 if msg.is_empty() {
9802 msg = "Died".to_string();
9803 }
9804 if !msg.ends_with('\n') {
9805 msg.push_str(&self.die_warn_at_suffix(line));
9806 msg.push('\n');
9807 }
9808 Err(PerlError::die(msg, line).into())
9809 }
9810 ExprKind::Warn(args) => {
9811 let mut msg = String::new();
9812 for a in args {
9813 let v = self.eval_expr(a)?;
9814 msg.push_str(&v.to_string());
9815 }
9816 if msg.is_empty() {
9817 msg = "Warning: something's wrong".to_string();
9818 }
9819 if !msg.ends_with('\n') {
9820 msg.push_str(&self.die_warn_at_suffix(line));
9821 msg.push('\n');
9822 }
9823 eprint!("{}", msg);
9824 Ok(PerlValue::integer(1))
9825 }
9826
9827 ExprKind::Match {
9829 expr,
9830 pattern,
9831 flags,
9832 scalar_g,
9833 delim: _,
9834 } => {
9835 let val = self.eval_expr(expr)?;
9836 if val.is_iterator() {
9837 let source = crate::map_stream::into_pull_iter(val);
9838 let re = self.compile_regex(pattern, flags, line)?;
9839 let global = flags.contains('g');
9840 if global {
9841 return Ok(PerlValue::iterator(std::sync::Arc::new(
9842 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
9843 )));
9844 } else {
9845 return Ok(PerlValue::iterator(std::sync::Arc::new(
9846 crate::map_stream::MatchStreamIterator::new(source, re),
9847 )));
9848 }
9849 }
9850 let s = val.to_string();
9851 let pos_key = match &expr.kind {
9852 ExprKind::ScalarVar(n) => n.as_str(),
9853 _ => "_",
9854 };
9855 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
9856 }
9857 ExprKind::Substitution {
9858 expr,
9859 pattern,
9860 replacement,
9861 flags,
9862 delim: _,
9863 } => {
9864 let val = self.eval_expr(expr)?;
9865 if val.is_iterator() {
9866 let source = crate::map_stream::into_pull_iter(val);
9867 let re = self.compile_regex(pattern, flags, line)?;
9868 let global = flags.contains('g');
9869 return Ok(PerlValue::iterator(std::sync::Arc::new(
9870 crate::map_stream::SubstStreamIterator::new(
9871 source,
9872 re,
9873 normalize_replacement_backrefs(replacement),
9874 global,
9875 ),
9876 )));
9877 }
9878 let s = val.to_string();
9879 self.regex_subst_execute(
9880 s,
9881 pattern,
9882 replacement.as_str(),
9883 flags.as_str(),
9884 expr,
9885 line,
9886 )
9887 }
9888 ExprKind::Transliterate {
9889 expr,
9890 from,
9891 to,
9892 flags,
9893 delim: _,
9894 } => {
9895 let val = self.eval_expr(expr)?;
9896 if val.is_iterator() {
9897 let source = crate::map_stream::into_pull_iter(val);
9898 return Ok(PerlValue::iterator(std::sync::Arc::new(
9899 crate::map_stream::TransliterateStreamIterator::new(
9900 source, from, to, flags,
9901 ),
9902 )));
9903 }
9904 let s = val.to_string();
9905 self.regex_transliterate_execute(
9906 s,
9907 from.as_str(),
9908 to.as_str(),
9909 flags.as_str(),
9910 expr,
9911 line,
9912 )
9913 }
9914
9915 ExprKind::MapExpr {
9917 block,
9918 list,
9919 flatten_array_refs,
9920 stream,
9921 } => {
9922 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9923 if *stream {
9924 let out =
9925 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
9926 if ctx == WantarrayCtx::List {
9927 return Ok(out);
9928 }
9929 return Ok(PerlValue::integer(out.to_list().len() as i64));
9930 }
9931 let items = list_val.to_list();
9932 if items.len() == 1 {
9933 if let Some(p) = items[0].as_pipeline() {
9934 if *flatten_array_refs {
9935 return Err(PerlError::runtime(
9936 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
9937 line,
9938 )
9939 .into());
9940 }
9941 let sub = self.anon_coderef_from_block(block);
9942 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
9943 return Ok(PerlValue::pipeline(Arc::clone(&p)));
9944 }
9945 }
9946 let mut result = Vec::new();
9951 for item in items {
9952 self.scope.set_topic(item);
9953 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
9954 result.extend(val.map_flatten_outputs(*flatten_array_refs));
9955 }
9956 if ctx == WantarrayCtx::List {
9957 Ok(PerlValue::array(result))
9958 } else {
9959 Ok(PerlValue::integer(result.len() as i64))
9960 }
9961 }
9962 ExprKind::ForEachExpr { block, list } => {
9963 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9964 if list_val.is_iterator() {
9966 let iter = list_val.into_iterator();
9967 let mut count = 0i64;
9968 while let Some(item) = iter.next_item() {
9969 count += 1;
9970 self.scope.set_topic(item);
9971 self.exec_block(block)?;
9972 }
9973 return Ok(PerlValue::integer(count));
9974 }
9975 let items = list_val.to_list();
9976 let count = items.len();
9977 for item in items {
9978 self.scope.set_topic(item);
9979 self.exec_block(block)?;
9980 }
9981 Ok(PerlValue::integer(count as i64))
9982 }
9983 ExprKind::MapExprComma {
9984 expr,
9985 list,
9986 flatten_array_refs,
9987 stream,
9988 } => {
9989 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9990 if *stream {
9991 let out =
9992 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
9993 if ctx == WantarrayCtx::List {
9994 return Ok(out);
9995 }
9996 return Ok(PerlValue::integer(out.to_list().len() as i64));
9997 }
9998 let items = list_val.to_list();
9999 let mut result = Vec::new();
10000 for item in items {
10001 self.scope.set_topic(item.clone());
10002 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10003 result.extend(val.map_flatten_outputs(*flatten_array_refs));
10004 }
10005 if ctx == WantarrayCtx::List {
10006 Ok(PerlValue::array(result))
10007 } else {
10008 Ok(PerlValue::integer(result.len() as i64))
10009 }
10010 }
10011 ExprKind::GrepExpr {
10012 block,
10013 list,
10014 keyword,
10015 } => {
10016 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10017 if keyword.is_stream() {
10018 let out = self.filter_stream_block_output(list_val, block, line)?;
10019 if ctx == WantarrayCtx::List {
10020 return Ok(out);
10021 }
10022 return Ok(PerlValue::integer(out.to_list().len() as i64));
10023 }
10024 let items = list_val.to_list();
10025 if items.len() == 1 {
10026 if let Some(p) = items[0].as_pipeline() {
10027 let sub = self.anon_coderef_from_block(block);
10028 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
10029 return Ok(PerlValue::pipeline(Arc::clone(&p)));
10030 }
10031 }
10032 let mut result = Vec::new();
10033 for item in items {
10034 self.scope.set_topic(item.clone());
10035 let val = self.exec_block(block)?;
10036 let keep = if let Some(re) = val.as_regex() {
10039 re.is_match(&item.to_string())
10040 } else {
10041 val.is_true()
10042 };
10043 if keep {
10044 result.push(item);
10045 }
10046 }
10047 if ctx == WantarrayCtx::List {
10048 Ok(PerlValue::array(result))
10049 } else {
10050 Ok(PerlValue::integer(result.len() as i64))
10051 }
10052 }
10053 ExprKind::GrepExprComma {
10054 expr,
10055 list,
10056 keyword,
10057 } => {
10058 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10059 if keyword.is_stream() {
10060 let out = self.filter_stream_expr_output(list_val, expr, line)?;
10061 if ctx == WantarrayCtx::List {
10062 return Ok(out);
10063 }
10064 return Ok(PerlValue::integer(out.to_list().len() as i64));
10065 }
10066 let items = list_val.to_list();
10067 let mut result = Vec::new();
10068 for item in items {
10069 self.scope.set_topic(item.clone());
10070 let val = self.eval_expr(expr)?;
10071 let keep = if let Some(re) = val.as_regex() {
10072 re.is_match(&item.to_string())
10073 } else {
10074 val.is_true()
10075 };
10076 if keep {
10077 result.push(item);
10078 }
10079 }
10080 if ctx == WantarrayCtx::List {
10081 Ok(PerlValue::array(result))
10082 } else {
10083 Ok(PerlValue::integer(result.len() as i64))
10084 }
10085 }
10086 ExprKind::SortExpr { cmp, list } => {
10087 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10088 let mut items = list_val.to_list();
10089 match cmp {
10090 Some(SortComparator::Code(code_expr)) => {
10091 let sub = self.eval_expr(code_expr)?;
10092 let Some(sub) = sub.as_code_ref() else {
10093 return Err(PerlError::runtime(
10094 "sort: comparator must be a code reference",
10095 line,
10096 )
10097 .into());
10098 };
10099 let sub = sub.clone();
10100 items.sort_by(|a, b| {
10101 let _ = self.scope.set_scalar("a", a.clone());
10102 let _ = self.scope.set_scalar("b", b.clone());
10103 let _ = self.scope.set_scalar("_0", a.clone());
10104 let _ = self.scope.set_scalar("_1", b.clone());
10105 match self.call_sub(&sub, vec![], ctx, line) {
10106 Ok(v) => {
10107 let n = v.to_int();
10108 if n < 0 {
10109 Ordering::Less
10110 } else if n > 0 {
10111 Ordering::Greater
10112 } else {
10113 Ordering::Equal
10114 }
10115 }
10116 Err(_) => Ordering::Equal,
10117 }
10118 });
10119 }
10120 Some(SortComparator::Block(cmp_block)) => {
10121 if let Some(mode) = detect_sort_block_fast(cmp_block) {
10122 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
10123 } else {
10124 let cmp_block = cmp_block.clone();
10125 items.sort_by(|a, b| {
10126 let _ = self.scope.set_scalar("a", a.clone());
10127 let _ = self.scope.set_scalar("b", b.clone());
10128 let _ = self.scope.set_scalar("_0", a.clone());
10129 let _ = self.scope.set_scalar("_1", b.clone());
10130 match self.exec_block(&cmp_block) {
10131 Ok(v) => {
10132 let n = v.to_int();
10133 if n < 0 {
10134 Ordering::Less
10135 } else if n > 0 {
10136 Ordering::Greater
10137 } else {
10138 Ordering::Equal
10139 }
10140 }
10141 Err(_) => Ordering::Equal,
10142 }
10143 });
10144 }
10145 }
10146 None => {
10147 items.sort_by_key(|a| a.to_string());
10148 }
10149 }
10150 Ok(PerlValue::array(items))
10151 }
10152 ExprKind::Rev(expr) => {
10153 let val = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
10155 if val.is_iterator() {
10156 return Ok(PerlValue::iterator(Arc::new(
10157 crate::value::RevIterator::new(val.into_iterator()),
10158 )));
10159 }
10160 if let Some(s) = crate::value::set_payload(&val) {
10161 let mut out = crate::value::PerlSet::new();
10162 for (k, v) in s.iter().rev() {
10163 out.insert(k.clone(), v.clone());
10164 }
10165 return Ok(PerlValue::set(Arc::new(out)));
10166 }
10167 if let Some(ar) = val.as_array_ref() {
10168 let items: Vec<_> = ar.read().iter().rev().cloned().collect();
10169 return Ok(PerlValue::array_ref(Arc::new(parking_lot::RwLock::new(
10170 items,
10171 ))));
10172 }
10173 if let Some(hr) = val.as_hash_ref() {
10174 let mut out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
10175 for (k, v) in hr.read().iter() {
10176 out.insert(v.to_string(), PerlValue::string(k.clone()));
10177 }
10178 return Ok(PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(out))));
10179 }
10180 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10182 if let Some(hm) = val.as_hash_map() {
10183 let mut out: indexmap::IndexMap<String, PerlValue> = indexmap::IndexMap::new();
10184 for (k, v) in hm.iter() {
10185 out.insert(v.to_string(), PerlValue::string(k.clone()));
10186 }
10187 return Ok(PerlValue::hash(out));
10188 }
10189 if val.as_array_vec().is_some() {
10190 let mut items = val.to_list();
10191 items.reverse();
10192 Ok(PerlValue::array(items))
10193 } else {
10194 let items = val.to_list();
10195 if items.len() > 1 {
10196 let mut items = items;
10197 items.reverse();
10198 Ok(PerlValue::array(items))
10199 } else {
10200 let s = val.to_string();
10201 Ok(PerlValue::string(s.chars().rev().collect()))
10202 }
10203 }
10204 }
10205 ExprKind::ReverseExpr(list) => {
10206 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10207 match ctx {
10208 WantarrayCtx::List => {
10209 let mut items = val.to_list();
10210 items.reverse();
10211 Ok(PerlValue::array(items))
10212 }
10213 _ => {
10214 let items = val.to_list();
10215 let s: String = items.iter().map(|v| v.to_string()).collect();
10216 Ok(PerlValue::string(s.chars().rev().collect()))
10217 }
10218 }
10219 }
10220
10221 ExprKind::ParLinesExpr {
10223 path,
10224 callback,
10225 progress,
10226 } => self.eval_par_lines_expr(
10227 path.as_ref(),
10228 callback.as_ref(),
10229 progress.as_deref(),
10230 line,
10231 ),
10232 ExprKind::ParWalkExpr {
10233 path,
10234 callback,
10235 progress,
10236 } => {
10237 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
10238 }
10239 ExprKind::PwatchExpr { path, callback } => {
10240 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
10241 }
10242 ExprKind::PMapExpr {
10243 block,
10244 list,
10245 progress,
10246 flat_outputs,
10247 on_cluster,
10248 stream,
10249 } => {
10250 let show_progress = progress
10251 .as_ref()
10252 .map(|p| self.eval_expr(p))
10253 .transpose()?
10254 .map(|v| v.is_true())
10255 .unwrap_or(false);
10256 let list_val = self.eval_expr(list)?;
10257 if let Some(cluster_e) = on_cluster {
10258 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
10259 return self.eval_pmap_remote(
10260 cluster_val,
10261 list_val,
10262 show_progress,
10263 block,
10264 *flat_outputs,
10265 line,
10266 );
10267 }
10268 if *stream {
10269 let source = crate::map_stream::into_pull_iter(list_val);
10270 let sub = self.anon_coderef_from_block(block);
10271 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
10272 return Ok(PerlValue::iterator(Arc::new(
10273 crate::map_stream::PMapStreamIterator::new(
10274 source,
10275 sub,
10276 self.subs.clone(),
10277 capture,
10278 atomic_arrays,
10279 atomic_hashes,
10280 *flat_outputs,
10281 ),
10282 )));
10283 }
10284 let items = list_val.to_list();
10285 let block = block.clone();
10286 let subs = self.subs.clone();
10287 let (scope_capture, atomic_arrays, atomic_hashes) =
10288 self.scope.capture_with_atomics();
10289 let pmap_progress = PmapProgress::new(show_progress, items.len());
10290
10291 if *flat_outputs {
10292 let mut indexed: Vec<(usize, Vec<PerlValue>)> = items
10293 .into_par_iter()
10294 .enumerate()
10295 .map(|(i, item)| {
10296 let mut local_interp = Interpreter::new();
10297 local_interp.subs = subs.clone();
10298 local_interp.scope.restore_capture(&scope_capture);
10299 local_interp
10300 .scope
10301 .restore_atomics(&atomic_arrays, &atomic_hashes);
10302 local_interp.enable_parallel_guard();
10303 local_interp.scope.set_topic(item);
10304 let val = match local_interp.exec_block(&block) {
10305 Ok(val) => val,
10306 Err(_) => PerlValue::UNDEF,
10307 };
10308 let chunk = val.map_flatten_outputs(true);
10309 pmap_progress.tick();
10310 (i, chunk)
10311 })
10312 .collect();
10313 pmap_progress.finish();
10314 indexed.sort_by_key(|(i, _)| *i);
10315 let results: Vec<PerlValue> =
10316 indexed.into_iter().flat_map(|(_, v)| v).collect();
10317 Ok(PerlValue::array(results))
10318 } else {
10319 let results: Vec<PerlValue> = items
10320 .into_par_iter()
10321 .map(|item| {
10322 let mut local_interp = Interpreter::new();
10323 local_interp.subs = subs.clone();
10324 local_interp.scope.restore_capture(&scope_capture);
10325 local_interp
10326 .scope
10327 .restore_atomics(&atomic_arrays, &atomic_hashes);
10328 local_interp.enable_parallel_guard();
10329 local_interp.scope.set_topic(item);
10330 let val = match local_interp.exec_block(&block) {
10331 Ok(val) => val,
10332 Err(_) => PerlValue::UNDEF,
10333 };
10334 pmap_progress.tick();
10335 val
10336 })
10337 .collect();
10338 pmap_progress.finish();
10339 Ok(PerlValue::array(results))
10340 }
10341 }
10342 ExprKind::PMapChunkedExpr {
10343 chunk_size,
10344 block,
10345 list,
10346 progress,
10347 } => {
10348 let show_progress = progress
10349 .as_ref()
10350 .map(|p| self.eval_expr(p))
10351 .transpose()?
10352 .map(|v| v.is_true())
10353 .unwrap_or(false);
10354 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
10355 let list_val = self.eval_expr(list)?;
10356 let items = list_val.to_list();
10357 let block = block.clone();
10358 let subs = self.subs.clone();
10359 let (scope_capture, atomic_arrays, atomic_hashes) =
10360 self.scope.capture_with_atomics();
10361
10362 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = items
10363 .chunks(chunk_n)
10364 .enumerate()
10365 .map(|(i, c)| (i, c.to_vec()))
10366 .collect();
10367
10368 let n_chunks = indexed_chunks.len();
10369 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
10370
10371 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
10372 .into_par_iter()
10373 .map(|(chunk_idx, chunk)| {
10374 let mut local_interp = Interpreter::new();
10375 local_interp.subs = subs.clone();
10376 local_interp.scope.restore_capture(&scope_capture);
10377 local_interp
10378 .scope
10379 .restore_atomics(&atomic_arrays, &atomic_hashes);
10380 local_interp.enable_parallel_guard();
10381 let mut out = Vec::with_capacity(chunk.len());
10382 for item in chunk {
10383 local_interp.scope.set_topic(item);
10384 match local_interp.exec_block(&block) {
10385 Ok(val) => out.push(val),
10386 Err(_) => out.push(PerlValue::UNDEF),
10387 }
10388 }
10389 pmap_progress.tick();
10390 (chunk_idx, out)
10391 })
10392 .collect();
10393
10394 pmap_progress.finish();
10395 chunk_results.sort_by_key(|(i, _)| *i);
10396 let results: Vec<PerlValue> =
10397 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
10398 Ok(PerlValue::array(results))
10399 }
10400 ExprKind::PGrepExpr {
10401 block,
10402 list,
10403 progress,
10404 stream,
10405 } => {
10406 let show_progress = progress
10407 .as_ref()
10408 .map(|p| self.eval_expr(p))
10409 .transpose()?
10410 .map(|v| v.is_true())
10411 .unwrap_or(false);
10412 let list_val = self.eval_expr(list)?;
10413 if *stream {
10414 let source = crate::map_stream::into_pull_iter(list_val);
10415 let sub = self.anon_coderef_from_block(block);
10416 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
10417 return Ok(PerlValue::iterator(Arc::new(
10418 crate::map_stream::PGrepStreamIterator::new(
10419 source,
10420 sub,
10421 self.subs.clone(),
10422 capture,
10423 atomic_arrays,
10424 atomic_hashes,
10425 ),
10426 )));
10427 }
10428 let items = list_val.to_list();
10429 let block = block.clone();
10430 let subs = self.subs.clone();
10431 let (scope_capture, atomic_arrays, atomic_hashes) =
10432 self.scope.capture_with_atomics();
10433 let pmap_progress = PmapProgress::new(show_progress, items.len());
10434
10435 let results: Vec<PerlValue> = items
10436 .into_par_iter()
10437 .filter_map(|item| {
10438 let mut local_interp = Interpreter::new();
10439 local_interp.subs = subs.clone();
10440 local_interp.scope.restore_capture(&scope_capture);
10441 local_interp
10442 .scope
10443 .restore_atomics(&atomic_arrays, &atomic_hashes);
10444 local_interp.enable_parallel_guard();
10445 local_interp.scope.set_topic(item.clone());
10446 let keep = match local_interp.exec_block(&block) {
10447 Ok(val) => val.is_true(),
10448 Err(_) => false,
10449 };
10450 pmap_progress.tick();
10451 if keep {
10452 Some(item)
10453 } else {
10454 None
10455 }
10456 })
10457 .collect();
10458 pmap_progress.finish();
10459 Ok(PerlValue::array(results))
10460 }
10461 ExprKind::PForExpr {
10462 block,
10463 list,
10464 progress,
10465 } => {
10466 let show_progress = progress
10467 .as_ref()
10468 .map(|p| self.eval_expr(p))
10469 .transpose()?
10470 .map(|v| v.is_true())
10471 .unwrap_or(false);
10472 let list_val = self.eval_expr(list)?;
10473 let items = list_val.to_list();
10474 let block = block.clone();
10475 let subs = self.subs.clone();
10476 let (scope_capture, atomic_arrays, atomic_hashes) =
10477 self.scope.capture_with_atomics();
10478
10479 let pmap_progress = PmapProgress::new(show_progress, items.len());
10480 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10481 items.into_par_iter().for_each(|item| {
10482 if first_err.lock().is_some() {
10483 return;
10484 }
10485 let mut local_interp = Interpreter::new();
10486 local_interp.subs = subs.clone();
10487 local_interp.scope.restore_capture(&scope_capture);
10488 local_interp
10489 .scope
10490 .restore_atomics(&atomic_arrays, &atomic_hashes);
10491 local_interp.enable_parallel_guard();
10492 local_interp.scope.set_topic(item);
10493 match local_interp.exec_block(&block) {
10494 Ok(_) => {}
10495 Err(e) => {
10496 let stryke = match e {
10497 FlowOrError::Error(stryke) => stryke,
10498 FlowOrError::Flow(_) => PerlError::runtime(
10499 "return/last/next/redo not supported inside pfor block",
10500 line,
10501 ),
10502 };
10503 let mut g = first_err.lock();
10504 if g.is_none() {
10505 *g = Some(stryke);
10506 }
10507 }
10508 }
10509 pmap_progress.tick();
10510 });
10511 pmap_progress.finish();
10512 if let Some(e) = first_err.lock().take() {
10513 return Err(FlowOrError::Error(e));
10514 }
10515 Ok(PerlValue::UNDEF)
10516 }
10517 ExprKind::FanExpr {
10518 count,
10519 block,
10520 progress,
10521 capture,
10522 } => {
10523 let show_progress = progress
10524 .as_ref()
10525 .map(|p| self.eval_expr(p))
10526 .transpose()?
10527 .map(|v| v.is_true())
10528 .unwrap_or(false);
10529 let n = match count {
10530 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
10531 None => self.parallel_thread_count(),
10532 };
10533 let block = block.clone();
10534 let subs = self.subs.clone();
10535 let (scope_capture, atomic_arrays, atomic_hashes) =
10536 self.scope.capture_with_atomics();
10537
10538 let fan_progress = FanProgress::new(show_progress, n);
10539 if *capture {
10540 if n == 0 {
10541 return Ok(PerlValue::array(Vec::new()));
10542 }
10543 let pairs: Vec<(usize, ExecResult)> = (0..n)
10544 .into_par_iter()
10545 .map(|i| {
10546 fan_progress.start_worker(i);
10547 let mut local_interp = Interpreter::new();
10548 local_interp.subs = subs.clone();
10549 local_interp.suppress_stdout = show_progress;
10550 local_interp.scope.restore_capture(&scope_capture);
10551 local_interp
10552 .scope
10553 .restore_atomics(&atomic_arrays, &atomic_hashes);
10554 local_interp.enable_parallel_guard();
10555 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10556 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10557 let res = local_interp.exec_block(&block);
10558 crate::parallel_trace::fan_worker_set_index(None);
10559 fan_progress.finish_worker(i);
10560 (i, res)
10561 })
10562 .collect();
10563 fan_progress.finish();
10564 let mut pairs = pairs;
10565 pairs.sort_by_key(|(i, _)| *i);
10566 let mut out = Vec::with_capacity(n);
10567 for (_, r) in pairs {
10568 match r {
10569 Ok(v) => out.push(v),
10570 Err(e) => return Err(e),
10571 }
10572 }
10573 return Ok(PerlValue::array(out));
10574 }
10575 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10576 (0..n).into_par_iter().for_each(|i| {
10577 if first_err.lock().is_some() {
10578 return;
10579 }
10580 fan_progress.start_worker(i);
10581 let mut local_interp = Interpreter::new();
10582 local_interp.subs = subs.clone();
10583 local_interp.suppress_stdout = show_progress;
10584 local_interp.scope.restore_capture(&scope_capture);
10585 local_interp
10586 .scope
10587 .restore_atomics(&atomic_arrays, &atomic_hashes);
10588 local_interp.enable_parallel_guard();
10589 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10590 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10591 match local_interp.exec_block(&block) {
10592 Ok(_) => {}
10593 Err(e) => {
10594 let stryke = match e {
10595 FlowOrError::Error(stryke) => stryke,
10596 FlowOrError::Flow(_) => PerlError::runtime(
10597 "return/last/next/redo not supported inside fan block",
10598 line,
10599 ),
10600 };
10601 let mut g = first_err.lock();
10602 if g.is_none() {
10603 *g = Some(stryke);
10604 }
10605 }
10606 }
10607 crate::parallel_trace::fan_worker_set_index(None);
10608 fan_progress.finish_worker(i);
10609 });
10610 fan_progress.finish();
10611 if let Some(e) = first_err.lock().take() {
10612 return Err(FlowOrError::Error(e));
10613 }
10614 Ok(PerlValue::UNDEF)
10615 }
10616 ExprKind::RetryBlock {
10617 body,
10618 times,
10619 backoff,
10620 } => self.eval_retry_block(body, times, *backoff, line),
10621 ExprKind::RateLimitBlock {
10622 slot,
10623 max,
10624 window,
10625 body,
10626 } => self.eval_rate_limit_block(*slot, max, window, body, line),
10627 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
10628 ExprKind::GenBlock { body } => {
10629 let g = Arc::new(PerlGenerator {
10630 block: body.clone(),
10631 pc: Mutex::new(0),
10632 scope_started: Mutex::new(false),
10633 exhausted: Mutex::new(false),
10634 });
10635 Ok(PerlValue::generator(g))
10636 }
10637 ExprKind::Yield(e) => {
10638 if !self.in_generator {
10639 return Err(PerlError::runtime("yield outside gen block", line).into());
10640 }
10641 let v = self.eval_expr(e)?;
10642 Err(FlowOrError::Flow(Flow::Yield(v)))
10643 }
10644 ExprKind::AlgebraicMatch { subject, arms } => {
10645 self.eval_algebraic_match(subject, arms, line)
10646 }
10647 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
10648 Ok(self.spawn_async_block(body))
10649 }
10650 ExprKind::Trace { body } => {
10651 crate::parallel_trace::trace_enter();
10652 let out = self.exec_block(body);
10653 crate::parallel_trace::trace_leave();
10654 out
10655 }
10656 ExprKind::Spinner { message, body } => {
10657 use std::io::Write as _;
10658 let msg = self.eval_expr(message)?.to_string();
10659 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
10660 let done2 = done.clone();
10661 let handle = std::thread::spawn(move || {
10662 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
10663 let mut i = 0;
10664 let stderr = std::io::stderr();
10665 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
10666 {
10667 let stdout = std::io::stdout();
10668 let _stdout_lock = stdout.lock();
10669 let mut err = stderr.lock();
10670 let _ = write!(
10671 err,
10672 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
10673 frames[i % frames.len()],
10674 msg
10675 );
10676 let _ = err.flush();
10677 }
10678 std::thread::sleep(std::time::Duration::from_millis(80));
10679 i += 1;
10680 }
10681 let mut err = stderr.lock();
10682 let _ = write!(err, "\r\x1b[2K");
10683 let _ = err.flush();
10684 });
10685 let result = self.exec_block(body);
10686 done.store(true, std::sync::atomic::Ordering::Relaxed);
10687 let _ = handle.join();
10688 result
10689 }
10690 ExprKind::Timer { body } => {
10691 let start = std::time::Instant::now();
10692 self.exec_block(body)?;
10693 let ms = start.elapsed().as_secs_f64() * 1000.0;
10694 Ok(PerlValue::float(ms))
10695 }
10696 ExprKind::Bench { body, times } => {
10697 let n = self.eval_expr(times)?.to_int();
10698 if n < 0 {
10699 return Err(PerlError::runtime(
10700 "bench: iteration count must be non-negative",
10701 line,
10702 )
10703 .into());
10704 }
10705 self.run_bench_block(body, n as usize, line)
10706 }
10707 ExprKind::Await(expr) => {
10708 let v = self.eval_expr(expr)?;
10709 if let Some(t) = v.as_async_task() {
10710 t.await_result().map_err(FlowOrError::from)
10711 } else {
10712 Ok(v)
10713 }
10714 }
10715 ExprKind::Slurp(e) => {
10716 let path = self.eval_expr(e)?.to_string();
10717 read_file_text_perl_compat(&path)
10718 .map(PerlValue::string)
10719 .map_err(|e| {
10720 FlowOrError::Error(PerlError::runtime(format!("slurp: {}", e), line))
10721 })
10722 }
10723 ExprKind::Capture(e) => {
10724 let cmd = self.eval_expr(e)?.to_string();
10725 let output = Command::new("sh")
10726 .arg("-c")
10727 .arg(&cmd)
10728 .output()
10729 .map_err(|e| {
10730 FlowOrError::Error(PerlError::runtime(format!("capture: {}", e), line))
10731 })?;
10732 self.record_child_exit_status(output.status);
10733 let exitcode = output.status.code().unwrap_or(-1) as i64;
10734 let stdout = decode_utf8_or_latin1(&output.stdout);
10735 let stderr = decode_utf8_or_latin1(&output.stderr);
10736 Ok(PerlValue::capture(Arc::new(CaptureResult {
10737 stdout,
10738 stderr,
10739 exitcode,
10740 })))
10741 }
10742 ExprKind::Qx(e) => {
10743 let cmd = self.eval_expr(e)?.to_string();
10744 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)
10745 }
10746 ExprKind::FetchUrl(e) => {
10747 let url = self.eval_expr(e)?.to_string();
10748 ureq::get(&url)
10749 .call()
10750 .map_err(|e| {
10751 FlowOrError::Error(PerlError::runtime(format!("fetch_url: {}", e), line))
10752 })
10753 .and_then(|r| {
10754 r.into_string().map(PerlValue::string).map_err(|e| {
10755 FlowOrError::Error(PerlError::runtime(
10756 format!("fetch_url: {}", e),
10757 line,
10758 ))
10759 })
10760 })
10761 }
10762 ExprKind::Pchannel { capacity } => {
10763 if let Some(c) = capacity {
10764 let n = self.eval_expr(c)?.to_int().max(1) as usize;
10765 Ok(crate::pchannel::create_bounded_pair(n))
10766 } else {
10767 Ok(crate::pchannel::create_pair())
10768 }
10769 }
10770 ExprKind::PSortExpr {
10771 cmp,
10772 list,
10773 progress,
10774 } => {
10775 let show_progress = progress
10776 .as_ref()
10777 .map(|p| self.eval_expr(p))
10778 .transpose()?
10779 .map(|v| v.is_true())
10780 .unwrap_or(false);
10781 let list_val = self.eval_expr(list)?;
10782 let mut items = list_val.to_list();
10783 let pmap_progress = PmapProgress::new(show_progress, 2);
10784 pmap_progress.tick();
10785 if let Some(cmp_block) = cmp {
10786 if let Some(mode) = detect_sort_block_fast(cmp_block) {
10787 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
10788 } else {
10789 let cmp_block = cmp_block.clone();
10790 let subs = self.subs.clone();
10791 let scope_capture = self.scope.capture();
10792 items.par_sort_by(|a, b| {
10793 let mut local_interp = Interpreter::new();
10794 local_interp.subs = subs.clone();
10795 local_interp.scope.restore_capture(&scope_capture);
10796 let _ = local_interp.scope.set_scalar("a", a.clone());
10797 let _ = local_interp.scope.set_scalar("b", b.clone());
10798 let _ = local_interp.scope.set_scalar("_0", a.clone());
10799 let _ = local_interp.scope.set_scalar("_1", b.clone());
10800 match local_interp.exec_block(&cmp_block) {
10801 Ok(v) => {
10802 let n = v.to_int();
10803 if n < 0 {
10804 std::cmp::Ordering::Less
10805 } else if n > 0 {
10806 std::cmp::Ordering::Greater
10807 } else {
10808 std::cmp::Ordering::Equal
10809 }
10810 }
10811 Err(_) => std::cmp::Ordering::Equal,
10812 }
10813 });
10814 }
10815 } else {
10816 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
10817 }
10818 pmap_progress.tick();
10819 pmap_progress.finish();
10820 Ok(PerlValue::array(items))
10821 }
10822
10823 ExprKind::ReduceExpr { block, list } => {
10824 let list_val = self.eval_expr(list)?;
10825 let items = list_val.to_list();
10826 if items.is_empty() {
10827 return Ok(PerlValue::UNDEF);
10828 }
10829 if items.len() == 1 {
10830 return Ok(items.into_iter().next().unwrap());
10831 }
10832 let block = block.clone();
10833 let subs = self.subs.clone();
10834 let scope_capture = self.scope.capture();
10835 let mut acc = items[0].clone();
10836 for b in items.into_iter().skip(1) {
10837 let mut local_interp = Interpreter::new();
10838 local_interp.subs = subs.clone();
10839 local_interp.scope.restore_capture(&scope_capture);
10840 let _ = local_interp.scope.set_scalar("a", acc.clone());
10841 let _ = local_interp.scope.set_scalar("b", b.clone());
10842 let _ = local_interp.scope.set_scalar("_0", acc);
10843 let _ = local_interp.scope.set_scalar("_1", b);
10844 acc = match local_interp.exec_block(&block) {
10845 Ok(val) => val,
10846 Err(_) => PerlValue::UNDEF,
10847 };
10848 }
10849 Ok(acc)
10850 }
10851
10852 ExprKind::PReduceExpr {
10853 block,
10854 list,
10855 progress,
10856 } => {
10857 let show_progress = progress
10858 .as_ref()
10859 .map(|p| self.eval_expr(p))
10860 .transpose()?
10861 .map(|v| v.is_true())
10862 .unwrap_or(false);
10863 let list_val = self.eval_expr(list)?;
10864 let items = list_val.to_list();
10865 if items.is_empty() {
10866 return Ok(PerlValue::UNDEF);
10867 }
10868 if items.len() == 1 {
10869 return Ok(items.into_iter().next().unwrap());
10870 }
10871 let block = block.clone();
10872 let subs = self.subs.clone();
10873 let scope_capture = self.scope.capture();
10874 let pmap_progress = PmapProgress::new(show_progress, items.len());
10875
10876 let result = items
10877 .into_par_iter()
10878 .map(|x| {
10879 pmap_progress.tick();
10880 x
10881 })
10882 .reduce_with(|a, b| {
10883 let mut local_interp = Interpreter::new();
10884 local_interp.subs = subs.clone();
10885 local_interp.scope.restore_capture(&scope_capture);
10886 let _ = local_interp.scope.set_scalar("a", a.clone());
10887 let _ = local_interp.scope.set_scalar("b", b.clone());
10888 let _ = local_interp.scope.set_scalar("_0", a);
10889 let _ = local_interp.scope.set_scalar("_1", b);
10890 match local_interp.exec_block(&block) {
10891 Ok(val) => val,
10892 Err(_) => PerlValue::UNDEF,
10893 }
10894 });
10895 pmap_progress.finish();
10896 Ok(result.unwrap_or(PerlValue::UNDEF))
10897 }
10898
10899 ExprKind::PReduceInitExpr {
10900 init,
10901 block,
10902 list,
10903 progress,
10904 } => {
10905 let show_progress = progress
10906 .as_ref()
10907 .map(|p| self.eval_expr(p))
10908 .transpose()?
10909 .map(|v| v.is_true())
10910 .unwrap_or(false);
10911 let init_val = self.eval_expr(init)?;
10912 let list_val = self.eval_expr(list)?;
10913 let items = list_val.to_list();
10914 if items.is_empty() {
10915 return Ok(init_val);
10916 }
10917 let block = block.clone();
10918 let subs = self.subs.clone();
10919 let scope_capture = self.scope.capture();
10920 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
10921 if items.len() == 1 {
10922 return Ok(fold_preduce_init_step(
10923 &subs,
10924 cap,
10925 &block,
10926 preduce_init_fold_identity(&init_val),
10927 items.into_iter().next().unwrap(),
10928 ));
10929 }
10930 let pmap_progress = PmapProgress::new(show_progress, items.len());
10931 let result = items
10932 .into_par_iter()
10933 .fold(
10934 || preduce_init_fold_identity(&init_val),
10935 |acc, item| {
10936 pmap_progress.tick();
10937 fold_preduce_init_step(&subs, cap, &block, acc, item)
10938 },
10939 )
10940 .reduce(
10941 || preduce_init_fold_identity(&init_val),
10942 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
10943 );
10944 pmap_progress.finish();
10945 Ok(result)
10946 }
10947
10948 ExprKind::PMapReduceExpr {
10949 map_block,
10950 reduce_block,
10951 list,
10952 progress,
10953 } => {
10954 let show_progress = progress
10955 .as_ref()
10956 .map(|p| self.eval_expr(p))
10957 .transpose()?
10958 .map(|v| v.is_true())
10959 .unwrap_or(false);
10960 let list_val = self.eval_expr(list)?;
10961 let items = list_val.to_list();
10962 if items.is_empty() {
10963 return Ok(PerlValue::UNDEF);
10964 }
10965 let map_block = map_block.clone();
10966 let reduce_block = reduce_block.clone();
10967 let subs = self.subs.clone();
10968 let scope_capture = self.scope.capture();
10969 if items.len() == 1 {
10970 let mut local_interp = Interpreter::new();
10971 local_interp.subs = subs.clone();
10972 local_interp.scope.restore_capture(&scope_capture);
10973 local_interp.scope.set_topic(items[0].clone());
10974 return match local_interp.exec_block_no_scope(&map_block) {
10975 Ok(v) => Ok(v),
10976 Err(_) => Ok(PerlValue::UNDEF),
10977 };
10978 }
10979 let pmap_progress = PmapProgress::new(show_progress, items.len());
10980 let result = items
10981 .into_par_iter()
10982 .map(|item| {
10983 let mut local_interp = Interpreter::new();
10984 local_interp.subs = subs.clone();
10985 local_interp.scope.restore_capture(&scope_capture);
10986 local_interp.scope.set_topic(item);
10987 let val = match local_interp.exec_block_no_scope(&map_block) {
10988 Ok(val) => val,
10989 Err(_) => PerlValue::UNDEF,
10990 };
10991 pmap_progress.tick();
10992 val
10993 })
10994 .reduce_with(|a, b| {
10995 let mut local_interp = Interpreter::new();
10996 local_interp.subs = subs.clone();
10997 local_interp.scope.restore_capture(&scope_capture);
10998 let _ = local_interp.scope.set_scalar("a", a.clone());
10999 let _ = local_interp.scope.set_scalar("b", b.clone());
11000 let _ = local_interp.scope.set_scalar("_0", a);
11001 let _ = local_interp.scope.set_scalar("_1", b);
11002 match local_interp.exec_block_no_scope(&reduce_block) {
11003 Ok(val) => val,
11004 Err(_) => PerlValue::UNDEF,
11005 }
11006 });
11007 pmap_progress.finish();
11008 Ok(result.unwrap_or(PerlValue::UNDEF))
11009 }
11010
11011 ExprKind::PcacheExpr {
11012 block,
11013 list,
11014 progress,
11015 } => {
11016 let show_progress = progress
11017 .as_ref()
11018 .map(|p| self.eval_expr(p))
11019 .transpose()?
11020 .map(|v| v.is_true())
11021 .unwrap_or(false);
11022 let list_val = self.eval_expr(list)?;
11023 let items = list_val.to_list();
11024 let block = block.clone();
11025 let subs = self.subs.clone();
11026 let scope_capture = self.scope.capture();
11027 let cache = &*crate::pcache::GLOBAL_PCACHE;
11028 let pmap_progress = PmapProgress::new(show_progress, items.len());
11029 let results: Vec<PerlValue> = items
11030 .into_par_iter()
11031 .map(|item| {
11032 let k = crate::pcache::cache_key(&item);
11033 if let Some(v) = cache.get(&k) {
11034 pmap_progress.tick();
11035 return v.clone();
11036 }
11037 let mut local_interp = Interpreter::new();
11038 local_interp.subs = subs.clone();
11039 local_interp.scope.restore_capture(&scope_capture);
11040 local_interp.scope.set_topic(item.clone());
11041 let val = match local_interp.exec_block_no_scope(&block) {
11042 Ok(v) => v,
11043 Err(_) => PerlValue::UNDEF,
11044 };
11045 cache.insert(k, val.clone());
11046 pmap_progress.tick();
11047 val
11048 })
11049 .collect();
11050 pmap_progress.finish();
11051 Ok(PerlValue::array(results))
11052 }
11053
11054 ExprKind::PselectExpr { receivers, timeout } => {
11055 let mut rx_vals = Vec::with_capacity(receivers.len());
11056 for r in receivers {
11057 rx_vals.push(self.eval_expr(r)?);
11058 }
11059 let dur = if let Some(t) = timeout.as_ref() {
11060 Some(std::time::Duration::from_secs_f64(
11061 self.eval_expr(t)?.to_number().max(0.0),
11062 ))
11063 } else {
11064 None
11065 };
11066 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
11067 &rx_vals, dur, line,
11068 )?)
11069 }
11070
11071 ExprKind::Push { array, values } => {
11073 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
11074 }
11075 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
11076 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
11077 ExprKind::Unshift { array, values } => {
11078 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
11079 }
11080 ExprKind::Splice {
11081 array,
11082 offset,
11083 length,
11084 replacement,
11085 } => self.eval_splice_expr(
11086 array.as_ref(),
11087 offset.as_deref(),
11088 length.as_deref(),
11089 replacement.as_slice(),
11090 ctx,
11091 line,
11092 ),
11093 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
11094 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
11095 ExprKind::Keys(expr) => {
11096 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11097 let keys = Self::keys_from_value(val, line)?;
11098 if ctx == WantarrayCtx::List {
11099 Ok(keys)
11100 } else {
11101 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
11102 Ok(PerlValue::integer(n as i64))
11103 }
11104 }
11105 ExprKind::Values(expr) => {
11106 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11107 let vals = Self::values_from_value(val, line)?;
11108 if ctx == WantarrayCtx::List {
11109 Ok(vals)
11110 } else {
11111 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
11112 Ok(PerlValue::integer(n as i64))
11113 }
11114 }
11115 ExprKind::Each(_) => {
11116 Ok(PerlValue::array(vec![]))
11118 }
11119
11120 ExprKind::Chomp(expr) => {
11122 let val = self.eval_expr(expr)?;
11123 self.chomp_inplace_execute(val, expr)
11124 }
11125 ExprKind::Chop(expr) => {
11126 let val = self.eval_expr(expr)?;
11127 self.chop_inplace_execute(val, expr)
11128 }
11129 ExprKind::Length(expr) => {
11130 let val = self.eval_expr(expr)?;
11131 Ok(if let Some(a) = val.as_array_vec() {
11132 PerlValue::integer(a.len() as i64)
11133 } else if let Some(h) = val.as_hash_map() {
11134 PerlValue::integer(h.len() as i64)
11135 } else if let Some(b) = val.as_bytes_arc() {
11136 PerlValue::integer(b.len() as i64)
11137 } else {
11138 PerlValue::integer(val.to_string().len() as i64)
11139 })
11140 }
11141 ExprKind::Substr {
11142 string,
11143 offset,
11144 length,
11145 replacement,
11146 } => self.eval_substr_expr(
11147 string.as_ref(),
11148 offset.as_ref(),
11149 length.as_deref(),
11150 replacement.as_deref(),
11151 line,
11152 ),
11153 ExprKind::Index {
11154 string,
11155 substr,
11156 position,
11157 } => {
11158 let s = self.eval_expr(string)?.to_string();
11159 let sub = self.eval_expr(substr)?.to_string();
11160 let pos = if let Some(p) = position {
11161 self.eval_expr(p)?.to_int() as usize
11162 } else {
11163 0
11164 };
11165 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
11166 Ok(PerlValue::integer(result))
11167 }
11168 ExprKind::Rindex {
11169 string,
11170 substr,
11171 position,
11172 } => {
11173 let s = self.eval_expr(string)?.to_string();
11174 let sub = self.eval_expr(substr)?.to_string();
11175 let end = if let Some(p) = position {
11176 self.eval_expr(p)?.to_int() as usize + sub.len()
11177 } else {
11178 s.len()
11179 };
11180 let search = &s[..end.min(s.len())];
11181 let result = search.rfind(&sub).map(|i| i as i64).unwrap_or(-1);
11182 Ok(PerlValue::integer(result))
11183 }
11184 ExprKind::Sprintf { format, args } => {
11185 let fmt = self.eval_expr(format)?.to_string();
11186 let mut arg_vals = Vec::new();
11189 for a in args {
11190 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
11191 if let Some(items) = v.as_array_vec() {
11192 arg_vals.extend(items);
11193 } else {
11194 arg_vals.push(v);
11195 }
11196 }
11197 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
11198 Ok(PerlValue::string(s))
11199 }
11200 ExprKind::JoinExpr { separator, list } => {
11201 let sep = self.eval_expr(separator)?.to_string();
11202 let items = if let ExprKind::List(exprs) = &list.kind {
11206 let saved = self.wantarray_kind;
11207 self.wantarray_kind = WantarrayCtx::List;
11208 let mut vals = Vec::new();
11209 for e in exprs {
11210 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
11211 if let Some(items) = v.as_array_vec() {
11212 vals.extend(items);
11213 } else {
11214 vals.push(v);
11215 }
11216 }
11217 self.wantarray_kind = saved;
11218 vals
11219 } else {
11220 let saved = self.wantarray_kind;
11221 self.wantarray_kind = WantarrayCtx::List;
11222 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11223 self.wantarray_kind = saved;
11224 if let Some(items) = v.as_array_vec() {
11225 items
11226 } else {
11227 vec![v]
11228 }
11229 };
11230 let mut strs = Vec::with_capacity(items.len());
11231 for v in &items {
11232 strs.push(self.stringify_value(v.clone(), line)?);
11233 }
11234 Ok(PerlValue::string(strs.join(&sep)))
11235 }
11236 ExprKind::SplitExpr {
11237 pattern,
11238 string,
11239 limit,
11240 } => {
11241 let pat = self.eval_expr(pattern)?.to_string();
11242 let s = self.eval_expr(string)?.to_string();
11243 let lim = if let Some(l) = limit {
11244 self.eval_expr(l)?.to_int() as usize
11245 } else {
11246 0
11247 };
11248 let re = self.compile_regex(&pat, "", line)?;
11249 let parts: Vec<PerlValue> = if lim > 0 {
11250 re.splitn_strings(&s, lim)
11251 .into_iter()
11252 .map(PerlValue::string)
11253 .collect()
11254 } else {
11255 re.split_strings(&s)
11256 .into_iter()
11257 .map(PerlValue::string)
11258 .collect()
11259 };
11260 Ok(PerlValue::array(parts))
11261 }
11262
11263 ExprKind::Abs(expr) => {
11265 let val = self.eval_expr(expr)?;
11266 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
11267 return r;
11268 }
11269 Ok(PerlValue::float(val.to_number().abs()))
11270 }
11271 ExprKind::Int(expr) => {
11272 let val = self.eval_expr(expr)?;
11273 Ok(PerlValue::integer(val.to_number() as i64))
11274 }
11275 ExprKind::Sqrt(expr) => {
11276 let val = self.eval_expr(expr)?;
11277 Ok(PerlValue::float(val.to_number().sqrt()))
11278 }
11279 ExprKind::Sin(expr) => {
11280 let val = self.eval_expr(expr)?;
11281 Ok(PerlValue::float(val.to_number().sin()))
11282 }
11283 ExprKind::Cos(expr) => {
11284 let val = self.eval_expr(expr)?;
11285 Ok(PerlValue::float(val.to_number().cos()))
11286 }
11287 ExprKind::Atan2 { y, x } => {
11288 let yv = self.eval_expr(y)?.to_number();
11289 let xv = self.eval_expr(x)?.to_number();
11290 Ok(PerlValue::float(yv.atan2(xv)))
11291 }
11292 ExprKind::Exp(expr) => {
11293 let val = self.eval_expr(expr)?;
11294 Ok(PerlValue::float(val.to_number().exp()))
11295 }
11296 ExprKind::Log(expr) => {
11297 let val = self.eval_expr(expr)?;
11298 Ok(PerlValue::float(val.to_number().ln()))
11299 }
11300 ExprKind::Rand(upper) => {
11301 let u = match upper {
11302 Some(e) => self.eval_expr(e)?.to_number(),
11303 None => 1.0,
11304 };
11305 Ok(PerlValue::float(self.perl_rand(u)))
11306 }
11307 ExprKind::Srand(seed) => {
11308 let s = match seed {
11309 Some(e) => Some(self.eval_expr(e)?.to_number()),
11310 None => None,
11311 };
11312 Ok(PerlValue::integer(self.perl_srand(s)))
11313 }
11314 ExprKind::Hex(expr) => {
11315 let val = self.eval_expr(expr)?.to_string();
11316 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
11317 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
11318 Ok(PerlValue::integer(n))
11319 }
11320 ExprKind::Oct(expr) => {
11321 let val = self.eval_expr(expr)?.to_string();
11322 let s = val.trim();
11323 let n = if s.starts_with("0x") || s.starts_with("0X") {
11324 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
11325 } else if s.starts_with("0b") || s.starts_with("0B") {
11326 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
11327 } else if s.starts_with("0o") || s.starts_with("0O") {
11328 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
11329 } else {
11330 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
11331 };
11332 Ok(PerlValue::integer(n))
11333 }
11334
11335 ExprKind::Lc(expr) => Ok(PerlValue::string(
11337 self.eval_expr(expr)?.to_string().to_lowercase(),
11338 )),
11339 ExprKind::Uc(expr) => Ok(PerlValue::string(
11340 self.eval_expr(expr)?.to_string().to_uppercase(),
11341 )),
11342 ExprKind::Lcfirst(expr) => {
11343 let s = self.eval_expr(expr)?.to_string();
11344 let mut chars = s.chars();
11345 let result = match chars.next() {
11346 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
11347 None => String::new(),
11348 };
11349 Ok(PerlValue::string(result))
11350 }
11351 ExprKind::Ucfirst(expr) => {
11352 let s = self.eval_expr(expr)?.to_string();
11353 let mut chars = s.chars();
11354 let result = match chars.next() {
11355 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
11356 None => String::new(),
11357 };
11358 Ok(PerlValue::string(result))
11359 }
11360 ExprKind::Fc(expr) => Ok(PerlValue::string(default_case_fold_str(
11361 &self.eval_expr(expr)?.to_string(),
11362 ))),
11363 ExprKind::Crypt { plaintext, salt } => {
11364 let p = self.eval_expr(plaintext)?.to_string();
11365 let sl = self.eval_expr(salt)?.to_string();
11366 Ok(PerlValue::string(perl_crypt(&p, &sl)))
11367 }
11368 ExprKind::Pos(e) => {
11369 let key = match e {
11370 None => "_".to_string(),
11371 Some(expr) => match &expr.kind {
11372 ExprKind::ScalarVar(n) => n.clone(),
11373 _ => self.eval_expr(expr)?.to_string(),
11374 },
11375 };
11376 Ok(self
11377 .regex_pos
11378 .get(&key)
11379 .copied()
11380 .flatten()
11381 .map(|p| PerlValue::integer(p as i64))
11382 .unwrap_or(PerlValue::UNDEF))
11383 }
11384 ExprKind::Study(expr) => {
11385 let s = self.eval_expr(expr)?.to_string();
11386 Ok(Self::study_return_value(&s))
11387 }
11388
11389 ExprKind::Defined(expr) => {
11391 if let ExprKind::SubroutineRef(name) = &expr.kind {
11393 let exists = self.resolve_sub_by_name(name).is_some();
11394 return Ok(PerlValue::integer(if exists { 1 } else { 0 }));
11395 }
11396 let val = self.eval_expr(expr)?;
11397 Ok(PerlValue::integer(if val.is_undef() { 0 } else { 1 }))
11398 }
11399 ExprKind::Ref(expr) => {
11400 let val = self.eval_expr(expr)?;
11401 Ok(val.ref_type())
11402 }
11403 ExprKind::ScalarContext(expr) => {
11404 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11405 Ok(v.scalar_context())
11406 }
11407
11408 ExprKind::Chr(expr) => {
11410 let n = self.eval_expr(expr)?.to_int() as u32;
11411 Ok(PerlValue::string(
11412 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
11413 ))
11414 }
11415 ExprKind::Ord(expr) => {
11416 let s = self.eval_expr(expr)?.to_string();
11417 Ok(PerlValue::integer(
11418 s.chars().next().map(|c| c as i64).unwrap_or(0),
11419 ))
11420 }
11421
11422 ExprKind::OpenMyHandle { .. } => Err(PerlError::runtime(
11424 "internal: `open my $fh` handle used outside open()",
11425 line,
11426 )
11427 .into()),
11428 ExprKind::Open { handle, mode, file } => {
11429 if let ExprKind::OpenMyHandle { name } = &handle.kind {
11430 self.scope
11431 .declare_scalar_frozen(name, PerlValue::UNDEF, false, None)?;
11432 self.english_note_lexical_scalar(name);
11433 let mode_s = self.eval_expr(mode)?.to_string();
11434 let file_opt = if let Some(f) = file {
11435 Some(self.eval_expr(f)?.to_string())
11436 } else {
11437 None
11438 };
11439 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
11440 self.scope.set_scalar(name, ret.clone())?;
11441 return Ok(ret);
11442 }
11443 let handle_s = self.eval_expr(handle)?.to_string();
11444 let handle_name = self.resolve_io_handle_name(&handle_s);
11445 let mode_s = self.eval_expr(mode)?.to_string();
11446 let file_opt = if let Some(f) = file {
11447 Some(self.eval_expr(f)?.to_string())
11448 } else {
11449 None
11450 };
11451 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
11452 .map_err(Into::into)
11453 }
11454 ExprKind::Close(expr) => {
11455 let s = self.eval_expr(expr)?.to_string();
11456 let name = self.resolve_io_handle_name(&s);
11457 self.close_builtin_execute(name).map_err(Into::into)
11458 }
11459 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
11460 self.readline_builtin_execute_list(handle.as_deref())
11461 } else {
11462 self.readline_builtin_execute(handle.as_deref())
11463 }
11464 .map_err(Into::into),
11465 ExprKind::Eof(expr) => match expr {
11466 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
11467 Some(e) => {
11468 let name = self.eval_expr(e)?;
11469 self.eof_builtin_execute(&[name], line).map_err(Into::into)
11470 }
11471 },
11472
11473 ExprKind::Opendir { handle, path } => {
11474 let h = self.eval_expr(handle)?.to_string();
11475 let p = self.eval_expr(path)?.to_string();
11476 Ok(self.opendir_handle(&h, &p))
11477 }
11478 ExprKind::Readdir(e) => {
11479 let h = self.eval_expr(e)?.to_string();
11480 Ok(if ctx == WantarrayCtx::List {
11481 self.readdir_handle_list(&h)
11482 } else {
11483 self.readdir_handle(&h)
11484 })
11485 }
11486 ExprKind::Closedir(e) => {
11487 let h = self.eval_expr(e)?.to_string();
11488 Ok(self.closedir_handle(&h))
11489 }
11490 ExprKind::Rewinddir(e) => {
11491 let h = self.eval_expr(e)?.to_string();
11492 Ok(self.rewinddir_handle(&h))
11493 }
11494 ExprKind::Telldir(e) => {
11495 let h = self.eval_expr(e)?.to_string();
11496 Ok(self.telldir_handle(&h))
11497 }
11498 ExprKind::Seekdir { handle, position } => {
11499 let h = self.eval_expr(handle)?.to_string();
11500 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
11501 Ok(self.seekdir_handle(&h, pos))
11502 }
11503
11504 ExprKind::FileTest { op, expr } => {
11506 let path = self.eval_expr(expr)?.to_string();
11507 if matches!(op, 'M' | 'A' | 'C') {
11509 #[cfg(unix)]
11510 {
11511 return match crate::perl_fs::filetest_age_days(&path, *op) {
11512 Some(days) => Ok(PerlValue::float(days)),
11513 None => Ok(PerlValue::UNDEF),
11514 };
11515 }
11516 #[cfg(not(unix))]
11517 return Ok(PerlValue::UNDEF);
11518 }
11519 if *op == 's' {
11521 return match std::fs::metadata(&path) {
11522 Ok(m) => Ok(PerlValue::integer(m.len() as i64)),
11523 Err(_) => Ok(PerlValue::UNDEF),
11524 };
11525 }
11526 let result = match op {
11527 'e' => std::path::Path::new(&path).exists(),
11528 'f' => std::path::Path::new(&path).is_file(),
11529 'd' => std::path::Path::new(&path).is_dir(),
11530 'l' => std::path::Path::new(&path).is_symlink(),
11531 #[cfg(unix)]
11532 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
11533 #[cfg(not(unix))]
11534 'r' => std::fs::metadata(&path).is_ok(),
11535 #[cfg(unix)]
11536 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
11537 #[cfg(not(unix))]
11538 'w' => std::fs::metadata(&path).is_ok(),
11539 #[cfg(unix)]
11540 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
11541 #[cfg(not(unix))]
11542 'x' => false,
11543 #[cfg(unix)]
11544 'o' => crate::perl_fs::filetest_owned_effective(&path),
11545 #[cfg(not(unix))]
11546 'o' => false,
11547 #[cfg(unix)]
11548 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
11549 #[cfg(not(unix))]
11550 'R' => false,
11551 #[cfg(unix)]
11552 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
11553 #[cfg(not(unix))]
11554 'W' => false,
11555 #[cfg(unix)]
11556 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
11557 #[cfg(not(unix))]
11558 'X' => false,
11559 #[cfg(unix)]
11560 'O' => crate::perl_fs::filetest_owned_real(&path),
11561 #[cfg(not(unix))]
11562 'O' => false,
11563 'z' => std::fs::metadata(&path)
11564 .map(|m| m.len() == 0)
11565 .unwrap_or(true),
11566 't' => crate::perl_fs::filetest_is_tty(&path),
11567 #[cfg(unix)]
11568 'p' => crate::perl_fs::filetest_is_pipe(&path),
11569 #[cfg(not(unix))]
11570 'p' => false,
11571 #[cfg(unix)]
11572 'S' => crate::perl_fs::filetest_is_socket(&path),
11573 #[cfg(not(unix))]
11574 'S' => false,
11575 #[cfg(unix)]
11576 'b' => crate::perl_fs::filetest_is_block_device(&path),
11577 #[cfg(not(unix))]
11578 'b' => false,
11579 #[cfg(unix)]
11580 'c' => crate::perl_fs::filetest_is_char_device(&path),
11581 #[cfg(not(unix))]
11582 'c' => false,
11583 #[cfg(unix)]
11584 'u' => crate::perl_fs::filetest_is_setuid(&path),
11585 #[cfg(not(unix))]
11586 'u' => false,
11587 #[cfg(unix)]
11588 'g' => crate::perl_fs::filetest_is_setgid(&path),
11589 #[cfg(not(unix))]
11590 'g' => false,
11591 #[cfg(unix)]
11592 'k' => crate::perl_fs::filetest_is_sticky(&path),
11593 #[cfg(not(unix))]
11594 'k' => false,
11595 'T' => crate::perl_fs::filetest_is_text(&path),
11596 'B' => crate::perl_fs::filetest_is_binary(&path),
11597 _ => false,
11598 };
11599 Ok(PerlValue::integer(if result { 1 } else { 0 }))
11600 }
11601
11602 ExprKind::System(args) => {
11604 let mut cmd_args = Vec::new();
11605 for a in args {
11606 cmd_args.push(self.eval_expr(a)?.to_string());
11607 }
11608 if cmd_args.is_empty() {
11609 return Ok(PerlValue::integer(-1));
11610 }
11611 let status = Command::new("sh")
11612 .arg("-c")
11613 .arg(cmd_args.join(" "))
11614 .status();
11615 match status {
11616 Ok(s) => {
11617 self.record_child_exit_status(s);
11618 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
11619 }
11620 Err(e) => {
11621 self.apply_io_error_to_errno(&e);
11622 Ok(PerlValue::integer(-1))
11623 }
11624 }
11625 }
11626 ExprKind::Exec(args) => {
11627 let mut cmd_args = Vec::new();
11628 for a in args {
11629 cmd_args.push(self.eval_expr(a)?.to_string());
11630 }
11631 if cmd_args.is_empty() {
11632 return Ok(PerlValue::integer(-1));
11633 }
11634 let status = Command::new("sh")
11635 .arg("-c")
11636 .arg(cmd_args.join(" "))
11637 .status();
11638 match status {
11639 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
11640 Err(e) => {
11641 self.apply_io_error_to_errno(&e);
11642 Ok(PerlValue::integer(-1))
11643 }
11644 }
11645 }
11646 ExprKind::Eval(expr) => {
11647 self.eval_nesting += 1;
11648 let out = match &expr.kind {
11649 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
11650 Ok(v) => {
11651 self.clear_eval_error();
11652 Ok(v)
11653 }
11654 Err(FlowOrError::Error(e)) => {
11655 self.set_eval_error_from_perl_error(&e);
11656 Ok(PerlValue::UNDEF)
11657 }
11658 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
11659 },
11660 _ => {
11661 let code = self.eval_expr(expr)?.to_string();
11662 match crate::parse_and_run_string(&code, self) {
11664 Ok(v) => {
11665 self.clear_eval_error();
11666 Ok(v)
11667 }
11668 Err(e) => {
11669 self.set_eval_error(e.to_string());
11670 Ok(PerlValue::UNDEF)
11671 }
11672 }
11673 }
11674 };
11675 self.eval_nesting -= 1;
11676 out
11677 }
11678 ExprKind::Do(expr) => match &expr.kind {
11679 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
11680 _ => {
11681 let val = self.eval_expr(expr)?;
11682 let filename = val.to_string();
11683 match read_file_text_perl_compat(&filename) {
11684 Ok(code) => {
11685 let code = crate::data_section::strip_perl_end_marker(&code);
11686 match crate::parse_and_run_string_in_file(code, self, &filename) {
11687 Ok(v) => Ok(v),
11688 Err(e) => {
11689 self.set_eval_error(e.to_string());
11690 Ok(PerlValue::UNDEF)
11691 }
11692 }
11693 }
11694 Err(e) => {
11695 self.apply_io_error_to_errno(&e);
11696 Ok(PerlValue::UNDEF)
11697 }
11698 }
11699 }
11700 },
11701 ExprKind::Require(expr) => {
11702 let spec = self.eval_expr(expr)?.to_string();
11703 self.require_execute(&spec, line)
11704 .map_err(FlowOrError::Error)
11705 }
11706 ExprKind::Exit(code) => {
11707 let c = if let Some(e) = code {
11708 self.eval_expr(e)?.to_int() as i32
11709 } else {
11710 0
11711 };
11712 Err(PerlError::new(ErrorKind::Exit(c), "", line, &self.file).into())
11713 }
11714 ExprKind::Chdir(expr) => {
11715 let path = self.eval_expr(expr)?.to_string();
11716 match std::env::set_current_dir(&path) {
11717 Ok(_) => Ok(PerlValue::integer(1)),
11718 Err(e) => {
11719 self.apply_io_error_to_errno(&e);
11720 Ok(PerlValue::integer(0))
11721 }
11722 }
11723 }
11724 ExprKind::Mkdir { path, mode: _ } => {
11725 let p = self.eval_expr(path)?.to_string();
11726 match std::fs::create_dir(&p) {
11727 Ok(_) => Ok(PerlValue::integer(1)),
11728 Err(e) => {
11729 self.apply_io_error_to_errno(&e);
11730 Ok(PerlValue::integer(0))
11731 }
11732 }
11733 }
11734 ExprKind::Unlink(args) => {
11735 let mut count = 0i64;
11736 for a in args {
11737 let path = self.eval_expr(a)?.to_string();
11738 if std::fs::remove_file(&path).is_ok() {
11739 count += 1;
11740 }
11741 }
11742 Ok(PerlValue::integer(count))
11743 }
11744 ExprKind::Rename { old, new } => {
11745 let o = self.eval_expr(old)?.to_string();
11746 let n = self.eval_expr(new)?.to_string();
11747 Ok(crate::perl_fs::rename_paths(&o, &n))
11748 }
11749 ExprKind::Chmod(args) => {
11750 let mode = self.eval_expr(&args[0])?.to_int();
11751 let mut paths = Vec::new();
11752 for a in &args[1..] {
11753 paths.push(self.eval_expr(a)?.to_string());
11754 }
11755 Ok(PerlValue::integer(crate::perl_fs::chmod_paths(
11756 &paths, mode,
11757 )))
11758 }
11759 ExprKind::Chown(args) => {
11760 let uid = self.eval_expr(&args[0])?.to_int();
11761 let gid = self.eval_expr(&args[1])?.to_int();
11762 let mut paths = Vec::new();
11763 for a in &args[2..] {
11764 paths.push(self.eval_expr(a)?.to_string());
11765 }
11766 Ok(PerlValue::integer(crate::perl_fs::chown_paths(
11767 &paths, uid, gid,
11768 )))
11769 }
11770 ExprKind::Stat(e) => {
11771 let path = self.eval_expr(e)?.to_string();
11772 Ok(crate::perl_fs::stat_path(&path, false))
11773 }
11774 ExprKind::Lstat(e) => {
11775 let path = self.eval_expr(e)?.to_string();
11776 Ok(crate::perl_fs::stat_path(&path, true))
11777 }
11778 ExprKind::Link { old, new } => {
11779 let o = self.eval_expr(old)?.to_string();
11780 let n = self.eval_expr(new)?.to_string();
11781 Ok(crate::perl_fs::link_hard(&o, &n))
11782 }
11783 ExprKind::Symlink { old, new } => {
11784 let o = self.eval_expr(old)?.to_string();
11785 let n = self.eval_expr(new)?.to_string();
11786 Ok(crate::perl_fs::link_sym(&o, &n))
11787 }
11788 ExprKind::Readlink(e) => {
11789 let path = self.eval_expr(e)?.to_string();
11790 Ok(crate::perl_fs::read_link(&path))
11791 }
11792 ExprKind::Files(args) => {
11793 let dir = if args.is_empty() {
11794 ".".to_string()
11795 } else {
11796 self.eval_expr(&args[0])?.to_string()
11797 };
11798 Ok(crate::perl_fs::list_files(&dir))
11799 }
11800 ExprKind::Filesf(args) => {
11801 let dir = if args.is_empty() {
11802 ".".to_string()
11803 } else {
11804 self.eval_expr(&args[0])?.to_string()
11805 };
11806 Ok(crate::perl_fs::list_filesf(&dir))
11807 }
11808 ExprKind::FilesfRecursive(args) => {
11809 let dir = if args.is_empty() {
11810 ".".to_string()
11811 } else {
11812 self.eval_expr(&args[0])?.to_string()
11813 };
11814 Ok(PerlValue::iterator(Arc::new(
11815 crate::value::FsWalkIterator::new(&dir, true),
11816 )))
11817 }
11818 ExprKind::Dirs(args) => {
11819 let dir = if args.is_empty() {
11820 ".".to_string()
11821 } else {
11822 self.eval_expr(&args[0])?.to_string()
11823 };
11824 Ok(crate::perl_fs::list_dirs(&dir))
11825 }
11826 ExprKind::DirsRecursive(args) => {
11827 let dir = if args.is_empty() {
11828 ".".to_string()
11829 } else {
11830 self.eval_expr(&args[0])?.to_string()
11831 };
11832 Ok(PerlValue::iterator(Arc::new(
11833 crate::value::FsWalkIterator::new(&dir, false),
11834 )))
11835 }
11836 ExprKind::SymLinks(args) => {
11837 let dir = if args.is_empty() {
11838 ".".to_string()
11839 } else {
11840 self.eval_expr(&args[0])?.to_string()
11841 };
11842 Ok(crate::perl_fs::list_sym_links(&dir))
11843 }
11844 ExprKind::Sockets(args) => {
11845 let dir = if args.is_empty() {
11846 ".".to_string()
11847 } else {
11848 self.eval_expr(&args[0])?.to_string()
11849 };
11850 Ok(crate::perl_fs::list_sockets(&dir))
11851 }
11852 ExprKind::Pipes(args) => {
11853 let dir = if args.is_empty() {
11854 ".".to_string()
11855 } else {
11856 self.eval_expr(&args[0])?.to_string()
11857 };
11858 Ok(crate::perl_fs::list_pipes(&dir))
11859 }
11860 ExprKind::BlockDevices(args) => {
11861 let dir = if args.is_empty() {
11862 ".".to_string()
11863 } else {
11864 self.eval_expr(&args[0])?.to_string()
11865 };
11866 Ok(crate::perl_fs::list_block_devices(&dir))
11867 }
11868 ExprKind::CharDevices(args) => {
11869 let dir = if args.is_empty() {
11870 ".".to_string()
11871 } else {
11872 self.eval_expr(&args[0])?.to_string()
11873 };
11874 Ok(crate::perl_fs::list_char_devices(&dir))
11875 }
11876 ExprKind::Executables(args) => {
11877 let dir = if args.is_empty() {
11878 ".".to_string()
11879 } else {
11880 self.eval_expr(&args[0])?.to_string()
11881 };
11882 Ok(crate::perl_fs::list_executables(&dir))
11883 }
11884 ExprKind::Glob(args) => {
11885 let mut pats = Vec::new();
11886 for a in args {
11887 pats.push(self.eval_expr(a)?.to_string());
11888 }
11889 Ok(crate::perl_fs::glob_patterns(&pats))
11890 }
11891 ExprKind::GlobPar { args, progress } => {
11892 let mut pats = Vec::new();
11893 for a in args {
11894 pats.push(self.eval_expr(a)?.to_string());
11895 }
11896 let show_progress = progress
11897 .as_ref()
11898 .map(|p| self.eval_expr(p))
11899 .transpose()?
11900 .map(|v| v.is_true())
11901 .unwrap_or(false);
11902 if show_progress {
11903 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
11904 } else {
11905 Ok(crate::perl_fs::glob_par_patterns(&pats))
11906 }
11907 }
11908 ExprKind::ParSed { args, progress } => {
11909 let has_progress = progress.is_some();
11910 let mut vals: Vec<PerlValue> = Vec::new();
11911 for a in args {
11912 vals.push(self.eval_expr(a)?);
11913 }
11914 if let Some(p) = progress {
11915 vals.push(self.eval_expr(p.as_ref())?);
11916 }
11917 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
11918 }
11919 ExprKind::Bless { ref_expr, class } => {
11920 let val = self.eval_expr(ref_expr)?;
11921 let class_name = if let Some(c) = class {
11922 self.eval_expr(c)?.to_string()
11923 } else {
11924 self.scope.get_scalar("__PACKAGE__").to_string()
11925 };
11926 Ok(PerlValue::blessed(Arc::new(
11927 crate::value::BlessedRef::new_blessed(class_name, val),
11928 )))
11929 }
11930 ExprKind::Caller(_) => {
11931 Ok(PerlValue::array(vec![
11933 PerlValue::string("main".into()),
11934 PerlValue::string(self.file.clone()),
11935 PerlValue::integer(line as i64),
11936 ]))
11937 }
11938 ExprKind::Wantarray => Ok(match self.wantarray_kind {
11939 WantarrayCtx::Void => PerlValue::UNDEF,
11940 WantarrayCtx::Scalar => PerlValue::integer(0),
11941 WantarrayCtx::List => PerlValue::integer(1),
11942 }),
11943
11944 ExprKind::List(exprs) => {
11945 if ctx == WantarrayCtx::Scalar {
11947 if let Some(last) = exprs.last() {
11948 for e in &exprs[..exprs.len() - 1] {
11950 self.eval_expr(e)?;
11951 }
11952 return self.eval_expr(last);
11953 } else {
11954 return Ok(PerlValue::UNDEF);
11955 }
11956 }
11957 let mut vals = Vec::new();
11958 for e in exprs {
11959 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
11960 if let Some(items) = v.as_array_vec() {
11961 vals.extend(items);
11962 } else {
11963 vals.push(v);
11964 }
11965 }
11966 if vals.len() == 1 {
11967 Ok(vals.pop().unwrap())
11968 } else {
11969 Ok(PerlValue::array(vals))
11970 }
11971 }
11972
11973 ExprKind::PostfixIf { expr, condition } => {
11975 if self.eval_postfix_condition(condition)? {
11976 self.eval_expr(expr)
11977 } else {
11978 Ok(PerlValue::UNDEF)
11979 }
11980 }
11981 ExprKind::PostfixUnless { expr, condition } => {
11982 if !self.eval_postfix_condition(condition)? {
11983 self.eval_expr(expr)
11984 } else {
11985 Ok(PerlValue::UNDEF)
11986 }
11987 }
11988 ExprKind::PostfixWhile { expr, condition } => {
11989 let is_do_block = matches!(
11992 &expr.kind,
11993 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
11994 );
11995 let mut last = PerlValue::UNDEF;
11996 if is_do_block {
11997 loop {
11998 last = self.eval_expr(expr)?;
11999 if !self.eval_postfix_condition(condition)? {
12000 break;
12001 }
12002 }
12003 } else {
12004 loop {
12005 if !self.eval_postfix_condition(condition)? {
12006 break;
12007 }
12008 last = self.eval_expr(expr)?;
12009 }
12010 }
12011 Ok(last)
12012 }
12013 ExprKind::PostfixUntil { expr, condition } => {
12014 let is_do_block = matches!(
12015 &expr.kind,
12016 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
12017 );
12018 let mut last = PerlValue::UNDEF;
12019 if is_do_block {
12020 loop {
12021 last = self.eval_expr(expr)?;
12022 if self.eval_postfix_condition(condition)? {
12023 break;
12024 }
12025 }
12026 } else {
12027 loop {
12028 if self.eval_postfix_condition(condition)? {
12029 break;
12030 }
12031 last = self.eval_expr(expr)?;
12032 }
12033 }
12034 Ok(last)
12035 }
12036 ExprKind::PostfixForeach { expr, list } => {
12037 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
12038 let mut last = PerlValue::UNDEF;
12039 for item in items {
12040 self.scope.set_topic(item);
12041 last = self.eval_expr(expr)?;
12042 }
12043 Ok(last)
12044 }
12045 }
12046 }
12047
12048 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
12051 match op {
12052 BinOp::Add => Some("+"),
12053 BinOp::Sub => Some("-"),
12054 BinOp::Mul => Some("*"),
12055 BinOp::Div => Some("/"),
12056 BinOp::Mod => Some("%"),
12057 BinOp::Pow => Some("**"),
12058 BinOp::Concat => Some("."),
12059 BinOp::StrEq => Some("eq"),
12060 BinOp::NumEq => Some("=="),
12061 BinOp::StrNe => Some("ne"),
12062 BinOp::NumNe => Some("!="),
12063 BinOp::StrLt => Some("lt"),
12064 BinOp::StrGt => Some("gt"),
12065 BinOp::StrLe => Some("le"),
12066 BinOp::StrGe => Some("ge"),
12067 BinOp::NumLt => Some("<"),
12068 BinOp::NumGt => Some(">"),
12069 BinOp::NumLe => Some("<="),
12070 BinOp::NumGe => Some(">="),
12071 BinOp::Spaceship => Some("<=>"),
12072 BinOp::StrCmp => Some("cmp"),
12073 _ => None,
12074 }
12075 }
12076
12077 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
12079 map.get("").or_else(|| map.get("\"\""))
12080 }
12081
12082 pub(crate) fn stringify_value(
12084 &mut self,
12085 v: PerlValue,
12086 line: usize,
12087 ) -> Result<String, FlowOrError> {
12088 if let Some(r) = self.try_overload_stringify(&v, line) {
12089 let pv = r?;
12090 return Ok(pv.to_string());
12091 }
12092 Ok(v.to_string())
12093 }
12094
12095 pub(crate) fn perl_sprintf_stringify(
12097 &mut self,
12098 fmt: &str,
12099 args: &[PerlValue],
12100 line: usize,
12101 ) -> Result<String, FlowOrError> {
12102 perl_sprintf_format_with(fmt, args, |v| self.stringify_value(v.clone(), line))
12103 }
12104
12105 pub(crate) fn render_format_template(
12107 &mut self,
12108 tmpl: &crate::format::FormatTemplate,
12109 line: usize,
12110 ) -> Result<String, FlowOrError> {
12111 use crate::format::{FormatRecord, PictureSegment};
12112 let mut buf = String::new();
12113 for rec in &tmpl.records {
12114 match rec {
12115 FormatRecord::Literal(s) => {
12116 buf.push_str(s);
12117 buf.push('\n');
12118 }
12119 FormatRecord::Picture { segments, exprs } => {
12120 let mut vals: Vec<String> = Vec::new();
12121 for e in exprs {
12122 let v = self.eval_expr(e)?;
12123 vals.push(self.stringify_value(v, line)?);
12124 }
12125 let mut vi = 0usize;
12126 let mut line_out = String::new();
12127 for seg in segments {
12128 match seg {
12129 PictureSegment::Literal(t) => line_out.push_str(t),
12130 PictureSegment::Field {
12131 width,
12132 align,
12133 kind: _,
12134 } => {
12135 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
12136 vi += 1;
12137 line_out.push_str(&crate::format::pad_field(s, *width, *align));
12138 }
12139 }
12140 }
12141 buf.push_str(line_out.trim_end());
12142 buf.push('\n');
12143 }
12144 }
12145 }
12146 Ok(buf)
12147 }
12148
12149 pub(crate) fn resolve_write_output_handle(
12151 &self,
12152 v: &PerlValue,
12153 line: usize,
12154 ) -> PerlResult<String> {
12155 if let Some(n) = v.as_io_handle_name() {
12156 let n = self.resolve_io_handle_name(&n);
12157 if self.is_bound_handle(&n) {
12158 return Ok(n);
12159 }
12160 }
12161 if let Some(s) = v.as_str() {
12162 if self.is_bound_handle(&s) {
12163 return Ok(self.resolve_io_handle_name(&s));
12164 }
12165 }
12166 let s = v.to_string();
12167 if self.is_bound_handle(&s) {
12168 return Ok(self.resolve_io_handle_name(&s));
12169 }
12170 Err(PerlError::runtime(
12171 format!("write: invalid or unopened filehandle {}", s),
12172 line,
12173 ))
12174 }
12175
12176 pub(crate) fn write_format_execute(
12180 &mut self,
12181 args: &[PerlValue],
12182 line: usize,
12183 ) -> PerlResult<PerlValue> {
12184 let handle_name = match args.len() {
12185 0 => self.default_print_handle.clone(),
12186 1 => self.resolve_write_output_handle(&args[0], line)?,
12187 _ => {
12188 return Err(PerlError::runtime("write: too many arguments", line));
12189 }
12190 };
12191 let pkg = self.current_package();
12192 let mut fmt_name = self.scope.get_scalar("~").to_string();
12193 if fmt_name.is_empty() {
12194 fmt_name = "STDOUT".to_string();
12195 }
12196 let key = format!("{}::{}", pkg, fmt_name);
12197 let tmpl = self
12198 .format_templates
12199 .get(&key)
12200 .map(Arc::clone)
12201 .ok_or_else(|| {
12202 PerlError::runtime(
12203 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
12204 line,
12205 )
12206 })?;
12207 let out = self
12208 .render_format_template(&tmpl, line)
12209 .map_err(|e| match e {
12210 FlowOrError::Error(e) => e,
12211 FlowOrError::Flow(_) => PerlError::runtime("write: unexpected control flow", line),
12212 })?;
12213 self.write_formatted_print(handle_name.as_str(), &out, line)?;
12214 Ok(PerlValue::integer(1))
12215 }
12216
12217 pub(crate) fn try_overload_stringify(
12218 &mut self,
12219 v: &PerlValue,
12220 line: usize,
12221 ) -> Option<ExecResult> {
12222 if let Some(c) = v.as_class_inst() {
12224 let method_name = c
12225 .def
12226 .method("stringify")
12227 .or_else(|| c.def.method("\"\""))
12228 .filter(|m| m.body.is_some())?;
12229 let body = method_name.body.clone().unwrap();
12230 let params = method_name.params.clone();
12231 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
12232 }
12233 let br = v.as_blessed_ref()?;
12234 let class = br.class.clone();
12235 let map = self.overload_table.get(&class)?;
12236 let sub_short = Self::overload_stringify_method(map)?;
12237 let fq = format!("{}::{}", class, sub_short);
12238 let sub = self.subs.get(&fq)?.clone();
12239 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
12240 }
12241
12242 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
12244 match key {
12245 "+" => Some("op_add"),
12246 "-" => Some("op_sub"),
12247 "*" => Some("op_mul"),
12248 "/" => Some("op_div"),
12249 "%" => Some("op_mod"),
12250 "**" => Some("op_pow"),
12251 "." => Some("op_concat"),
12252 "==" => Some("op_eq"),
12253 "!=" => Some("op_ne"),
12254 "<" => Some("op_lt"),
12255 ">" => Some("op_gt"),
12256 "<=" => Some("op_le"),
12257 ">=" => Some("op_ge"),
12258 "<=>" => Some("op_spaceship"),
12259 "eq" => Some("op_str_eq"),
12260 "ne" => Some("op_str_ne"),
12261 "lt" => Some("op_str_lt"),
12262 "gt" => Some("op_str_gt"),
12263 "le" => Some("op_str_le"),
12264 "ge" => Some("op_str_ge"),
12265 "cmp" => Some("op_cmp"),
12266 _ => None,
12267 }
12268 }
12269
12270 pub(crate) fn try_overload_binop(
12271 &mut self,
12272 op: BinOp,
12273 lv: &PerlValue,
12274 rv: &PerlValue,
12275 line: usize,
12276 ) -> Option<ExecResult> {
12277 let key = Self::overload_key_for_binop(op)?;
12278 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
12280 (Some(c.def.clone()), lv.clone(), rv.clone())
12281 } else if let Some(c) = rv.as_class_inst() {
12282 (Some(c.def.clone()), rv.clone(), lv.clone())
12283 } else {
12284 (None, lv.clone(), rv.clone())
12285 };
12286 if let Some(ref def) = ci_def {
12287 if let Some(method_name) = Self::overload_method_name_for_key(key) {
12288 if let Some((m, _)) = self.find_class_method(def, method_name) {
12289 if let Some(ref body) = m.body {
12290 let params = m.params.clone();
12291 return Some(self.call_class_method(
12292 body,
12293 ¶ms,
12294 vec![invocant, other],
12295 line,
12296 ));
12297 }
12298 }
12299 }
12300 }
12301 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
12303 (br.class.clone(), lv.clone(), rv.clone())
12304 } else if let Some(br) = rv.as_blessed_ref() {
12305 (br.class.clone(), rv.clone(), lv.clone())
12306 } else {
12307 return None;
12308 };
12309 let map = self.overload_table.get(&class)?;
12310 let sub_short = if let Some(s) = map.get(key) {
12311 s.clone()
12312 } else if let Some(nm) = map.get("nomethod") {
12313 let fq = format!("{}::{}", class, nm);
12314 let sub = self.subs.get(&fq)?.clone();
12315 return Some(self.call_sub(
12316 &sub,
12317 vec![invocant, other, PerlValue::string(key.to_string())],
12318 WantarrayCtx::Scalar,
12319 line,
12320 ));
12321 } else {
12322 return None;
12323 };
12324 let fq = format!("{}::{}", class, sub_short);
12325 let sub = self.subs.get(&fq)?.clone();
12326 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
12327 }
12328
12329 pub(crate) fn try_overload_unary_dispatch(
12331 &mut self,
12332 op_key: &str,
12333 val: &PerlValue,
12334 line: usize,
12335 ) -> Option<ExecResult> {
12336 if let Some(c) = val.as_class_inst() {
12338 let method_name = match op_key {
12339 "neg" => "op_neg",
12340 "bool" => "op_bool",
12341 "abs" => "op_abs",
12342 "0+" => "op_numify",
12343 _ => return None,
12344 };
12345 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
12346 if let Some(ref body) = m.body {
12347 let params = m.params.clone();
12348 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
12349 }
12350 }
12351 return None;
12352 }
12353 let br = val.as_blessed_ref()?;
12355 let class = br.class.clone();
12356 let map = self.overload_table.get(&class)?;
12357 if let Some(s) = map.get(op_key) {
12358 let fq = format!("{}::{}", class, s);
12359 let sub = self.subs.get(&fq)?.clone();
12360 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
12361 }
12362 if let Some(nm) = map.get("nomethod") {
12363 let fq = format!("{}::{}", class, nm);
12364 let sub = self.subs.get(&fq)?.clone();
12365 return Some(self.call_sub(
12366 &sub,
12367 vec![val.clone(), PerlValue::string(op_key.to_string())],
12368 WantarrayCtx::Scalar,
12369 line,
12370 ));
12371 }
12372 None
12373 }
12374
12375 #[inline]
12376 fn eval_binop(
12377 &mut self,
12378 op: BinOp,
12379 lv: &PerlValue,
12380 rv: &PerlValue,
12381 _line: usize,
12382 ) -> ExecResult {
12383 Ok(match op {
12384 BinOp::Add => {
12387 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12388 PerlValue::integer(a.wrapping_add(b))
12389 } else {
12390 PerlValue::float(lv.to_number() + rv.to_number())
12391 }
12392 }
12393 BinOp::Sub => {
12394 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12395 PerlValue::integer(a.wrapping_sub(b))
12396 } else {
12397 PerlValue::float(lv.to_number() - rv.to_number())
12398 }
12399 }
12400 BinOp::Mul => {
12401 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12402 PerlValue::integer(a.wrapping_mul(b))
12403 } else {
12404 PerlValue::float(lv.to_number() * rv.to_number())
12405 }
12406 }
12407 BinOp::Div => {
12408 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12409 if b == 0 {
12410 return Err(PerlError::runtime("Illegal division by zero", _line).into());
12411 }
12412 if a % b == 0 {
12413 PerlValue::integer(a / b)
12414 } else {
12415 PerlValue::float(a as f64 / b as f64)
12416 }
12417 } else {
12418 let d = rv.to_number();
12419 if d == 0.0 {
12420 return Err(PerlError::runtime("Illegal division by zero", _line).into());
12421 }
12422 PerlValue::float(lv.to_number() / d)
12423 }
12424 }
12425 BinOp::Mod => {
12426 let d = rv.to_int();
12427 if d == 0 {
12428 return Err(PerlError::runtime("Illegal modulus zero", _line).into());
12429 }
12430 PerlValue::integer(lv.to_int() % d)
12431 }
12432 BinOp::Pow => {
12433 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12434 let int_pow = (b >= 0)
12435 .then(|| u32::try_from(b).ok())
12436 .flatten()
12437 .and_then(|bu| a.checked_pow(bu))
12438 .map(PerlValue::integer);
12439 int_pow.unwrap_or_else(|| PerlValue::float(lv.to_number().powf(rv.to_number())))
12440 } else {
12441 PerlValue::float(lv.to_number().powf(rv.to_number()))
12442 }
12443 }
12444 BinOp::Concat => {
12445 let mut s = String::new();
12446 lv.append_to(&mut s);
12447 rv.append_to(&mut s);
12448 PerlValue::string(s)
12449 }
12450 BinOp::NumEq => {
12451 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
12453 if a.def.name != b.def.name {
12454 PerlValue::integer(0)
12455 } else {
12456 let av = a.get_values();
12457 let bv = b.get_values();
12458 let eq = av.len() == bv.len()
12459 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
12460 PerlValue::integer(if eq { 1 } else { 0 })
12461 }
12462 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12463 PerlValue::integer(if a == b { 1 } else { 0 })
12464 } else {
12465 PerlValue::integer(if lv.to_number() == rv.to_number() {
12466 1
12467 } else {
12468 0
12469 })
12470 }
12471 }
12472 BinOp::NumNe => {
12473 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12474 PerlValue::integer(if a != b { 1 } else { 0 })
12475 } else {
12476 PerlValue::integer(if lv.to_number() != rv.to_number() {
12477 1
12478 } else {
12479 0
12480 })
12481 }
12482 }
12483 BinOp::NumLt => {
12484 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12485 PerlValue::integer(if a < b { 1 } else { 0 })
12486 } else {
12487 PerlValue::integer(if lv.to_number() < rv.to_number() {
12488 1
12489 } else {
12490 0
12491 })
12492 }
12493 }
12494 BinOp::NumGt => {
12495 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12496 PerlValue::integer(if a > b { 1 } else { 0 })
12497 } else {
12498 PerlValue::integer(if lv.to_number() > rv.to_number() {
12499 1
12500 } else {
12501 0
12502 })
12503 }
12504 }
12505 BinOp::NumLe => {
12506 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12507 PerlValue::integer(if a <= b { 1 } else { 0 })
12508 } else {
12509 PerlValue::integer(if lv.to_number() <= rv.to_number() {
12510 1
12511 } else {
12512 0
12513 })
12514 }
12515 }
12516 BinOp::NumGe => {
12517 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12518 PerlValue::integer(if a >= b { 1 } else { 0 })
12519 } else {
12520 PerlValue::integer(if lv.to_number() >= rv.to_number() {
12521 1
12522 } else {
12523 0
12524 })
12525 }
12526 }
12527 BinOp::Spaceship => {
12528 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12529 PerlValue::integer(if a < b {
12530 -1
12531 } else if a > b {
12532 1
12533 } else {
12534 0
12535 })
12536 } else {
12537 let a = lv.to_number();
12538 let b = rv.to_number();
12539 PerlValue::integer(if a < b {
12540 -1
12541 } else if a > b {
12542 1
12543 } else {
12544 0
12545 })
12546 }
12547 }
12548 BinOp::StrEq => PerlValue::integer(if lv.to_string() == rv.to_string() {
12549 1
12550 } else {
12551 0
12552 }),
12553 BinOp::StrNe => PerlValue::integer(if lv.to_string() != rv.to_string() {
12554 1
12555 } else {
12556 0
12557 }),
12558 BinOp::StrLt => PerlValue::integer(if lv.to_string() < rv.to_string() {
12559 1
12560 } else {
12561 0
12562 }),
12563 BinOp::StrGt => PerlValue::integer(if lv.to_string() > rv.to_string() {
12564 1
12565 } else {
12566 0
12567 }),
12568 BinOp::StrLe => PerlValue::integer(if lv.to_string() <= rv.to_string() {
12569 1
12570 } else {
12571 0
12572 }),
12573 BinOp::StrGe => PerlValue::integer(if lv.to_string() >= rv.to_string() {
12574 1
12575 } else {
12576 0
12577 }),
12578 BinOp::StrCmp => {
12579 let cmp = lv.to_string().cmp(&rv.to_string());
12580 PerlValue::integer(match cmp {
12581 std::cmp::Ordering::Less => -1,
12582 std::cmp::Ordering::Greater => 1,
12583 std::cmp::Ordering::Equal => 0,
12584 })
12585 }
12586 BinOp::BitAnd => {
12587 if let Some(s) = crate::value::set_intersection(lv, rv) {
12588 s
12589 } else {
12590 PerlValue::integer(lv.to_int() & rv.to_int())
12591 }
12592 }
12593 BinOp::BitOr => {
12594 if let Some(s) = crate::value::set_union(lv, rv) {
12595 s
12596 } else {
12597 PerlValue::integer(lv.to_int() | rv.to_int())
12598 }
12599 }
12600 BinOp::BitXor => PerlValue::integer(lv.to_int() ^ rv.to_int()),
12601 BinOp::ShiftLeft => PerlValue::integer(lv.to_int() << rv.to_int()),
12602 BinOp::ShiftRight => PerlValue::integer(lv.to_int() >> rv.to_int()),
12603 BinOp::LogAnd
12605 | BinOp::LogOr
12606 | BinOp::DefinedOr
12607 | BinOp::LogAndWord
12608 | BinOp::LogOrWord => unreachable!(),
12609 BinOp::BindMatch | BinOp::BindNotMatch => {
12610 unreachable!("regex bind handled in eval_expr BinOp arm")
12611 }
12612 })
12613 }
12614
12615 fn err_modify_symbolic_aggregate_deref_inc_dec(
12619 kind: Sigil,
12620 is_pre: bool,
12621 is_inc: bool,
12622 line: usize,
12623 ) -> FlowOrError {
12624 let agg = match kind {
12625 Sigil::Array => "array",
12626 Sigil::Hash => "hash",
12627 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
12628 };
12629 let op = match (is_pre, is_inc) {
12630 (true, true) => "preincrement (++)",
12631 (true, false) => "predecrement (--)",
12632 (false, true) => "postincrement (++)",
12633 (false, false) => "postdecrement (--)",
12634 };
12635 FlowOrError::Error(PerlError::runtime(
12636 format!("Can't modify {agg} dereference in {op}"),
12637 line,
12638 ))
12639 }
12640
12641 pub(crate) fn symbolic_scalar_ref_postfix(
12643 &mut self,
12644 ref_val: PerlValue,
12645 decrement: bool,
12646 line: usize,
12647 ) -> Result<PerlValue, FlowOrError> {
12648 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
12649 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12650 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
12651 Ok(old)
12652 }
12653
12654 pub(crate) fn assign_scalar_ref_deref(
12657 &mut self,
12658 ref_val: PerlValue,
12659 val: PerlValue,
12660 line: usize,
12661 ) -> ExecResult {
12662 if let Some(name) = ref_val.as_scalar_binding_name() {
12663 self.set_special_var(&name, &val)
12664 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12665 return Ok(PerlValue::UNDEF);
12666 }
12667 if let Some(r) = ref_val.as_scalar_ref() {
12668 *r.write() = val;
12669 return Ok(PerlValue::UNDEF);
12670 }
12671 Err(PerlError::runtime("Can't assign to non-scalar reference", line).into())
12672 }
12673
12674 pub(crate) fn assign_symbolic_array_ref_deref(
12676 &mut self,
12677 ref_val: PerlValue,
12678 val: PerlValue,
12679 line: usize,
12680 ) -> ExecResult {
12681 if let Some(a) = ref_val.as_array_ref() {
12682 *a.write() = val.to_list();
12683 return Ok(PerlValue::UNDEF);
12684 }
12685 if let Some(name) = ref_val.as_array_binding_name() {
12686 self.scope
12687 .set_array(&name, val.to_list())
12688 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12689 return Ok(PerlValue::UNDEF);
12690 }
12691 if let Some(s) = ref_val.as_str() {
12692 if self.strict_refs {
12693 return Err(PerlError::runtime(
12694 format!(
12695 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
12696 s
12697 ),
12698 line,
12699 )
12700 .into());
12701 }
12702 self.scope
12703 .set_array(&s, val.to_list())
12704 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12705 return Ok(PerlValue::UNDEF);
12706 }
12707 Err(PerlError::runtime("Can't assign to non-array reference", line).into())
12708 }
12709
12710 pub(crate) fn assign_symbolic_typeglob_ref_deref(
12713 &mut self,
12714 ref_val: PerlValue,
12715 val: PerlValue,
12716 line: usize,
12717 ) -> ExecResult {
12718 let lhs_name = if let Some(s) = ref_val.as_str() {
12719 if self.strict_refs {
12720 return Err(PerlError::runtime(
12721 format!(
12722 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
12723 s
12724 ),
12725 line,
12726 )
12727 .into());
12728 }
12729 s.to_string()
12730 } else {
12731 return Err(
12732 PerlError::runtime("Can't assign to non-glob symbolic reference", line).into(),
12733 );
12734 };
12735 let is_coderef = val.as_code_ref().is_some()
12736 || val
12737 .as_scalar_ref()
12738 .map(|r| r.read().as_code_ref().is_some())
12739 .unwrap_or(false);
12740 if is_coderef {
12741 return self.assign_typeglob_value(&lhs_name, val, line);
12742 }
12743 let rhs_key = val.to_string();
12744 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
12745 .map_err(FlowOrError::Error)?;
12746 Ok(PerlValue::UNDEF)
12747 }
12748
12749 pub(crate) fn assign_symbolic_hash_ref_deref(
12751 &mut self,
12752 ref_val: PerlValue,
12753 val: PerlValue,
12754 line: usize,
12755 ) -> ExecResult {
12756 let items = val.to_list();
12757 let mut map = IndexMap::new();
12758 let mut i = 0;
12759 while i + 1 < items.len() {
12760 map.insert(items[i].to_string(), items[i + 1].clone());
12761 i += 2;
12762 }
12763 if let Some(h) = ref_val.as_hash_ref() {
12764 *h.write() = map;
12765 return Ok(PerlValue::UNDEF);
12766 }
12767 if let Some(name) = ref_val.as_hash_binding_name() {
12768 self.touch_env_hash(&name);
12769 self.scope
12770 .set_hash(&name, map)
12771 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12772 return Ok(PerlValue::UNDEF);
12773 }
12774 if let Some(s) = ref_val.as_str() {
12775 if self.strict_refs {
12776 return Err(PerlError::runtime(
12777 format!(
12778 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
12779 s
12780 ),
12781 line,
12782 )
12783 .into());
12784 }
12785 self.touch_env_hash(&s);
12786 self.scope
12787 .set_hash(&s, map)
12788 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12789 return Ok(PerlValue::UNDEF);
12790 }
12791 Err(PerlError::runtime("Can't assign to non-hash reference", line).into())
12792 }
12793
12794 pub(crate) fn assign_arrow_hash_deref(
12796 &mut self,
12797 container: PerlValue,
12798 key: String,
12799 val: PerlValue,
12800 line: usize,
12801 ) -> ExecResult {
12802 if let Some(b) = container.as_blessed_ref() {
12803 let mut data = b.data.write();
12804 if let Some(r) = data.as_hash_ref() {
12805 r.write().insert(key, val);
12806 return Ok(PerlValue::UNDEF);
12807 }
12808 if let Some(mut map) = data.as_hash_map() {
12809 map.insert(key, val);
12810 *data = PerlValue::hash(map);
12811 return Ok(PerlValue::UNDEF);
12812 }
12813 return Err(PerlError::runtime("Can't assign into non-hash blessed ref", line).into());
12814 }
12815 if let Some(r) = container.as_hash_ref() {
12816 r.write().insert(key, val);
12817 return Ok(PerlValue::UNDEF);
12818 }
12819 if let Some(name) = container.as_hash_binding_name() {
12820 self.touch_env_hash(&name);
12821 self.scope
12822 .set_hash_element(&name, &key, val)
12823 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12824 return Ok(PerlValue::UNDEF);
12825 }
12826 Err(PerlError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
12827 }
12828
12829 pub(crate) fn eval_arrow_array_base(
12832 &mut self,
12833 expr: &Expr,
12834 _line: usize,
12835 ) -> Result<PerlValue, FlowOrError> {
12836 match &expr.kind {
12837 ExprKind::Deref {
12838 expr: inner,
12839 kind: Sigil::Array | Sigil::Scalar,
12840 } => self.eval_expr(inner),
12841 _ => self.eval_expr(expr),
12842 }
12843 }
12844
12845 pub(crate) fn eval_arrow_hash_base(
12847 &mut self,
12848 expr: &Expr,
12849 _line: usize,
12850 ) -> Result<PerlValue, FlowOrError> {
12851 match &expr.kind {
12852 ExprKind::Deref {
12853 expr: inner,
12854 kind: Sigil::Scalar,
12855 } => self.eval_expr(inner),
12856 _ => self.eval_expr(expr),
12857 }
12858 }
12859
12860 pub(crate) fn read_arrow_array_element(
12862 &self,
12863 container: PerlValue,
12864 idx: i64,
12865 line: usize,
12866 ) -> Result<PerlValue, FlowOrError> {
12867 if let Some(a) = container.as_array_ref() {
12868 let arr = a.read();
12869 let i = if idx < 0 {
12870 (arr.len() as i64 + idx) as usize
12871 } else {
12872 idx as usize
12873 };
12874 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12875 }
12876 if let Some(name) = container.as_array_binding_name() {
12877 return Ok(self.scope.get_array_element(&name, idx));
12878 }
12879 if let Some(arr) = container.as_array_vec() {
12880 let i = if idx < 0 {
12881 (arr.len() as i64 + idx) as usize
12882 } else {
12883 idx as usize
12884 };
12885 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12886 }
12887 if let Some(b) = container.as_blessed_ref() {
12890 let inner = b.data.read().clone();
12891 if let Some(a) = inner.as_array_ref() {
12892 let arr = a.read();
12893 let i = if idx < 0 {
12894 (arr.len() as i64 + idx) as usize
12895 } else {
12896 idx as usize
12897 };
12898 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12899 }
12900 }
12901 Err(PerlError::runtime("Can't use arrow deref on non-array-ref", line).into())
12902 }
12903
12904 pub(crate) fn read_arrow_hash_element(
12906 &mut self,
12907 container: PerlValue,
12908 key: &str,
12909 line: usize,
12910 ) -> Result<PerlValue, FlowOrError> {
12911 if let Some(r) = container.as_hash_ref() {
12912 let h = r.read();
12913 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12914 }
12915 if let Some(name) = container.as_hash_binding_name() {
12916 self.touch_env_hash(&name);
12917 return Ok(self.scope.get_hash_element(&name, key));
12918 }
12919 if let Some(b) = container.as_blessed_ref() {
12920 let data = b.data.read();
12921 if let Some(v) = data.hash_get(key) {
12922 return Ok(v);
12923 }
12924 if let Some(r) = data.as_hash_ref() {
12925 let h = r.read();
12926 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12927 }
12928 return Err(PerlError::runtime(
12929 "Can't access hash field on non-hash blessed ref",
12930 line,
12931 )
12932 .into());
12933 }
12934 if let Some(s) = container.as_struct_inst() {
12936 if let Some(idx) = s.def.field_index(key) {
12937 return Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF));
12938 }
12939 return Err(PerlError::runtime(
12940 format!("struct {} has no field `{}`", s.def.name, key),
12941 line,
12942 )
12943 .into());
12944 }
12945 if let Some(c) = container.as_class_inst() {
12947 if let Some(idx) = c.def.field_index(key) {
12948 return Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF));
12949 }
12950 return Err(PerlError::runtime(
12951 format!("class {} has no field `{}`", c.def.name, key),
12952 line,
12953 )
12954 .into());
12955 }
12956 Err(PerlError::runtime("Can't use arrow deref on non-hash-ref", line).into())
12957 }
12958
12959 pub(crate) fn arrow_array_postfix(
12961 &mut self,
12962 container: PerlValue,
12963 idx: i64,
12964 decrement: bool,
12965 line: usize,
12966 ) -> Result<PerlValue, FlowOrError> {
12967 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
12968 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12969 self.assign_arrow_array_deref(container, idx, new_val, line)?;
12970 Ok(old)
12971 }
12972
12973 pub(crate) fn arrow_hash_postfix(
12975 &mut self,
12976 container: PerlValue,
12977 key: String,
12978 decrement: bool,
12979 line: usize,
12980 ) -> Result<PerlValue, FlowOrError> {
12981 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
12982 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12983 self.assign_arrow_hash_deref(container, key, new_val, line)?;
12984 Ok(old)
12985 }
12986
12987 pub(crate) fn resolve_bareword_rvalue(
12995 &mut self,
12996 name: &str,
12997 want: WantarrayCtx,
12998 line: usize,
12999 ) -> Result<PerlValue, FlowOrError> {
13000 if name == "__PACKAGE__" {
13001 return Ok(PerlValue::string(self.current_package()));
13002 }
13003 if let Some(sub) = self.resolve_sub_by_name(name) {
13004 return self.call_sub(&sub, vec![], want, line);
13005 }
13006 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
13008 return r.map_err(Into::into);
13009 }
13010 Ok(PerlValue::string(name.to_string()))
13011 }
13012
13013 pub(crate) fn arrow_array_slice_values(
13017 &mut self,
13018 container: PerlValue,
13019 indices: &[i64],
13020 line: usize,
13021 ) -> Result<PerlValue, FlowOrError> {
13022 let mut out = Vec::with_capacity(indices.len());
13023 for &idx in indices {
13024 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
13025 out.push(v);
13026 }
13027 Ok(PerlValue::array(out))
13028 }
13029
13030 pub(crate) fn assign_arrow_array_slice(
13034 &mut self,
13035 container: PerlValue,
13036 indices: Vec<i64>,
13037 val: PerlValue,
13038 line: usize,
13039 ) -> Result<PerlValue, FlowOrError> {
13040 if indices.is_empty() {
13041 return Err(PerlError::runtime("assign to empty array slice", line).into());
13042 }
13043 let vals = val.to_list();
13044 for (i, idx) in indices.iter().enumerate() {
13045 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13046 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
13047 }
13048 Ok(PerlValue::UNDEF)
13049 }
13050
13051 pub(crate) fn flatten_array_slice_index_specs(
13053 &mut self,
13054 indices: &[Expr],
13055 ) -> Result<Vec<i64>, FlowOrError> {
13056 let mut out = Vec::new();
13057 for idx_expr in indices {
13058 let v = if matches!(
13059 idx_expr.kind,
13060 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
13061 ) {
13062 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
13063 } else {
13064 self.eval_expr(idx_expr)?
13065 };
13066 if let Some(list) = v.as_array_vec() {
13067 for idx in list {
13068 out.push(idx.to_int());
13069 }
13070 } else {
13071 out.push(v.to_int());
13072 }
13073 }
13074 Ok(out)
13075 }
13076
13077 pub(crate) fn assign_named_array_slice(
13079 &mut self,
13080 stash_array_name: &str,
13081 indices: Vec<i64>,
13082 val: PerlValue,
13083 line: usize,
13084 ) -> Result<PerlValue, FlowOrError> {
13085 if indices.is_empty() {
13086 return Err(PerlError::runtime("assign to empty array slice", line).into());
13087 }
13088 let vals = val.to_list();
13089 for (i, idx) in indices.iter().enumerate() {
13090 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13091 self.scope
13092 .set_array_element(stash_array_name, *idx, v)
13093 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13094 }
13095 Ok(PerlValue::UNDEF)
13096 }
13097
13098 pub(crate) fn compound_assign_arrow_array_slice(
13101 &mut self,
13102 container: PerlValue,
13103 indices: Vec<i64>,
13104 op: BinOp,
13105 rhs: PerlValue,
13106 line: usize,
13107 ) -> Result<PerlValue, FlowOrError> {
13108 if indices.is_empty() {
13109 return Err(PerlError::runtime("assign to empty array slice", line).into());
13110 }
13111 let last_idx = *indices.last().expect("non-empty indices");
13112 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
13113 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
13114 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
13115 Ok(new_val)
13116 }
13117
13118 pub(crate) fn arrow_array_slice_inc_dec(
13123 &mut self,
13124 container: PerlValue,
13125 indices: Vec<i64>,
13126 kind: u8,
13127 line: usize,
13128 ) -> Result<PerlValue, FlowOrError> {
13129 if indices.is_empty() {
13130 return Err(
13131 PerlError::runtime("array slice increment needs at least one index", line).into(),
13132 );
13133 }
13134 let last_idx = *indices.last().expect("non-empty indices");
13135 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
13136 let new_val = if kind & 1 == 0 {
13137 PerlValue::integer(last_old.to_int() + 1)
13138 } else {
13139 PerlValue::integer(last_old.to_int() - 1)
13140 };
13141 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
13142 Ok(if kind < 2 { new_val } else { last_old })
13143 }
13144
13145 pub(crate) fn named_array_slice_inc_dec(
13148 &mut self,
13149 stash_array_name: &str,
13150 indices: Vec<i64>,
13151 kind: u8,
13152 line: usize,
13153 ) -> Result<PerlValue, FlowOrError> {
13154 let last_idx = *indices.last().ok_or_else(|| {
13155 PerlError::runtime("array slice increment needs at least one index", line)
13156 })?;
13157 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
13158 let new_val = if kind & 1 == 0 {
13159 PerlValue::integer(last_old.to_int() + 1)
13160 } else {
13161 PerlValue::integer(last_old.to_int() - 1)
13162 };
13163 self.scope
13164 .set_array_element(stash_array_name, last_idx, new_val.clone())
13165 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13166 Ok(if kind < 2 { new_val } else { last_old })
13167 }
13168
13169 pub(crate) fn compound_assign_named_array_slice(
13171 &mut self,
13172 stash_array_name: &str,
13173 indices: Vec<i64>,
13174 op: BinOp,
13175 rhs: PerlValue,
13176 line: usize,
13177 ) -> Result<PerlValue, FlowOrError> {
13178 if indices.is_empty() {
13179 return Err(PerlError::runtime("assign to empty array slice", line).into());
13180 }
13181 let last_idx = *indices.last().expect("non-empty indices");
13182 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
13183 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
13184 self.scope
13185 .set_array_element(stash_array_name, last_idx, new_val.clone())
13186 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13187 Ok(new_val)
13188 }
13189
13190 pub(crate) fn assign_arrow_array_deref(
13192 &mut self,
13193 container: PerlValue,
13194 idx: i64,
13195 val: PerlValue,
13196 line: usize,
13197 ) -> ExecResult {
13198 if let Some(a) = container.as_array_ref() {
13199 let mut arr = a.write();
13200 let i = if idx < 0 {
13201 (arr.len() as i64 + idx) as usize
13202 } else {
13203 idx as usize
13204 };
13205 if i >= arr.len() {
13206 arr.resize(i + 1, PerlValue::UNDEF);
13207 }
13208 arr[i] = val;
13209 return Ok(PerlValue::UNDEF);
13210 }
13211 if let Some(name) = container.as_array_binding_name() {
13212 self.scope
13213 .set_array_element(&name, idx, val)
13214 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
13215 return Ok(PerlValue::UNDEF);
13216 }
13217 Err(PerlError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
13218 }
13219
13220 pub(crate) fn assign_typeglob_value(
13222 &mut self,
13223 name: &str,
13224 val: PerlValue,
13225 line: usize,
13226 ) -> ExecResult {
13227 let sub = if let Some(c) = val.as_code_ref() {
13228 Some(c)
13229 } else if let Some(r) = val.as_scalar_ref() {
13230 r.read().as_code_ref().map(|c| Arc::clone(&c))
13231 } else {
13232 None
13233 };
13234 if let Some(sub) = sub {
13235 let lhs_sub = self.qualify_typeglob_sub_key(name);
13236 self.subs.insert(lhs_sub, sub);
13237 return Ok(PerlValue::UNDEF);
13238 }
13239 Err(PerlError::runtime(
13240 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
13241 line,
13242 )
13243 .into())
13244 }
13245
13246 fn assign_value(&mut self, target: &Expr, val: PerlValue) -> ExecResult {
13247 match &target.kind {
13248 ExprKind::ScalarVar(name) => {
13249 let stor = self.tree_scalar_storage_name(name);
13250 if self.scope.is_scalar_frozen(&stor) {
13251 return Err(FlowOrError::Error(PerlError::runtime(
13252 format!("Modification of a frozen value: ${}", name),
13253 target.line,
13254 )));
13255 }
13256 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
13257 let class = obj
13258 .as_blessed_ref()
13259 .map(|b| b.class.clone())
13260 .unwrap_or_default();
13261 let full = format!("{}::STORE", class);
13262 if let Some(sub) = self.subs.get(&full).cloned() {
13263 let arg_vals = vec![obj, val];
13264 return match self.call_sub(
13265 &sub,
13266 arg_vals,
13267 WantarrayCtx::Scalar,
13268 target.line,
13269 ) {
13270 Ok(_) => Ok(PerlValue::UNDEF),
13271 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13272 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13273 };
13274 }
13275 }
13276 self.set_special_var(&stor, &val)
13277 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
13278 Ok(PerlValue::UNDEF)
13279 }
13280 ExprKind::ArrayVar(name) => {
13281 if self.scope.is_array_frozen(name) {
13282 return Err(PerlError::runtime(
13283 format!("Modification of a frozen value: @{}", name),
13284 target.line,
13285 )
13286 .into());
13287 }
13288 if self.strict_vars
13289 && !name.contains("::")
13290 && !self.scope.array_binding_exists(name)
13291 {
13292 return Err(PerlError::runtime(
13293 format!(
13294 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
13295 name, name
13296 ),
13297 target.line,
13298 )
13299 .into());
13300 }
13301 self.scope.set_array(name, val.to_list())?;
13302 Ok(PerlValue::UNDEF)
13303 }
13304 ExprKind::HashVar(name) => {
13305 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
13306 {
13307 return Err(PerlError::runtime(
13308 format!(
13309 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13310 name, name
13311 ),
13312 target.line,
13313 )
13314 .into());
13315 }
13316 let items = val.to_list();
13317 let mut map = IndexMap::new();
13318 let mut i = 0;
13319 while i + 1 < items.len() {
13320 map.insert(items[i].to_string(), items[i + 1].clone());
13321 i += 2;
13322 }
13323 self.scope.set_hash(name, map)?;
13324 Ok(PerlValue::UNDEF)
13325 }
13326 ExprKind::ArrayElement { array, index } => {
13327 if self.strict_vars
13328 && !array.contains("::")
13329 && !self.scope.array_binding_exists(array)
13330 {
13331 return Err(PerlError::runtime(
13332 format!(
13333 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
13334 array, array
13335 ),
13336 target.line,
13337 )
13338 .into());
13339 }
13340 if self.scope.is_array_frozen(array) {
13341 return Err(PerlError::runtime(
13342 format!("Modification of a frozen value: @{}", array),
13343 target.line,
13344 )
13345 .into());
13346 }
13347 let idx = self.eval_expr(index)?.to_int();
13348 let aname = self.stash_array_name_for_package(array);
13349 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
13350 let class = obj
13351 .as_blessed_ref()
13352 .map(|b| b.class.clone())
13353 .unwrap_or_default();
13354 let full = format!("{}::STORE", class);
13355 if let Some(sub) = self.subs.get(&full).cloned() {
13356 let arg_vals = vec![obj, PerlValue::integer(idx), val];
13357 return match self.call_sub(
13358 &sub,
13359 arg_vals,
13360 WantarrayCtx::Scalar,
13361 target.line,
13362 ) {
13363 Ok(_) => Ok(PerlValue::UNDEF),
13364 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13365 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13366 };
13367 }
13368 }
13369 self.scope.set_array_element(&aname, idx, val)?;
13370 Ok(PerlValue::UNDEF)
13371 }
13372 ExprKind::ArraySlice { array, indices } => {
13373 if indices.is_empty() {
13374 return Err(
13375 PerlError::runtime("assign to empty array slice", target.line).into(),
13376 );
13377 }
13378 self.check_strict_array_var(array, target.line)?;
13379 if self.scope.is_array_frozen(array) {
13380 return Err(PerlError::runtime(
13381 format!("Modification of a frozen value: @{}", array),
13382 target.line,
13383 )
13384 .into());
13385 }
13386 let aname = self.stash_array_name_for_package(array);
13387 let flat = self.flatten_array_slice_index_specs(indices)?;
13388 self.assign_named_array_slice(&aname, flat, val, target.line)
13389 }
13390 ExprKind::HashElement { hash, key } => {
13391 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
13392 {
13393 return Err(PerlError::runtime(
13394 format!(
13395 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13396 hash, hash
13397 ),
13398 target.line,
13399 )
13400 .into());
13401 }
13402 if self.scope.is_hash_frozen(hash) {
13403 return Err(PerlError::runtime(
13404 format!("Modification of a frozen value: %%{}", hash),
13405 target.line,
13406 )
13407 .into());
13408 }
13409 let k = self.eval_expr(key)?.to_string();
13410 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
13411 let class = obj
13412 .as_blessed_ref()
13413 .map(|b| b.class.clone())
13414 .unwrap_or_default();
13415 let full = format!("{}::STORE", class);
13416 if let Some(sub) = self.subs.get(&full).cloned() {
13417 let arg_vals = vec![obj, PerlValue::string(k), val];
13418 return match self.call_sub(
13419 &sub,
13420 arg_vals,
13421 WantarrayCtx::Scalar,
13422 target.line,
13423 ) {
13424 Ok(_) => Ok(PerlValue::UNDEF),
13425 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13426 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13427 };
13428 }
13429 }
13430 self.scope.set_hash_element(hash, &k, val)?;
13431 Ok(PerlValue::UNDEF)
13432 }
13433 ExprKind::HashSlice { hash, keys } => {
13434 if keys.is_empty() {
13435 return Err(
13436 PerlError::runtime("assign to empty hash slice", target.line).into(),
13437 );
13438 }
13439 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
13440 {
13441 return Err(PerlError::runtime(
13442 format!(
13443 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13444 hash, hash
13445 ),
13446 target.line,
13447 )
13448 .into());
13449 }
13450 if self.scope.is_hash_frozen(hash) {
13451 return Err(PerlError::runtime(
13452 format!("Modification of a frozen value: %%{}", hash),
13453 target.line,
13454 )
13455 .into());
13456 }
13457 let mut key_vals = Vec::with_capacity(keys.len());
13458 for key_expr in keys {
13459 let v = if matches!(
13460 key_expr.kind,
13461 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
13462 ) {
13463 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
13464 } else {
13465 self.eval_expr(key_expr)?
13466 };
13467 key_vals.push(v);
13468 }
13469 self.assign_named_hash_slice(hash, key_vals, val, target.line)
13470 }
13471 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
13472 ExprKind::TypeglobExpr(e) => {
13473 let name = self.eval_expr(e)?.to_string();
13474 let synthetic = Expr {
13475 kind: ExprKind::Typeglob(name),
13476 line: target.line,
13477 };
13478 self.assign_value(&synthetic, val)
13479 }
13480 ExprKind::AnonymousListSlice { source, indices } => {
13481 if let ExprKind::Deref {
13482 expr: inner,
13483 kind: Sigil::Array,
13484 } = &source.kind
13485 {
13486 let container = self.eval_arrow_array_base(inner, target.line)?;
13487 let vals = val.to_list();
13488 let n = indices.len().min(vals.len());
13489 for i in 0..n {
13490 let idx = self.eval_expr(&indices[i])?.to_int();
13491 self.assign_arrow_array_deref(
13492 container.clone(),
13493 idx,
13494 vals[i].clone(),
13495 target.line,
13496 )?;
13497 }
13498 return Ok(PerlValue::UNDEF);
13499 }
13500 Err(
13501 PerlError::runtime("assign to list slice: unsupported base", target.line)
13502 .into(),
13503 )
13504 }
13505 ExprKind::ArrowDeref {
13506 expr,
13507 index,
13508 kind: DerefKind::Hash,
13509 } => {
13510 let key = self.eval_expr(index)?.to_string();
13511 let container = self.eval_expr(expr)?;
13512 self.assign_arrow_hash_deref(container, key, val, target.line)
13513 }
13514 ExprKind::ArrowDeref {
13515 expr,
13516 index,
13517 kind: DerefKind::Array,
13518 } => {
13519 let container = self.eval_arrow_array_base(expr, target.line)?;
13520 if let ExprKind::List(indices) = &index.kind {
13521 let vals = val.to_list();
13522 let n = indices.len().min(vals.len());
13523 for i in 0..n {
13524 let idx = self.eval_expr(&indices[i])?.to_int();
13525 self.assign_arrow_array_deref(
13526 container.clone(),
13527 idx,
13528 vals[i].clone(),
13529 target.line,
13530 )?;
13531 }
13532 return Ok(PerlValue::UNDEF);
13533 }
13534 let idx = self.eval_expr(index)?.to_int();
13535 self.assign_arrow_array_deref(container, idx, val, target.line)
13536 }
13537 ExprKind::HashSliceDeref { container, keys } => {
13538 let href = self.eval_expr(container)?;
13539 let mut key_vals = Vec::with_capacity(keys.len());
13540 for key_expr in keys {
13541 key_vals.push(self.eval_expr(key_expr)?);
13542 }
13543 self.assign_hash_slice_deref(href, key_vals, val, target.line)
13544 }
13545 ExprKind::Deref {
13546 expr,
13547 kind: Sigil::Scalar,
13548 } => {
13549 let ref_val = self.eval_expr(expr)?;
13550 self.assign_scalar_ref_deref(ref_val, val, target.line)
13551 }
13552 ExprKind::Deref {
13553 expr,
13554 kind: Sigil::Array,
13555 } => {
13556 let ref_val = self.eval_expr(expr)?;
13557 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
13558 }
13559 ExprKind::Deref {
13560 expr,
13561 kind: Sigil::Hash,
13562 } => {
13563 let ref_val = self.eval_expr(expr)?;
13564 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
13565 }
13566 ExprKind::Deref {
13567 expr,
13568 kind: Sigil::Typeglob,
13569 } => {
13570 let ref_val = self.eval_expr(expr)?;
13571 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
13572 }
13573 ExprKind::Pos(inner) => {
13574 let key = match inner {
13575 None => "_".to_string(),
13576 Some(expr) => match &expr.kind {
13577 ExprKind::ScalarVar(n) => n.clone(),
13578 _ => self.eval_expr(expr)?.to_string(),
13579 },
13580 };
13581 if val.is_undef() {
13582 self.regex_pos.insert(key, None);
13583 } else {
13584 let u = val.to_int().max(0) as usize;
13585 self.regex_pos.insert(key, Some(u));
13586 }
13587 Ok(PerlValue::UNDEF)
13588 }
13589 ExprKind::List(targets) => {
13592 let items = val.to_list();
13593 for (i, t) in targets.iter().enumerate() {
13594 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13595 self.assign_value(t, v)?;
13596 }
13597 Ok(PerlValue::UNDEF)
13598 }
13599 ExprKind::Assign { target, .. } => self.assign_value(target, val),
13602 _ => Ok(PerlValue::UNDEF),
13603 }
13604 }
13605
13606 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
13608 (name.starts_with('#') && name.len() > 1)
13609 || name.starts_with('^')
13610 || matches!(
13611 name,
13612 "$$" | "0"
13613 | "!"
13614 | "@"
13615 | "/"
13616 | "\\"
13617 | ","
13618 | "."
13619 | "]"
13620 | ";"
13621 | "ARGV"
13622 | "^I"
13623 | "^D"
13624 | "^P"
13625 | "^S"
13626 | "^W"
13627 | "^O"
13628 | "^T"
13629 | "^V"
13630 | "^E"
13631 | "^H"
13632 | "^WARNING_BITS"
13633 | "^GLOBAL_PHASE"
13634 | "^MATCH"
13635 | "^PREMATCH"
13636 | "^POSTMATCH"
13637 | "^LAST_SUBMATCH_RESULT"
13638 | "<"
13639 | ">"
13640 | "("
13641 | ")"
13642 | "?"
13643 | "|"
13644 | "\""
13645 | "+"
13646 | "%"
13647 | "="
13648 | "-"
13649 | ":"
13650 | "*"
13651 | "INC"
13652 )
13653 || crate::english::is_known_alias(name)
13654 }
13655
13656 #[inline]
13661 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
13662 if !self.english_enabled {
13663 return name;
13664 }
13665 if self
13666 .english_lexical_scalars
13667 .iter()
13668 .any(|s| s.contains(name))
13669 {
13670 return name;
13671 }
13672 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
13673 return short;
13674 }
13675 name
13676 }
13677
13678 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
13680 name.starts_with('^')
13681 || matches!(
13682 name,
13683 "0" | "/"
13684 | "\\"
13685 | ","
13686 | ";"
13687 | "\""
13688 | "%"
13689 | "="
13690 | "-"
13691 | ":"
13692 | "*"
13693 | "INC"
13694 | "^I"
13695 | "^D"
13696 | "^P"
13697 | "^W"
13698 | "^H"
13699 | "^WARNING_BITS"
13700 | "$$"
13701 | "]"
13702 | "^S"
13703 | "ARGV"
13704 | "|"
13705 | "+"
13706 | "?"
13707 | "!"
13708 | "@"
13709 | "."
13710 )
13711 || crate::english::is_known_alias(name)
13712 }
13713
13714 pub(crate) fn get_special_var(&self, name: &str) -> PerlValue {
13715 let name = if !crate::compat_mode() {
13717 match name {
13718 "NR" => ".",
13719 "RS" => "/",
13720 "OFS" => ",",
13721 "ORS" => "\\",
13722 "NF" => {
13723 let len = self.scope.array_len("F");
13724 return PerlValue::integer(len as i64);
13725 }
13726 _ => self.english_scalar_name(name),
13727 }
13728 } else {
13729 self.english_scalar_name(name)
13730 };
13731 match name {
13732 "$$" => PerlValue::integer(std::process::id() as i64),
13733 "_" => self.scope.get_scalar("_"),
13734 "^MATCH" => PerlValue::string(self.last_match.clone()),
13735 "^PREMATCH" => PerlValue::string(self.prematch.clone()),
13736 "^POSTMATCH" => PerlValue::string(self.postmatch.clone()),
13737 "^LAST_SUBMATCH_RESULT" => PerlValue::string(self.last_paren_match.clone()),
13738 "0" => PerlValue::string(self.program_name.clone()),
13739 "!" => PerlValue::errno_dual(self.errno_code, self.errno.clone()),
13740 "@" => {
13741 if let Some(ref v) = self.eval_error_value {
13742 v.clone()
13743 } else {
13744 PerlValue::errno_dual(self.eval_error_code, self.eval_error.clone())
13745 }
13746 }
13747 "/" => match &self.irs {
13748 Some(s) => PerlValue::string(s.clone()),
13749 None => PerlValue::UNDEF,
13750 },
13751 "\\" => PerlValue::string(self.ors.clone()),
13752 "," => PerlValue::string(self.ofs.clone()),
13753 "." => {
13754 if self.last_readline_handle.is_empty() {
13756 if self.line_number == 0 {
13757 PerlValue::UNDEF
13758 } else {
13759 PerlValue::integer(self.line_number)
13760 }
13761 } else {
13762 PerlValue::integer(
13763 *self
13764 .handle_line_numbers
13765 .get(&self.last_readline_handle)
13766 .unwrap_or(&0),
13767 )
13768 }
13769 }
13770 "]" => PerlValue::float(perl_bracket_version()),
13771 ";" => PerlValue::string(self.subscript_sep.clone()),
13772 "ARGV" => PerlValue::string(self.argv_current_file.clone()),
13773 "^I" => PerlValue::string(self.inplace_edit.clone()),
13774 "^D" => PerlValue::integer(self.debug_flags),
13775 "^P" => PerlValue::integer(self.perl_debug_flags),
13776 "^S" => PerlValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
13777 "^W" => PerlValue::integer(if self.warnings { 1 } else { 0 }),
13778 "^O" => PerlValue::string(perl_osname()),
13779 "^T" => PerlValue::integer(self.script_start_time),
13780 "^V" => PerlValue::string(perl_version_v_string()),
13781 "^E" => PerlValue::string(extended_os_error_string()),
13782 "^H" => PerlValue::integer(self.compile_hints),
13783 "^WARNING_BITS" => PerlValue::integer(self.warning_bits),
13784 "^GLOBAL_PHASE" => PerlValue::string(self.global_phase.clone()),
13785 "<" | ">" => PerlValue::integer(unix_id_for_special(name)),
13786 "(" | ")" => PerlValue::string(unix_group_list_for_special(name)),
13787 "?" => PerlValue::integer(self.child_exit_status),
13788 "|" => PerlValue::integer(if self.output_autoflush { 1 } else { 0 }),
13789 "\"" => PerlValue::string(self.list_separator.clone()),
13790 "+" => PerlValue::string(self.last_paren_match.clone()),
13791 "%" => PerlValue::integer(self.format_page_number),
13792 "=" => PerlValue::integer(self.format_lines_per_page),
13793 "-" => PerlValue::integer(self.format_lines_left),
13794 ":" => PerlValue::string(self.format_line_break_chars.clone()),
13795 "*" => PerlValue::integer(if self.multiline_match { 1 } else { 0 }),
13796 "^" => PerlValue::string(self.format_top_name.clone()),
13797 "INC" => PerlValue::integer(self.inc_hook_index),
13798 "^A" => PerlValue::string(self.accumulator_format.clone()),
13799 "^C" => PerlValue::integer(if self.sigint_pending_caret.replace(false) {
13800 1
13801 } else {
13802 0
13803 }),
13804 "^F" => PerlValue::integer(self.max_system_fd),
13805 "^L" => PerlValue::string(self.formfeed_string.clone()),
13806 "^M" => PerlValue::string(self.emergency_memory.clone()),
13807 "^N" => PerlValue::string(self.last_subpattern_name.clone()),
13808 "^X" => PerlValue::string(self.executable_path.clone()),
13809 "^TAINT" | "^TAINTED" => PerlValue::integer(0),
13811 "^UNICODE" => PerlValue::integer(if self.utf8_pragma { 1 } else { 0 }),
13812 "^OPEN" => PerlValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
13813 "^UTF8LOCALE" => PerlValue::integer(0),
13814 "^UTF8CACHE" => PerlValue::integer(-1),
13815 _ if name.starts_with('^') && name.len() > 1 => self
13816 .special_caret_scalars
13817 .get(name)
13818 .cloned()
13819 .unwrap_or(PerlValue::UNDEF),
13820 _ if name.starts_with('#') && name.len() > 1 => {
13821 let arr = &name[1..];
13822 let aname = self.stash_array_name_for_package(arr);
13823 let len = self.scope.array_len(&aname);
13824 PerlValue::integer(len as i64 - 1)
13825 }
13826 _ => self.scope.get_scalar(name),
13827 }
13828 }
13829
13830 pub(crate) fn set_special_var(&mut self, name: &str, val: &PerlValue) -> Result<(), PerlError> {
13831 let name = self.english_scalar_name(name);
13832 match name {
13833 "!" => {
13834 let code = val.to_int() as i32;
13835 self.errno_code = code;
13836 self.errno = if code == 0 {
13837 String::new()
13838 } else {
13839 std::io::Error::from_raw_os_error(code).to_string()
13840 };
13841 }
13842 "@" => {
13843 if let Some((code, msg)) = val.errno_dual_parts() {
13844 self.eval_error_code = code;
13845 self.eval_error = msg;
13846 } else {
13847 self.eval_error = val.to_string();
13848 let mut code = val.to_int() as i32;
13849 if code == 0 && !self.eval_error.is_empty() {
13850 code = 1;
13851 }
13852 self.eval_error_code = code;
13853 }
13854 }
13855 "." => {
13856 let n = val.to_int();
13859 if self.last_readline_handle.is_empty() {
13860 self.line_number = n;
13861 } else {
13862 self.handle_line_numbers
13863 .insert(self.last_readline_handle.clone(), n);
13864 }
13865 }
13866 "0" => self.program_name = val.to_string(),
13867 "/" => {
13868 self.irs = if val.is_undef() {
13869 None
13870 } else {
13871 Some(val.to_string())
13872 }
13873 }
13874 "\\" => self.ors = val.to_string(),
13875 "," => self.ofs = val.to_string(),
13876 ";" => self.subscript_sep = val.to_string(),
13877 "\"" => self.list_separator = val.to_string(),
13878 "%" => self.format_page_number = val.to_int(),
13879 "=" => self.format_lines_per_page = val.to_int(),
13880 "-" => self.format_lines_left = val.to_int(),
13881 ":" => self.format_line_break_chars = val.to_string(),
13882 "*" => self.multiline_match = val.to_int() != 0,
13883 "^" => self.format_top_name = val.to_string(),
13884 "INC" => self.inc_hook_index = val.to_int(),
13885 "^A" => self.accumulator_format = val.to_string(),
13886 "^F" => self.max_system_fd = val.to_int(),
13887 "^L" => self.formfeed_string = val.to_string(),
13888 "^M" => self.emergency_memory = val.to_string(),
13889 "^I" => self.inplace_edit = val.to_string(),
13890 "^D" => self.debug_flags = val.to_int(),
13891 "^P" => self.perl_debug_flags = val.to_int(),
13892 "^W" => self.warnings = val.to_int() != 0,
13893 "^H" => self.compile_hints = val.to_int(),
13894 "^WARNING_BITS" => self.warning_bits = val.to_int(),
13895 "|" => {
13896 self.output_autoflush = val.to_int() != 0;
13897 if self.output_autoflush {
13898 let _ = io::stdout().flush();
13899 }
13900 }
13901 "$$"
13903 | "]"
13904 | "^S"
13905 | "ARGV"
13906 | "?"
13907 | "^O"
13908 | "^T"
13909 | "^V"
13910 | "^E"
13911 | "^GLOBAL_PHASE"
13912 | "^MATCH"
13913 | "^PREMATCH"
13914 | "^POSTMATCH"
13915 | "^LAST_SUBMATCH_RESULT"
13916 | "^C"
13917 | "^N"
13918 | "^X"
13919 | "^TAINT"
13920 | "^TAINTED"
13921 | "^UNICODE"
13922 | "^UTF8LOCALE"
13923 | "^UTF8CACHE"
13924 | "+"
13925 | "<"
13926 | ">"
13927 | "("
13928 | ")" => {}
13929 _ if name.starts_with('^') && name.len() > 1 => {
13930 self.special_caret_scalars
13931 .insert(name.to_string(), val.clone());
13932 }
13933 _ => self.scope.set_scalar(name, val.clone())?,
13934 }
13935 Ok(())
13936 }
13937
13938 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
13939 match &expr.kind {
13940 ExprKind::ArrayVar(name) => Ok(name.clone()),
13941 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(PerlError::runtime("Expected array", expr.line).into()),
13943 }
13944 }
13945
13946 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
13948 match &expr.kind {
13949 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
13950 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
13951 _ => expr,
13952 }
13953 }
13954
13955 fn try_eval_array_deref_container(
13957 &mut self,
13958 expr: &Expr,
13959 ) -> Result<Option<PerlValue>, FlowOrError> {
13960 let e = Self::peel_array_builtin_operand(expr);
13961 if let ExprKind::Deref {
13962 expr: inner,
13963 kind: Sigil::Array,
13964 } = &e.kind
13965 {
13966 return Ok(Some(self.eval_expr(inner)?));
13967 }
13968 Ok(None)
13969 }
13970
13971 fn current_package(&self) -> String {
13973 let s = self.scope.get_scalar("__PACKAGE__").to_string();
13974 if s.is_empty() {
13975 "main".to_string()
13976 } else {
13977 s
13978 }
13979 }
13980
13981 pub(crate) fn package_version_scalar(
13984 &mut self,
13985 package: &str,
13986 ) -> PerlResult<Option<PerlValue>> {
13987 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
13988 let _ = self
13989 .scope
13990 .set_scalar("__PACKAGE__", PerlValue::string(package.to_string()));
13991 let ver = self.get_special_var("VERSION");
13992 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
13993 Ok(if ver.is_undef() { None } else { Some(ver) })
13994 }
13995
13996 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<PerlSub>> {
13998 let root = if start_package.is_empty() {
13999 "main"
14000 } else {
14001 start_package
14002 };
14003 for pkg in self.mro_linearize(root) {
14004 let key = if pkg == "main" {
14005 "AUTOLOAD".to_string()
14006 } else {
14007 format!("{}::AUTOLOAD", pkg)
14008 };
14009 if let Some(s) = self.subs.get(&key) {
14010 return Some(s.clone());
14011 }
14012 }
14013 None
14014 }
14015
14016 pub(crate) fn try_autoload_call(
14021 &mut self,
14022 missing_name: &str,
14023 args: Vec<PerlValue>,
14024 line: usize,
14025 want: WantarrayCtx,
14026 method_invocant_class: Option<&str>,
14027 ) -> Option<ExecResult> {
14028 let pkg = self.current_package();
14029 let full = if missing_name.contains("::") {
14030 missing_name.to_string()
14031 } else {
14032 format!("{}::{}", pkg, missing_name)
14033 };
14034 let start_pkg = method_invocant_class.unwrap_or_else(|| {
14035 full.rsplit_once("::")
14036 .map(|(p, _)| p)
14037 .filter(|p| !p.is_empty())
14038 .unwrap_or("main")
14039 });
14040 let sub = self.resolve_autoload_sub(start_pkg)?;
14041 if let Err(e) = self
14042 .scope
14043 .set_scalar("AUTOLOAD", PerlValue::string(full.clone()))
14044 {
14045 return Some(Err(e.into()));
14046 }
14047 Some(self.call_sub(&sub, args, want, line))
14048 }
14049
14050 pub(crate) fn with_topic_default_args(&self, args: Vec<PerlValue>) -> Vec<PerlValue> {
14051 if args.is_empty() {
14052 vec![self.scope.get_scalar("_").clone()]
14053 } else {
14054 args
14055 }
14056 }
14057
14058 pub(crate) fn dispatch_indirect_call(
14061 &mut self,
14062 target: PerlValue,
14063 arg_vals: Vec<PerlValue>,
14064 want: WantarrayCtx,
14065 line: usize,
14066 ) -> ExecResult {
14067 if let Some(sub) = target.as_code_ref() {
14068 return self.call_sub(&sub, arg_vals, want, line);
14069 }
14070 if let Some(name) = target.as_str() {
14071 return self.call_named_sub(&name, arg_vals, line, want);
14072 }
14073 Err(PerlError::runtime("Can't use non-code reference as a subroutine", line).into())
14074 }
14075
14076 pub(crate) fn call_bare_list_builtin(
14081 &mut self,
14082 name: &str,
14083 args: Vec<PerlValue>,
14084 line: usize,
14085 want: WantarrayCtx,
14086 ) -> ExecResult {
14087 let canonical = match name {
14088 "distinct" | "uq" => "uniq",
14089 "shuf" => "shuffle",
14090 "chk" => "chunked",
14091 "win" => "windowed",
14092 "zp" => "zip",
14093 "fst" => "first",
14094 "rd" => "reduce",
14095 "med" => "median",
14096 "std" => "stddev",
14097 "var" => "variance",
14098 other => other,
14099 };
14100 match crate::list_builtins::dispatch_by_name(self, canonical, &args, want) {
14105 Some(r) => r,
14106 None => Err(PerlError::runtime(
14107 format!("internal: not a stryke list builtin: {name}"),
14108 line,
14109 )
14110 .into()),
14111 }
14112 }
14113
14114 fn call_named_sub(
14115 &mut self,
14116 name: &str,
14117 args: Vec<PerlValue>,
14118 line: usize,
14119 want: WantarrayCtx,
14120 ) -> ExecResult {
14121 if let Some(sub) = self.resolve_sub_by_name(name) {
14122 let args = self.with_topic_default_args(args);
14123 return self.call_sub(&sub, args, want, line);
14124 }
14125 match name {
14126 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
14127 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
14128 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
14129 | "none" | "notall" | "first" | "fst" | "reduce" | "rd" | "reductions" | "sum"
14130 | "sum0" | "product" | "min" | "max" | "minstr" | "maxstr" | "mean" | "median"
14131 | "med" | "mode" | "stddev" | "std" | "variance" | "var" | "pairs" | "unpairs"
14132 | "pairkeys" | "pairvalues" | "pairgrep" | "pairmap" | "pairfirst" | "blessed"
14133 | "refaddr" | "reftype" | "weaken" | "unweaken" | "isweak" | "set_subname"
14134 | "subname" | "unicode_to_native" => {
14135 self.call_bare_list_builtin(name, args, line, want)
14136 }
14137 "deque" => {
14138 if !args.is_empty() {
14139 return Err(PerlError::runtime("deque() takes no arguments", line).into());
14140 }
14141 Ok(PerlValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
14142 }
14143 "defer__internal" => {
14144 if args.len() != 1 {
14145 return Err(PerlError::runtime(
14146 "defer__internal expects one coderef argument",
14147 line,
14148 )
14149 .into());
14150 }
14151 self.scope.push_defer(args[0].clone());
14152 Ok(PerlValue::UNDEF)
14153 }
14154 "heap" => {
14155 if args.len() != 1 {
14156 return Err(
14157 PerlError::runtime("heap() expects one comparator sub", line).into(),
14158 );
14159 }
14160 if let Some(sub) = args[0].as_code_ref() {
14161 Ok(PerlValue::heap(Arc::new(Mutex::new(PerlHeap {
14162 items: Vec::new(),
14163 cmp: Arc::clone(&sub),
14164 }))))
14165 } else {
14166 Err(PerlError::runtime("heap() requires a code reference", line).into())
14167 }
14168 }
14169 "pipeline" => {
14170 let mut items = Vec::new();
14171 for v in args {
14172 if let Some(a) = v.as_array_vec() {
14173 items.extend(a);
14174 } else {
14175 items.push(v);
14176 }
14177 }
14178 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
14179 source: items,
14180 ops: Vec::new(),
14181 has_scalar_terminal: false,
14182 par_stream: false,
14183 streaming: false,
14184 streaming_workers: 0,
14185 streaming_buffer: 256,
14186 }))))
14187 }
14188 "par_pipeline" => {
14189 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
14190 return crate::par_pipeline::run_par_pipeline(self, &args, line)
14191 .map_err(Into::into);
14192 }
14193 Ok(self.builtin_par_pipeline_stream(&args, line)?)
14194 }
14195 "par_pipeline_stream" => {
14196 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
14197 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
14198 .map_err(Into::into);
14199 }
14200 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
14201 }
14202 "ppool" => {
14203 if args.len() != 1 {
14204 return Err(PerlError::runtime(
14205 "ppool() expects one argument (worker count)",
14206 line,
14207 )
14208 .into());
14209 }
14210 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
14211 }
14212 "barrier" => {
14213 if args.len() != 1 {
14214 return Err(PerlError::runtime(
14215 "barrier() expects one argument (party count)",
14216 line,
14217 )
14218 .into());
14219 }
14220 let n = args[0].to_int().max(1) as usize;
14221 Ok(PerlValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
14222 }
14223 "cluster" => {
14224 let items = if args.len() == 1 {
14225 args[0].to_list()
14226 } else {
14227 args.to_vec()
14228 };
14229 let c = RemoteCluster::from_list_args(&items)
14230 .map_err(|msg| PerlError::runtime(msg, line))?;
14231 Ok(PerlValue::remote_cluster(Arc::new(c)))
14232 }
14233 _ => {
14234 if let Some(method_name) = name.strip_prefix("static::") {
14236 let self_val = self.scope.get_scalar("self");
14237 if let Some(c) = self_val.as_class_inst() {
14238 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
14239 if let Some(ref body) = m.body {
14240 let params = m.params.clone();
14241 let mut call_args = vec![self_val.clone()];
14242 call_args.extend(args);
14243 return match self.call_class_method(body, ¶ms, call_args, line)
14244 {
14245 Ok(v) => Ok(v),
14246 Err(FlowOrError::Error(e)) => Err(e.into()),
14247 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14248 Err(e) => Err(e),
14249 };
14250 }
14251 }
14252 return Err(PerlError::runtime(
14253 format!(
14254 "static::{} — method not found on class {}",
14255 method_name, c.def.name
14256 ),
14257 line,
14258 )
14259 .into());
14260 }
14261 return Err(PerlError::runtime(
14262 "static:: can only be used inside a class method",
14263 line,
14264 )
14265 .into());
14266 }
14267 if let Some(def) = self.struct_defs.get(name).cloned() {
14269 return self.struct_construct(&def, args, line);
14270 }
14271 if let Some(def) = self.class_defs.get(name).cloned() {
14273 return self.class_construct(&def, args, line);
14274 }
14275 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
14277 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
14278 return self.enum_construct(&def, variant_name, args, line);
14279 }
14280 }
14281 if let Some((class_name, member_name)) = name.rsplit_once("::") {
14283 if let Some(def) = self.class_defs.get(class_name).cloned() {
14284 if let Some(m) = def.method(member_name) {
14286 if m.is_static {
14287 if let Some(ref body) = m.body {
14288 let params = m.params.clone();
14289 return match self.call_static_class_method(
14290 body,
14291 ¶ms,
14292 args.clone(),
14293 line,
14294 ) {
14295 Ok(v) => Ok(v),
14296 Err(FlowOrError::Error(e)) => Err(e.into()),
14297 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14298 Err(e) => Err(e),
14299 };
14300 }
14301 }
14302 }
14303 if def.static_fields.iter().any(|sf| sf.name == member_name) {
14305 let key = format!("{}::{}", class_name, member_name);
14306 match args.len() {
14307 0 => {
14308 let val = self.scope.get_scalar(&key);
14309 return Ok(val);
14310 }
14311 1 => {
14312 let _ = self.scope.set_scalar(&key, args[0].clone());
14313 return Ok(args[0].clone());
14314 }
14315 _ => {
14316 return Err(PerlError::runtime(
14317 format!(
14318 "static field `{}::{}` takes 0 or 1 arguments",
14319 class_name, member_name
14320 ),
14321 line,
14322 )
14323 .into());
14324 }
14325 }
14326 }
14327 }
14328 }
14329 let args = self.with_topic_default_args(args);
14330 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
14331 return r;
14332 }
14333 Err(PerlError::runtime(self.undefined_subroutine_call_message(name), line).into())
14334 }
14335 }
14336 }
14337
14338 pub(crate) fn struct_construct(
14340 &mut self,
14341 def: &Arc<StructDef>,
14342 args: Vec<PerlValue>,
14343 line: usize,
14344 ) -> ExecResult {
14345 let is_named = args.len() >= 2
14348 && args.len().is_multiple_of(2)
14349 && args.iter().step_by(2).all(|v| {
14350 let s = v.to_string();
14351 def.field_index(&s).is_some()
14352 });
14353
14354 let provided = if is_named {
14355 let mut pairs = Vec::new();
14357 let mut i = 0;
14358 while i + 1 < args.len() {
14359 let k = args[i].to_string();
14360 let v = args[i + 1].clone();
14361 pairs.push((k, v));
14362 i += 2;
14363 }
14364 pairs
14365 } else {
14366 def.fields
14368 .iter()
14369 .zip(args.iter())
14370 .map(|(f, v)| (f.name.clone(), v.clone()))
14371 .collect()
14372 };
14373
14374 let mut defaults = Vec::with_capacity(def.fields.len());
14376 for field in &def.fields {
14377 if let Some(ref expr) = field.default {
14378 let val = self.eval_expr(expr)?;
14379 defaults.push(Some(val));
14380 } else {
14381 defaults.push(None);
14382 }
14383 }
14384
14385 Ok(crate::native_data::struct_new_with_defaults(
14386 def, &provided, &defaults, line,
14387 )?)
14388 }
14389
14390 pub(crate) fn class_construct(
14392 &mut self,
14393 def: &Arc<ClassDef>,
14394 args: Vec<PerlValue>,
14395 _line: usize,
14396 ) -> ExecResult {
14397 use crate::value::ClassInstance;
14398
14399 if def.is_abstract {
14401 return Err(PerlError::runtime(
14402 format!("cannot instantiate abstract class `{}`", def.name),
14403 _line,
14404 )
14405 .into());
14406 }
14407
14408 let all_fields = self.collect_class_fields(def);
14410
14411 let is_named = args.len() >= 2
14413 && args.len().is_multiple_of(2)
14414 && args.iter().step_by(2).all(|v| {
14415 let s = v.to_string();
14416 all_fields.iter().any(|(name, _, _)| name == &s)
14417 });
14418
14419 let provided: Vec<(String, PerlValue)> = if is_named {
14420 let mut pairs = Vec::new();
14421 let mut i = 0;
14422 while i + 1 < args.len() {
14423 let k = args[i].to_string();
14424 let v = args[i + 1].clone();
14425 pairs.push((k, v));
14426 i += 2;
14427 }
14428 pairs
14429 } else {
14430 all_fields
14431 .iter()
14432 .zip(args.iter())
14433 .map(|((name, _, _), v)| (name.clone(), v.clone()))
14434 .collect()
14435 };
14436
14437 let mut values = Vec::with_capacity(all_fields.len());
14439 for (name, default, ty) in &all_fields {
14440 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
14441 val.clone()
14442 } else if let Some(ref expr) = default {
14443 self.eval_expr(expr)?
14444 } else {
14445 PerlValue::UNDEF
14446 };
14447 ty.check_value(&val).map_err(|msg| {
14448 PerlError::type_error(
14449 format!("class {} field `{}`: {}", def.name, name, msg),
14450 _line,
14451 )
14452 })?;
14453 values.push(val);
14454 }
14455
14456 let isa_chain = self.mro_linearize(&def.name);
14458 let instance = PerlValue::class_inst(Arc::new(ClassInstance::new_with_isa(
14459 Arc::clone(def),
14460 values,
14461 isa_chain,
14462 )));
14463
14464 let build_chain = self.collect_build_chain(def);
14466 if !build_chain.is_empty() {
14467 for (body, params) in &build_chain {
14468 let call_args = vec![instance.clone()];
14469 match self.call_class_method(body, params, call_args, _line) {
14470 Ok(_) => {}
14471 Err(FlowOrError::Flow(Flow::Return(_))) => {}
14472 Err(e) => return Err(e),
14473 }
14474 }
14475 }
14476
14477 Ok(instance)
14478 }
14479
14480 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14482 let mut chain = Vec::new();
14483 for parent_name in &def.extends {
14485 if let Some(parent_def) = self.class_defs.get(parent_name) {
14486 chain.extend(self.collect_build_chain(parent_def));
14487 }
14488 }
14489 if let Some(m) = def.method("BUILD") {
14491 if let Some(ref body) = m.body {
14492 chain.push((body.clone(), m.params.clone()));
14493 }
14494 }
14495 chain
14496 }
14497
14498 fn collect_class_fields(
14501 &self,
14502 def: &ClassDef,
14503 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
14504 self.collect_class_fields_full(def)
14505 .into_iter()
14506 .map(|(name, default, ty, _, _)| (name, default, ty))
14507 .collect()
14508 }
14509
14510 fn collect_class_fields_full(
14512 &self,
14513 def: &ClassDef,
14514 ) -> Vec<(
14515 String,
14516 Option<Expr>,
14517 crate::ast::PerlTypeName,
14518 crate::ast::Visibility,
14519 String,
14520 )> {
14521 let mut all_fields = Vec::new();
14522
14523 for parent_name in &def.extends {
14524 if let Some(parent_def) = self.class_defs.get(parent_name) {
14525 let parent_fields = self.collect_class_fields_full(parent_def);
14526 all_fields.extend(parent_fields);
14527 }
14528 }
14529
14530 for field in &def.fields {
14531 all_fields.push((
14532 field.name.clone(),
14533 field.default.clone(),
14534 field.ty.clone(),
14535 field.visibility,
14536 def.name.clone(),
14537 ));
14538 }
14539
14540 all_fields
14541 }
14542
14543 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
14545 for parent_name in &def.extends {
14547 if let Some(parent_def) = self.class_defs.get(parent_name) {
14548 self.collect_class_method_names(parent_def, names);
14549 }
14550 }
14551 for m in &def.methods {
14553 if !m.is_static && !names.contains(&m.name) {
14554 names.push(m.name.clone());
14555 }
14556 }
14557 }
14558
14559 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14561 let mut chain = Vec::new();
14562 if let Some(m) = def.method("DESTROY") {
14564 if let Some(ref body) = m.body {
14565 chain.push((body.clone(), m.params.clone()));
14566 }
14567 }
14568 for parent_name in &def.extends {
14570 if let Some(parent_def) = self.class_defs.get(parent_name) {
14571 chain.extend(self.collect_destroy_chain(parent_def));
14572 }
14573 }
14574 chain
14575 }
14576
14577 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
14579 if let Some(def) = self.class_defs.get(child) {
14580 for parent in &def.extends {
14581 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
14582 return true;
14583 }
14584 }
14585 }
14586 false
14587 }
14588
14589 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
14591 if let Some(m) = def.method(method) {
14593 return Some((m.clone(), def.name.clone()));
14594 }
14595 for parent_name in &def.extends {
14597 if let Some(parent_def) = self.class_defs.get(parent_name) {
14598 if let Some(result) = self.find_class_method(parent_def, method) {
14599 return Some(result);
14600 }
14601 }
14602 }
14603 None
14604 }
14605
14606 pub(crate) fn enum_construct(
14608 &mut self,
14609 def: &Arc<EnumDef>,
14610 variant_name: &str,
14611 args: Vec<PerlValue>,
14612 line: usize,
14613 ) -> ExecResult {
14614 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
14615 FlowOrError::Error(PerlError::runtime(
14616 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
14617 line,
14618 ))
14619 })?;
14620 let variant = &def.variants[variant_idx];
14621 let data = if variant.ty.is_some() {
14622 if args.is_empty() {
14623 return Err(PerlError::runtime(
14624 format!(
14625 "enum variant `{}::{}` requires data",
14626 def.name, variant_name
14627 ),
14628 line,
14629 )
14630 .into());
14631 }
14632 if args.len() == 1 {
14633 args.into_iter().next().unwrap()
14634 } else {
14635 PerlValue::array(args)
14636 }
14637 } else {
14638 if !args.is_empty() {
14639 return Err(PerlError::runtime(
14640 format!(
14641 "enum variant `{}::{}` does not take data",
14642 def.name, variant_name
14643 ),
14644 line,
14645 )
14646 .into());
14647 }
14648 PerlValue::UNDEF
14649 };
14650 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
14651 Ok(PerlValue::enum_inst(Arc::new(inst)))
14652 }
14653
14654 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
14656 matches!(name, "STDIN" | "STDOUT" | "STDERR")
14657 || self.input_handles.contains_key(name)
14658 || self.output_handles.contains_key(name)
14659 || self.io_file_slots.contains_key(name)
14660 || self.pipe_children.contains_key(name)
14661 }
14662
14663 pub(crate) fn io_handle_method(
14665 &mut self,
14666 name: &str,
14667 method: &str,
14668 args: &[PerlValue],
14669 line: usize,
14670 ) -> PerlResult<PerlValue> {
14671 match method {
14672 "print" => self.io_handle_print(name, args, false, line),
14673 "say" => self.io_handle_print(name, args, true, line),
14674 "printf" => self.io_handle_printf(name, args, line),
14675 "getline" | "readline" => {
14676 if !args.is_empty() {
14677 return Err(PerlError::runtime(
14678 format!("{}: too many arguments", method),
14679 line,
14680 ));
14681 }
14682 self.readline_builtin_execute(Some(name))
14683 }
14684 "close" => {
14685 if !args.is_empty() {
14686 return Err(PerlError::runtime("close: too many arguments", line));
14687 }
14688 self.close_builtin_execute(name.to_string())
14689 }
14690 "eof" => {
14691 if !args.is_empty() {
14692 return Err(PerlError::runtime("eof: too many arguments", line));
14693 }
14694 let at_eof = !self.has_input_handle(name);
14695 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
14696 }
14697 "getc" => {
14698 if !args.is_empty() {
14699 return Err(PerlError::runtime("getc: too many arguments", line));
14700 }
14701 match crate::builtins::try_builtin(
14702 self,
14703 "getc",
14704 &[PerlValue::string(name.to_string())],
14705 line,
14706 ) {
14707 Some(r) => r,
14708 None => Err(PerlError::runtime("getc: not available", line)),
14709 }
14710 }
14711 "binmode" => match crate::builtins::try_builtin(
14712 self,
14713 "binmode",
14714 &[PerlValue::string(name.to_string())],
14715 line,
14716 ) {
14717 Some(r) => r,
14718 None => Err(PerlError::runtime("binmode: not available", line)),
14719 },
14720 "fileno" => match crate::builtins::try_builtin(
14721 self,
14722 "fileno",
14723 &[PerlValue::string(name.to_string())],
14724 line,
14725 ) {
14726 Some(r) => r,
14727 None => Err(PerlError::runtime("fileno: not available", line)),
14728 },
14729 "flush" => {
14730 if !args.is_empty() {
14731 return Err(PerlError::runtime("flush: too many arguments", line));
14732 }
14733 self.io_handle_flush(name, line)
14734 }
14735 _ => Err(PerlError::runtime(
14736 format!("Unknown method for filehandle: {}", method),
14737 line,
14738 )),
14739 }
14740 }
14741
14742 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> PerlResult<PerlValue> {
14743 match handle_name {
14744 "STDOUT" => {
14745 let _ = IoWrite::flush(&mut io::stdout());
14746 }
14747 "STDERR" => {
14748 let _ = IoWrite::flush(&mut io::stderr());
14749 }
14750 name => {
14751 if let Some(writer) = self.output_handles.get_mut(name) {
14752 let _ = IoWrite::flush(&mut *writer);
14753 } else {
14754 return Err(PerlError::runtime(
14755 format!("flush on unopened filehandle {}", name),
14756 line,
14757 ));
14758 }
14759 }
14760 }
14761 Ok(PerlValue::integer(1))
14762 }
14763
14764 fn io_handle_print(
14765 &mut self,
14766 handle_name: &str,
14767 args: &[PerlValue],
14768 newline: bool,
14769 line: usize,
14770 ) -> PerlResult<PerlValue> {
14771 if newline && (self.feature_bits & FEAT_SAY) == 0 {
14772 return Err(PerlError::runtime(
14773 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
14774 line,
14775 ));
14776 }
14777 let mut output = String::new();
14778 if args.is_empty() {
14779 output.push_str(&self.scope.get_scalar("_").to_string());
14781 } else {
14782 for (i, val) in args.iter().enumerate() {
14783 if i > 0 && !self.ofs.is_empty() {
14784 output.push_str(&self.ofs);
14785 }
14786 output.push_str(&val.to_string());
14787 }
14788 }
14789 if newline {
14790 output.push('\n');
14791 }
14792 output.push_str(&self.ors);
14793
14794 self.write_formatted_print(handle_name, &output, line)?;
14795 Ok(PerlValue::integer(1))
14796 }
14797
14798 pub(crate) fn write_formatted_print(
14801 &mut self,
14802 handle_name: &str,
14803 output: &str,
14804 line: usize,
14805 ) -> PerlResult<()> {
14806 match handle_name {
14807 "STDOUT" => {
14808 if !self.suppress_stdout {
14809 print!("{}", output);
14810 if self.output_autoflush {
14811 let _ = io::stdout().flush();
14812 }
14813 }
14814 }
14815 "STDERR" => {
14816 eprint!("{}", output);
14817 let _ = io::stderr().flush();
14818 }
14819 name => {
14820 if let Some(writer) = self.output_handles.get_mut(name) {
14821 let _ = writer.write_all(output.as_bytes());
14822 if self.output_autoflush {
14823 let _ = writer.flush();
14824 }
14825 } else {
14826 return Err(PerlError::runtime(
14827 format!("print on unopened filehandle {}", name),
14828 line,
14829 ));
14830 }
14831 }
14832 }
14833 Ok(())
14834 }
14835
14836 fn io_handle_printf(
14837 &mut self,
14838 handle_name: &str,
14839 args: &[PerlValue],
14840 line: usize,
14841 ) -> PerlResult<PerlValue> {
14842 let (fmt, rest): (String, &[PerlValue]) = if args.is_empty() {
14843 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
14844 Ok(s) => s,
14845 Err(FlowOrError::Error(e)) => return Err(e),
14846 Err(FlowOrError::Flow(_)) => {
14847 return Err(PerlError::runtime(
14848 "printf: unexpected control flow in sprintf",
14849 line,
14850 ));
14851 }
14852 };
14853 (s, &[])
14854 } else {
14855 (args[0].to_string(), &args[1..])
14856 };
14857 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
14858 Ok(s) => s,
14859 Err(FlowOrError::Error(e)) => return Err(e),
14860 Err(FlowOrError::Flow(_)) => {
14861 return Err(PerlError::runtime(
14862 "printf: unexpected control flow in sprintf",
14863 line,
14864 ));
14865 }
14866 };
14867 match handle_name {
14868 "STDOUT" => {
14869 if !self.suppress_stdout {
14870 print!("{}", output);
14871 if self.output_autoflush {
14872 let _ = IoWrite::flush(&mut io::stdout());
14873 }
14874 }
14875 }
14876 "STDERR" => {
14877 eprint!("{}", output);
14878 let _ = IoWrite::flush(&mut io::stderr());
14879 }
14880 name => {
14881 if let Some(writer) = self.output_handles.get_mut(name) {
14882 let _ = writer.write_all(output.as_bytes());
14883 if self.output_autoflush {
14884 let _ = writer.flush();
14885 }
14886 } else {
14887 return Err(PerlError::runtime(
14888 format!("printf on unopened filehandle {}", name),
14889 line,
14890 ));
14891 }
14892 }
14893 }
14894 Ok(PerlValue::integer(1))
14895 }
14896
14897 pub(crate) fn try_native_method(
14899 &mut self,
14900 receiver: &PerlValue,
14901 method: &str,
14902 args: &[PerlValue],
14903 line: usize,
14904 ) -> Option<PerlResult<PerlValue>> {
14905 if let Some(name) = receiver.as_io_handle_name() {
14906 return Some(self.io_handle_method(&name, method, args, line));
14907 }
14908 if let Some(ref s) = receiver.as_str() {
14909 if self.is_bound_handle(s) {
14910 return Some(self.io_handle_method(s, method, args, line));
14911 }
14912 }
14913 if let Some(c) = receiver.as_sqlite_conn() {
14914 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
14915 }
14916 if let Some(s) = receiver.as_struct_inst() {
14917 if let Some(idx) = s.def.field_index(method) {
14919 match args.len() {
14920 0 => {
14921 return Some(Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF)));
14922 }
14923 1 => {
14924 let field = &s.def.fields[idx];
14925 let new_val = args[0].clone();
14926 if let Err(msg) = field.ty.check_value(&new_val) {
14927 return Some(Err(PerlError::type_error(
14928 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
14929 line,
14930 )));
14931 }
14932 s.set_field(idx, new_val.clone());
14933 return Some(Ok(new_val));
14934 }
14935 _ => {
14936 return Some(Err(PerlError::runtime(
14937 format!(
14938 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
14939 method,
14940 args.len()
14941 ),
14942 line,
14943 )));
14944 }
14945 }
14946 }
14947 match method {
14949 "with" => {
14950 let mut new_values = s.get_values();
14952 let mut i = 0;
14953 while i + 1 < args.len() {
14954 let k = args[i].to_string();
14955 let v = args[i + 1].clone();
14956 if let Some(idx) = s.def.field_index(&k) {
14957 let field = &s.def.fields[idx];
14958 if let Err(msg) = field.ty.check_value(&v) {
14959 return Some(Err(PerlError::type_error(
14960 format!(
14961 "struct {} field `{}`: {}",
14962 s.def.name, field.name, msg
14963 ),
14964 line,
14965 )));
14966 }
14967 new_values[idx] = v;
14968 } else {
14969 return Some(Err(PerlError::runtime(
14970 format!("struct {}: unknown field `{}`", s.def.name, k),
14971 line,
14972 )));
14973 }
14974 i += 2;
14975 }
14976 return Some(Ok(PerlValue::struct_inst(Arc::new(
14977 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
14978 ))));
14979 }
14980 "to_hash" => {
14981 if !args.is_empty() {
14983 return Some(Err(PerlError::runtime(
14984 "struct to_hash takes no arguments",
14985 line,
14986 )));
14987 }
14988 let mut map = IndexMap::new();
14989 let values = s.get_values();
14990 for (i, field) in s.def.fields.iter().enumerate() {
14991 map.insert(field.name.clone(), values[i].clone());
14992 }
14993 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
14994 }
14995 "fields" => {
14996 if !args.is_empty() {
14998 return Some(Err(PerlError::runtime(
14999 "struct fields takes no arguments",
15000 line,
15001 )));
15002 }
15003 let names: Vec<PerlValue> = s
15004 .def
15005 .fields
15006 .iter()
15007 .map(|f| PerlValue::string(f.name.clone()))
15008 .collect();
15009 return Some(Ok(PerlValue::array(names)));
15010 }
15011 "clone" => {
15012 if !args.is_empty() {
15014 return Some(Err(PerlError::runtime(
15015 "struct clone takes no arguments",
15016 line,
15017 )));
15018 }
15019 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
15020 return Some(Ok(PerlValue::struct_inst(Arc::new(
15021 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
15022 ))));
15023 }
15024 _ => {}
15025 }
15026 if let Some(m) = s.def.method(method) {
15028 let body = m.body.clone();
15029 let params = m.params.clone();
15030 let mut call_args = vec![receiver.clone()];
15032 call_args.extend(args.iter().cloned());
15033 return Some(
15034 match self.call_struct_method(&body, ¶ms, call_args, line) {
15035 Ok(v) => Ok(v),
15036 Err(FlowOrError::Error(e)) => Err(e),
15037 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15038 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
15039 "unexpected control flow in struct method",
15040 line,
15041 )),
15042 },
15043 );
15044 }
15045 return None;
15046 }
15047 if let Some(c) = receiver.as_class_inst() {
15049 let all_fields_full = self.collect_class_fields_full(&c.def);
15051 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
15052 .iter()
15053 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
15054 .collect();
15055
15056 if let Some(idx) = all_fields_full
15058 .iter()
15059 .position(|(name, _, _, _, _)| name == method)
15060 {
15061 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
15062
15063 match vis {
15065 crate::ast::Visibility::Private => {
15066 let caller_class = self
15068 .scope
15069 .get_scalar("self")
15070 .as_class_inst()
15071 .map(|ci| ci.def.name.clone());
15072 if caller_class.as_deref() != Some(owner_class.as_str()) {
15073 return Some(Err(PerlError::runtime(
15074 format!("field `{}` of class {} is private", method, owner_class),
15075 line,
15076 )));
15077 }
15078 }
15079 crate::ast::Visibility::Protected => {
15080 let caller_class = self
15082 .scope
15083 .get_scalar("self")
15084 .as_class_inst()
15085 .map(|ci| ci.def.name.clone());
15086 let allowed = caller_class.as_deref().is_some_and(|caller| {
15087 caller == owner_class || self.class_inherits_from(caller, owner_class)
15088 });
15089 if !allowed {
15090 return Some(Err(PerlError::runtime(
15091 format!("field `{}` of class {} is protected", method, owner_class),
15092 line,
15093 )));
15094 }
15095 }
15096 crate::ast::Visibility::Public => {}
15097 }
15098
15099 match args.len() {
15100 0 => {
15101 return Some(Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF)));
15102 }
15103 1 => {
15104 let new_val = args[0].clone();
15105 if let Err(msg) = ty.check_value(&new_val) {
15106 return Some(Err(PerlError::type_error(
15107 format!("class {} field `{}`: {}", c.def.name, method, msg),
15108 line,
15109 )));
15110 }
15111 c.set_field(idx, new_val.clone());
15112 return Some(Ok(new_val));
15113 }
15114 _ => {
15115 return Some(Err(PerlError::runtime(
15116 format!(
15117 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
15118 method,
15119 args.len()
15120 ),
15121 line,
15122 )));
15123 }
15124 }
15125 }
15126 match method {
15128 "with" => {
15129 let mut new_values = c.get_values();
15130 let mut i = 0;
15131 while i + 1 < args.len() {
15132 let k = args[i].to_string();
15133 let v = args[i + 1].clone();
15134 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
15135 let (_, _, ref ty) = all_fields[idx];
15136 if let Err(msg) = ty.check_value(&v) {
15137 return Some(Err(PerlError::type_error(
15138 format!("class {} field `{}`: {}", c.def.name, k, msg),
15139 line,
15140 )));
15141 }
15142 new_values[idx] = v;
15143 } else {
15144 return Some(Err(PerlError::runtime(
15145 format!("class {}: unknown field `{}`", c.def.name, k),
15146 line,
15147 )));
15148 }
15149 i += 2;
15150 }
15151 return Some(Ok(PerlValue::class_inst(Arc::new(
15152 crate::value::ClassInstance::new_with_isa(
15153 Arc::clone(&c.def),
15154 new_values,
15155 c.isa_chain.clone(),
15156 ),
15157 ))));
15158 }
15159 "to_hash" => {
15160 if !args.is_empty() {
15161 return Some(Err(PerlError::runtime(
15162 "class to_hash takes no arguments",
15163 line,
15164 )));
15165 }
15166 let mut map = IndexMap::new();
15167 let values = c.get_values();
15168 for (i, (name, _, _)) in all_fields.iter().enumerate() {
15169 if let Some(v) = values.get(i) {
15170 map.insert(name.clone(), v.clone());
15171 }
15172 }
15173 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
15174 }
15175 "fields" => {
15176 if !args.is_empty() {
15177 return Some(Err(PerlError::runtime(
15178 "class fields takes no arguments",
15179 line,
15180 )));
15181 }
15182 let names: Vec<PerlValue> = all_fields
15183 .iter()
15184 .map(|(name, _, _)| PerlValue::string(name.clone()))
15185 .collect();
15186 return Some(Ok(PerlValue::array(names)));
15187 }
15188 "clone" => {
15189 if !args.is_empty() {
15190 return Some(Err(PerlError::runtime(
15191 "class clone takes no arguments",
15192 line,
15193 )));
15194 }
15195 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
15196 return Some(Ok(PerlValue::class_inst(Arc::new(
15197 crate::value::ClassInstance::new_with_isa(
15198 Arc::clone(&c.def),
15199 new_values,
15200 c.isa_chain.clone(),
15201 ),
15202 ))));
15203 }
15204 "isa" => {
15205 if args.len() != 1 {
15206 return Some(Err(PerlError::runtime("isa requires one argument", line)));
15207 }
15208 let class_name = args[0].to_string();
15209 let is_a = c.isa(&class_name);
15210 return Some(Ok(if is_a {
15211 PerlValue::integer(1)
15212 } else {
15213 PerlValue::string(String::new())
15214 }));
15215 }
15216 "does" => {
15217 if args.len() != 1 {
15218 return Some(Err(PerlError::runtime("does requires one argument", line)));
15219 }
15220 let trait_name = args[0].to_string();
15221 let implements = c.def.implements.contains(&trait_name);
15222 return Some(Ok(if implements {
15223 PerlValue::integer(1)
15224 } else {
15225 PerlValue::string(String::new())
15226 }));
15227 }
15228 "methods" => {
15229 if !args.is_empty() {
15230 return Some(Err(PerlError::runtime("methods takes no arguments", line)));
15231 }
15232 let mut names = Vec::new();
15233 self.collect_class_method_names(&c.def, &mut names);
15234 let values: Vec<PerlValue> = names.into_iter().map(PerlValue::string).collect();
15235 return Some(Ok(PerlValue::array(values)));
15236 }
15237 "superclass" => {
15238 if !args.is_empty() {
15239 return Some(Err(PerlError::runtime(
15240 "superclass takes no arguments",
15241 line,
15242 )));
15243 }
15244 let parents: Vec<PerlValue> = c
15245 .def
15246 .extends
15247 .iter()
15248 .map(|s| PerlValue::string(s.clone()))
15249 .collect();
15250 return Some(Ok(PerlValue::array(parents)));
15251 }
15252 "destroy" => {
15253 let destroy_chain = self.collect_destroy_chain(&c.def);
15255 for (body, params) in &destroy_chain {
15256 let call_args = vec![receiver.clone()];
15257 match self.call_class_method(body, params, call_args, line) {
15258 Ok(_) => {}
15259 Err(FlowOrError::Flow(Flow::Return(_))) => {}
15260 Err(FlowOrError::Error(e)) => return Some(Err(e)),
15261 Err(_) => {}
15262 }
15263 }
15264 return Some(Ok(PerlValue::UNDEF));
15265 }
15266 _ => {}
15267 }
15268 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
15270 match m.visibility {
15272 crate::ast::Visibility::Private => {
15273 let caller_class = self
15274 .scope
15275 .get_scalar("self")
15276 .as_class_inst()
15277 .map(|ci| ci.def.name.clone());
15278 if caller_class.as_deref() != Some(owner_class.as_str()) {
15279 return Some(Err(PerlError::runtime(
15280 format!("method `{}` of class {} is private", method, owner_class),
15281 line,
15282 )));
15283 }
15284 }
15285 crate::ast::Visibility::Protected => {
15286 let caller_class = self
15287 .scope
15288 .get_scalar("self")
15289 .as_class_inst()
15290 .map(|ci| ci.def.name.clone());
15291 let allowed = caller_class.as_deref().is_some_and(|caller| {
15292 caller == owner_class.as_str()
15293 || self.class_inherits_from(caller, owner_class)
15294 });
15295 if !allowed {
15296 return Some(Err(PerlError::runtime(
15297 format!(
15298 "method `{}` of class {} is protected",
15299 method, owner_class
15300 ),
15301 line,
15302 )));
15303 }
15304 }
15305 crate::ast::Visibility::Public => {}
15306 }
15307 if let Some(ref body) = m.body {
15308 let params = m.params.clone();
15309 let mut call_args = vec![receiver.clone()];
15310 call_args.extend(args.iter().cloned());
15311 return Some(
15312 match self.call_class_method(body, ¶ms, call_args, line) {
15313 Ok(v) => Ok(v),
15314 Err(FlowOrError::Error(e)) => Err(e),
15315 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15316 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
15317 "unexpected control flow in class method",
15318 line,
15319 )),
15320 },
15321 );
15322 }
15323 }
15324 return None;
15325 }
15326 if let Some(d) = receiver.as_dataframe() {
15327 return Some(self.dataframe_method(d, method, args, line));
15328 }
15329 if let Some(s) = crate::value::set_payload(receiver) {
15330 return Some(self.set_method(s, method, args, line));
15331 }
15332 if let Some(d) = receiver.as_deque() {
15333 return Some(self.deque_method(d, method, args, line));
15334 }
15335 if let Some(h) = receiver.as_heap_pq() {
15336 return Some(self.heap_method(h, method, args, line));
15337 }
15338 if let Some(p) = receiver.as_pipeline() {
15339 return Some(self.pipeline_method(p, method, args, line));
15340 }
15341 if let Some(c) = receiver.as_capture() {
15342 return Some(self.capture_method(c, method, args, line));
15343 }
15344 if let Some(p) = receiver.as_ppool() {
15345 return Some(self.ppool_method(p, method, args, line));
15346 }
15347 if let Some(b) = receiver.as_barrier() {
15348 return Some(self.barrier_method(b, method, args, line));
15349 }
15350 if let Some(g) = receiver.as_generator() {
15351 if method == "next" {
15352 if !args.is_empty() {
15353 return Some(Err(PerlError::runtime(
15354 "generator->next takes no arguments",
15355 line,
15356 )));
15357 }
15358 return Some(self.generator_next(&g));
15359 }
15360 return None;
15361 }
15362 if let Some(arc) = receiver.as_atomic_arc() {
15363 let inner = arc.lock().clone();
15364 if let Some(d) = inner.as_deque() {
15365 return Some(self.deque_method(d, method, args, line));
15366 }
15367 if let Some(h) = inner.as_heap_pq() {
15368 return Some(self.heap_method(h, method, args, line));
15369 }
15370 }
15371 None
15372 }
15373
15374 fn dataframe_method(
15376 &mut self,
15377 d: Arc<Mutex<PerlDataFrame>>,
15378 method: &str,
15379 args: &[PerlValue],
15380 line: usize,
15381 ) -> PerlResult<PerlValue> {
15382 match method {
15383 "nrow" | "nrows" => {
15384 if !args.is_empty() {
15385 return Err(PerlError::runtime(
15386 format!("dataframe {} takes no arguments", method),
15387 line,
15388 ));
15389 }
15390 Ok(PerlValue::integer(d.lock().nrows() as i64))
15391 }
15392 "ncol" | "ncols" => {
15393 if !args.is_empty() {
15394 return Err(PerlError::runtime(
15395 format!("dataframe {} takes no arguments", method),
15396 line,
15397 ));
15398 }
15399 Ok(PerlValue::integer(d.lock().ncols() as i64))
15400 }
15401 "filter" => {
15402 if args.len() != 1 {
15403 return Err(PerlError::runtime(
15404 "dataframe filter expects 1 argument (sub)",
15405 line,
15406 ));
15407 }
15408 let Some(sub) = args[0].as_code_ref() else {
15409 return Err(PerlError::runtime(
15410 "dataframe filter expects a code reference",
15411 line,
15412 ));
15413 };
15414 let df_guard = d.lock();
15415 let n = df_guard.nrows();
15416 let mut keep = vec![false; n];
15417 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
15418 let row = df_guard.row_hashref(r);
15419 self.scope_push_hook();
15420 self.scope.set_topic(row);
15421 if let Some(ref env) = sub.closure_env {
15422 self.scope.restore_capture(env);
15423 }
15424 let pass = match self.exec_block_no_scope(&sub.body) {
15425 Ok(v) => v.is_true(),
15426 Err(_) => false,
15427 };
15428 self.scope_pop_hook();
15429 *row_keep = pass;
15430 }
15431 let columns = df_guard.columns.clone();
15432 let cols: Vec<Vec<PerlValue>> = (0..df_guard.ncols())
15433 .map(|i| {
15434 let mut out = Vec::new();
15435 for (r, pass_row) in keep.iter().enumerate().take(n) {
15436 if *pass_row {
15437 out.push(df_guard.cols[i][r].clone());
15438 }
15439 }
15440 out
15441 })
15442 .collect();
15443 let group_by = df_guard.group_by.clone();
15444 drop(df_guard);
15445 let new_df = PerlDataFrame {
15446 columns,
15447 cols,
15448 group_by,
15449 };
15450 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15451 }
15452 "group_by" => {
15453 if args.len() != 1 {
15454 return Err(PerlError::runtime(
15455 "dataframe group_by expects 1 column name",
15456 line,
15457 ));
15458 }
15459 let key = args[0].to_string();
15460 let inner = d.lock();
15461 if inner.col_index(&key).is_none() {
15462 return Err(PerlError::runtime(
15463 format!("dataframe group_by: unknown column \"{}\"", key),
15464 line,
15465 ));
15466 }
15467 let new_df = PerlDataFrame {
15468 columns: inner.columns.clone(),
15469 cols: inner.cols.clone(),
15470 group_by: Some(key),
15471 };
15472 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15473 }
15474 "sum" => {
15475 if args.len() != 1 {
15476 return Err(PerlError::runtime(
15477 "dataframe sum expects 1 column name",
15478 line,
15479 ));
15480 }
15481 let col_name = args[0].to_string();
15482 let inner = d.lock();
15483 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
15484 PerlError::runtime(
15485 format!("dataframe sum: unknown column \"{}\"", col_name),
15486 line,
15487 )
15488 })?;
15489 match &inner.group_by {
15490 Some(gcol) => {
15491 let gi = inner.col_index(gcol).ok_or_else(|| {
15492 PerlError::runtime(
15493 format!("dataframe sum: unknown group column \"{}\"", gcol),
15494 line,
15495 )
15496 })?;
15497 let mut acc: IndexMap<String, f64> = IndexMap::new();
15498 for r in 0..inner.nrows() {
15499 let k = inner.cols[gi][r].to_string();
15500 let v = inner.cols[val_idx][r].to_number();
15501 *acc.entry(k).or_insert(0.0) += v;
15502 }
15503 let keys: Vec<String> = acc.keys().cloned().collect();
15504 let sums: Vec<f64> = acc.values().copied().collect();
15505 let cols = vec![
15506 keys.into_iter().map(PerlValue::string).collect(),
15507 sums.into_iter().map(PerlValue::float).collect(),
15508 ];
15509 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
15510 let out = PerlDataFrame {
15511 columns,
15512 cols,
15513 group_by: None,
15514 };
15515 Ok(PerlValue::dataframe(Arc::new(Mutex::new(out))))
15516 }
15517 None => {
15518 let total: f64 = (0..inner.nrows())
15519 .map(|r| inner.cols[val_idx][r].to_number())
15520 .sum();
15521 Ok(PerlValue::float(total))
15522 }
15523 }
15524 }
15525 _ => Err(PerlError::runtime(
15526 format!("Unknown method for dataframe: {}", method),
15527 line,
15528 )),
15529 }
15530 }
15531
15532 fn set_method(
15534 &self,
15535 s: Arc<crate::value::PerlSet>,
15536 method: &str,
15537 args: &[PerlValue],
15538 line: usize,
15539 ) -> PerlResult<PerlValue> {
15540 match method {
15541 "has" | "contains" | "member" => {
15542 if args.len() != 1 {
15543 return Err(PerlError::runtime(
15544 "set->has expects one argument (element)",
15545 line,
15546 ));
15547 }
15548 let k = crate::value::set_member_key(&args[0]);
15549 Ok(PerlValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
15550 }
15551 "size" | "len" | "count" => {
15552 if !args.is_empty() {
15553 return Err(PerlError::runtime("set->size takes no arguments", line));
15554 }
15555 Ok(PerlValue::integer(s.len() as i64))
15556 }
15557 "values" | "list" | "elements" => {
15558 if !args.is_empty() {
15559 return Err(PerlError::runtime("set->values takes no arguments", line));
15560 }
15561 Ok(PerlValue::array(s.values().cloned().collect()))
15562 }
15563 _ => Err(PerlError::runtime(
15564 format!("Unknown method for set: {}", method),
15565 line,
15566 )),
15567 }
15568 }
15569
15570 fn deque_method(
15571 &mut self,
15572 d: Arc<Mutex<VecDeque<PerlValue>>>,
15573 method: &str,
15574 args: &[PerlValue],
15575 line: usize,
15576 ) -> PerlResult<PerlValue> {
15577 match method {
15578 "push_back" => {
15579 if args.len() != 1 {
15580 return Err(PerlError::runtime("push_back expects 1 argument", line));
15581 }
15582 d.lock().push_back(args[0].clone());
15583 Ok(PerlValue::integer(d.lock().len() as i64))
15584 }
15585 "push_front" => {
15586 if args.len() != 1 {
15587 return Err(PerlError::runtime("push_front expects 1 argument", line));
15588 }
15589 d.lock().push_front(args[0].clone());
15590 Ok(PerlValue::integer(d.lock().len() as i64))
15591 }
15592 "pop_back" => Ok(d.lock().pop_back().unwrap_or(PerlValue::UNDEF)),
15593 "pop_front" => Ok(d.lock().pop_front().unwrap_or(PerlValue::UNDEF)),
15594 "size" | "len" => Ok(PerlValue::integer(d.lock().len() as i64)),
15595 _ => Err(PerlError::runtime(
15596 format!("Unknown method for deque: {}", method),
15597 line,
15598 )),
15599 }
15600 }
15601
15602 fn heap_method(
15603 &mut self,
15604 h: Arc<Mutex<PerlHeap>>,
15605 method: &str,
15606 args: &[PerlValue],
15607 line: usize,
15608 ) -> PerlResult<PerlValue> {
15609 match method {
15610 "push" => {
15611 if args.len() != 1 {
15612 return Err(PerlError::runtime("heap push expects 1 argument", line));
15613 }
15614 let mut g = h.lock();
15615 let n = g.items.len();
15616 g.items.push(args[0].clone());
15617 let cmp = g.cmp.clone();
15618 drop(g);
15619 let mut g = h.lock();
15620 self.heap_sift_up(&mut g.items, &cmp, n);
15621 Ok(PerlValue::integer(g.items.len() as i64))
15622 }
15623 "pop" => {
15624 let mut g = h.lock();
15625 if g.items.is_empty() {
15626 return Ok(PerlValue::UNDEF);
15627 }
15628 let cmp = g.cmp.clone();
15629 let n = g.items.len();
15630 g.items.swap(0, n - 1);
15631 let v = g.items.pop().unwrap();
15632 if !g.items.is_empty() {
15633 self.heap_sift_down(&mut g.items, &cmp, 0);
15634 }
15635 Ok(v)
15636 }
15637 "peek" => Ok(h.lock().items.first().cloned().unwrap_or(PerlValue::UNDEF)),
15638 _ => Err(PerlError::runtime(
15639 format!("Unknown method for heap: {}", method),
15640 line,
15641 )),
15642 }
15643 }
15644
15645 fn ppool_method(
15646 &mut self,
15647 pool: PerlPpool,
15648 method: &str,
15649 args: &[PerlValue],
15650 line: usize,
15651 ) -> PerlResult<PerlValue> {
15652 match method {
15653 "submit" => pool.submit(self, args, line),
15654 "collect" => {
15655 if !args.is_empty() {
15656 return Err(PerlError::runtime("collect() takes no arguments", line));
15657 }
15658 pool.collect(line)
15659 }
15660 _ => Err(PerlError::runtime(
15661 format!("Unknown method for ppool: {}", method),
15662 line,
15663 )),
15664 }
15665 }
15666
15667 fn barrier_method(
15668 &self,
15669 barrier: PerlBarrier,
15670 method: &str,
15671 args: &[PerlValue],
15672 line: usize,
15673 ) -> PerlResult<PerlValue> {
15674 match method {
15675 "wait" => {
15676 if !args.is_empty() {
15677 return Err(PerlError::runtime("wait() takes no arguments", line));
15678 }
15679 let _ = barrier.0.wait();
15680 Ok(PerlValue::integer(1))
15681 }
15682 _ => Err(PerlError::runtime(
15683 format!("Unknown method for barrier: {}", method),
15684 line,
15685 )),
15686 }
15687 }
15688
15689 fn capture_method(
15690 &self,
15691 c: Arc<CaptureResult>,
15692 method: &str,
15693 args: &[PerlValue],
15694 line: usize,
15695 ) -> PerlResult<PerlValue> {
15696 if !args.is_empty() {
15697 return Err(PerlError::runtime(
15698 format!("capture: {} takes no arguments", method),
15699 line,
15700 ));
15701 }
15702 match method {
15703 "stdout" => Ok(PerlValue::string(c.stdout.clone())),
15704 "stderr" => Ok(PerlValue::string(c.stderr.clone())),
15705 "exitcode" => Ok(PerlValue::integer(c.exitcode)),
15706 "failed" => Ok(PerlValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
15707 _ => Err(PerlError::runtime(
15708 format!("Unknown method for capture: {}", method),
15709 line,
15710 )),
15711 }
15712 }
15713
15714 pub(crate) fn builtin_par_pipeline_stream(
15715 &mut self,
15716 args: &[PerlValue],
15717 _line: usize,
15718 ) -> PerlResult<PerlValue> {
15719 let mut items = Vec::new();
15720 for v in args {
15721 if let Some(a) = v.as_array_vec() {
15722 items.extend(a);
15723 } else {
15724 items.push(v.clone());
15725 }
15726 }
15727 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15728 source: items,
15729 ops: Vec::new(),
15730 has_scalar_terminal: false,
15731 par_stream: true,
15732 streaming: false,
15733 streaming_workers: 0,
15734 streaming_buffer: 256,
15735 }))))
15736 }
15737
15738 pub(crate) fn builtin_par_pipeline_stream_new(
15741 &mut self,
15742 args: &[PerlValue],
15743 _line: usize,
15744 ) -> PerlResult<PerlValue> {
15745 let mut items = Vec::new();
15746 let mut workers: usize = 0;
15747 let mut buffer: usize = 256;
15748 let mut i = 0;
15750 while i < args.len() {
15751 let s = args[i].to_string();
15752 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
15753 let val = args[i + 1].to_int().max(1) as usize;
15754 if s == "workers" {
15755 workers = val;
15756 } else {
15757 buffer = val;
15758 }
15759 i += 2;
15760 } else if let Some(a) = args[i].as_array_vec() {
15761 items.extend(a);
15762 i += 1;
15763 } else {
15764 items.push(args[i].clone());
15765 i += 1;
15766 }
15767 }
15768 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15769 source: items,
15770 ops: Vec::new(),
15771 has_scalar_terminal: false,
15772 par_stream: false,
15773 streaming: true,
15774 streaming_workers: workers,
15775 streaming_buffer: buffer,
15776 }))))
15777 }
15778
15779 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<PerlSub> {
15781 let line = 1usize;
15782 let body = vec![Statement {
15783 label: None,
15784 kind: StmtKind::Expression(Expr {
15785 kind: ExprKind::BinOp {
15786 left: Box::new(Expr {
15787 kind: ExprKind::ScalarVar("_".into()),
15788 line,
15789 }),
15790 op: BinOp::Mul,
15791 right: Box::new(Expr {
15792 kind: ExprKind::Integer(k),
15793 line,
15794 }),
15795 },
15796 line,
15797 }),
15798 line,
15799 }];
15800 Arc::new(PerlSub {
15801 name: "__pipeline_int_mul__".into(),
15802 params: vec![],
15803 body,
15804 closure_env: None,
15805 prototype: None,
15806 fib_like: None,
15807 })
15808 }
15809
15810 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<PerlSub> {
15811 let captured = self.scope.capture();
15812 Arc::new(PerlSub {
15813 name: "__ANON__".into(),
15814 params: vec![],
15815 body: block.clone(),
15816 closure_env: Some(captured),
15817 prototype: None,
15818 fib_like: None,
15819 })
15820 }
15821
15822 pub(crate) fn builtin_collect_execute(
15823 &mut self,
15824 args: &[PerlValue],
15825 line: usize,
15826 ) -> PerlResult<PerlValue> {
15827 if args.is_empty() {
15828 return Err(PerlError::runtime(
15829 "collect() expects at least one argument",
15830 line,
15831 ));
15832 }
15833 if args.len() == 1 {
15836 if let Some(p) = args[0].as_pipeline() {
15837 return self.pipeline_collect(&p, line);
15838 }
15839 return Ok(PerlValue::array(args[0].to_list()));
15840 }
15841 Ok(PerlValue::array(args.to_vec()))
15842 }
15843
15844 pub(crate) fn pipeline_push(
15845 &self,
15846 p: &Arc<Mutex<PipelineInner>>,
15847 op: PipelineOp,
15848 line: usize,
15849 ) -> PerlResult<()> {
15850 let mut g = p.lock();
15851 if g.has_scalar_terminal {
15852 return Err(PerlError::runtime(
15853 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
15854 line,
15855 ));
15856 }
15857 if matches!(
15858 &op,
15859 PipelineOp::PReduce { .. }
15860 | PipelineOp::PReduceInit { .. }
15861 | PipelineOp::PMapReduce { .. }
15862 ) {
15863 g.has_scalar_terminal = true;
15864 }
15865 g.ops.push(op);
15866 Ok(())
15867 }
15868
15869 fn pipeline_parse_sub_progress(
15870 args: &[PerlValue],
15871 line: usize,
15872 name: &str,
15873 ) -> PerlResult<(Arc<PerlSub>, bool)> {
15874 if args.is_empty() {
15875 return Err(PerlError::runtime(
15876 format!("pipeline {}: expects at least 1 argument (code ref)", name),
15877 line,
15878 ));
15879 }
15880 let Some(sub) = args[0].as_code_ref() else {
15881 return Err(PerlError::runtime(
15882 format!("pipeline {}: first argument must be a code reference", name),
15883 line,
15884 ));
15885 };
15886 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
15887 if args.len() > 2 {
15888 return Err(PerlError::runtime(
15889 format!(
15890 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
15891 name
15892 ),
15893 line,
15894 ));
15895 }
15896 Ok((sub, progress))
15897 }
15898
15899 pub(crate) fn pipeline_method(
15900 &mut self,
15901 p: Arc<Mutex<PipelineInner>>,
15902 method: &str,
15903 args: &[PerlValue],
15904 line: usize,
15905 ) -> PerlResult<PerlValue> {
15906 match method {
15907 "filter" | "f" | "grep" => {
15908 if args.len() != 1 {
15909 return Err(PerlError::runtime(
15910 "pipeline filter/grep expects 1 argument (sub)",
15911 line,
15912 ));
15913 }
15914 let Some(sub) = args[0].as_code_ref() else {
15915 return Err(PerlError::runtime(
15916 "pipeline filter/grep expects a code reference",
15917 line,
15918 ));
15919 };
15920 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
15921 Ok(PerlValue::pipeline(Arc::clone(&p)))
15922 }
15923 "map" => {
15924 if args.len() != 1 {
15925 return Err(PerlError::runtime(
15926 "pipeline map expects 1 argument (sub)",
15927 line,
15928 ));
15929 }
15930 let Some(sub) = args[0].as_code_ref() else {
15931 return Err(PerlError::runtime(
15932 "pipeline map expects a code reference",
15933 line,
15934 ));
15935 };
15936 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
15937 Ok(PerlValue::pipeline(Arc::clone(&p)))
15938 }
15939 "tap" | "peek" => {
15940 if args.len() != 1 {
15941 return Err(PerlError::runtime(
15942 "pipeline tap/peek expects 1 argument (sub)",
15943 line,
15944 ));
15945 }
15946 let Some(sub) = args[0].as_code_ref() else {
15947 return Err(PerlError::runtime(
15948 "pipeline tap/peek expects a code reference",
15949 line,
15950 ));
15951 };
15952 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
15953 Ok(PerlValue::pipeline(Arc::clone(&p)))
15954 }
15955 "take" => {
15956 if args.len() != 1 {
15957 return Err(PerlError::runtime("pipeline take expects 1 argument", line));
15958 }
15959 let n = args[0].to_int();
15960 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
15961 Ok(PerlValue::pipeline(Arc::clone(&p)))
15962 }
15963 "pmap" => {
15964 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
15965 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
15966 Ok(PerlValue::pipeline(Arc::clone(&p)))
15967 }
15968 "pgrep" => {
15969 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
15970 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
15971 Ok(PerlValue::pipeline(Arc::clone(&p)))
15972 }
15973 "pfor" => {
15974 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
15975 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
15976 Ok(PerlValue::pipeline(Arc::clone(&p)))
15977 }
15978 "pmap_chunked" => {
15979 if args.len() < 2 {
15980 return Err(PerlError::runtime(
15981 "pipeline pmap_chunked expects chunk size and a code reference",
15982 line,
15983 ));
15984 }
15985 let chunk = args[0].to_int().max(1);
15986 let Some(sub) = args[1].as_code_ref() else {
15987 return Err(PerlError::runtime(
15988 "pipeline pmap_chunked: second argument must be a code reference",
15989 line,
15990 ));
15991 };
15992 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15993 if args.len() > 3 {
15994 return Err(PerlError::runtime(
15995 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
15996 line,
15997 ));
15998 }
15999 self.pipeline_push(
16000 &p,
16001 PipelineOp::PMapChunked {
16002 chunk,
16003 sub,
16004 progress,
16005 },
16006 line,
16007 )?;
16008 Ok(PerlValue::pipeline(Arc::clone(&p)))
16009 }
16010 "psort" => {
16011 let (cmp, progress) = match args.len() {
16012 0 => (None, false),
16013 1 => {
16014 if let Some(s) = args[0].as_code_ref() {
16015 (Some(s), false)
16016 } else {
16017 (None, args[0].is_true())
16018 }
16019 }
16020 2 => {
16021 let Some(s) = args[0].as_code_ref() else {
16022 return Err(PerlError::runtime(
16023 "pipeline psort: with two arguments, the first must be a comparator sub",
16024 line,
16025 ));
16026 };
16027 (Some(s), args[1].is_true())
16028 }
16029 _ => {
16030 return Err(PerlError::runtime(
16031 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
16032 line,
16033 ));
16034 }
16035 };
16036 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
16037 Ok(PerlValue::pipeline(Arc::clone(&p)))
16038 }
16039 "pcache" => {
16040 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
16041 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
16042 Ok(PerlValue::pipeline(Arc::clone(&p)))
16043 }
16044 "preduce" => {
16045 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
16046 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
16047 Ok(PerlValue::pipeline(Arc::clone(&p)))
16048 }
16049 "preduce_init" => {
16050 if args.len() < 2 {
16051 return Err(PerlError::runtime(
16052 "pipeline preduce_init expects init value and a code reference",
16053 line,
16054 ));
16055 }
16056 let init = args[0].clone();
16057 let Some(sub) = args[1].as_code_ref() else {
16058 return Err(PerlError::runtime(
16059 "pipeline preduce_init: second argument must be a code reference",
16060 line,
16061 ));
16062 };
16063 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
16064 if args.len() > 3 {
16065 return Err(PerlError::runtime(
16066 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
16067 line,
16068 ));
16069 }
16070 self.pipeline_push(
16071 &p,
16072 PipelineOp::PReduceInit {
16073 init,
16074 sub,
16075 progress,
16076 },
16077 line,
16078 )?;
16079 Ok(PerlValue::pipeline(Arc::clone(&p)))
16080 }
16081 "pmap_reduce" => {
16082 if args.len() < 2 {
16083 return Err(PerlError::runtime(
16084 "pipeline pmap_reduce expects map sub and reduce sub",
16085 line,
16086 ));
16087 }
16088 let Some(map) = args[0].as_code_ref() else {
16089 return Err(PerlError::runtime(
16090 "pipeline pmap_reduce: first argument must be a code reference (map)",
16091 line,
16092 ));
16093 };
16094 let Some(reduce) = args[1].as_code_ref() else {
16095 return Err(PerlError::runtime(
16096 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
16097 line,
16098 ));
16099 };
16100 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
16101 if args.len() > 3 {
16102 return Err(PerlError::runtime(
16103 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
16104 line,
16105 ));
16106 }
16107 self.pipeline_push(
16108 &p,
16109 PipelineOp::PMapReduce {
16110 map,
16111 reduce,
16112 progress,
16113 },
16114 line,
16115 )?;
16116 Ok(PerlValue::pipeline(Arc::clone(&p)))
16117 }
16118 "collect" => {
16119 if !args.is_empty() {
16120 return Err(PerlError::runtime(
16121 "pipeline collect takes no arguments",
16122 line,
16123 ));
16124 }
16125 self.pipeline_collect(&p, line)
16126 }
16127 _ => {
16128 if let Some(sub) = self.resolve_sub_by_name(method) {
16131 if !args.is_empty() {
16132 return Err(PerlError::runtime(
16133 format!(
16134 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
16135 method
16136 ),
16137 line,
16138 ));
16139 }
16140 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
16141 Ok(PerlValue::pipeline(Arc::clone(&p)))
16142 } else {
16143 Err(PerlError::runtime(
16144 format!("Unknown method for pipeline: {}", method),
16145 line,
16146 ))
16147 }
16148 }
16149 }
16150 }
16151
16152 fn pipeline_parallel_map(
16153 &mut self,
16154 items: Vec<PerlValue>,
16155 sub: &Arc<PerlSub>,
16156 progress: bool,
16157 ) -> Vec<PerlValue> {
16158 let subs = self.subs.clone();
16159 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16160 let pmap_progress = PmapProgress::new(progress, items.len());
16161 let results: Vec<PerlValue> = items
16162 .into_par_iter()
16163 .map(|item| {
16164 let mut local_interp = Interpreter::new();
16165 local_interp.subs = subs.clone();
16166 local_interp.scope.restore_capture(&scope_capture);
16167 local_interp
16168 .scope
16169 .restore_atomics(&atomic_arrays, &atomic_hashes);
16170 local_interp.enable_parallel_guard();
16171 local_interp.scope.set_topic(item);
16172 local_interp.scope_push_hook();
16173 let val = match local_interp.exec_block_no_scope(&sub.body) {
16174 Ok(val) => val,
16175 Err(_) => PerlValue::UNDEF,
16176 };
16177 local_interp.scope_pop_hook();
16178 pmap_progress.tick();
16179 val
16180 })
16181 .collect();
16182 pmap_progress.finish();
16183 results
16184 }
16185
16186 fn pipeline_par_stream_filter(
16188 &mut self,
16189 items: Vec<PerlValue>,
16190 sub: &Arc<PerlSub>,
16191 ) -> Vec<PerlValue> {
16192 if items.is_empty() {
16193 return items;
16194 }
16195 let subs = self.subs.clone();
16196 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16197 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
16198 let mut kept: Vec<(usize, PerlValue)> = indexed
16199 .into_par_iter()
16200 .filter_map(|(i, item)| {
16201 let mut local_interp = Interpreter::new();
16202 local_interp.subs = subs.clone();
16203 local_interp.scope.restore_capture(&scope_capture);
16204 local_interp
16205 .scope
16206 .restore_atomics(&atomic_arrays, &atomic_hashes);
16207 local_interp.enable_parallel_guard();
16208 local_interp.scope.set_topic(item.clone());
16209 local_interp.scope_push_hook();
16210 let keep = match local_interp.exec_block_no_scope(&sub.body) {
16211 Ok(val) => val.is_true(),
16212 Err(_) => false,
16213 };
16214 local_interp.scope_pop_hook();
16215 if keep {
16216 Some((i, item))
16217 } else {
16218 None
16219 }
16220 })
16221 .collect();
16222 kept.sort_by_key(|(i, _)| *i);
16223 kept.into_iter().map(|(_, x)| x).collect()
16224 }
16225
16226 fn pipeline_par_stream_map(
16228 &mut self,
16229 items: Vec<PerlValue>,
16230 sub: &Arc<PerlSub>,
16231 ) -> Vec<PerlValue> {
16232 if items.is_empty() {
16233 return items;
16234 }
16235 let subs = self.subs.clone();
16236 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16237 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
16238 let mut mapped: Vec<(usize, PerlValue)> = indexed
16239 .into_par_iter()
16240 .map(|(i, item)| {
16241 let mut local_interp = Interpreter::new();
16242 local_interp.subs = subs.clone();
16243 local_interp.scope.restore_capture(&scope_capture);
16244 local_interp
16245 .scope
16246 .restore_atomics(&atomic_arrays, &atomic_hashes);
16247 local_interp.enable_parallel_guard();
16248 local_interp.scope.set_topic(item);
16249 local_interp.scope_push_hook();
16250 let val = match local_interp.exec_block_no_scope(&sub.body) {
16251 Ok(val) => val,
16252 Err(_) => PerlValue::UNDEF,
16253 };
16254 local_interp.scope_pop_hook();
16255 (i, val)
16256 })
16257 .collect();
16258 mapped.sort_by_key(|(i, _)| *i);
16259 mapped.into_iter().map(|(_, x)| x).collect()
16260 }
16261
16262 fn pipeline_collect(
16263 &mut self,
16264 p: &Arc<Mutex<PipelineInner>>,
16265 line: usize,
16266 ) -> PerlResult<PerlValue> {
16267 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
16268 let g = p.lock();
16269 (
16270 g.source.clone(),
16271 g.ops.clone(),
16272 g.par_stream,
16273 g.streaming,
16274 g.streaming_workers,
16275 g.streaming_buffer,
16276 )
16277 };
16278 if streaming {
16279 return self.pipeline_collect_streaming(
16280 v,
16281 &ops,
16282 streaming_workers,
16283 streaming_buffer,
16284 line,
16285 );
16286 }
16287 for op in ops {
16288 match op {
16289 PipelineOp::Filter(sub) => {
16290 if par_stream {
16291 v = self.pipeline_par_stream_filter(v, &sub);
16292 } else {
16293 let mut out = Vec::new();
16294 for item in v {
16295 self.scope_push_hook();
16296 self.scope.set_topic(item.clone());
16297 if let Some(ref env) = sub.closure_env {
16298 self.scope.restore_capture(env);
16299 }
16300 let keep = match self.exec_block_no_scope(&sub.body) {
16301 Ok(val) => val.is_true(),
16302 Err(_) => false,
16303 };
16304 self.scope_pop_hook();
16305 if keep {
16306 out.push(item);
16307 }
16308 }
16309 v = out;
16310 }
16311 }
16312 PipelineOp::Map(sub) => {
16313 if par_stream {
16314 v = self.pipeline_par_stream_map(v, &sub);
16315 } else {
16316 let mut out = Vec::new();
16317 for item in v {
16318 self.scope_push_hook();
16319 self.scope.set_topic(item);
16320 if let Some(ref env) = sub.closure_env {
16321 self.scope.restore_capture(env);
16322 }
16323 let mapped = match self.exec_block_no_scope(&sub.body) {
16324 Ok(val) => val,
16325 Err(_) => PerlValue::UNDEF,
16326 };
16327 self.scope_pop_hook();
16328 out.push(mapped);
16329 }
16330 v = out;
16331 }
16332 }
16333 PipelineOp::Tap(sub) => {
16334 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
16335 Ok(_) => {}
16336 Err(FlowOrError::Error(e)) => return Err(e),
16337 Err(FlowOrError::Flow(_)) => {
16338 return Err(PerlError::runtime(
16339 "tap: unsupported control flow in block",
16340 line,
16341 ));
16342 }
16343 }
16344 }
16345 PipelineOp::Take(n) => {
16346 let n = n.max(0) as usize;
16347 if v.len() > n {
16348 v.truncate(n);
16349 }
16350 }
16351 PipelineOp::PMap { sub, progress } => {
16352 v = self.pipeline_parallel_map(v, &sub, progress);
16353 }
16354 PipelineOp::PGrep { sub, progress } => {
16355 let subs = self.subs.clone();
16356 let (scope_capture, atomic_arrays, atomic_hashes) =
16357 self.scope.capture_with_atomics();
16358 let pmap_progress = PmapProgress::new(progress, v.len());
16359 v = v
16360 .into_par_iter()
16361 .filter_map(|item| {
16362 let mut local_interp = Interpreter::new();
16363 local_interp.subs = subs.clone();
16364 local_interp.scope.restore_capture(&scope_capture);
16365 local_interp
16366 .scope
16367 .restore_atomics(&atomic_arrays, &atomic_hashes);
16368 local_interp.enable_parallel_guard();
16369 local_interp.scope.set_topic(item.clone());
16370 local_interp.scope_push_hook();
16371 let keep = match local_interp.exec_block_no_scope(&sub.body) {
16372 Ok(val) => val.is_true(),
16373 Err(_) => false,
16374 };
16375 local_interp.scope_pop_hook();
16376 pmap_progress.tick();
16377 if keep {
16378 Some(item)
16379 } else {
16380 None
16381 }
16382 })
16383 .collect();
16384 pmap_progress.finish();
16385 }
16386 PipelineOp::PFor { sub, progress } => {
16387 let subs = self.subs.clone();
16388 let (scope_capture, atomic_arrays, atomic_hashes) =
16389 self.scope.capture_with_atomics();
16390 let pmap_progress = PmapProgress::new(progress, v.len());
16391 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
16392 v.clone().into_par_iter().for_each(|item| {
16393 if first_err.lock().is_some() {
16394 return;
16395 }
16396 let mut local_interp = Interpreter::new();
16397 local_interp.subs = subs.clone();
16398 local_interp.scope.restore_capture(&scope_capture);
16399 local_interp
16400 .scope
16401 .restore_atomics(&atomic_arrays, &atomic_hashes);
16402 local_interp.enable_parallel_guard();
16403 local_interp.scope.set_topic(item);
16404 local_interp.scope_push_hook();
16405 match local_interp.exec_block_no_scope(&sub.body) {
16406 Ok(_) => {}
16407 Err(e) => {
16408 let stryke = match e {
16409 FlowOrError::Error(stryke) => stryke,
16410 FlowOrError::Flow(_) => PerlError::runtime(
16411 "return/last/next/redo not supported inside pipeline pfor block",
16412 line,
16413 ),
16414 };
16415 let mut g = first_err.lock();
16416 if g.is_none() {
16417 *g = Some(stryke);
16418 }
16419 }
16420 }
16421 local_interp.scope_pop_hook();
16422 pmap_progress.tick();
16423 });
16424 pmap_progress.finish();
16425 let pfor_err = first_err.lock().take();
16426 if let Some(e) = pfor_err {
16427 return Err(e);
16428 }
16429 }
16430 PipelineOp::PMapChunked {
16431 chunk,
16432 sub,
16433 progress,
16434 } => {
16435 let chunk_n = chunk.max(1) as usize;
16436 let subs = self.subs.clone();
16437 let (scope_capture, atomic_arrays, atomic_hashes) =
16438 self.scope.capture_with_atomics();
16439 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = v
16440 .chunks(chunk_n)
16441 .enumerate()
16442 .map(|(i, c)| (i, c.to_vec()))
16443 .collect();
16444 let n_chunks = indexed_chunks.len();
16445 let pmap_progress = PmapProgress::new(progress, n_chunks);
16446 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
16447 .into_par_iter()
16448 .map(|(chunk_idx, chunk)| {
16449 let mut local_interp = Interpreter::new();
16450 local_interp.subs = subs.clone();
16451 local_interp.scope.restore_capture(&scope_capture);
16452 local_interp
16453 .scope
16454 .restore_atomics(&atomic_arrays, &atomic_hashes);
16455 local_interp.enable_parallel_guard();
16456 let mut out = Vec::with_capacity(chunk.len());
16457 for item in chunk {
16458 local_interp.scope.set_topic(item);
16459 local_interp.scope_push_hook();
16460 match local_interp.exec_block_no_scope(&sub.body) {
16461 Ok(val) => {
16462 local_interp.scope_pop_hook();
16463 out.push(val);
16464 }
16465 Err(_) => {
16466 local_interp.scope_pop_hook();
16467 out.push(PerlValue::UNDEF);
16468 }
16469 }
16470 }
16471 pmap_progress.tick();
16472 (chunk_idx, out)
16473 })
16474 .collect();
16475 pmap_progress.finish();
16476 chunk_results.sort_by_key(|(i, _)| *i);
16477 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
16478 }
16479 PipelineOp::PSort { cmp, progress } => {
16480 let pmap_progress = PmapProgress::new(progress, 2);
16481 pmap_progress.tick();
16482 match cmp {
16483 Some(cmp_block) => {
16484 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
16485 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
16486 } else {
16487 let subs = self.subs.clone();
16488 let scope_capture = self.scope.capture();
16489 v.par_sort_by(|a, b| {
16490 let mut local_interp = Interpreter::new();
16491 local_interp.subs = subs.clone();
16492 local_interp.scope.restore_capture(&scope_capture);
16493 local_interp.enable_parallel_guard();
16494 let _ = local_interp.scope.set_scalar("a", a.clone());
16495 let _ = local_interp.scope.set_scalar("b", b.clone());
16496 let _ = local_interp.scope.set_scalar("_0", a.clone());
16497 let _ = local_interp.scope.set_scalar("_1", b.clone());
16498 local_interp.scope_push_hook();
16499 let ord =
16500 match local_interp.exec_block_no_scope(&cmp_block.body) {
16501 Ok(v) => {
16502 let n = v.to_int();
16503 if n < 0 {
16504 std::cmp::Ordering::Less
16505 } else if n > 0 {
16506 std::cmp::Ordering::Greater
16507 } else {
16508 std::cmp::Ordering::Equal
16509 }
16510 }
16511 Err(_) => std::cmp::Ordering::Equal,
16512 };
16513 local_interp.scope_pop_hook();
16514 ord
16515 });
16516 }
16517 }
16518 None => {
16519 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
16520 }
16521 }
16522 pmap_progress.tick();
16523 pmap_progress.finish();
16524 }
16525 PipelineOp::PCache { sub, progress } => {
16526 let subs = self.subs.clone();
16527 let scope_capture = self.scope.capture();
16528 let cache = &*crate::pcache::GLOBAL_PCACHE;
16529 let pmap_progress = PmapProgress::new(progress, v.len());
16530 v = v
16531 .into_par_iter()
16532 .map(|item| {
16533 let k = crate::pcache::cache_key(&item);
16534 if let Some(cached) = cache.get(&k) {
16535 pmap_progress.tick();
16536 return cached.clone();
16537 }
16538 let mut local_interp = Interpreter::new();
16539 local_interp.subs = subs.clone();
16540 local_interp.scope.restore_capture(&scope_capture);
16541 local_interp.enable_parallel_guard();
16542 local_interp.scope.set_topic(item.clone());
16543 local_interp.scope_push_hook();
16544 let val = match local_interp.exec_block_no_scope(&sub.body) {
16545 Ok(v) => v,
16546 Err(_) => PerlValue::UNDEF,
16547 };
16548 local_interp.scope_pop_hook();
16549 cache.insert(k, val.clone());
16550 pmap_progress.tick();
16551 val
16552 })
16553 .collect();
16554 pmap_progress.finish();
16555 }
16556 PipelineOp::PReduce { sub, progress } => {
16557 if v.is_empty() {
16558 return Ok(PerlValue::UNDEF);
16559 }
16560 if v.len() == 1 {
16561 return Ok(v.into_iter().next().unwrap());
16562 }
16563 let block = sub.body.clone();
16564 let subs = self.subs.clone();
16565 let scope_capture = self.scope.capture();
16566 let pmap_progress = PmapProgress::new(progress, v.len());
16567 let result = v
16568 .into_par_iter()
16569 .map(|x| {
16570 pmap_progress.tick();
16571 x
16572 })
16573 .reduce_with(|a, b| {
16574 let mut local_interp = Interpreter::new();
16575 local_interp.subs = subs.clone();
16576 local_interp.scope.restore_capture(&scope_capture);
16577 local_interp.enable_parallel_guard();
16578 let _ = local_interp.scope.set_scalar("a", a.clone());
16579 let _ = local_interp.scope.set_scalar("b", b.clone());
16580 let _ = local_interp.scope.set_scalar("_0", a);
16581 let _ = local_interp.scope.set_scalar("_1", b);
16582 match local_interp.exec_block(&block) {
16583 Ok(val) => val,
16584 Err(_) => PerlValue::UNDEF,
16585 }
16586 });
16587 pmap_progress.finish();
16588 return Ok(result.unwrap_or(PerlValue::UNDEF));
16589 }
16590 PipelineOp::PReduceInit {
16591 init,
16592 sub,
16593 progress,
16594 } => {
16595 if v.is_empty() {
16596 return Ok(init);
16597 }
16598 let block = sub.body.clone();
16599 let subs = self.subs.clone();
16600 let scope_capture = self.scope.capture();
16601 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
16602 if v.len() == 1 {
16603 return Ok(fold_preduce_init_step(
16604 &subs,
16605 cap,
16606 &block,
16607 preduce_init_fold_identity(&init),
16608 v.into_iter().next().unwrap(),
16609 ));
16610 }
16611 let pmap_progress = PmapProgress::new(progress, v.len());
16612 let result = v
16613 .into_par_iter()
16614 .fold(
16615 || preduce_init_fold_identity(&init),
16616 |acc, item| {
16617 pmap_progress.tick();
16618 fold_preduce_init_step(&subs, cap, &block, acc, item)
16619 },
16620 )
16621 .reduce(
16622 || preduce_init_fold_identity(&init),
16623 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
16624 );
16625 pmap_progress.finish();
16626 return Ok(result);
16627 }
16628 PipelineOp::PMapReduce {
16629 map,
16630 reduce,
16631 progress,
16632 } => {
16633 if v.is_empty() {
16634 return Ok(PerlValue::UNDEF);
16635 }
16636 let map_block = map.body.clone();
16637 let reduce_block = reduce.body.clone();
16638 let subs = self.subs.clone();
16639 let scope_capture = self.scope.capture();
16640 if v.len() == 1 {
16641 let mut local_interp = Interpreter::new();
16642 local_interp.subs = subs.clone();
16643 local_interp.scope.restore_capture(&scope_capture);
16644 local_interp.scope.set_topic(v[0].clone());
16645 return match local_interp.exec_block_no_scope(&map_block) {
16646 Ok(val) => Ok(val),
16647 Err(_) => Ok(PerlValue::UNDEF),
16648 };
16649 }
16650 let pmap_progress = PmapProgress::new(progress, v.len());
16651 let result = v
16652 .into_par_iter()
16653 .map(|item| {
16654 let mut local_interp = Interpreter::new();
16655 local_interp.subs = subs.clone();
16656 local_interp.scope.restore_capture(&scope_capture);
16657 local_interp.scope.set_topic(item);
16658 let val = match local_interp.exec_block_no_scope(&map_block) {
16659 Ok(val) => val,
16660 Err(_) => PerlValue::UNDEF,
16661 };
16662 pmap_progress.tick();
16663 val
16664 })
16665 .reduce_with(|a, b| {
16666 let mut local_interp = Interpreter::new();
16667 local_interp.subs = subs.clone();
16668 local_interp.scope.restore_capture(&scope_capture);
16669 let _ = local_interp.scope.set_scalar("a", a.clone());
16670 let _ = local_interp.scope.set_scalar("b", b.clone());
16671 let _ = local_interp.scope.set_scalar("_0", a);
16672 let _ = local_interp.scope.set_scalar("_1", b);
16673 match local_interp.exec_block_no_scope(&reduce_block) {
16674 Ok(val) => val,
16675 Err(_) => PerlValue::UNDEF,
16676 }
16677 });
16678 pmap_progress.finish();
16679 return Ok(result.unwrap_or(PerlValue::UNDEF));
16680 }
16681 }
16682 }
16683 Ok(PerlValue::array(v))
16684 }
16685
16686 fn pipeline_collect_streaming(
16689 &mut self,
16690 source: Vec<PerlValue>,
16691 ops: &[PipelineOp],
16692 workers_per_stage: usize,
16693 buffer: usize,
16694 line: usize,
16695 ) -> PerlResult<PerlValue> {
16696 use crossbeam::channel::{bounded, Receiver, Sender};
16697
16698 for op in ops {
16700 match op {
16701 PipelineOp::PSort { .. }
16702 | PipelineOp::PReduce { .. }
16703 | PipelineOp::PReduceInit { .. }
16704 | PipelineOp::PMapReduce { .. }
16705 | PipelineOp::PMapChunked { .. } => {
16706 return Err(PerlError::runtime(
16707 format!(
16708 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
16709 std::mem::discriminant(op)
16710 ),
16711 line,
16712 ));
16713 }
16714 _ => {}
16715 }
16716 }
16717
16718 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
16721 if streamable_ops.is_empty() {
16722 return Ok(PerlValue::array(source));
16723 }
16724
16725 let n_stages = streamable_ops.len();
16726 let wn = if workers_per_stage > 0 {
16727 workers_per_stage
16728 } else {
16729 self.parallel_thread_count()
16730 };
16731 let subs = self.subs.clone();
16732 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16733
16734 let mut channels: Vec<(Sender<PerlValue>, Receiver<PerlValue>)> =
16739 (0..=n_stages).map(|_| bounded(buffer)).collect();
16740
16741 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
16742 let take_done: Arc<std::sync::atomic::AtomicBool> =
16743 Arc::new(std::sync::atomic::AtomicBool::new(false));
16744
16745 let source_tx = channels[0].0.clone();
16748 let result_rx = channels[n_stages].1.clone();
16749 let results: Arc<Mutex<Vec<PerlValue>>> = Arc::new(Mutex::new(Vec::new()));
16750
16751 std::thread::scope(|scope| {
16752 let result_rx_c = result_rx.clone();
16755 let results_c = Arc::clone(&results);
16756 scope.spawn(move || {
16757 while let Ok(item) = result_rx_c.recv() {
16758 results_c.lock().push(item);
16759 }
16760 });
16761
16762 let err_s = Arc::clone(&err);
16764 let take_done_s = Arc::clone(&take_done);
16765 scope.spawn(move || {
16766 for item in source {
16767 if err_s.lock().is_some()
16768 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
16769 {
16770 break;
16771 }
16772 if source_tx.send(item).is_err() {
16773 break;
16774 }
16775 }
16776 });
16777
16778 for (stage_idx, op) in streamable_ops.iter().enumerate() {
16780 let rx = channels[stage_idx].1.clone();
16781 let tx = channels[stage_idx + 1].0.clone();
16782
16783 for _ in 0..wn {
16784 let rx = rx.clone();
16785 let tx = tx.clone();
16786 let subs = subs.clone();
16787 let capture = capture.clone();
16788 let atomic_arrays = atomic_arrays.clone();
16789 let atomic_hashes = atomic_hashes.clone();
16790 let err_w = Arc::clone(&err);
16791 let take_done_w = Arc::clone(&take_done);
16792
16793 match *op {
16794 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
16795 let sub = Arc::clone(sub);
16796 scope.spawn(move || {
16797 while let Ok(item) = rx.recv() {
16798 if err_w.lock().is_some() {
16799 break;
16800 }
16801 let mut interp = Interpreter::new();
16802 interp.subs = subs.clone();
16803 interp.scope.restore_capture(&capture);
16804 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16805 interp.enable_parallel_guard();
16806 interp.scope.set_topic(item.clone());
16807 interp.scope_push_hook();
16808 let keep = match interp.exec_block_no_scope(&sub.body) {
16809 Ok(val) => val.is_true(),
16810 Err(_) => false,
16811 };
16812 interp.scope_pop_hook();
16813 if keep && tx.send(item).is_err() {
16814 break;
16815 }
16816 }
16817 });
16818 }
16819 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
16820 let sub = Arc::clone(sub);
16821 scope.spawn(move || {
16822 while let Ok(item) = rx.recv() {
16823 if err_w.lock().is_some() {
16824 break;
16825 }
16826 let mut interp = Interpreter::new();
16827 interp.subs = subs.clone();
16828 interp.scope.restore_capture(&capture);
16829 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16830 interp.enable_parallel_guard();
16831 interp.scope.set_topic(item);
16832 interp.scope_push_hook();
16833 let mapped = match interp.exec_block_no_scope(&sub.body) {
16834 Ok(val) => val,
16835 Err(_) => PerlValue::UNDEF,
16836 };
16837 interp.scope_pop_hook();
16838 if tx.send(mapped).is_err() {
16839 break;
16840 }
16841 }
16842 });
16843 }
16844 PipelineOp::Take(n) => {
16845 let limit = (*n).max(0) as usize;
16846 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
16847 let count_w = Arc::clone(&count);
16848 scope.spawn(move || {
16849 while let Ok(item) = rx.recv() {
16850 let prev =
16851 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
16852 if prev >= limit {
16853 take_done_w
16854 .store(true, std::sync::atomic::Ordering::Relaxed);
16855 break;
16856 }
16857 if tx.send(item).is_err() {
16858 break;
16859 }
16860 }
16861 });
16862 break;
16864 }
16865 PipelineOp::PFor { ref sub, .. } => {
16866 let sub = Arc::clone(sub);
16867 scope.spawn(move || {
16868 while let Ok(item) = rx.recv() {
16869 if err_w.lock().is_some() {
16870 break;
16871 }
16872 let mut interp = Interpreter::new();
16873 interp.subs = subs.clone();
16874 interp.scope.restore_capture(&capture);
16875 interp
16876 .scope
16877 .restore_atomics(&atomic_arrays, &atomic_hashes);
16878 interp.enable_parallel_guard();
16879 interp.scope.set_topic(item.clone());
16880 interp.scope_push_hook();
16881 match interp.exec_block_no_scope(&sub.body) {
16882 Ok(_) => {}
16883 Err(e) => {
16884 let msg = match e {
16885 FlowOrError::Error(stryke) => stryke.to_string(),
16886 FlowOrError::Flow(_) => {
16887 "unexpected control flow in par_pipeline_stream pfor".into()
16888 }
16889 };
16890 let mut g = err_w.lock();
16891 if g.is_none() {
16892 *g = Some(msg);
16893 }
16894 interp.scope_pop_hook();
16895 break;
16896 }
16897 }
16898 interp.scope_pop_hook();
16899 if tx.send(item).is_err() {
16900 break;
16901 }
16902 }
16903 });
16904 }
16905 PipelineOp::Tap(ref sub) => {
16906 let sub = Arc::clone(sub);
16907 scope.spawn(move || {
16908 while let Ok(item) = rx.recv() {
16909 if err_w.lock().is_some() {
16910 break;
16911 }
16912 let mut interp = Interpreter::new();
16913 interp.subs = subs.clone();
16914 interp.scope.restore_capture(&capture);
16915 interp
16916 .scope
16917 .restore_atomics(&atomic_arrays, &atomic_hashes);
16918 interp.enable_parallel_guard();
16919 match interp.call_sub(
16920 &sub,
16921 vec![item.clone()],
16922 WantarrayCtx::Void,
16923 line,
16924 )
16925 {
16926 Ok(_) => {}
16927 Err(e) => {
16928 let msg = match e {
16929 FlowOrError::Error(stryke) => stryke.to_string(),
16930 FlowOrError::Flow(_) => {
16931 "unexpected control flow in par_pipeline_stream tap"
16932 .into()
16933 }
16934 };
16935 let mut g = err_w.lock();
16936 if g.is_none() {
16937 *g = Some(msg);
16938 }
16939 break;
16940 }
16941 }
16942 if tx.send(item).is_err() {
16943 break;
16944 }
16945 }
16946 });
16947 }
16948 PipelineOp::PCache { ref sub, .. } => {
16949 let sub = Arc::clone(sub);
16950 scope.spawn(move || {
16951 while let Ok(item) = rx.recv() {
16952 if err_w.lock().is_some() {
16953 break;
16954 }
16955 let k = crate::pcache::cache_key(&item);
16956 let val = if let Some(cached) =
16957 crate::pcache::GLOBAL_PCACHE.get(&k)
16958 {
16959 cached.clone()
16960 } else {
16961 let mut interp = Interpreter::new();
16962 interp.subs = subs.clone();
16963 interp.scope.restore_capture(&capture);
16964 interp
16965 .scope
16966 .restore_atomics(&atomic_arrays, &atomic_hashes);
16967 interp.enable_parallel_guard();
16968 interp.scope.set_topic(item);
16969 interp.scope_push_hook();
16970 let v = match interp.exec_block_no_scope(&sub.body) {
16971 Ok(v) => v,
16972 Err(_) => PerlValue::UNDEF,
16973 };
16974 interp.scope_pop_hook();
16975 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
16976 v
16977 };
16978 if tx.send(val).is_err() {
16979 break;
16980 }
16981 }
16982 });
16983 }
16984 _ => unreachable!(),
16986 }
16987 }
16988 }
16989
16990 channels.clear();
16994 drop(result_rx);
16995 });
16996
16997 if let Some(msg) = err.lock().take() {
16998 return Err(PerlError::runtime(msg, line));
16999 }
17000
17001 let results = std::mem::take(&mut *results.lock());
17002 Ok(PerlValue::array(results))
17003 }
17004
17005 fn heap_compare(&mut self, cmp: &Arc<PerlSub>, a: &PerlValue, b: &PerlValue) -> Ordering {
17006 self.scope_push_hook();
17007 if let Some(ref env) = cmp.closure_env {
17008 self.scope.restore_capture(env);
17009 }
17010 let _ = self.scope.set_scalar("a", a.clone());
17011 let _ = self.scope.set_scalar("b", b.clone());
17012 let _ = self.scope.set_scalar("_0", a.clone());
17013 let _ = self.scope.set_scalar("_1", b.clone());
17014 let ord = match self.exec_block_no_scope(&cmp.body) {
17015 Ok(v) => {
17016 let n = v.to_int();
17017 if n < 0 {
17018 Ordering::Less
17019 } else if n > 0 {
17020 Ordering::Greater
17021 } else {
17022 Ordering::Equal
17023 }
17024 }
17025 Err(_) => Ordering::Equal,
17026 };
17027 self.scope_pop_hook();
17028 ord
17029 }
17030
17031 fn heap_sift_up(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
17032 while i > 0 {
17033 let p = (i - 1) / 2;
17034 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
17035 break;
17036 }
17037 items.swap(i, p);
17038 i = p;
17039 }
17040 }
17041
17042 fn heap_sift_down(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
17043 let n = items.len();
17044 loop {
17045 let mut sm = i;
17046 let l = 2 * i + 1;
17047 let r = 2 * i + 2;
17048 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
17049 sm = l;
17050 }
17051 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
17052 sm = r;
17053 }
17054 if sm == i {
17055 break;
17056 }
17057 items.swap(i, sm);
17058 i = sm;
17059 }
17060 }
17061
17062 fn hash_for_signature_destruct(
17063 &mut self,
17064 v: &PerlValue,
17065 line: usize,
17066 ) -> PerlResult<IndexMap<String, PerlValue>> {
17067 let Some(m) = self.match_subject_as_hash(v) else {
17068 return Err(PerlError::runtime(
17069 format!(
17070 "sub signature hash destruct: expected HASH or HASH reference, got {}",
17071 v.ref_type()
17072 ),
17073 line,
17074 ));
17075 };
17076 Ok(m)
17077 }
17078
17079 pub(crate) fn apply_sub_signature(
17081 &mut self,
17082 sub: &PerlSub,
17083 argv: &[PerlValue],
17084 line: usize,
17085 ) -> PerlResult<()> {
17086 if sub.params.is_empty() {
17087 return Ok(());
17088 }
17089 let mut i = 0usize;
17090 for p in &sub.params {
17091 match p {
17092 SubSigParam::Scalar(name, ty, default) => {
17093 let val = if i < argv.len() {
17094 argv[i].clone()
17095 } else if let Some(default_expr) = default {
17096 match self.eval_expr(default_expr) {
17097 Ok(v) => v,
17098 Err(FlowOrError::Error(e)) => return Err(e),
17099 Err(FlowOrError::Flow(_)) => {
17100 return Err(PerlError::runtime(
17101 "unexpected control flow in parameter default",
17102 line,
17103 ))
17104 }
17105 }
17106 } else {
17107 PerlValue::UNDEF
17108 };
17109 i += 1;
17110 if let Some(t) = ty {
17111 if let Err(e) = t.check_value(&val) {
17112 return Err(PerlError::runtime(
17113 format!("sub parameter ${}: {}", name, e),
17114 line,
17115 ));
17116 }
17117 }
17118 let n = self.english_scalar_name(name);
17119 self.scope.declare_scalar(n, val);
17120 }
17121 SubSigParam::Array(name, default) => {
17122 let rest: Vec<PerlValue> = if i < argv.len() {
17123 let r = argv[i..].to_vec();
17124 i = argv.len();
17125 r
17126 } else if let Some(default_expr) = default {
17127 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17128 Ok(v) => v,
17129 Err(FlowOrError::Error(e)) => return Err(e),
17130 Err(FlowOrError::Flow(_)) => {
17131 return Err(PerlError::runtime(
17132 "unexpected control flow in parameter default",
17133 line,
17134 ))
17135 }
17136 };
17137 val.to_list()
17138 } else {
17139 vec![]
17140 };
17141 let aname = self.stash_array_name_for_package(name);
17142 self.scope.declare_array(&aname, rest);
17143 }
17144 SubSigParam::Hash(name, default) => {
17145 let rest: Vec<PerlValue> = if i < argv.len() {
17146 let r = argv[i..].to_vec();
17147 i = argv.len();
17148 r
17149 } else if let Some(default_expr) = default {
17150 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17151 Ok(v) => v,
17152 Err(FlowOrError::Error(e)) => return Err(e),
17153 Err(FlowOrError::Flow(_)) => {
17154 return Err(PerlError::runtime(
17155 "unexpected control flow in parameter default",
17156 line,
17157 ))
17158 }
17159 };
17160 val.to_list()
17161 } else {
17162 vec![]
17163 };
17164 let mut map = IndexMap::new();
17165 let mut j = 0;
17166 while j + 1 < rest.len() {
17167 map.insert(rest[j].to_string(), rest[j + 1].clone());
17168 j += 2;
17169 }
17170 self.scope.declare_hash(name, map);
17171 }
17172 SubSigParam::ArrayDestruct(elems) => {
17173 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17174 i += 1;
17175 let Some(arr) = self.match_subject_as_array(&arg) else {
17176 return Err(PerlError::runtime(
17177 format!(
17178 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
17179 arg.ref_type()
17180 ),
17181 line,
17182 ));
17183 };
17184 let binds = self
17185 .match_array_pattern_elems(&arr, elems, line)
17186 .map_err(|e| match e {
17187 FlowOrError::Error(stryke) => stryke,
17188 FlowOrError::Flow(_) => PerlError::runtime(
17189 "unexpected flow in sub signature array destruct",
17190 line,
17191 ),
17192 })?;
17193 let Some(binds) = binds else {
17194 return Err(PerlError::runtime(
17195 "sub signature array destruct: length or element mismatch",
17196 line,
17197 ));
17198 };
17199 for b in binds {
17200 match b {
17201 PatternBinding::Scalar(name, v) => {
17202 let n = self.english_scalar_name(&name);
17203 self.scope.declare_scalar(n, v);
17204 }
17205 PatternBinding::Array(name, elems) => {
17206 self.scope.declare_array(&name, elems);
17207 }
17208 }
17209 }
17210 }
17211 SubSigParam::HashDestruct(pairs) => {
17212 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17213 i += 1;
17214 let map = self.hash_for_signature_destruct(&arg, line)?;
17215 for (key, varname) in pairs {
17216 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
17217 let n = self.english_scalar_name(varname);
17218 self.scope.declare_scalar(n, v);
17219 }
17220 }
17221 }
17222 }
17223 Ok(())
17224 }
17225
17226 pub(crate) fn try_hof_dispatch(
17230 &mut self,
17231 sub: &PerlSub,
17232 args: &[PerlValue],
17233 want: WantarrayCtx,
17234 line: usize,
17235 ) -> Option<ExecResult> {
17236 let env = sub.closure_env.as_ref()?;
17237 fn env_get<'a>(env: &'a [(String, PerlValue)], key: &str) -> Option<&'a PerlValue> {
17238 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
17239 }
17240
17241 match sub.name.as_str() {
17242 "__comp__" => {
17244 let fns = env_get(env, "__comp_fns__")?.to_list();
17245 let mut val = args.first().cloned().unwrap_or(PerlValue::UNDEF);
17246 for f in fns.iter().rev() {
17247 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
17248 Ok(v) => val = v,
17249 Err(e) => return Some(Err(e)),
17250 }
17251 }
17252 Some(Ok(val))
17253 }
17254 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
17256 "__juxt__" => {
17258 let fns = env_get(env, "__juxt_fns__")?.to_list();
17259 let mut results = Vec::with_capacity(fns.len());
17260 for f in &fns {
17261 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
17262 Ok(v) => results.push(v),
17263 Err(e) => return Some(Err(e)),
17264 }
17265 }
17266 Some(Ok(PerlValue::array(results)))
17267 }
17268 "__partial__" => {
17270 let fn_val = env_get(env, "__partial_fn__")?.clone();
17271 let bound = env_get(env, "__partial_args__")?.to_list();
17272 let mut all_args = bound;
17273 all_args.extend_from_slice(args);
17274 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
17275 }
17276 "__complement__" => {
17278 let fn_val = env_get(env, "__complement_fn__")?.clone();
17279 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17280 Ok(v) => Some(Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }))),
17281 Err(e) => Some(Err(e)),
17282 }
17283 }
17284 "__fnil__" => {
17286 let fn_val = env_get(env, "__fnil_fn__")?.clone();
17287 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
17288 let mut patched = args.to_vec();
17289 for (i, d) in defaults.iter().enumerate() {
17290 if i < patched.len() {
17291 if patched[i].is_undef() {
17292 patched[i] = d.clone();
17293 }
17294 } else {
17295 patched.push(d.clone());
17296 }
17297 }
17298 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
17299 }
17300 "__memoize__" => {
17302 let fn_val = env_get(env, "__memoize_fn__")?.clone();
17303 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
17304 let key = args
17305 .iter()
17306 .map(|a| a.to_string())
17307 .collect::<Vec<_>>()
17308 .join("\x00");
17309 if let Some(href) = cache_ref.as_hash_ref() {
17310 if let Some(cached) = href.read().get(&key) {
17311 return Some(Ok(cached.clone()));
17312 }
17313 }
17314 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17315 Ok(v) => {
17316 if let Some(href) = cache_ref.as_hash_ref() {
17317 href.write().insert(key, v.clone());
17318 }
17319 Some(Ok(v))
17320 }
17321 Err(e) => Some(Err(e)),
17322 }
17323 }
17324 "__curry__" => {
17326 let fn_val = env_get(env, "__curry_fn__")?.clone();
17327 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
17328 let bound = env_get(env, "__curry_bound__")?.to_list();
17329 let mut all = bound;
17330 all.extend_from_slice(args);
17331 if all.len() >= arity {
17332 Some(self.dispatch_indirect_call(fn_val, all, want, line))
17333 } else {
17334 let curry_sub = PerlSub {
17335 name: "__curry__".to_string(),
17336 params: vec![],
17337 body: vec![],
17338 closure_env: Some(vec![
17339 ("__curry_fn__".to_string(), fn_val),
17340 (
17341 "__curry_arity__".to_string(),
17342 PerlValue::integer(arity as i64),
17343 ),
17344 ("__curry_bound__".to_string(), PerlValue::array(all)),
17345 ]),
17346 prototype: None,
17347 fib_like: None,
17348 };
17349 Some(Ok(PerlValue::code_ref(Arc::new(curry_sub))))
17350 }
17351 }
17352 "__once__" => {
17354 let cache_ref = env_get(env, "__once_cache__")?.clone();
17355 if let Some(href) = cache_ref.as_hash_ref() {
17356 let r = href.read();
17357 if r.contains_key("done") {
17358 return Some(Ok(r.get("val").cloned().unwrap_or(PerlValue::UNDEF)));
17359 }
17360 }
17361 let fn_val = env_get(env, "__once_fn__")?.clone();
17362 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17363 Ok(v) => {
17364 if let Some(href) = cache_ref.as_hash_ref() {
17365 let mut w = href.write();
17366 w.insert("done".to_string(), PerlValue::integer(1));
17367 w.insert("val".to_string(), v.clone());
17368 }
17369 Some(Ok(v))
17370 }
17371 Err(e) => Some(Err(e)),
17372 }
17373 }
17374 _ => None,
17375 }
17376 }
17377
17378 pub(crate) fn call_sub(
17379 &mut self,
17380 sub: &PerlSub,
17381 args: Vec<PerlValue>,
17382 want: WantarrayCtx,
17383 _line: usize,
17384 ) -> ExecResult {
17385 self.current_sub_stack.push(Arc::new(sub.clone()));
17387
17388 self.scope_push_hook();
17391 self.scope.declare_array("_", args.clone());
17392 if let Some(ref env) = sub.closure_env {
17393 self.scope.restore_capture(env);
17394 }
17395 self.scope.set_closure_args(&args);
17399 let argv = self.scope.take_sub_underscore().unwrap_or_default();
17401 self.apply_sub_signature(sub, &argv, _line)?;
17402 let saved = self.wantarray_kind;
17403 self.wantarray_kind = want;
17404 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
17405 self.wantarray_kind = saved;
17406 self.scope_pop_hook();
17407 self.current_sub_stack.pop();
17408 return match r {
17409 Ok(v) => Ok(v),
17410 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17411 Err(e) => Err(e),
17412 };
17413 }
17414 if let Some(pat) = sub.fib_like.as_ref() {
17415 if argv.len() == 1 {
17416 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
17417 let t0 = self.profiler.is_some().then(std::time::Instant::now);
17418 if let Some(p) = &mut self.profiler {
17419 p.enter_sub(&sub.name);
17420 }
17421 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
17422 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
17423 p.exit_sub(t0.elapsed());
17424 }
17425 self.wantarray_kind = saved;
17426 self.scope_pop_hook();
17427 self.current_sub_stack.pop();
17428 return Ok(PerlValue::integer(n));
17429 }
17430 }
17431 }
17432 self.scope.declare_array("_", argv.clone());
17433 let t0 = self.profiler.is_some().then(std::time::Instant::now);
17436 if let Some(p) = &mut self.profiler {
17437 p.enter_sub(&sub.name);
17438 }
17439 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
17443 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
17444 p.exit_sub(t0.elapsed());
17445 }
17446 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
17448 Some(self.scope.get_array("_"))
17449 } else {
17450 None
17451 };
17452 self.wantarray_kind = saved;
17453 self.scope_pop_hook();
17454 self.current_sub_stack.pop();
17455 match result {
17456 Ok(v) => Ok(v),
17457 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17458 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
17459 let goto_args = goto_args.unwrap_or_default();
17461 let fqn = if target_name.contains("::") {
17462 target_name.clone()
17463 } else {
17464 format!("{}::{}", self.current_package(), target_name)
17465 };
17466 if let Some(target_sub) = self
17467 .subs
17468 .get(&fqn)
17469 .cloned()
17470 .or_else(|| self.subs.get(&target_name).cloned())
17471 {
17472 self.call_sub(&target_sub, goto_args, want, _line)
17473 } else {
17474 Err(
17475 PerlError::runtime(format!("Undefined subroutine &{}", target_name), _line)
17476 .into(),
17477 )
17478 }
17479 }
17480 Err(FlowOrError::Flow(Flow::Yield(_))) => {
17481 Err(PerlError::runtime("yield is only valid inside gen { }", 0).into())
17482 }
17483 Err(e) => Err(e),
17484 }
17485 }
17486
17487 fn call_struct_method(
17489 &mut self,
17490 body: &Block,
17491 params: &[SubSigParam],
17492 args: Vec<PerlValue>,
17493 line: usize,
17494 ) -> ExecResult {
17495 self.scope_push_hook();
17496 self.scope.declare_array("_", args.clone());
17497 if let Some(self_val) = args.first() {
17499 self.scope.declare_scalar("self", self_val.clone());
17500 }
17501 self.scope.set_closure_args(&args);
17503 let user_args: Vec<PerlValue> = args.iter().skip(1).cloned().collect();
17505 self.apply_params_to_argv(params, &user_args, line)?;
17506 let result = self.exec_block_no_scope(body);
17507 self.scope_pop_hook();
17508 match result {
17509 Ok(v) => Ok(v),
17510 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17511 Err(e) => Err(e),
17512 }
17513 }
17514
17515 pub(crate) fn call_class_method(
17517 &mut self,
17518 body: &Block,
17519 params: &[SubSigParam],
17520 args: Vec<PerlValue>,
17521 line: usize,
17522 ) -> ExecResult {
17523 self.call_class_method_inner(body, params, args, line, false)
17524 }
17525
17526 pub(crate) fn call_static_class_method(
17528 &mut self,
17529 body: &Block,
17530 params: &[SubSigParam],
17531 args: Vec<PerlValue>,
17532 line: usize,
17533 ) -> ExecResult {
17534 self.call_class_method_inner(body, params, args, line, true)
17535 }
17536
17537 fn call_class_method_inner(
17538 &mut self,
17539 body: &Block,
17540 params: &[SubSigParam],
17541 args: Vec<PerlValue>,
17542 line: usize,
17543 is_static: bool,
17544 ) -> ExecResult {
17545 self.scope_push_hook();
17546 self.scope.declare_array("_", args.clone());
17547 if !is_static {
17548 if let Some(self_val) = args.first() {
17550 self.scope.declare_scalar("self", self_val.clone());
17551 }
17552 }
17553 self.scope.set_closure_args(&args);
17555 let user_args: Vec<PerlValue> = if is_static {
17557 args.clone()
17558 } else {
17559 args.iter().skip(1).cloned().collect()
17560 };
17561 self.apply_params_to_argv(params, &user_args, line)?;
17562 let result = self.exec_block_no_scope(body);
17563 self.scope_pop_hook();
17564 match result {
17565 Ok(v) => Ok(v),
17566 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17567 Err(e) => Err(e),
17568 }
17569 }
17570
17571 fn apply_params_to_argv(
17573 &mut self,
17574 params: &[SubSigParam],
17575 argv: &[PerlValue],
17576 line: usize,
17577 ) -> PerlResult<()> {
17578 let mut i = 0;
17579 for param in params {
17580 match param {
17581 SubSigParam::Scalar(name, ty_opt, default) => {
17582 let v = if i < argv.len() {
17583 argv[i].clone()
17584 } else if let Some(default_expr) = default {
17585 match self.eval_expr(default_expr) {
17586 Ok(v) => v,
17587 Err(FlowOrError::Error(e)) => return Err(e),
17588 Err(FlowOrError::Flow(_)) => {
17589 return Err(PerlError::runtime(
17590 "unexpected control flow in parameter default",
17591 line,
17592 ))
17593 }
17594 }
17595 } else {
17596 PerlValue::UNDEF
17597 };
17598 i += 1;
17599 if let Some(ty) = ty_opt {
17600 ty.check_value(&v).map_err(|msg| {
17601 PerlError::type_error(
17602 format!("method parameter ${}: {}", name, msg),
17603 line,
17604 )
17605 })?;
17606 }
17607 let n = self.english_scalar_name(name);
17608 self.scope.declare_scalar(n, v);
17609 }
17610 SubSigParam::Array(name, default) => {
17611 let rest: Vec<PerlValue> = if i < argv.len() {
17612 let r = argv[i..].to_vec();
17613 i = argv.len();
17614 r
17615 } else if let Some(default_expr) = default {
17616 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17617 Ok(v) => v,
17618 Err(FlowOrError::Error(e)) => return Err(e),
17619 Err(FlowOrError::Flow(_)) => {
17620 return Err(PerlError::runtime(
17621 "unexpected control flow in parameter default",
17622 line,
17623 ))
17624 }
17625 };
17626 val.to_list()
17627 } else {
17628 vec![]
17629 };
17630 let aname = self.stash_array_name_for_package(name);
17631 self.scope.declare_array(&aname, rest);
17632 }
17633 SubSigParam::Hash(name, default) => {
17634 let rest: Vec<PerlValue> = if i < argv.len() {
17635 let r = argv[i..].to_vec();
17636 i = argv.len();
17637 r
17638 } else if let Some(default_expr) = default {
17639 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
17640 Ok(v) => v,
17641 Err(FlowOrError::Error(e)) => return Err(e),
17642 Err(FlowOrError::Flow(_)) => {
17643 return Err(PerlError::runtime(
17644 "unexpected control flow in parameter default",
17645 line,
17646 ))
17647 }
17648 };
17649 val.to_list()
17650 } else {
17651 vec![]
17652 };
17653 let mut map = IndexMap::new();
17654 let mut j = 0;
17655 while j + 1 < rest.len() {
17656 map.insert(rest[j].to_string(), rest[j + 1].clone());
17657 j += 2;
17658 }
17659 self.scope.declare_hash(name, map);
17660 }
17661 SubSigParam::ArrayDestruct(elems) => {
17662 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17663 i += 1;
17664 let Some(arr) = self.match_subject_as_array(&arg) else {
17665 return Err(PerlError::runtime(
17666 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
17667 line,
17668 ));
17669 };
17670 let binds = self
17671 .match_array_pattern_elems(&arr, elems, line)
17672 .map_err(|e| match e {
17673 FlowOrError::Error(stryke) => stryke,
17674 FlowOrError::Flow(_) => {
17675 PerlError::runtime("unexpected flow in method array destruct", line)
17676 }
17677 })?;
17678 let Some(binds) = binds else {
17679 return Err(PerlError::runtime(
17680 format!(
17681 "method parameter: array destructure failed at position {}",
17682 i
17683 ),
17684 line,
17685 ));
17686 };
17687 for b in binds {
17688 match b {
17689 PatternBinding::Scalar(name, v) => {
17690 let n = self.english_scalar_name(&name);
17691 self.scope.declare_scalar(n, v);
17692 }
17693 PatternBinding::Array(name, elems) => {
17694 self.scope.declare_array(&name, elems);
17695 }
17696 }
17697 }
17698 }
17699 SubSigParam::HashDestruct(pairs) => {
17700 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17701 i += 1;
17702 let map = self.hash_for_signature_destruct(&arg, line)?;
17703 for (key, varname) in pairs {
17704 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
17705 let n = self.english_scalar_name(varname);
17706 self.scope.declare_scalar(n, v);
17707 }
17708 }
17709 }
17710 }
17711 Ok(())
17712 }
17713
17714 fn builtin_new(&mut self, class: &str, args: Vec<PerlValue>, line: usize) -> ExecResult {
17715 if class == "Set" {
17716 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
17717 }
17718 if let Some(def) = self.struct_defs.get(class).cloned() {
17719 let mut provided = Vec::new();
17720 let mut i = 1;
17721 while i + 1 < args.len() {
17722 let k = args[i].to_string();
17723 let v = args[i + 1].clone();
17724 provided.push((k, v));
17725 i += 2;
17726 }
17727 let mut defaults = Vec::with_capacity(def.fields.len());
17728 for field in &def.fields {
17729 if let Some(ref expr) = field.default {
17730 let val = self.eval_expr(expr)?;
17731 defaults.push(Some(val));
17732 } else {
17733 defaults.push(None);
17734 }
17735 }
17736 return Ok(crate::native_data::struct_new_with_defaults(
17737 &def, &provided, &defaults, line,
17738 )?);
17739 }
17740 let mut map = IndexMap::new();
17742 let mut i = 1; while i + 1 < args.len() {
17744 let k = args[i].to_string();
17745 let v = args[i + 1].clone();
17746 map.insert(k, v);
17747 i += 2;
17748 }
17749 Ok(PerlValue::blessed(Arc::new(
17750 crate::value::BlessedRef::new_blessed(class.to_string(), PerlValue::hash(map)),
17751 )))
17752 }
17753
17754 fn exec_print(
17755 &mut self,
17756 handle: Option<&str>,
17757 args: &[Expr],
17758 newline: bool,
17759 line: usize,
17760 ) -> ExecResult {
17761 if newline && (self.feature_bits & FEAT_SAY) == 0 {
17762 return Err(PerlError::runtime(
17763 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
17764 line,
17765 )
17766 .into());
17767 }
17768 let mut output = String::new();
17769 if args.is_empty() {
17770 let topic = self.scope.get_scalar("_").clone();
17772 let s = self.stringify_value(topic, line)?;
17773 output.push_str(&s);
17774 } else {
17775 for (i, a) in args.iter().enumerate() {
17778 if i > 0 {
17779 output.push_str(&self.ofs);
17780 }
17781 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17782 for item in val.to_list() {
17783 let s = self.stringify_value(item, line)?;
17784 output.push_str(&s);
17785 }
17786 }
17787 }
17788 if newline {
17789 output.push('\n');
17790 }
17791 output.push_str(&self.ors);
17792
17793 let handle_name =
17794 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17795 self.write_formatted_print(handle_name.as_str(), &output, line)?;
17796 Ok(PerlValue::integer(1))
17797 }
17798
17799 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
17800 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
17801 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
17803 (s, &[])
17804 } else {
17805 (self.eval_expr(&args[0])?.to_string(), &args[1..])
17806 };
17807 let mut arg_vals = Vec::new();
17811 for a in rest {
17812 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17813 if let Some(items) = v.as_array_vec() {
17814 arg_vals.extend(items);
17815 } else {
17816 arg_vals.push(v);
17817 }
17818 }
17819 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
17820 let handle_name =
17821 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17822 match handle_name.as_str() {
17823 "STDOUT" => {
17824 if !self.suppress_stdout {
17825 print!("{}", output);
17826 if self.output_autoflush {
17827 let _ = io::stdout().flush();
17828 }
17829 }
17830 }
17831 "STDERR" => {
17832 eprint!("{}", output);
17833 let _ = io::stderr().flush();
17834 }
17835 name => {
17836 if let Some(writer) = self.output_handles.get_mut(name) {
17837 let _ = writer.write_all(output.as_bytes());
17838 if self.output_autoflush {
17839 let _ = writer.flush();
17840 }
17841 }
17842 }
17843 }
17844 Ok(PerlValue::integer(1))
17845 }
17846
17847 pub(crate) fn eval_substr_expr(
17849 &mut self,
17850 string: &Expr,
17851 offset: &Expr,
17852 length: Option<&Expr>,
17853 replacement: Option<&Expr>,
17854 _line: usize,
17855 ) -> Result<PerlValue, FlowOrError> {
17856 let s = self.eval_expr(string)?.to_string();
17857 let off = self.eval_expr(offset)?.to_int();
17858 let start = if off < 0 {
17859 (s.len() as i64 + off).max(0) as usize
17860 } else {
17861 off as usize
17862 };
17863 let len = if let Some(l) = length {
17864 let len_val = self.eval_expr(l)?.to_int();
17865 if len_val < 0 {
17866 let remaining = s.len().saturating_sub(start) as i64;
17868 (remaining + len_val).max(0) as usize
17869 } else {
17870 len_val as usize
17871 }
17872 } else {
17873 s.len().saturating_sub(start)
17874 };
17875 let end = start.saturating_add(len).min(s.len());
17876 let result = s.get(start..end).unwrap_or("").to_string();
17877 if let Some(rep) = replacement {
17878 let rep_s = self.eval_expr(rep)?.to_string();
17879 let mut new_s = String::new();
17880 new_s.push_str(&s[..start]);
17881 new_s.push_str(&rep_s);
17882 new_s.push_str(&s[end..]);
17883 self.assign_value(string, PerlValue::string(new_s))?;
17884 }
17885 Ok(PerlValue::string(result))
17886 }
17887
17888 pub(crate) fn eval_push_expr(
17889 &mut self,
17890 array: &Expr,
17891 values: &[Expr],
17892 line: usize,
17893 ) -> Result<PerlValue, FlowOrError> {
17894 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17895 for v in values {
17896 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17897 self.push_array_deref_value(aref.clone(), val, line)?;
17898 }
17899 let len = self.array_deref_len(aref, line)?;
17900 return Ok(PerlValue::integer(len));
17901 }
17902 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17903 if self.scope.is_array_frozen(&arr_name) {
17904 return Err(PerlError::runtime(
17905 format!("Modification of a frozen value: @{}", arr_name),
17906 line,
17907 )
17908 .into());
17909 }
17910 for v in values {
17911 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17912 if let Some(items) = val.as_array_vec() {
17913 for item in items {
17914 self.scope
17915 .push_to_array(&arr_name, item)
17916 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17917 }
17918 } else {
17919 self.scope
17920 .push_to_array(&arr_name, val)
17921 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17922 }
17923 }
17924 let len = self.scope.array_len(&arr_name);
17925 Ok(PerlValue::integer(len as i64))
17926 }
17927
17928 pub(crate) fn eval_pop_expr(
17929 &mut self,
17930 array: &Expr,
17931 line: usize,
17932 ) -> Result<PerlValue, FlowOrError> {
17933 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17934 return self.pop_array_deref(aref, line);
17935 }
17936 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17937 self.scope
17938 .pop_from_array(&arr_name)
17939 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17940 }
17941
17942 pub(crate) fn eval_shift_expr(
17943 &mut self,
17944 array: &Expr,
17945 line: usize,
17946 ) -> Result<PerlValue, FlowOrError> {
17947 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17948 return self.shift_array_deref(aref, line);
17949 }
17950 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17951 self.scope
17952 .shift_from_array(&arr_name)
17953 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17954 }
17955
17956 pub(crate) fn eval_unshift_expr(
17957 &mut self,
17958 array: &Expr,
17959 values: &[Expr],
17960 line: usize,
17961 ) -> Result<PerlValue, FlowOrError> {
17962 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17963 let mut vals = Vec::new();
17964 for v in values {
17965 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17966 if let Some(items) = val.as_array_vec() {
17967 vals.extend(items);
17968 } else {
17969 vals.push(val);
17970 }
17971 }
17972 let len = self.unshift_array_deref_multi(aref, vals, line)?;
17973 return Ok(PerlValue::integer(len));
17974 }
17975 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17976 let mut vals = Vec::new();
17977 for v in values {
17978 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17979 if let Some(items) = val.as_array_vec() {
17980 vals.extend(items);
17981 } else {
17982 vals.push(val);
17983 }
17984 }
17985 let arr = self
17986 .scope
17987 .get_array_mut(&arr_name)
17988 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17989 for (i, v) in vals.into_iter().enumerate() {
17990 arr.insert(i, v);
17991 }
17992 let len = arr.len();
17993 Ok(PerlValue::integer(len as i64))
17994 }
17995
17996 pub(crate) fn push_array_deref_value(
17998 &mut self,
17999 arr_ref: PerlValue,
18000 val: PerlValue,
18001 line: usize,
18002 ) -> Result<(), FlowOrError> {
18003 let val = self.scope.resolve_container_binding_ref(val);
18006 if let Some(r) = arr_ref.as_array_ref() {
18007 let mut w = r.write();
18008 if let Some(items) = val.as_array_vec() {
18009 w.extend(items.iter().cloned());
18010 } else {
18011 w.push(val);
18012 }
18013 return Ok(());
18014 }
18015 if let Some(name) = arr_ref.as_array_binding_name() {
18016 if let Some(items) = val.as_array_vec() {
18017 for item in items {
18018 self.scope
18019 .push_to_array(&name, item)
18020 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18021 }
18022 } else {
18023 self.scope
18024 .push_to_array(&name, val)
18025 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18026 }
18027 return Ok(());
18028 }
18029 if let Some(s) = arr_ref.as_str() {
18030 if self.strict_refs {
18031 return Err(PerlError::runtime(
18032 format!(
18033 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18034 s
18035 ),
18036 line,
18037 )
18038 .into());
18039 }
18040 let name = s.to_string();
18041 if let Some(items) = val.as_array_vec() {
18042 for item in items {
18043 self.scope
18044 .push_to_array(&name, item)
18045 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18046 }
18047 } else {
18048 self.scope
18049 .push_to_array(&name, val)
18050 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18051 }
18052 return Ok(());
18053 }
18054 Err(PerlError::runtime("push argument is not an ARRAY reference", line).into())
18055 }
18056
18057 pub(crate) fn array_deref_len(
18058 &self,
18059 arr_ref: PerlValue,
18060 line: usize,
18061 ) -> Result<i64, FlowOrError> {
18062 if let Some(r) = arr_ref.as_array_ref() {
18063 return Ok(r.read().len() as i64);
18064 }
18065 if let Some(name) = arr_ref.as_array_binding_name() {
18066 return Ok(self.scope.array_len(&name) as i64);
18067 }
18068 if let Some(s) = arr_ref.as_str() {
18069 if self.strict_refs {
18070 return Err(PerlError::runtime(
18071 format!(
18072 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18073 s
18074 ),
18075 line,
18076 )
18077 .into());
18078 }
18079 return Ok(self.scope.array_len(&s) as i64);
18080 }
18081 Err(PerlError::runtime("argument is not an ARRAY reference", line).into())
18082 }
18083
18084 pub(crate) fn pop_array_deref(
18085 &mut self,
18086 arr_ref: PerlValue,
18087 line: usize,
18088 ) -> Result<PerlValue, FlowOrError> {
18089 if let Some(r) = arr_ref.as_array_ref() {
18090 let mut w = r.write();
18091 return Ok(w.pop().unwrap_or(PerlValue::UNDEF));
18092 }
18093 if let Some(name) = arr_ref.as_array_binding_name() {
18094 return self
18095 .scope
18096 .pop_from_array(&name)
18097 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18098 }
18099 if let Some(s) = arr_ref.as_str() {
18100 if self.strict_refs {
18101 return Err(PerlError::runtime(
18102 format!(
18103 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18104 s
18105 ),
18106 line,
18107 )
18108 .into());
18109 }
18110 return self
18111 .scope
18112 .pop_from_array(&s)
18113 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18114 }
18115 Err(PerlError::runtime("pop argument is not an ARRAY reference", line).into())
18116 }
18117
18118 pub(crate) fn shift_array_deref(
18119 &mut self,
18120 arr_ref: PerlValue,
18121 line: usize,
18122 ) -> Result<PerlValue, FlowOrError> {
18123 if let Some(r) = arr_ref.as_array_ref() {
18124 let mut w = r.write();
18125 return Ok(if w.is_empty() {
18126 PerlValue::UNDEF
18127 } else {
18128 w.remove(0)
18129 });
18130 }
18131 if let Some(name) = arr_ref.as_array_binding_name() {
18132 return self
18133 .scope
18134 .shift_from_array(&name)
18135 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18136 }
18137 if let Some(s) = arr_ref.as_str() {
18138 if self.strict_refs {
18139 return Err(PerlError::runtime(
18140 format!(
18141 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18142 s
18143 ),
18144 line,
18145 )
18146 .into());
18147 }
18148 return self
18149 .scope
18150 .shift_from_array(&s)
18151 .map_err(|e| FlowOrError::Error(e.at_line(line)));
18152 }
18153 Err(PerlError::runtime("shift argument is not an ARRAY reference", line).into())
18154 }
18155
18156 pub(crate) fn unshift_array_deref_multi(
18157 &mut self,
18158 arr_ref: PerlValue,
18159 vals: Vec<PerlValue>,
18160 line: usize,
18161 ) -> Result<i64, FlowOrError> {
18162 let mut flat: Vec<PerlValue> = Vec::new();
18163 for v in vals {
18164 if let Some(items) = v.as_array_vec() {
18165 flat.extend(items);
18166 } else {
18167 flat.push(v);
18168 }
18169 }
18170 if let Some(r) = arr_ref.as_array_ref() {
18171 let mut w = r.write();
18172 for (i, v) in flat.into_iter().enumerate() {
18173 w.insert(i, v);
18174 }
18175 return Ok(w.len() as i64);
18176 }
18177 if let Some(name) = arr_ref.as_array_binding_name() {
18178 let arr = self
18179 .scope
18180 .get_array_mut(&name)
18181 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18182 for (i, v) in flat.into_iter().enumerate() {
18183 arr.insert(i, v);
18184 }
18185 return Ok(arr.len() as i64);
18186 }
18187 if let Some(s) = arr_ref.as_str() {
18188 if self.strict_refs {
18189 return Err(PerlError::runtime(
18190 format!(
18191 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18192 s
18193 ),
18194 line,
18195 )
18196 .into());
18197 }
18198 let name = s.to_string();
18199 let arr = self
18200 .scope
18201 .get_array_mut(&name)
18202 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18203 for (i, v) in flat.into_iter().enumerate() {
18204 arr.insert(i, v);
18205 }
18206 return Ok(arr.len() as i64);
18207 }
18208 Err(PerlError::runtime("unshift argument is not an ARRAY reference", line).into())
18209 }
18210
18211 pub(crate) fn splice_array_deref(
18214 &mut self,
18215 aref: PerlValue,
18216 offset_val: PerlValue,
18217 length_val: PerlValue,
18218 rep_vals: Vec<PerlValue>,
18219 line: usize,
18220 ) -> Result<PerlValue, FlowOrError> {
18221 let ctx = self.wantarray_kind;
18222 if let Some(r) = aref.as_array_ref() {
18223 let arr_len = r.read().len();
18224 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18225 let mut w = r.write();
18226 let removed: Vec<PerlValue> = w.drain(off..end).collect();
18227 for (i, v) in rep_vals.into_iter().enumerate() {
18228 w.insert(off + i, v);
18229 }
18230 return Ok(match ctx {
18231 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18232 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18233 });
18234 }
18235 if let Some(name) = aref.as_array_binding_name() {
18236 let arr_len = self.scope.array_len(&name);
18237 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18238 let arr = self
18239 .scope
18240 .get_array_mut(&name)
18241 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18242 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
18243 for (i, v) in rep_vals.into_iter().enumerate() {
18244 arr.insert(off + i, v);
18245 }
18246 return Ok(match ctx {
18247 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18248 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18249 });
18250 }
18251 if let Some(s) = aref.as_str() {
18252 if self.strict_refs {
18253 return Err(PerlError::runtime(
18254 format!(
18255 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
18256 s
18257 ),
18258 line,
18259 )
18260 .into());
18261 }
18262 let arr_len = self.scope.array_len(&s);
18263 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18264 let arr = self
18265 .scope
18266 .get_array_mut(&s)
18267 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18268 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
18269 for (i, v) in rep_vals.into_iter().enumerate() {
18270 arr.insert(off + i, v);
18271 }
18272 return Ok(match ctx {
18273 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18274 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18275 });
18276 }
18277 Err(PerlError::runtime("splice argument is not an ARRAY reference", line).into())
18278 }
18279
18280 pub(crate) fn eval_splice_expr(
18281 &mut self,
18282 array: &Expr,
18283 offset: Option<&Expr>,
18284 length: Option<&Expr>,
18285 replacement: &[Expr],
18286 ctx: WantarrayCtx,
18287 line: usize,
18288 ) -> Result<PerlValue, FlowOrError> {
18289 if let Some(aref) = self.try_eval_array_deref_container(array)? {
18290 let offset_val = if let Some(o) = offset {
18291 self.eval_expr(o)?
18292 } else {
18293 PerlValue::integer(0)
18294 };
18295 let length_val = if let Some(l) = length {
18296 self.eval_expr(l)?
18297 } else {
18298 PerlValue::UNDEF
18299 };
18300 let mut rep_vals = Vec::new();
18301 for r in replacement {
18302 rep_vals.push(self.eval_expr(r)?);
18303 }
18304 let saved = self.wantarray_kind;
18305 self.wantarray_kind = ctx;
18306 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
18307 self.wantarray_kind = saved;
18308 return out;
18309 }
18310 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
18311 let arr_len = self.scope.array_len(&arr_name);
18312 let offset_val = if let Some(o) = offset {
18313 self.eval_expr(o)?
18314 } else {
18315 PerlValue::integer(0)
18316 };
18317 let length_val = if let Some(l) = length {
18318 self.eval_expr(l)?
18319 } else {
18320 PerlValue::UNDEF
18321 };
18322 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
18323 let mut rep_vals = Vec::new();
18324 for r in replacement {
18325 rep_vals.push(self.eval_expr(r)?);
18326 }
18327 let arr = self
18328 .scope
18329 .get_array_mut(&arr_name)
18330 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
18331 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
18332 for (i, v) in rep_vals.into_iter().enumerate() {
18333 arr.insert(off + i, v);
18334 }
18335 Ok(match ctx {
18336 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18337 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18338 })
18339 }
18340
18341 pub(crate) fn keys_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
18343 if let Some(h) = val.as_hash_map() {
18344 Ok(PerlValue::array(
18345 h.keys().map(|k| PerlValue::string(k.clone())).collect(),
18346 ))
18347 } else if let Some(r) = val.as_hash_ref() {
18348 Ok(PerlValue::array(
18349 r.read()
18350 .keys()
18351 .map(|k| PerlValue::string(k.clone()))
18352 .collect(),
18353 ))
18354 } else {
18355 Err(PerlError::runtime("keys requires hash", line).into())
18356 }
18357 }
18358
18359 pub(crate) fn eval_keys_expr(
18360 &mut self,
18361 expr: &Expr,
18362 line: usize,
18363 ) -> Result<PerlValue, FlowOrError> {
18364 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
18367 Self::keys_from_value(val, line)
18368 }
18369
18370 pub(crate) fn values_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
18372 if let Some(h) = val.as_hash_map() {
18373 Ok(PerlValue::array(h.values().cloned().collect()))
18374 } else if let Some(r) = val.as_hash_ref() {
18375 Ok(PerlValue::array(r.read().values().cloned().collect()))
18376 } else {
18377 Err(PerlError::runtime("values requires hash", line).into())
18378 }
18379 }
18380
18381 pub(crate) fn eval_values_expr(
18382 &mut self,
18383 expr: &Expr,
18384 line: usize,
18385 ) -> Result<PerlValue, FlowOrError> {
18386 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
18387 Self::values_from_value(val, line)
18388 }
18389
18390 pub(crate) fn eval_delete_operand(
18391 &mut self,
18392 expr: &Expr,
18393 line: usize,
18394 ) -> Result<PerlValue, FlowOrError> {
18395 match &expr.kind {
18396 ExprKind::HashElement { hash, key } => {
18397 let k = self.eval_expr(key)?.to_string();
18398 self.touch_env_hash(hash);
18399 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
18400 let class = obj
18401 .as_blessed_ref()
18402 .map(|b| b.class.clone())
18403 .unwrap_or_default();
18404 let full = format!("{}::DELETE", class);
18405 if let Some(sub) = self.subs.get(&full).cloned() {
18406 return self.call_sub(
18407 &sub,
18408 vec![obj, PerlValue::string(k)],
18409 WantarrayCtx::Scalar,
18410 line,
18411 );
18412 }
18413 }
18414 self.scope
18415 .delete_hash_element(hash, &k)
18416 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18417 }
18418 ExprKind::ArrayElement { array, index } => {
18419 self.check_strict_array_var(array, line)?;
18420 let idx = self.eval_expr(index)?.to_int();
18421 let aname = self.stash_array_name_for_package(array);
18422 self.scope
18423 .delete_array_element(&aname, idx)
18424 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18425 }
18426 ExprKind::ArrowDeref {
18427 expr: inner,
18428 index,
18429 kind: DerefKind::Hash,
18430 } => {
18431 let k = self.eval_expr(index)?.to_string();
18432 let container = self.eval_expr(inner)?;
18433 self.delete_arrow_hash_element(container, &k, line)
18434 .map_err(Into::into)
18435 }
18436 ExprKind::ArrowDeref {
18437 expr: inner,
18438 index,
18439 kind: DerefKind::Array,
18440 } => {
18441 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
18442 return Err(PerlError::runtime(
18443 "delete on array element needs scalar subscript",
18444 line,
18445 )
18446 .into());
18447 }
18448 let container = self.eval_expr(inner)?;
18449 let idx = self.eval_expr(index)?.to_int();
18450 self.delete_arrow_array_element(container, idx, line)
18451 .map_err(Into::into)
18452 }
18453 _ => Err(PerlError::runtime("delete requires hash or array element", line).into()),
18454 }
18455 }
18456
18457 pub(crate) fn eval_exists_operand(
18458 &mut self,
18459 expr: &Expr,
18460 line: usize,
18461 ) -> Result<PerlValue, FlowOrError> {
18462 match &expr.kind {
18463 ExprKind::HashElement { hash, key } => {
18464 let k = self.eval_expr(key)?.to_string();
18465 self.touch_env_hash(hash);
18466 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
18467 let class = obj
18468 .as_blessed_ref()
18469 .map(|b| b.class.clone())
18470 .unwrap_or_default();
18471 let full = format!("{}::EXISTS", class);
18472 if let Some(sub) = self.subs.get(&full).cloned() {
18473 return self.call_sub(
18474 &sub,
18475 vec![obj, PerlValue::string(k)],
18476 WantarrayCtx::Scalar,
18477 line,
18478 );
18479 }
18480 }
18481 Ok(PerlValue::integer(
18482 if self.scope.exists_hash_element(hash, &k) {
18483 1
18484 } else {
18485 0
18486 },
18487 ))
18488 }
18489 ExprKind::ArrayElement { array, index } => {
18490 self.check_strict_array_var(array, line)?;
18491 let idx = self.eval_expr(index)?.to_int();
18492 let aname = self.stash_array_name_for_package(array);
18493 Ok(PerlValue::integer(
18494 if self.scope.exists_array_element(&aname, idx) {
18495 1
18496 } else {
18497 0
18498 },
18499 ))
18500 }
18501 ExprKind::ArrowDeref {
18502 expr: inner,
18503 index,
18504 kind: DerefKind::Hash,
18505 } => {
18506 let k = self.eval_expr(index)?.to_string();
18507 let container = self.eval_expr(inner)?;
18508 let yes = self.exists_arrow_hash_element(container, &k, line)?;
18509 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
18510 }
18511 ExprKind::ArrowDeref {
18512 expr: inner,
18513 index,
18514 kind: DerefKind::Array,
18515 } => {
18516 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
18517 return Err(PerlError::runtime(
18518 "exists on array element needs scalar subscript",
18519 line,
18520 )
18521 .into());
18522 }
18523 let container = self.eval_expr(inner)?;
18524 let idx = self.eval_expr(index)?.to_int();
18525 let yes = self.exists_arrow_array_element(container, idx, line)?;
18526 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
18527 }
18528 _ => Err(PerlError::runtime("exists requires hash or array element", line).into()),
18529 }
18530 }
18531
18532 pub(crate) fn eval_pmap_remote(
18540 &mut self,
18541 cluster_pv: PerlValue,
18542 list_pv: PerlValue,
18543 show_progress: bool,
18544 block: &Block,
18545 flat_outputs: bool,
18546 line: usize,
18547 ) -> Result<PerlValue, FlowOrError> {
18548 let Some(cluster) = cluster_pv.as_remote_cluster() else {
18549 return Err(PerlError::runtime("pmap_on: expected cluster(...) value", line).into());
18550 };
18551 let items = list_pv.to_list();
18552 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18553 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
18554 return Err(PerlError::runtime(
18555 "pmap_on: mysync/atomic capture is not supported for remote workers",
18556 line,
18557 )
18558 .into());
18559 }
18560 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
18561 .map_err(|e| PerlError::runtime(e, line))?;
18562 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
18563 let block_src = crate::fmt::format_block(block);
18564 let item_jsons =
18565 crate::cluster::perl_items_to_json(&items).map_err(|e| PerlError::runtime(e, line))?;
18566
18567 let pmap_progress = PmapProgress::new(show_progress, items.len());
18570 let result_values =
18571 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
18572 .map_err(|e| PerlError::runtime(format!("pmap_on remote: {e}"), line))?;
18573 for _ in 0..result_values.len() {
18574 pmap_progress.tick();
18575 }
18576 pmap_progress.finish();
18577
18578 if flat_outputs {
18579 let flattened: Vec<PerlValue> = result_values
18580 .into_iter()
18581 .flat_map(|v| v.map_flatten_outputs(true))
18582 .collect();
18583 Ok(PerlValue::array(flattened))
18584 } else {
18585 Ok(PerlValue::array(result_values))
18586 }
18587 }
18588
18589 pub(crate) fn eval_par_lines_expr(
18591 &mut self,
18592 path: &Expr,
18593 callback: &Expr,
18594 progress: Option<&Expr>,
18595 line: usize,
18596 ) -> Result<PerlValue, FlowOrError> {
18597 let show_progress = progress
18598 .map(|p| self.eval_expr(p))
18599 .transpose()?
18600 .map(|v| v.is_true())
18601 .unwrap_or(false);
18602 let path_s = self.eval_expr(path)?.to_string();
18603 let cb_val = self.eval_expr(callback)?;
18604 let sub = if let Some(s) = cb_val.as_code_ref() {
18605 s
18606 } else {
18607 return Err(PerlError::runtime(
18608 "par_lines: second argument must be a code reference",
18609 line,
18610 )
18611 .into());
18612 };
18613 let subs = self.subs.clone();
18614 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18615 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
18616 FlowOrError::Error(PerlError::runtime(format!("par_lines: {}", e), line))
18617 })?;
18618 let mmap = unsafe {
18619 memmap2::Mmap::map(&file).map_err(|e| {
18620 FlowOrError::Error(PerlError::runtime(format!("par_lines: mmap: {}", e), line))
18621 })?
18622 };
18623 let data: &[u8] = &mmap;
18624 if data.is_empty() {
18625 return Ok(PerlValue::UNDEF);
18626 }
18627 let line_total = crate::par_lines::line_count_bytes(data);
18628 let pmap_progress = PmapProgress::new(show_progress, line_total);
18629 if self.num_threads == 0 {
18630 self.num_threads = rayon::current_num_threads();
18631 }
18632 let num_chunks = self.num_threads.saturating_mul(8).max(1);
18633 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
18634 chunks.into_par_iter().try_for_each(|(start, end)| {
18635 let slice = &data[start..end];
18636 let mut s = 0usize;
18637 while s < slice.len() {
18638 let e = slice[s..]
18639 .iter()
18640 .position(|&b| b == b'\n')
18641 .map(|p| s + p)
18642 .unwrap_or(slice.len());
18643 let line_bytes = &slice[s..e];
18644 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
18645 let mut local_interp = Interpreter::new();
18646 local_interp.subs = subs.clone();
18647 local_interp.scope.restore_capture(&scope_capture);
18648 local_interp
18649 .scope
18650 .restore_atomics(&atomic_arrays, &atomic_hashes);
18651 local_interp.enable_parallel_guard();
18652 local_interp.scope.set_topic(PerlValue::string(line_str));
18653 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
18654 Ok(_) => {}
18655 Err(e) => return Err(e),
18656 }
18657 pmap_progress.tick();
18658 if e >= slice.len() {
18659 break;
18660 }
18661 s = e + 1;
18662 }
18663 Ok(())
18664 })?;
18665 pmap_progress.finish();
18666 Ok(PerlValue::UNDEF)
18667 }
18668
18669 pub(crate) fn eval_par_walk_expr(
18671 &mut self,
18672 path: &Expr,
18673 callback: &Expr,
18674 progress: Option<&Expr>,
18675 line: usize,
18676 ) -> Result<PerlValue, FlowOrError> {
18677 let show_progress = progress
18678 .map(|p| self.eval_expr(p))
18679 .transpose()?
18680 .map(|v| v.is_true())
18681 .unwrap_or(false);
18682 let path_val = self.eval_expr(path)?;
18683 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
18684 arr.into_iter()
18685 .map(|v| PathBuf::from(v.to_string()))
18686 .collect()
18687 } else {
18688 vec![PathBuf::from(path_val.to_string())]
18689 };
18690 let cb_val = self.eval_expr(callback)?;
18691 let sub = if let Some(s) = cb_val.as_code_ref() {
18692 s
18693 } else {
18694 return Err(PerlError::runtime(
18695 "par_walk: second argument must be a code reference",
18696 line,
18697 )
18698 .into());
18699 };
18700 let subs = self.subs.clone();
18701 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18702
18703 if show_progress {
18704 let paths = crate::par_walk::collect_paths(&roots);
18705 let pmap_progress = PmapProgress::new(true, paths.len());
18706 paths.into_par_iter().try_for_each(|p| {
18707 let s = p.to_string_lossy().into_owned();
18708 let mut local_interp = Interpreter::new();
18709 local_interp.subs = subs.clone();
18710 local_interp.scope.restore_capture(&scope_capture);
18711 local_interp
18712 .scope
18713 .restore_atomics(&atomic_arrays, &atomic_hashes);
18714 local_interp.enable_parallel_guard();
18715 local_interp.scope.set_topic(PerlValue::string(s));
18716 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
18717 Ok(_) => {}
18718 Err(e) => return Err(e),
18719 }
18720 pmap_progress.tick();
18721 Ok(())
18722 })?;
18723 pmap_progress.finish();
18724 } else {
18725 for r in &roots {
18726 par_walk_recursive(
18727 r.as_path(),
18728 &sub,
18729 &subs,
18730 &scope_capture,
18731 &atomic_arrays,
18732 &atomic_hashes,
18733 line,
18734 )?;
18735 }
18736 }
18737 Ok(PerlValue::UNDEF)
18738 }
18739
18740 pub(crate) fn builtin_par_sed(
18742 &mut self,
18743 args: &[PerlValue],
18744 line: usize,
18745 has_progress: bool,
18746 ) -> PerlResult<PerlValue> {
18747 let show_progress = if has_progress {
18748 args.last().map(|v| v.is_true()).unwrap_or(false)
18749 } else {
18750 false
18751 };
18752 let slice = if has_progress {
18753 &args[..args.len().saturating_sub(1)]
18754 } else {
18755 args
18756 };
18757 if slice.len() < 3 {
18758 return Err(PerlError::runtime(
18759 "par_sed: need pattern, replacement, and at least one file path",
18760 line,
18761 ));
18762 }
18763 let pat_val = &slice[0];
18764 let repl = slice[1].to_string();
18765 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
18766
18767 let re = if let Some(rx) = pat_val.as_regex() {
18768 rx
18769 } else {
18770 let pattern = pat_val.to_string();
18771 match self.compile_regex(&pattern, "g", line) {
18772 Ok(r) => r,
18773 Err(FlowOrError::Error(e)) => return Err(e),
18774 Err(FlowOrError::Flow(f)) => {
18775 return Err(PerlError::runtime(format!("par_sed: {:?}", f), line))
18776 }
18777 }
18778 };
18779
18780 let pmap = PmapProgress::new(show_progress, files.len());
18781 let touched = AtomicUsize::new(0);
18782 files.par_iter().try_for_each(|path| {
18783 let content = read_file_text_perl_compat(path)
18784 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18785 let new_s = re.replace_all(&content, &repl);
18786 if new_s != content {
18787 std::fs::write(path, new_s.as_bytes())
18788 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18789 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
18790 }
18791 pmap.tick();
18792 Ok(())
18793 })?;
18794 pmap.finish();
18795 Ok(PerlValue::integer(
18796 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
18797 ))
18798 }
18799
18800 pub(crate) fn eval_pwatch_expr(
18802 &mut self,
18803 path: &Expr,
18804 callback: &Expr,
18805 line: usize,
18806 ) -> Result<PerlValue, FlowOrError> {
18807 let pattern_s = self.eval_expr(path)?.to_string();
18808 let cb_val = self.eval_expr(callback)?;
18809 let sub = if let Some(s) = cb_val.as_code_ref() {
18810 s
18811 } else {
18812 return Err(PerlError::runtime(
18813 "pwatch: second argument must be a code reference",
18814 line,
18815 )
18816 .into());
18817 };
18818 let subs = self.subs.clone();
18819 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18820 crate::pwatch::run_pwatch(
18821 &pattern_s,
18822 sub,
18823 subs,
18824 scope_capture,
18825 atomic_arrays,
18826 atomic_hashes,
18827 line,
18828 )
18829 .map_err(FlowOrError::Error)
18830 }
18831
18832 fn interpolate_replacement_string(&self, replacement: &str) -> String {
18834 let mut out = String::with_capacity(replacement.len());
18835 let chars: Vec<char> = replacement.chars().collect();
18836 let mut i = 0;
18837 while i < chars.len() {
18838 if chars[i] == '\\' && i + 1 < chars.len() {
18839 out.push(chars[i]);
18840 out.push(chars[i + 1]);
18841 i += 2;
18842 continue;
18843 }
18844 if chars[i] == '$' && i + 1 < chars.len() {
18845 let start = i;
18846 i += 1;
18847 if chars[i].is_ascii_digit() {
18848 out.push('$');
18849 while i < chars.len() && chars[i].is_ascii_digit() {
18850 out.push(chars[i]);
18851 i += 1;
18852 }
18853 continue;
18854 }
18855 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
18856 out.push('$');
18857 out.push(chars[i]);
18858 i += 1;
18859 continue;
18860 }
18861 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
18862 out.push('$');
18863 continue;
18864 }
18865 let mut name = String::new();
18866 if chars[i] == '{' {
18867 i += 1;
18868 while i < chars.len() && chars[i] != '}' {
18869 name.push(chars[i]);
18870 i += 1;
18871 }
18872 if i < chars.len() {
18873 i += 1;
18874 }
18875 } else {
18876 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18877 name.push(chars[i]);
18878 i += 1;
18879 }
18880 }
18881 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
18882 let val = self.scope.get_scalar(&name);
18883 out.push_str(&val.to_string());
18884 } else if !name.is_empty() {
18885 out.push_str(&replacement[start..i]);
18886 } else {
18887 out.push('$');
18888 }
18889 continue;
18890 }
18891 out.push(chars[i]);
18892 i += 1;
18893 }
18894 out
18895 }
18896
18897 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
18899 let mut out = String::with_capacity(pattern.len());
18900 let chars: Vec<char> = pattern.chars().collect();
18901 let mut i = 0;
18902 while i < chars.len() {
18903 if chars[i] == '\\' && i + 1 < chars.len() {
18904 out.push(chars[i]);
18906 out.push(chars[i + 1]);
18907 i += 2;
18908 continue;
18909 }
18910 if chars[i] == '$' && i + 1 < chars.len() {
18911 i += 1;
18912 if i >= chars.len()
18914 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
18915 {
18916 out.push('$');
18917 continue;
18918 }
18919 let mut name = String::new();
18920 if chars[i] == '{' {
18921 i += 1;
18922 while i < chars.len() && chars[i] != '}' {
18923 name.push(chars[i]);
18924 i += 1;
18925 }
18926 if i < chars.len() {
18927 i += 1;
18928 } } else {
18930 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18931 name.push(chars[i]);
18932 i += 1;
18933 }
18934 }
18935 if !name.is_empty() {
18936 let val = self.scope.get_scalar(&name);
18937 out.push_str(&val.to_string());
18938 } else {
18939 out.push('$');
18940 }
18941 continue;
18942 }
18943 out.push(chars[i]);
18944 i += 1;
18945 }
18946 out
18947 }
18948
18949 pub(crate) fn compile_regex(
18950 &mut self,
18951 pattern: &str,
18952 flags: &str,
18953 line: usize,
18954 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
18955 let pattern = if pattern.contains('$') || pattern.contains('@') {
18957 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
18958 } else {
18959 std::borrow::Cow::Borrowed(pattern)
18960 };
18961 let pattern = pattern.as_ref();
18962 let multiline = self.multiline_match;
18965 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
18966 if lp == pattern && lf == flags && *lm == multiline {
18967 return Ok(lr.clone());
18968 }
18969 }
18970 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
18972 if let Some(cached) = self.regex_cache.get(&key) {
18973 self.regex_last = Some((
18974 pattern.to_string(),
18975 flags.to_string(),
18976 multiline,
18977 cached.clone(),
18978 ));
18979 return Ok(cached.clone());
18980 }
18981 let expanded = expand_perl_regex_quotemeta(pattern);
18982 let expanded = expand_perl_regex_octal_escapes(&expanded);
18983 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
18984 let mut re_str = String::new();
18985 if flags.contains('i') {
18986 re_str.push_str("(?i)");
18987 }
18988 if flags.contains('s') {
18989 re_str.push_str("(?s)");
18990 }
18991 if flags.contains('m') {
18992 re_str.push_str("(?m)");
18993 }
18994 if flags.contains('x') {
18995 re_str.push_str("(?x)");
18996 }
18997 if multiline {
18999 re_str.push_str("(?s)");
19000 }
19001 re_str.push_str(&expanded);
19002 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
19003 FlowOrError::Error(PerlError::runtime(
19004 format!("Invalid regex /{}/: {}", pattern, e),
19005 line,
19006 ))
19007 })?;
19008 let arc = re;
19009 self.regex_last = Some((
19010 pattern.to_string(),
19011 flags.to_string(),
19012 multiline,
19013 arc.clone(),
19014 ));
19015 self.regex_cache.insert(key, arc.clone());
19016 Ok(arc)
19017 }
19018
19019 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
19021 if self.last_readline_handle.is_empty() {
19022 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
19023 }
19024 let n = *self
19025 .handle_line_numbers
19026 .get(&self.last_readline_handle)
19027 .unwrap_or(&0);
19028 if n <= 0 {
19029 return None;
19030 }
19031 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
19032 {
19033 return Some(("<>".to_string(), n));
19034 }
19035 if self.last_readline_handle == "STDIN" {
19036 return Some((self.last_stdin_die_bracket.clone(), n));
19037 }
19038 Some((format!("<{}>", self.last_readline_handle), n))
19039 }
19040
19041 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
19043 let mut s = format!(" at {} line {}", self.file, source_line);
19044 if let Some((bracket, n)) = self.die_warn_io_annotation() {
19045 s.push_str(&format!(", {} line {}.", bracket, n));
19046 } else {
19047 s.push('.');
19048 }
19049 s
19050 }
19051
19052 pub fn process_line(
19057 &mut self,
19058 line_str: &str,
19059 _program: &Program,
19060 is_last_input_line: bool,
19061 ) -> PerlResult<Option<String>> {
19062 let chunk = self
19063 .line_mode_chunk
19064 .as_ref()
19065 .expect("process_line called without compiled chunk — execute() must run first")
19066 .clone();
19067 crate::run_line_body(&chunk, self, line_str, is_last_input_line)
19068 }
19069}
19070
19071fn par_walk_invoke_entry(
19072 path: &Path,
19073 sub: &Arc<PerlSub>,
19074 subs: &HashMap<String, Arc<PerlSub>>,
19075 scope_capture: &[(String, PerlValue)],
19076 atomic_arrays: &[(String, crate::scope::AtomicArray)],
19077 atomic_hashes: &[(String, crate::scope::AtomicHash)],
19078 line: usize,
19079) -> Result<(), FlowOrError> {
19080 let s = path.to_string_lossy().into_owned();
19081 let mut local_interp = Interpreter::new();
19082 local_interp.subs = subs.clone();
19083 local_interp.scope.restore_capture(scope_capture);
19084 local_interp
19085 .scope
19086 .restore_atomics(atomic_arrays, atomic_hashes);
19087 local_interp.enable_parallel_guard();
19088 local_interp.scope.set_topic(PerlValue::string(s));
19089 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
19090 Ok(())
19091}
19092
19093fn par_walk_recursive(
19094 path: &Path,
19095 sub: &Arc<PerlSub>,
19096 subs: &HashMap<String, Arc<PerlSub>>,
19097 scope_capture: &[(String, PerlValue)],
19098 atomic_arrays: &[(String, crate::scope::AtomicArray)],
19099 atomic_hashes: &[(String, crate::scope::AtomicHash)],
19100 line: usize,
19101) -> Result<(), FlowOrError> {
19102 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
19103 return par_walk_invoke_entry(
19104 path,
19105 sub,
19106 subs,
19107 scope_capture,
19108 atomic_arrays,
19109 atomic_hashes,
19110 line,
19111 );
19112 }
19113 if !path.is_dir() {
19114 return Ok(());
19115 }
19116 par_walk_invoke_entry(
19117 path,
19118 sub,
19119 subs,
19120 scope_capture,
19121 atomic_arrays,
19122 atomic_hashes,
19123 line,
19124 )?;
19125 let read = match std::fs::read_dir(path) {
19126 Ok(r) => r,
19127 Err(_) => return Ok(()),
19128 };
19129 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
19130 entries.par_iter().try_for_each(|e| {
19131 par_walk_recursive(
19132 &e.path(),
19133 sub,
19134 subs,
19135 scope_capture,
19136 atomic_arrays,
19137 atomic_hashes,
19138 line,
19139 )
19140 })?;
19141 Ok(())
19142}
19143
19144pub(crate) fn perl_sprintf_format_with<F>(
19146 fmt: &str,
19147 args: &[PerlValue],
19148 mut string_for_s: F,
19149) -> Result<String, FlowOrError>
19150where
19151 F: FnMut(&PerlValue) -> Result<String, FlowOrError>,
19152{
19153 let mut result = String::new();
19154 let mut arg_idx = 0;
19155 let chars: Vec<char> = fmt.chars().collect();
19156 let mut i = 0;
19157
19158 while i < chars.len() {
19159 if chars[i] == '%' {
19160 i += 1;
19161 if i >= chars.len() {
19162 break;
19163 }
19164 if chars[i] == '%' {
19165 result.push('%');
19166 i += 1;
19167 continue;
19168 }
19169
19170 let mut flags = String::new();
19172 while i < chars.len() && "-+ #0".contains(chars[i]) {
19173 flags.push(chars[i]);
19174 i += 1;
19175 }
19176 let mut width = String::new();
19177 while i < chars.len() && chars[i].is_ascii_digit() {
19178 width.push(chars[i]);
19179 i += 1;
19180 }
19181 let mut precision = String::new();
19182 if i < chars.len() && chars[i] == '.' {
19183 i += 1;
19184 while i < chars.len() && chars[i].is_ascii_digit() {
19185 precision.push(chars[i]);
19186 i += 1;
19187 }
19188 }
19189 if i >= chars.len() {
19190 break;
19191 }
19192 let spec = chars[i];
19193 i += 1;
19194
19195 let arg = args.get(arg_idx).cloned().unwrap_or(PerlValue::UNDEF);
19196 arg_idx += 1;
19197
19198 let w: usize = width.parse().unwrap_or(0);
19199 let p: usize = precision.parse().unwrap_or(6);
19200
19201 let zero_pad = flags.contains('0') && !flags.contains('-');
19202 let left_align = flags.contains('-');
19203 let formatted = match spec {
19204 'd' | 'i' => {
19205 if zero_pad {
19206 format!("{:0width$}", arg.to_int(), width = w)
19207 } else if left_align {
19208 format!("{:<width$}", arg.to_int(), width = w)
19209 } else {
19210 format!("{:width$}", arg.to_int(), width = w)
19211 }
19212 }
19213 'u' => {
19214 if zero_pad {
19215 format!("{:0width$}", arg.to_int() as u64, width = w)
19216 } else {
19217 format!("{:width$}", arg.to_int() as u64, width = w)
19218 }
19219 }
19220 'f' => format!("{:width$.prec$}", arg.to_number(), width = w, prec = p),
19221 'e' => format!("{:width$.prec$e}", arg.to_number(), width = w, prec = p),
19222 'g' => {
19223 let n = arg.to_number();
19224 if n.abs() >= 1e-4 && n.abs() < 1e15 {
19225 format!("{:width$.prec$}", n, width = w, prec = p)
19226 } else {
19227 format!("{:width$.prec$e}", n, width = w, prec = p)
19228 }
19229 }
19230 's' => {
19231 let s = string_for_s(&arg)?;
19232 if !precision.is_empty() {
19233 let truncated: String = s.chars().take(p).collect();
19234 if flags.contains('-') {
19235 format!("{:<width$}", truncated, width = w)
19236 } else {
19237 format!("{:>width$}", truncated, width = w)
19238 }
19239 } else if flags.contains('-') {
19240 format!("{:<width$}", s, width = w)
19241 } else {
19242 format!("{:>width$}", s, width = w)
19243 }
19244 }
19245 'x' => {
19246 let v = arg.to_int();
19247 if zero_pad && w > 0 {
19248 format!("{:0width$x}", v, width = w)
19249 } else if left_align {
19250 format!("{:<width$x}", v, width = w)
19251 } else if w > 0 {
19252 format!("{:width$x}", v, width = w)
19253 } else {
19254 format!("{:x}", v)
19255 }
19256 }
19257 'X' => {
19258 let v = arg.to_int();
19259 if zero_pad && w > 0 {
19260 format!("{:0width$X}", v, width = w)
19261 } else if left_align {
19262 format!("{:<width$X}", v, width = w)
19263 } else if w > 0 {
19264 format!("{:width$X}", v, width = w)
19265 } else {
19266 format!("{:X}", v)
19267 }
19268 }
19269 'o' => {
19270 let v = arg.to_int();
19271 if zero_pad && w > 0 {
19272 format!("{:0width$o}", v, width = w)
19273 } else if left_align {
19274 format!("{:<width$o}", v, width = w)
19275 } else if w > 0 {
19276 format!("{:width$o}", v, width = w)
19277 } else {
19278 format!("{:o}", v)
19279 }
19280 }
19281 'b' => {
19282 let v = arg.to_int();
19283 if zero_pad && w > 0 {
19284 format!("{:0width$b}", v, width = w)
19285 } else if left_align {
19286 format!("{:<width$b}", v, width = w)
19287 } else if w > 0 {
19288 format!("{:width$b}", v, width = w)
19289 } else {
19290 format!("{:b}", v)
19291 }
19292 }
19293 'c' => char::from_u32(arg.to_int() as u32)
19294 .map(|c| c.to_string())
19295 .unwrap_or_default(),
19296 _ => arg.to_string(),
19297 };
19298
19299 result.push_str(&formatted);
19300 } else {
19301 result.push(chars[i]);
19302 i += 1;
19303 }
19304 }
19305 Ok(result)
19306}
19307
19308#[cfg(test)]
19309mod regex_expand_tests {
19310 use super::Interpreter;
19311
19312 #[test]
19313 fn compile_regex_quotemeta_qe_matches_literal() {
19314 let mut i = Interpreter::new();
19315 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
19316 assert!(re.is_match("a.c"));
19317 assert!(!re.is_match("abc"));
19318 }
19319
19320 #[test]
19323 fn compile_regex_char_class_leading_close_bracket_is_literal() {
19324 let mut i = Interpreter::new();
19325 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
19326 assert!(re.is_match("$"));
19327 assert!(re.is_match("]"));
19328 assert!(!re.is_match("x"));
19329 }
19330}
19331
19332#[cfg(test)]
19333mod special_scalar_name_tests {
19334 use super::Interpreter;
19335
19336 #[test]
19337 fn special_scalar_name_for_get_matches_magic_globals() {
19338 assert!(Interpreter::is_special_scalar_name_for_get("0"));
19339 assert!(Interpreter::is_special_scalar_name_for_get("!"));
19340 assert!(Interpreter::is_special_scalar_name_for_get("^W"));
19341 assert!(Interpreter::is_special_scalar_name_for_get("^O"));
19342 assert!(Interpreter::is_special_scalar_name_for_get("^MATCH"));
19343 assert!(Interpreter::is_special_scalar_name_for_get("<"));
19344 assert!(Interpreter::is_special_scalar_name_for_get("?"));
19345 assert!(Interpreter::is_special_scalar_name_for_get("|"));
19346 assert!(Interpreter::is_special_scalar_name_for_get("^UNICODE"));
19347 assert!(Interpreter::is_special_scalar_name_for_get("\""));
19348 assert!(!Interpreter::is_special_scalar_name_for_get("foo"));
19349 assert!(!Interpreter::is_special_scalar_name_for_get("plainvar"));
19350 }
19351
19352 #[test]
19353 fn special_scalar_name_for_set_matches_set_special_var_arms() {
19354 assert!(Interpreter::is_special_scalar_name_for_set("0"));
19355 assert!(Interpreter::is_special_scalar_name_for_set("^D"));
19356 assert!(Interpreter::is_special_scalar_name_for_set("^H"));
19357 assert!(Interpreter::is_special_scalar_name_for_set("^WARNING_BITS"));
19358 assert!(Interpreter::is_special_scalar_name_for_set("ARGV"));
19359 assert!(Interpreter::is_special_scalar_name_for_set("|"));
19360 assert!(Interpreter::is_special_scalar_name_for_set("?"));
19361 assert!(Interpreter::is_special_scalar_name_for_set("^UNICODE"));
19362 assert!(Interpreter::is_special_scalar_name_for_set("."));
19363 assert!(!Interpreter::is_special_scalar_name_for_set("foo"));
19364 assert!(!Interpreter::is_special_scalar_name_for_set("__PACKAGE__"));
19365 }
19366
19367 #[test]
19368 fn caret_and_id_specials_roundtrip_get() {
19369 let i = Interpreter::new();
19370 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
19371 assert_eq!(
19372 i.get_special_var("^V").to_string(),
19373 format!("v{}", env!("CARGO_PKG_VERSION"))
19374 );
19375 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
19376 assert!(i.get_special_var("^T").to_int() >= 0);
19377 #[cfg(unix)]
19378 {
19379 assert!(i.get_special_var("<").to_int() >= 0);
19380 }
19381 }
19382
19383 #[test]
19384 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
19385 let mut i = Interpreter::new();
19386 i.last_readline_handle.clear();
19387 i.line_number = 3;
19388 i.prepare_flip_flop_vm_slots(1);
19389 assert_eq!(
19390 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
19391 1
19392 );
19393 assert!(i.flip_flop_active[0]);
19394 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
19395 assert_eq!(
19397 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
19398 1
19399 );
19400 assert!(i.flip_flop_active[0]);
19401 }
19402
19403 #[test]
19404 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
19405 let mut i = Interpreter::new();
19406 i.last_readline_handle.clear();
19407 i.line_number = 2;
19408 i.prepare_flip_flop_vm_slots(1);
19409 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
19410 assert!(i.flip_flop_active[0]);
19411 i.line_number = 3;
19412 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
19413 assert!(!i.flip_flop_active[0]);
19414 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
19415 }
19416}