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
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
242pub(crate) enum WantarrayCtx {
243 #[default]
244 Scalar,
245 List,
246 Void,
247}
248
249impl WantarrayCtx {
250 #[inline]
251 pub(crate) fn from_byte(b: u8) -> Self {
252 match b {
253 1 => Self::List,
254 2 => Self::Void,
255 _ => Self::Scalar,
256 }
257 }
258
259 #[inline]
260 pub(crate) fn as_byte(self) -> u8 {
261 match self {
262 Self::Scalar => 0,
263 Self::List => 1,
264 Self::Void => 2,
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
271pub(crate) enum LogLevelFilter {
272 Trace,
273 Debug,
274 Info,
275 Warn,
276 Error,
277}
278
279impl LogLevelFilter {
280 pub(crate) fn parse(s: &str) -> Option<Self> {
281 match s.trim().to_ascii_lowercase().as_str() {
282 "trace" => Some(Self::Trace),
283 "debug" => Some(Self::Debug),
284 "info" => Some(Self::Info),
285 "warn" | "warning" => Some(Self::Warn),
286 "error" => Some(Self::Error),
287 _ => None,
288 }
289 }
290
291 pub(crate) fn as_str(self) -> &'static str {
292 match self {
293 Self::Trace => "trace",
294 Self::Debug => "debug",
295 Self::Info => "info",
296 Self::Warn => "warn",
297 Self::Error => "error",
298 }
299 }
300}
301
302fn arrow_deref_array_assign_rhs_list_ctx(index: &Expr) -> bool {
304 match &index.kind {
305 ExprKind::Range { .. } => true,
306 ExprKind::QW(ws) => ws.len() > 1,
307 ExprKind::List(el) => {
308 if el.len() > 1 {
309 true
310 } else if el.len() == 1 {
311 arrow_deref_array_assign_rhs_list_ctx(&el[0])
312 } else {
313 false
314 }
315 }
316 _ => false,
317 }
318}
319
320pub(crate) fn assign_rhs_wantarray(target: &Expr) -> WantarrayCtx {
323 match &target.kind {
324 ExprKind::ArrayVar(_) | ExprKind::HashVar(_) => WantarrayCtx::List,
325 ExprKind::ScalarVar(_) | ExprKind::ArrayElement { .. } | ExprKind::HashElement { .. } => {
326 WantarrayCtx::Scalar
327 }
328 ExprKind::Deref { kind, .. } => match kind {
329 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
330 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
331 },
332 ExprKind::ArrowDeref {
333 index,
334 kind: DerefKind::Array,
335 ..
336 } => {
337 if arrow_deref_array_assign_rhs_list_ctx(index) {
338 WantarrayCtx::List
339 } else {
340 WantarrayCtx::Scalar
341 }
342 }
343 ExprKind::ArrowDeref {
344 kind: DerefKind::Hash,
345 ..
346 }
347 | ExprKind::ArrowDeref {
348 kind: DerefKind::Call,
349 ..
350 } => WantarrayCtx::Scalar,
351 ExprKind::HashSliceDeref { .. } | ExprKind::HashSlice { .. } => WantarrayCtx::List,
352 ExprKind::ArraySlice { indices, .. } => {
353 if indices.len() > 1 {
354 WantarrayCtx::List
355 } else if indices.len() == 1 {
356 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
357 WantarrayCtx::List
358 } else {
359 WantarrayCtx::Scalar
360 }
361 } else {
362 WantarrayCtx::Scalar
363 }
364 }
365 ExprKind::AnonymousListSlice { indices, .. } => {
366 if indices.len() > 1 {
367 WantarrayCtx::List
368 } else if indices.len() == 1 {
369 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
370 WantarrayCtx::List
371 } else {
372 WantarrayCtx::Scalar
373 }
374 } else {
375 WantarrayCtx::Scalar
376 }
377 }
378 ExprKind::Typeglob(_) | ExprKind::TypeglobExpr(_) => WantarrayCtx::Scalar,
379 ExprKind::List(_) => WantarrayCtx::List,
380 _ => WantarrayCtx::Scalar,
381 }
382}
383
384#[derive(Clone)]
389pub(crate) struct RegexMatchMemo {
390 pub pattern: String,
391 pub flags: String,
392 pub multiline: bool,
393 pub haystack: String,
394 pub result: PerlValue,
395}
396
397#[derive(Clone, Copy, Default)]
399struct FlipFlopTreeState {
400 active: bool,
401 exclusive_left_line: Option<i64>,
405}
406
407#[derive(Clone)]
409pub(crate) struct IoSharedFile(pub Arc<Mutex<File>>);
410
411impl Read for IoSharedFile {
412 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
413 self.0.lock().read(buf)
414 }
415}
416
417pub(crate) struct IoSharedFileWrite(pub Arc<Mutex<File>>);
418
419impl IoWrite for IoSharedFileWrite {
420 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
421 self.0.lock().write(buf)
422 }
423
424 fn flush(&mut self) -> io::Result<()> {
425 self.0.lock().flush()
426 }
427}
428
429pub struct Interpreter {
430 pub scope: Scope,
431 pub(crate) subs: HashMap<String, Arc<PerlSub>>,
432 pub(crate) file: String,
433 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
435 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
436 pub ofs: String,
438 pub ors: String,
440 pub irs: Option<String>,
443 pub errno: String,
445 pub errno_code: i32,
447 pub eval_error: String,
449 pub eval_error_code: i32,
451 pub eval_error_value: Option<PerlValue>,
453 pub argv: Vec<String>,
455 pub env: IndexMap<String, PerlValue>,
457 pub env_materialized: bool,
459 pub program_name: String,
461 pub line_number: i64,
463 pub last_readline_handle: String,
465 pub(crate) last_stdin_die_bracket: String,
467 pub handle_line_numbers: HashMap<String, i64>,
469 pub(crate) flip_flop_active: Vec<bool>,
473 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
476 pub(crate) flip_flop_sequence: Vec<i64>,
480 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
484 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
486 pub sigint_pending_caret: Cell<bool>,
488 pub auto_split: bool,
490 pub field_separator: Option<String>,
492 begin_blocks: Vec<Block>,
494 unit_check_blocks: Vec<Block>,
496 check_blocks: Vec<Block>,
498 init_blocks: Vec<Block>,
500 end_blocks: Vec<Block>,
502 pub warnings: bool,
504 pub output_autoflush: bool,
506 pub default_print_handle: String,
508 pub suppress_stdout: bool,
510 pub child_exit_status: i64,
512 pub last_match: String,
514 pub prematch: String,
516 pub postmatch: String,
518 pub last_paren_match: String,
520 pub list_separator: String,
522 pub script_start_time: i64,
524 pub compile_hints: i64,
526 pub warning_bits: i64,
528 pub global_phase: String,
530 pub subscript_sep: String,
532 pub inplace_edit: String,
535 pub debug_flags: i64,
537 pub perl_debug_flags: i64,
539 pub eval_nesting: u32,
541 pub argv_current_file: String,
543 pub(crate) diamond_next_idx: usize,
545 pub(crate) diamond_reader: Option<BufReader<File>>,
547 pub strict_refs: bool,
549 pub strict_subs: bool,
550 pub strict_vars: bool,
551 pub utf8_pragma: bool,
553 pub open_pragma_utf8: bool,
555 pub feature_bits: u64,
557 pub num_threads: usize,
559 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
561 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
564 regex_match_memo: Option<RegexMatchMemo>,
573 regex_capture_scope_fresh: bool,
577 pub(crate) regex_pos: HashMap<String, Option<usize>>,
579 pub(crate) state_vars: HashMap<String, PerlValue>,
581 state_bindings_stack: Vec<Vec<(String, String)>>,
583 pub(crate) rand_rng: StdRng,
585 pub(crate) dir_handles: HashMap<String, DirHandleState>,
587 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
589 pub(crate) pipe_children: HashMap<String, Child>,
591 pub(crate) socket_handles: HashMap<String, PerlSocket>,
593 pub(crate) wantarray_kind: WantarrayCtx,
595 pub struct_defs: HashMap<String, Arc<StructDef>>,
597 pub enum_defs: HashMap<String, Arc<EnumDef>>,
599 pub class_defs: HashMap<String, Arc<ClassDef>>,
601 pub trait_defs: HashMap<String, Arc<TraitDef>>,
603 pub profiler: Option<Profiler>,
606 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
608 pub(crate) virtual_modules: HashMap<String, String>,
610 pub(crate) tied_hashes: HashMap<String, PerlValue>,
612 pub(crate) tied_scalars: HashMap<String, PerlValue>,
614 pub(crate) tied_arrays: HashMap<String, PerlValue>,
616 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
618 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
620 pub(crate) special_caret_scalars: HashMap<String, PerlValue>,
622 pub format_page_number: i64,
624 pub format_lines_per_page: i64,
626 pub format_lines_left: i64,
628 pub format_line_break_chars: String,
630 pub format_top_name: String,
632 pub accumulator_format: String,
634 pub max_system_fd: i64,
636 pub emergency_memory: String,
638 pub last_subpattern_name: String,
640 pub inc_hook_index: i64,
642 pub multiline_match: bool,
644 pub executable_path: String,
646 pub formfeed_string: String,
648 pub(crate) glob_handle_alias: HashMap<String, String>,
650 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
652 pub(crate) special_var_restore_frames: Vec<Vec<(String, PerlValue)>>,
657 pub(crate) reflection_hashes_ready: bool,
661 pub(crate) english_enabled: bool,
662 pub(crate) english_no_match_vars: bool,
664 pub(crate) english_match_vars_ever_enabled: bool,
668 english_lexical_scalars: Vec<HashSet<String>>,
670 our_lexical_scalars: Vec<HashSet<String>>,
672 pub vm_jit_enabled: bool,
675 pub disasm_bytecode: bool,
677 pub pec_precompiled_chunk: Option<crate::bytecode::Chunk>,
681 pub pec_cache_fingerprint: Option<[u8; 32]>,
685 pub(crate) in_generator: bool,
687 pub line_mode_skip_main: bool,
689 pub(crate) line_mode_eof_pending: bool,
693 pub line_mode_stdin_pending: VecDeque<String>,
696 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
698 pub(crate) log_level_override: Option<LogLevelFilter>,
700 pub(crate) current_sub_stack: Vec<Arc<PerlSub>>,
703 pub debugger: Option<crate::debugger::Debugger>,
705 pub(crate) debug_call_stack: Vec<(String, usize)>,
707}
708
709#[derive(Debug, Clone, Default)]
711pub struct ReplCompletionSnapshot {
712 pub subs: Vec<String>,
713 pub blessed_scalars: HashMap<String, String>,
714 pub isa_for_class: HashMap<String, Vec<String>>,
715}
716
717impl ReplCompletionSnapshot {
718 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
720 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
721 let mro = linearize_c3(class, &parents, 0);
722 let mut names = HashSet::new();
723 for pkg in &mro {
724 if pkg == "UNIVERSAL" {
725 continue;
726 }
727 let prefix = format!("{}::", pkg);
728 for k in &self.subs {
729 if k.starts_with(&prefix) {
730 let rest = &k[prefix.len()..];
731 if !rest.contains("::") {
732 names.insert(rest.to_string());
733 }
734 }
735 }
736 }
737 for k in &self.subs {
738 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
739 if !rest.contains("::") {
740 names.insert(rest.to_string());
741 }
742 }
743 }
744 let mut v: Vec<String> = names.into_iter().collect();
745 v.sort();
746 v
747 }
748}
749
750fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
751 let left = left.trim_end();
752 if left.is_empty() {
753 return None;
754 }
755 if let Some(i) = left.rfind('$') {
756 let name = left[i + 1..].trim();
757 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
758 return state.blessed_scalars.get(name).cloned();
759 }
760 }
761 let tok = left.split_whitespace().last()?;
762 if tok.contains("::") {
763 return Some(tok.to_string());
764 }
765 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
766 return Some(tok.to_string());
767 }
768 None
769}
770
771pub fn repl_arrow_method_completions(
773 state: &ReplCompletionSnapshot,
774 line: &str,
775 pos: usize,
776) -> Option<(usize, Vec<String>)> {
777 let pos = pos.min(line.len());
778 let before = &line[..pos];
779 let arrow_idx = before.rfind("->")?;
780 let after_arrow = &before[arrow_idx + 2..];
781 let rest = after_arrow.trim_start();
782 let ws_len = after_arrow.len() - rest.len();
783 let method_start = arrow_idx + 2 + ws_len;
784 let method_prefix = &line[method_start..pos];
785 if !method_prefix
786 .chars()
787 .all(|c| c.is_alphanumeric() || c == '_')
788 {
789 return None;
790 }
791 let left = line[..arrow_idx].trim_end();
792 let class = repl_resolve_class_for_arrow(state, left)?;
793 let mut methods = state.methods_for_class(&class);
794 methods.retain(|m| m.starts_with(method_prefix));
795 Some((method_start, methods))
796}
797
798#[derive(Debug, Clone, Default)]
800pub(crate) struct ModuleExportLists {
801 pub export: Vec<String>,
803 pub export_ok: Vec<String>,
805}
806
807fn piped_shell_command(cmd: &str) -> Command {
809 if cfg!(windows) {
810 let mut c = Command::new("cmd");
811 c.arg("/C").arg(cmd);
812 c
813 } else {
814 let mut c = Command::new("sh");
815 c.arg("-c").arg(cmd);
816 c
817 }
818}
819
820fn expand_perl_regex_octal_escapes(pat: &str) -> String {
827 let mut out = String::with_capacity(pat.len());
828 let mut it = pat.chars().peekable();
829 while let Some(c) = it.next() {
830 if c == '\\' {
831 if let Some(&'0') = it.peek() {
832 let mut oct = String::new();
834 while oct.len() < 3 {
835 if let Some(&d) = it.peek() {
836 if ('0'..='7').contains(&d) {
837 oct.push(d);
838 it.next();
839 } else {
840 break;
841 }
842 } else {
843 break;
844 }
845 }
846 if let Ok(val) = u8::from_str_radix(&oct, 8) {
847 out.push_str(&format!("\\x{:02x}", val));
848 } else {
849 out.push('\\');
850 out.push_str(&oct);
851 }
852 continue;
853 }
854 }
855 out.push(c);
856 }
857 out
858}
859
860fn expand_perl_regex_quotemeta(pat: &str) -> String {
861 let mut out = String::with_capacity(pat.len().saturating_mul(2));
862 let mut it = pat.chars().peekable();
863 let mut in_q = false;
864 while let Some(c) = it.next() {
865 if in_q {
866 if c == '\\' && it.peek() == Some(&'E') {
867 it.next();
868 in_q = false;
869 continue;
870 }
871 out.push_str(&perl_quotemeta(&c.to_string()));
872 continue;
873 }
874 if c == '\\' && it.peek() == Some(&'Q') {
875 it.next();
876 in_q = true;
877 continue;
878 }
879 out.push(c);
880 }
881 out
882}
883
884pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
890 let mut out = String::with_capacity(replacement.len() + 8);
891 let mut it = replacement.chars().peekable();
892 while let Some(c) = it.next() {
893 if c == '\\' {
894 match it.peek() {
895 Some(&d) if d.is_ascii_digit() => {
896 it.next();
897 out.push_str("${");
898 out.push(d);
899 while let Some(&d2) = it.peek() {
900 if !d2.is_ascii_digit() {
901 break;
902 }
903 it.next();
904 out.push(d2);
905 }
906 out.push('}');
907 }
908 Some(&'\\') => {
909 it.next();
910 out.push('\\');
911 }
912 _ => out.push('\\'),
913 }
914 } else if c == '$' {
915 match it.peek() {
916 Some(&d) if d.is_ascii_digit() => {
917 it.next();
918 out.push_str("${");
919 out.push(d);
920 while let Some(&d2) = it.peek() {
921 if !d2.is_ascii_digit() {
922 break;
923 }
924 it.next();
925 out.push(d2);
926 }
927 out.push('}');
928 }
929 Some(&'{') => {
930 out.push('$');
932 }
933 _ => out.push('$'),
934 }
935 } else {
936 out.push(c);
937 }
938 }
939 out
940}
941
942fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
945 debug_assert_eq!(chars.get(i), Some(&'['));
946 out.push('[');
947 i += 1;
948 if i < chars.len() && chars[i] == '^' {
949 out.push('^');
950 i += 1;
951 }
952 if i >= chars.len() {
953 return i;
954 }
955 if chars[i] == ']' {
959 if i + 1 < chars.len() && chars[i + 1] == ']' {
960 out.push(']');
962 i += 1;
963 } else {
964 let mut scan = i + 1;
965 let mut found_closing = false;
966 while scan < chars.len() {
967 if chars[scan] == '\\' && scan + 1 < chars.len() {
968 scan += 2;
969 continue;
970 }
971 if chars[scan] == ']' {
972 found_closing = true;
973 break;
974 }
975 scan += 1;
976 }
977 if found_closing {
978 out.push(']');
979 i += 1;
980 } else {
981 out.push(']');
982 return i + 1;
983 }
984 }
985 }
986 while i < chars.len() && chars[i] != ']' {
987 if chars[i] == '\\' && i + 1 < chars.len() {
988 out.push(chars[i]);
989 out.push(chars[i + 1]);
990 i += 2;
991 continue;
992 }
993 out.push(chars[i]);
994 i += 1;
995 }
996 if i < chars.len() {
997 out.push(']');
998 i += 1;
999 }
1000 i
1001}
1002
1003fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1008 if multiline_flag {
1009 return pat.to_string();
1010 }
1011 let chars: Vec<char> = pat.chars().collect();
1012 let mut out = String::with_capacity(pat.len().saturating_add(16));
1013 let mut i = 0usize;
1014 while i < chars.len() {
1015 let c = chars[i];
1016 if c == '\\' && i + 1 < chars.len() {
1017 out.push(c);
1018 out.push(chars[i + 1]);
1019 i += 2;
1020 continue;
1021 }
1022 if c == '[' {
1023 i = copy_regex_char_class(&chars, i, &mut out);
1024 continue;
1025 }
1026 if c == '$' {
1027 if let Some(&next) = chars.get(i + 1) {
1028 if next.is_ascii_digit() {
1029 out.push(c);
1030 i += 1;
1031 continue;
1032 }
1033 if next == '{' {
1034 out.push(c);
1035 i += 1;
1036 continue;
1037 }
1038 if next.is_ascii_alphanumeric() || next == '_' {
1039 out.push(c);
1040 i += 1;
1041 continue;
1042 }
1043 }
1044 out.push_str("(?=\\n?\\z)");
1045 i += 1;
1046 continue;
1047 }
1048 out.push(c);
1049 i += 1;
1050 }
1051 out
1052}
1053
1054#[derive(Debug, Clone)]
1056pub(crate) struct DirHandleState {
1057 pub entries: Vec<String>,
1058 pub pos: usize,
1059}
1060
1061pub(crate) fn perl_osname() -> String {
1063 match std::env::consts::OS {
1064 "linux" => "linux".to_string(),
1065 "macos" => "darwin".to_string(),
1066 "windows" => "MSWin32".to_string(),
1067 other => other.to_string(),
1068 }
1069}
1070
1071fn perl_version_v_string() -> String {
1072 format!("v{}", env!("CARGO_PKG_VERSION"))
1073}
1074
1075fn extended_os_error_string() -> String {
1076 std::io::Error::last_os_error().to_string()
1077}
1078
1079#[cfg(unix)]
1080fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1081 unsafe {
1082 (
1083 libc::getuid() as i64,
1084 libc::geteuid() as i64,
1085 libc::getgid() as i64,
1086 libc::getegid() as i64,
1087 )
1088 }
1089}
1090
1091#[cfg(not(unix))]
1092fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1093 (0, 0, 0, 0)
1094}
1095
1096fn unix_id_for_special(name: &str) -> i64 {
1097 let (r, e, _, _) = unix_real_effective_ids();
1098 match name {
1099 "<" => r,
1100 ">" => e,
1101 _ => 0,
1102 }
1103}
1104
1105#[cfg(unix)]
1106fn unix_group_list_string(primary: libc::gid_t) -> String {
1107 let mut buf = vec![0 as libc::gid_t; 256];
1108 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1109 if n <= 0 {
1110 return format!("{}", primary);
1111 }
1112 let mut parts = vec![format!("{}", primary)];
1113 for g in buf.iter().take(n as usize) {
1114 parts.push(format!("{}", g));
1115 }
1116 parts.join(" ")
1117}
1118
1119#[cfg(unix)]
1121fn unix_group_list_for_special(name: &str) -> String {
1122 let (_, _, gid, egid) = unix_real_effective_ids();
1123 match name {
1124 "(" => unix_group_list_string(gid as libc::gid_t),
1125 ")" => unix_group_list_string(egid as libc::gid_t),
1126 _ => String::new(),
1127 }
1128}
1129
1130#[cfg(not(unix))]
1131fn unix_group_list_for_special(_name: &str) -> String {
1132 String::new()
1133}
1134
1135#[cfg(unix)]
1138fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1139 use libc::{getpwuid_r, getuid};
1140 use std::ffi::CStr;
1141 use std::os::unix::ffi::OsStringExt;
1142 let uid = unsafe { getuid() };
1143 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1144 let mut result: *mut libc::passwd = std::ptr::null_mut();
1145 let mut buf = vec![0u8; 16_384];
1146 let rc = unsafe {
1147 getpwuid_r(
1148 uid,
1149 &mut pw,
1150 buf.as_mut_ptr().cast::<libc::c_char>(),
1151 buf.len(),
1152 &mut result,
1153 )
1154 };
1155 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1156 return None;
1157 }
1158 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1159 if bytes.is_empty() {
1160 return None;
1161 }
1162 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1163}
1164
1165#[cfg(unix)]
1167fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1168 use libc::getpwnam_r;
1169 use std::ffi::{CStr, CString};
1170 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1171 let bytes = login.as_bytes();
1172 if bytes.is_empty() || bytes.contains(&0) {
1173 return None;
1174 }
1175 let cname = CString::new(bytes).ok()?;
1176 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1177 let mut result: *mut libc::passwd = std::ptr::null_mut();
1178 let mut buf = vec![0u8; 16_384];
1179 let rc = unsafe {
1180 getpwnam_r(
1181 cname.as_ptr(),
1182 &mut pw,
1183 buf.as_mut_ptr().cast::<libc::c_char>(),
1184 buf.len(),
1185 &mut result,
1186 )
1187 };
1188 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1189 return None;
1190 }
1191 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1192 if dir_bytes.is_empty() {
1193 return None;
1194 }
1195 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1196}
1197
1198impl Default for Interpreter {
1199 fn default() -> Self {
1200 Self::new()
1201 }
1202}
1203
1204#[derive(Clone, Copy)]
1206pub(crate) enum CaptureAllMode {
1207 Empty,
1209 Append,
1211 Skip,
1213}
1214
1215impl Interpreter {
1216 pub fn new() -> Self {
1217 let mut scope = Scope::new();
1218 scope.declare_array("INC", vec![PerlValue::string(".".to_string())]);
1219 scope.declare_hash("INC", IndexMap::new());
1220 scope.declare_array("ARGV", vec![]);
1221 scope.declare_array("_", vec![]);
1222 scope.declare_hash("ENV", IndexMap::new());
1223 scope.declare_hash("SIG", IndexMap::new());
1224 scope.declare_scalar(
1243 "stryke::VERSION",
1244 PerlValue::string(env!("CARGO_PKG_VERSION").to_string()),
1245 );
1246 scope.declare_array("-", vec![]);
1247 scope.declare_array("+", vec![]);
1248 scope.declare_array("^CAPTURE", vec![]);
1249 scope.declare_array("^CAPTURE_ALL", vec![]);
1250 scope.declare_hash("^HOOK", IndexMap::new());
1251 scope.declare_scalar("~", PerlValue::string("STDOUT".to_string()));
1252
1253 let script_start_time = std::time::SystemTime::now()
1254 .duration_since(std::time::UNIX_EPOCH)
1255 .map(|d| d.as_secs() as i64)
1256 .unwrap_or(0);
1257
1258 let executable_path = cached_executable_path();
1259
1260 let mut special_caret_scalars: HashMap<String, PerlValue> = HashMap::new();
1261 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1262 special_caret_scalars.insert(format!("^{}", name), PerlValue::UNDEF);
1263 }
1264
1265 let mut s = Self {
1266 scope,
1267 subs: HashMap::new(),
1268 struct_defs: HashMap::new(),
1269 enum_defs: HashMap::new(),
1270 class_defs: HashMap::new(),
1271 trait_defs: HashMap::new(),
1272 file: "-e".to_string(),
1273 output_handles: HashMap::new(),
1274 input_handles: HashMap::new(),
1275 ofs: String::new(),
1276 ors: String::new(),
1277 irs: Some("\n".to_string()),
1278 errno: String::new(),
1279 errno_code: 0,
1280 eval_error: String::new(),
1281 eval_error_code: 0,
1282 eval_error_value: None,
1283 argv: Vec::new(),
1284 env: IndexMap::new(),
1285 env_materialized: false,
1286 program_name: "stryke".to_string(),
1287 line_number: 0,
1288 last_readline_handle: String::new(),
1289 last_stdin_die_bracket: "<STDIN>".to_string(),
1290 handle_line_numbers: HashMap::new(),
1291 flip_flop_active: Vec::new(),
1292 flip_flop_exclusive_left_line: Vec::new(),
1293 flip_flop_sequence: Vec::new(),
1294 flip_flop_last_dot: Vec::new(),
1295 flip_flop_tree: HashMap::new(),
1296 sigint_pending_caret: Cell::new(false),
1297 auto_split: false,
1298 field_separator: None,
1299 begin_blocks: Vec::new(),
1300 unit_check_blocks: Vec::new(),
1301 check_blocks: Vec::new(),
1302 init_blocks: Vec::new(),
1303 end_blocks: Vec::new(),
1304 warnings: false,
1305 output_autoflush: false,
1306 default_print_handle: "STDOUT".to_string(),
1307 suppress_stdout: false,
1308 child_exit_status: 0,
1309 last_match: String::new(),
1310 prematch: String::new(),
1311 postmatch: String::new(),
1312 last_paren_match: String::new(),
1313 list_separator: " ".to_string(),
1314 script_start_time,
1315 compile_hints: 0,
1316 warning_bits: 0,
1317 global_phase: "RUN".to_string(),
1318 subscript_sep: "\x1c".to_string(),
1319 inplace_edit: String::new(),
1320 debug_flags: 0,
1321 perl_debug_flags: 0,
1322 eval_nesting: 0,
1323 argv_current_file: String::new(),
1324 diamond_next_idx: 0,
1325 diamond_reader: None,
1326 strict_refs: false,
1327 strict_subs: false,
1328 strict_vars: false,
1329 utf8_pragma: false,
1330 open_pragma_utf8: false,
1331 feature_bits: FEAT_SAY,
1333 num_threads: 0, regex_cache: HashMap::new(),
1335 regex_last: None,
1336 regex_match_memo: None,
1337 regex_capture_scope_fresh: false,
1338 regex_pos: HashMap::new(),
1339 state_vars: HashMap::new(),
1340 state_bindings_stack: Vec::new(),
1341 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1342 dir_handles: HashMap::new(),
1343 io_file_slots: HashMap::new(),
1344 pipe_children: HashMap::new(),
1345 socket_handles: HashMap::new(),
1346 wantarray_kind: WantarrayCtx::Scalar,
1347 profiler: None,
1348 module_export_lists: HashMap::new(),
1349 virtual_modules: HashMap::new(),
1350 tied_hashes: HashMap::new(),
1351 tied_scalars: HashMap::new(),
1352 tied_arrays: HashMap::new(),
1353 overload_table: HashMap::new(),
1354 format_templates: HashMap::new(),
1355 special_caret_scalars,
1356 format_page_number: 0,
1357 format_lines_per_page: 60,
1358 format_lines_left: 0,
1359 format_line_break_chars: "\n".to_string(),
1360 format_top_name: String::new(),
1361 accumulator_format: String::new(),
1362 max_system_fd: 2,
1363 emergency_memory: String::new(),
1364 last_subpattern_name: String::new(),
1365 inc_hook_index: 0,
1366 multiline_match: false,
1367 executable_path,
1368 formfeed_string: "\x0c".to_string(),
1369 glob_handle_alias: HashMap::new(),
1370 glob_restore_frames: vec![Vec::new()],
1371 special_var_restore_frames: vec![Vec::new()],
1372 reflection_hashes_ready: false,
1373 english_enabled: false,
1374 english_no_match_vars: false,
1375 english_match_vars_ever_enabled: false,
1376 english_lexical_scalars: vec![HashSet::new()],
1377 our_lexical_scalars: vec![HashSet::new()],
1378 vm_jit_enabled: !matches!(
1379 std::env::var("STRYKE_NO_JIT"),
1380 Ok(v)
1381 if v == "1"
1382 || v.eq_ignore_ascii_case("true")
1383 || v.eq_ignore_ascii_case("yes")
1384 ),
1385 disasm_bytecode: false,
1386 pec_precompiled_chunk: None,
1387 pec_cache_fingerprint: None,
1388 in_generator: false,
1389 line_mode_skip_main: false,
1390 line_mode_eof_pending: false,
1391 line_mode_stdin_pending: VecDeque::new(),
1392 rate_limit_slots: Vec::new(),
1393 log_level_override: None,
1394 current_sub_stack: Vec::new(),
1395 debugger: None,
1396 debug_call_stack: Vec::new(),
1397 };
1398 s.install_overload_pragma_stubs();
1399 crate::list_util::install_scalar_util(&mut s);
1400 crate::list_util::install_sub_util(&mut s);
1401 s.install_utf8_unicode_to_native_stub();
1402 s
1403 }
1404
1405 fn install_utf8_unicode_to_native_stub(&mut self) {
1407 let empty: Block = vec![];
1408 let key = "utf8::unicode_to_native".to_string();
1409 self.subs.insert(
1410 key.clone(),
1411 Arc::new(PerlSub {
1412 name: key,
1413 params: vec![],
1414 body: empty,
1415 prototype: None,
1416 closure_env: None,
1417 fib_like: None,
1418 }),
1419 );
1420 }
1421
1422 pub(crate) fn ensure_reflection_hashes(&mut self) {
1426 if self.reflection_hashes_ready {
1427 return;
1428 }
1429 self.reflection_hashes_ready = true;
1430 let builtins_map = crate::builtins::builtins_hash_map();
1431 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1432 let extensions_map = crate::builtins::extensions_hash_map();
1433 let aliases_map = crate::builtins::aliases_hash_map();
1434 let descriptions_map = crate::builtins::descriptions_hash_map();
1435 let categories_map = crate::builtins::categories_hash_map();
1436 let primaries_map = crate::builtins::primaries_hash_map();
1437 let all_map = crate::builtins::all_hash_map();
1438 self.scope
1439 .declare_hash_global("stryke::builtins", builtins_map.clone());
1440 self.scope
1441 .declare_hash_global("stryke::perl_compats", perl_compats_map.clone());
1442 self.scope
1443 .declare_hash_global("stryke::extensions", extensions_map.clone());
1444 self.scope
1445 .declare_hash_global("stryke::aliases", aliases_map.clone());
1446 self.scope
1447 .declare_hash_global("stryke::descriptions", descriptions_map.clone());
1448 self.scope
1449 .declare_hash_global("stryke::categories", categories_map.clone());
1450 self.scope
1451 .declare_hash_global("stryke::primaries", primaries_map.clone());
1452 self.scope
1453 .declare_hash_global("stryke::all", all_map.clone());
1454 for (name, val) in [
1457 ("b", builtins_map),
1458 ("pc", perl_compats_map),
1459 ("e", extensions_map),
1460 ("a", aliases_map),
1461 ("d", descriptions_map),
1462 ("c", categories_map),
1463 ("p", primaries_map),
1464 ("all", all_map),
1465 ] {
1466 if !self.scope.any_frame_has_hash(name) {
1467 self.scope.declare_hash_global(name, val);
1468 }
1469 }
1470 }
1471
1472 fn install_overload_pragma_stubs(&mut self) {
1476 let empty: Block = vec![];
1477 for key in ["overload::import", "overload::unimport"] {
1478 let name = key.to_string();
1479 self.subs.insert(
1480 name.clone(),
1481 Arc::new(PerlSub {
1482 name,
1483 params: vec![],
1484 body: empty.clone(),
1485 prototype: None,
1486 closure_env: None,
1487 fib_like: None,
1488 }),
1489 );
1490 }
1491 }
1492
1493 pub fn line_mode_worker_clone(&self) -> Interpreter {
1496 Interpreter {
1497 scope: self.scope.clone(),
1498 subs: self.subs.clone(),
1499 struct_defs: self.struct_defs.clone(),
1500 enum_defs: self.enum_defs.clone(),
1501 class_defs: self.class_defs.clone(),
1502 trait_defs: self.trait_defs.clone(),
1503 file: self.file.clone(),
1504 output_handles: HashMap::new(),
1505 input_handles: HashMap::new(),
1506 ofs: self.ofs.clone(),
1507 ors: self.ors.clone(),
1508 irs: self.irs.clone(),
1509 errno: self.errno.clone(),
1510 errno_code: self.errno_code,
1511 eval_error: self.eval_error.clone(),
1512 eval_error_code: self.eval_error_code,
1513 eval_error_value: self.eval_error_value.clone(),
1514 argv: self.argv.clone(),
1515 env: self.env.clone(),
1516 env_materialized: self.env_materialized,
1517 program_name: self.program_name.clone(),
1518 line_number: 0,
1519 last_readline_handle: String::new(),
1520 last_stdin_die_bracket: "<STDIN>".to_string(),
1521 handle_line_numbers: HashMap::new(),
1522 flip_flop_active: Vec::new(),
1523 flip_flop_exclusive_left_line: Vec::new(),
1524 flip_flop_sequence: Vec::new(),
1525 flip_flop_last_dot: Vec::new(),
1526 flip_flop_tree: HashMap::new(),
1527 sigint_pending_caret: Cell::new(false),
1528 auto_split: self.auto_split,
1529 field_separator: self.field_separator.clone(),
1530 begin_blocks: self.begin_blocks.clone(),
1531 unit_check_blocks: self.unit_check_blocks.clone(),
1532 check_blocks: self.check_blocks.clone(),
1533 init_blocks: self.init_blocks.clone(),
1534 end_blocks: self.end_blocks.clone(),
1535 warnings: self.warnings,
1536 output_autoflush: self.output_autoflush,
1537 default_print_handle: self.default_print_handle.clone(),
1538 suppress_stdout: self.suppress_stdout,
1539 child_exit_status: self.child_exit_status,
1540 last_match: self.last_match.clone(),
1541 prematch: self.prematch.clone(),
1542 postmatch: self.postmatch.clone(),
1543 last_paren_match: self.last_paren_match.clone(),
1544 list_separator: self.list_separator.clone(),
1545 script_start_time: self.script_start_time,
1546 compile_hints: self.compile_hints,
1547 warning_bits: self.warning_bits,
1548 global_phase: self.global_phase.clone(),
1549 subscript_sep: self.subscript_sep.clone(),
1550 inplace_edit: self.inplace_edit.clone(),
1551 debug_flags: self.debug_flags,
1552 perl_debug_flags: self.perl_debug_flags,
1553 eval_nesting: self.eval_nesting,
1554 argv_current_file: String::new(),
1555 diamond_next_idx: 0,
1556 diamond_reader: None,
1557 strict_refs: self.strict_refs,
1558 strict_subs: self.strict_subs,
1559 strict_vars: self.strict_vars,
1560 utf8_pragma: self.utf8_pragma,
1561 open_pragma_utf8: self.open_pragma_utf8,
1562 feature_bits: self.feature_bits,
1563 num_threads: 0,
1564 regex_cache: self.regex_cache.clone(),
1565 regex_last: self.regex_last.clone(),
1566 regex_match_memo: self.regex_match_memo.clone(),
1567 regex_capture_scope_fresh: false,
1568 regex_pos: self.regex_pos.clone(),
1569 state_vars: self.state_vars.clone(),
1570 state_bindings_stack: Vec::new(),
1571 rand_rng: self.rand_rng.clone(),
1572 dir_handles: HashMap::new(),
1573 io_file_slots: HashMap::new(),
1574 pipe_children: HashMap::new(),
1575 socket_handles: HashMap::new(),
1576 wantarray_kind: self.wantarray_kind,
1577 profiler: None,
1578 module_export_lists: self.module_export_lists.clone(),
1579 virtual_modules: self.virtual_modules.clone(),
1580 tied_hashes: self.tied_hashes.clone(),
1581 tied_scalars: self.tied_scalars.clone(),
1582 tied_arrays: self.tied_arrays.clone(),
1583 overload_table: self.overload_table.clone(),
1584 format_templates: self.format_templates.clone(),
1585 special_caret_scalars: self.special_caret_scalars.clone(),
1586 format_page_number: self.format_page_number,
1587 format_lines_per_page: self.format_lines_per_page,
1588 format_lines_left: self.format_lines_left,
1589 format_line_break_chars: self.format_line_break_chars.clone(),
1590 format_top_name: self.format_top_name.clone(),
1591 accumulator_format: self.accumulator_format.clone(),
1592 max_system_fd: self.max_system_fd,
1593 emergency_memory: self.emergency_memory.clone(),
1594 last_subpattern_name: self.last_subpattern_name.clone(),
1595 inc_hook_index: self.inc_hook_index,
1596 multiline_match: self.multiline_match,
1597 executable_path: self.executable_path.clone(),
1598 formfeed_string: self.formfeed_string.clone(),
1599 glob_handle_alias: self.glob_handle_alias.clone(),
1600 glob_restore_frames: self.glob_restore_frames.clone(),
1601 special_var_restore_frames: self.special_var_restore_frames.clone(),
1602 reflection_hashes_ready: self.reflection_hashes_ready,
1603 english_enabled: self.english_enabled,
1604 english_no_match_vars: self.english_no_match_vars,
1605 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1606 english_lexical_scalars: self.english_lexical_scalars.clone(),
1607 our_lexical_scalars: self.our_lexical_scalars.clone(),
1608 vm_jit_enabled: self.vm_jit_enabled,
1609 disasm_bytecode: self.disasm_bytecode,
1610 pec_precompiled_chunk: None,
1612 pec_cache_fingerprint: None,
1613 in_generator: false,
1614 line_mode_skip_main: false,
1615 line_mode_eof_pending: false,
1616 line_mode_stdin_pending: VecDeque::new(),
1617 rate_limit_slots: Vec::new(),
1618 log_level_override: self.log_level_override,
1619 current_sub_stack: Vec::new(),
1620 debugger: None,
1621 debug_call_stack: Vec::new(),
1622 }
1623 }
1624
1625 pub(crate) fn parallel_thread_count(&mut self) -> usize {
1627 if self.num_threads == 0 {
1628 self.num_threads = rayon::current_num_threads();
1629 }
1630 self.num_threads
1631 }
1632
1633 pub(crate) fn eval_par_list_call(
1635 &mut self,
1636 name: &str,
1637 args: &[PerlValue],
1638 ctx: WantarrayCtx,
1639 line: usize,
1640 ) -> PerlResult<PerlValue> {
1641 match name {
1642 "puniq" => {
1643 let (list_src, show_prog) = match args.len() {
1644 0 => return Err(PerlError::runtime("puniq: expected LIST", line)),
1645 1 => (&args[0], false),
1646 2 => (&args[0], args[1].is_true()),
1647 _ => {
1648 return Err(PerlError::runtime(
1649 "puniq: expected LIST [, progress => EXPR]",
1650 line,
1651 ));
1652 }
1653 };
1654 let list = list_src.to_list();
1655 let n_threads = self.parallel_thread_count();
1656 let pmap_progress = PmapProgress::new(show_prog, list.len());
1657 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
1658 pmap_progress.finish();
1659 if ctx == WantarrayCtx::List {
1660 Ok(PerlValue::array(out))
1661 } else {
1662 Ok(PerlValue::integer(out.len() as i64))
1663 }
1664 }
1665 "pfirst" => {
1666 let (code_val, list_src, show_prog) = match args.len() {
1667 2 => (&args[0], &args[1], false),
1668 3 => (&args[0], &args[1], args[2].is_true()),
1669 _ => {
1670 return Err(PerlError::runtime(
1671 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
1672 line,
1673 ));
1674 }
1675 };
1676 let Some(sub) = code_val.as_code_ref() else {
1677 return Err(PerlError::runtime(
1678 "pfirst: first argument must be a code reference",
1679 line,
1680 ));
1681 };
1682 let sub = sub.clone();
1683 let list = list_src.to_list();
1684 if list.is_empty() {
1685 return Ok(PerlValue::UNDEF);
1686 }
1687 let pmap_progress = PmapProgress::new(show_prog, list.len());
1688 let subs = self.subs.clone();
1689 let (scope_capture, atomic_arrays, atomic_hashes) =
1690 self.scope.capture_with_atomics();
1691 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
1692 let mut local_interp = Interpreter::new();
1693 local_interp.subs = subs.clone();
1694 local_interp.scope.restore_capture(&scope_capture);
1695 local_interp
1696 .scope
1697 .restore_atomics(&atomic_arrays, &atomic_hashes);
1698 local_interp.enable_parallel_guard();
1699 local_interp.scope.set_topic(item);
1700 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1701 Ok(v) => v.is_true(),
1702 Err(_) => false,
1703 }
1704 });
1705 pmap_progress.finish();
1706 Ok(out.unwrap_or(PerlValue::UNDEF))
1707 }
1708 "pany" => {
1709 let (code_val, list_src, show_prog) = match args.len() {
1710 2 => (&args[0], &args[1], false),
1711 3 => (&args[0], &args[1], args[2].is_true()),
1712 _ => {
1713 return Err(PerlError::runtime(
1714 "pany: expected BLOCK, LIST [, progress => EXPR]",
1715 line,
1716 ));
1717 }
1718 };
1719 let Some(sub) = code_val.as_code_ref() else {
1720 return Err(PerlError::runtime(
1721 "pany: first argument must be a code reference",
1722 line,
1723 ));
1724 };
1725 let sub = sub.clone();
1726 let list = list_src.to_list();
1727 let pmap_progress = PmapProgress::new(show_prog, list.len());
1728 let subs = self.subs.clone();
1729 let (scope_capture, atomic_arrays, atomic_hashes) =
1730 self.scope.capture_with_atomics();
1731 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
1732 let mut local_interp = Interpreter::new();
1733 local_interp.subs = subs.clone();
1734 local_interp.scope.restore_capture(&scope_capture);
1735 local_interp
1736 .scope
1737 .restore_atomics(&atomic_arrays, &atomic_hashes);
1738 local_interp.enable_parallel_guard();
1739 local_interp.scope.set_topic(item);
1740 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1741 Ok(v) => v.is_true(),
1742 Err(_) => false,
1743 }
1744 });
1745 pmap_progress.finish();
1746 Ok(PerlValue::integer(if b { 1 } else { 0 }))
1747 }
1748 _ => Err(PerlError::runtime(
1749 format!("internal: unknown par_list builtin {name}"),
1750 line,
1751 )),
1752 }
1753 }
1754
1755 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
1756 #[cfg(unix)]
1757 if let Some(sig) = s.signal() {
1758 return sig as i64 & 0x7f;
1759 }
1760 let code = s.code().unwrap_or(0) as i64;
1761 code << 8
1762 }
1763
1764 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
1765 self.child_exit_status = self.encode_exit_status(s);
1766 }
1767
1768 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
1770 self.errno = e.to_string();
1771 self.errno_code = e.raw_os_error().unwrap_or(0);
1772 }
1773
1774 pub(crate) fn ssh_builtin_execute(&mut self, args: &[PerlValue]) -> PerlResult<PerlValue> {
1785 use std::process::Command;
1786 let mut cmd = Command::new("ssh");
1787 #[cfg(unix)]
1788 {
1789 use libc::geteuid;
1790 let home_for_ssh = if unsafe { geteuid() } == 0 {
1791 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
1792 } else {
1793 None
1794 };
1795 if let Some(h) = home_for_ssh {
1796 cmd.env("HOME", h);
1797 } else if std::env::var_os("HOME").is_none() {
1798 if let Some(h) = pw_home_dir_for_current_uid() {
1799 cmd.env("HOME", h);
1800 }
1801 }
1802 }
1803 for a in args {
1804 cmd.arg(a.to_string());
1805 }
1806 match cmd.status() {
1807 Ok(s) => {
1808 self.record_child_exit_status(s);
1809 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
1810 }
1811 Err(e) => {
1812 self.apply_io_error_to_errno(&e);
1813 Ok(PerlValue::integer(-1))
1814 }
1815 }
1816 }
1817
1818 pub(crate) fn set_eval_error(&mut self, msg: String) {
1820 self.eval_error = msg;
1821 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
1822 self.eval_error_value = None;
1823 }
1824
1825 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &PerlError) {
1826 self.eval_error = e.to_string();
1827 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
1828 self.eval_error_value = e.die_value.clone();
1829 }
1830
1831 pub(crate) fn clear_eval_error(&mut self) {
1832 self.eval_error = String::new();
1833 self.eval_error_code = 0;
1834 self.eval_error_value = None;
1835 }
1836
1837 fn bump_line_for_handle(&mut self, handle_key: &str) {
1839 self.last_readline_handle = handle_key.to_string();
1840 *self
1841 .handle_line_numbers
1842 .entry(handle_key.to_string())
1843 .or_insert(0) += 1;
1844 }
1845
1846 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
1848 if name.starts_with('^') {
1849 return name.to_string();
1850 }
1851 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
1852 let pkg = self.current_package();
1853 if !pkg.is_empty() && pkg != "main" {
1854 return format!("{}::{}", pkg, name);
1855 }
1856 }
1857 name.to_string()
1858 }
1859
1860 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
1862 if name.contains("::") {
1863 return name.to_string();
1864 }
1865 let pkg = self.current_package();
1866 if pkg.is_empty() || pkg == "main" {
1867 format!("main::{}", name)
1868 } else {
1869 format!("{}::{}", pkg, name)
1870 }
1871 }
1872
1873 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
1875 if name.contains("::") {
1876 return name.to_string();
1877 }
1878 for (lex, our) in self
1879 .english_lexical_scalars
1880 .iter()
1881 .zip(self.our_lexical_scalars.iter())
1882 .rev()
1883 {
1884 if lex.contains(name) {
1885 if our.contains(name) {
1886 return self.stash_scalar_name_for_package(name);
1887 }
1888 return name.to_string();
1889 }
1890 }
1891 name.to_string()
1892 }
1893
1894 pub(crate) fn tie_execute(
1896 &mut self,
1897 target_kind: u8,
1898 target_name: &str,
1899 class_and_args: Vec<PerlValue>,
1900 line: usize,
1901 ) -> PerlResult<PerlValue> {
1902 let mut it = class_and_args.into_iter();
1903 let class = it.next().unwrap_or(PerlValue::UNDEF);
1904 let pkg = class.to_string();
1905 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
1906 let tie_ctor = match target_kind {
1907 0 => "TIESCALAR",
1908 1 => "TIEARRAY",
1909 2 => "TIEHASH",
1910 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
1911 };
1912 let tie_fn = format!("{}::{}", pkg, tie_ctor);
1913 let sub = self
1914 .subs
1915 .get(&tie_fn)
1916 .cloned()
1917 .ok_or_else(|| PerlError::runtime(format!("tie: cannot find &{}", tie_fn), line))?;
1918 let mut call_args = vec![PerlValue::string(pkg.clone())];
1919 call_args.extend(it);
1920 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
1921 Ok(v) => v,
1922 Err(FlowOrError::Flow(_)) => PerlValue::UNDEF,
1923 Err(FlowOrError::Error(e)) => return Err(e),
1924 };
1925 match target_kind {
1926 0 => {
1927 self.tied_scalars.insert(target_name.to_string(), obj);
1928 }
1929 1 => {
1930 let key = self.stash_array_name_for_package(target_name);
1931 self.tied_arrays.insert(key, obj);
1932 }
1933 2 => {
1934 self.tied_hashes.insert(target_name.to_string(), obj);
1935 }
1936 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
1937 }
1938 Ok(PerlValue::UNDEF)
1939 }
1940
1941 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
1943 let key = format!("{}::ISA", class);
1944 self.scope
1945 .get_array(&key)
1946 .into_iter()
1947 .map(|v| v.to_string())
1948 .collect()
1949 }
1950
1951 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
1952 let p = |c: &str| self.parents_of_class(c);
1953 linearize_c3(class, &p, 0)
1954 }
1955
1956 pub(crate) fn resolve_method_full_name(
1958 &self,
1959 invocant_class: &str,
1960 method: &str,
1961 super_mode: bool,
1962 ) -> Option<String> {
1963 let mro = self.mro_linearize(invocant_class);
1964 let start = if super_mode {
1968 mro.iter()
1969 .position(|p| p == invocant_class)
1970 .map(|i| i + 1)
1971 .unwrap_or(1)
1974 } else {
1975 0
1976 };
1977 for pkg in mro.iter().skip(start) {
1978 if pkg == "UNIVERSAL" {
1979 continue;
1980 }
1981 let fq = format!("{}::{}", pkg, method);
1982 if self.subs.contains_key(&fq) {
1983 return Some(fq);
1984 }
1985 }
1986 mro.iter()
1987 .skip(start)
1988 .find(|p| *p != "UNIVERSAL")
1989 .map(|pkg| format!("{}::{}", pkg, method))
1990 }
1991
1992 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
1993 if let Some(alias) = self.glob_handle_alias.get(name) {
1994 return alias.clone();
1995 }
1996 if let Some(var_name) = name.strip_prefix('$') {
1999 let val = self.scope.get_scalar(var_name);
2000 let s = val.to_string();
2001 if !s.is_empty() {
2002 return self.resolve_io_handle_name(&s);
2003 }
2004 }
2005 name.to_string()
2006 }
2007
2008 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2010 if name.contains("::") {
2011 name.to_string()
2012 } else {
2013 self.qualify_sub_key(name)
2014 }
2015 }
2016
2017 pub(crate) fn copy_typeglob_slots(
2019 &mut self,
2020 lhs: &str,
2021 rhs: &str,
2022 line: usize,
2023 ) -> PerlResult<()> {
2024 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2025 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2026 match self.subs.get(&rhs_sub).cloned() {
2027 Some(s) => {
2028 self.subs.insert(lhs_sub, s);
2029 }
2030 None => {
2031 self.subs.remove(&lhs_sub);
2032 }
2033 }
2034 let sv = self.scope.get_scalar(rhs);
2035 self.scope
2036 .set_scalar(lhs, sv.clone())
2037 .map_err(|e| e.at_line(line))?;
2038 let lhs_an = self.stash_array_name_for_package(lhs);
2039 let rhs_an = self.stash_array_name_for_package(rhs);
2040 let av = self.scope.get_array(&rhs_an);
2041 self.scope
2042 .set_array(&lhs_an, av.clone())
2043 .map_err(|e| e.at_line(line))?;
2044 let hv = self.scope.get_hash(rhs);
2045 self.scope
2046 .set_hash(lhs, hv.clone())
2047 .map_err(|e| e.at_line(line))?;
2048 match self.glob_handle_alias.get(rhs).cloned() {
2049 Some(t) => {
2050 self.glob_handle_alias.insert(lhs.to_string(), t);
2051 }
2052 None => {
2053 self.glob_handle_alias.remove(lhs);
2054 }
2055 }
2056 Ok(())
2057 }
2058
2059 pub(crate) fn install_format_decl(
2061 &mut self,
2062 basename: &str,
2063 lines: &[String],
2064 line: usize,
2065 ) -> PerlResult<()> {
2066 let pkg = self.current_package();
2067 let key = format!("{}::{}", pkg, basename);
2068 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2069 self.format_templates.insert(key, Arc::new(tmpl));
2070 Ok(())
2071 }
2072
2073 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2075 let pkg = self.current_package();
2076 let ent = self.overload_table.entry(pkg).or_default();
2077 for (k, v) in pairs {
2078 ent.insert(k.clone(), v.clone());
2079 }
2080 }
2081
2082 pub(crate) fn local_declare_typeglob(
2085 &mut self,
2086 lhs: &str,
2087 rhs: Option<&str>,
2088 line: usize,
2089 ) -> PerlResult<()> {
2090 let old = self.glob_handle_alias.remove(lhs);
2091 let Some(frame) = self.glob_restore_frames.last_mut() else {
2092 return Err(PerlError::runtime(
2093 "internal: no glob restore frame for local *GLOB",
2094 line,
2095 ));
2096 };
2097 frame.push((lhs.to_string(), old));
2098 if let Some(r) = rhs {
2099 self.glob_handle_alias
2100 .insert(lhs.to_string(), r.to_string());
2101 }
2102 Ok(())
2103 }
2104
2105 pub(crate) fn scope_push_hook(&mut self) {
2106 self.scope.push_frame();
2107 self.glob_restore_frames.push(Vec::new());
2108 self.special_var_restore_frames.push(Vec::new());
2109 self.english_lexical_scalars.push(HashSet::new());
2110 self.our_lexical_scalars.push(HashSet::new());
2111 self.state_bindings_stack.push(Vec::new());
2112 }
2113
2114 #[inline]
2115 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2116 if let Some(s) = self.english_lexical_scalars.last_mut() {
2117 s.insert(name.to_string());
2118 }
2119 }
2120
2121 #[inline]
2122 fn note_our_scalar(&mut self, bare_name: &str) {
2123 if let Some(s) = self.our_lexical_scalars.last_mut() {
2124 s.insert(bare_name.to_string());
2125 }
2126 }
2127
2128 pub(crate) fn scope_pop_hook(&mut self) {
2129 if !self.scope.can_pop_frame() {
2130 return;
2131 }
2132 let defers = self.scope.take_defers();
2136 for coderef in defers {
2137 if let Some(sub) = coderef.as_code_ref() {
2138 let saved_wa = self.wantarray_kind;
2142 self.wantarray_kind = WantarrayCtx::Void;
2143 let _ = self.exec_block_no_scope(&sub.body);
2144 self.wantarray_kind = saved_wa;
2145 }
2146 }
2147 if let Some(bindings) = self.state_bindings_stack.pop() {
2149 for (var_name, state_key) in &bindings {
2150 let val = self.scope.get_scalar(var_name).clone();
2151 self.state_vars.insert(state_key.clone(), val);
2152 }
2153 }
2154 if let Some(entries) = self.special_var_restore_frames.pop() {
2157 for (name, old) in entries.into_iter().rev() {
2158 let _ = self.set_special_var(&name, &old);
2159 }
2160 }
2161 if let Some(entries) = self.glob_restore_frames.pop() {
2162 for (name, old) in entries.into_iter().rev() {
2163 match old {
2164 Some(s) => {
2165 self.glob_handle_alias.insert(name, s);
2166 }
2167 None => {
2168 self.glob_handle_alias.remove(&name);
2169 }
2170 }
2171 }
2172 }
2173 self.scope.pop_frame();
2174 let _ = self.english_lexical_scalars.pop();
2175 let _ = self.our_lexical_scalars.pop();
2176 }
2177
2178 #[inline]
2181 pub(crate) fn enable_parallel_guard(&mut self) {
2182 self.scope.set_parallel_guard(true);
2183 }
2184
2185 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2188 self.begin_blocks.clear();
2189 self.unit_check_blocks.clear();
2190 self.check_blocks.clear();
2191 self.init_blocks.clear();
2192 self.end_blocks.clear();
2193 }
2194
2195 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2201 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2202 self.scope_pop_hook();
2203 }
2204 }
2205
2206 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> PerlResult<()> {
2213 self.touch_env_hash("SIG");
2214 let v = self.scope.get_hash_element("SIG", sig);
2215 if v.is_undef() {
2216 return Self::default_sig_action(sig);
2217 }
2218 if let Some(s) = v.as_str() {
2219 if s == "IGNORE" {
2220 return Ok(());
2221 }
2222 if s == "DEFAULT" {
2223 return Self::default_sig_action(sig);
2224 }
2225 }
2226 if let Some(sub) = v.as_code_ref() {
2227 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2228 Ok(_) => Ok(()),
2229 Err(FlowOrError::Flow(_)) => Ok(()),
2230 Err(FlowOrError::Error(e)) => Err(e),
2231 }
2232 } else {
2233 Self::default_sig_action(sig)
2234 }
2235 }
2236
2237 #[inline]
2239 fn default_sig_action(sig: &str) -> PerlResult<()> {
2240 match sig {
2241 "INT" => std::process::exit(130),
2243 "TERM" => std::process::exit(143),
2244 "ALRM" => std::process::exit(142),
2245 "CHLD" => Ok(()),
2247 _ => Ok(()),
2248 }
2249 }
2250
2251 pub fn materialize_env_if_needed(&mut self) {
2254 if self.env_materialized {
2255 return;
2256 }
2257 self.env = std::env::vars()
2258 .map(|(k, v)| (k, PerlValue::string(v)))
2259 .collect();
2260 self.scope
2261 .set_hash("ENV", self.env.clone())
2262 .expect("set %ENV");
2263 self.env_materialized = true;
2264 }
2265
2266 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2268 self.materialize_env_if_needed();
2269 if let Some(x) = self.log_level_override {
2270 return x;
2271 }
2272 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2273 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2274 }
2275
2276 pub(crate) fn no_color_effective(&mut self) -> bool {
2278 self.materialize_env_if_needed();
2279 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2280 if v.is_undef() {
2281 return false;
2282 }
2283 !v.to_string().is_empty()
2284 }
2285
2286 #[inline]
2287 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2288 if hash_name == "ENV" {
2289 self.materialize_env_if_needed();
2290 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2291 match hash_name {
2292 "b"
2293 | "pc"
2294 | "e"
2295 | "a"
2296 | "d"
2297 | "c"
2298 | "p"
2299 | "all"
2300 | "stryke::builtins"
2301 | "stryke::perl_compats"
2302 | "stryke::extensions"
2303 | "stryke::aliases"
2304 | "stryke::descriptions"
2305 | "stryke::categories"
2306 | "stryke::primaries"
2307 | "stryke::all" => {
2308 self.ensure_reflection_hashes();
2309 }
2310 _ => {}
2311 }
2312 }
2313 }
2314
2315 pub(crate) fn exists_arrow_hash_element(
2317 &self,
2318 container: PerlValue,
2319 key: &str,
2320 line: usize,
2321 ) -> PerlResult<bool> {
2322 if let Some(r) = container.as_hash_ref() {
2323 return Ok(r.read().contains_key(key));
2324 }
2325 if let Some(b) = container.as_blessed_ref() {
2326 let data = b.data.read();
2327 if let Some(r) = data.as_hash_ref() {
2328 return Ok(r.read().contains_key(key));
2329 }
2330 if let Some(hm) = data.as_hash_map() {
2331 return Ok(hm.contains_key(key));
2332 }
2333 return Err(PerlError::runtime(
2334 "exists argument is not a HASH reference",
2335 line,
2336 ));
2337 }
2338 Err(PerlError::runtime(
2339 "exists argument is not a HASH reference",
2340 line,
2341 ))
2342 }
2343
2344 pub(crate) fn delete_arrow_hash_element(
2346 &self,
2347 container: PerlValue,
2348 key: &str,
2349 line: usize,
2350 ) -> PerlResult<PerlValue> {
2351 if let Some(r) = container.as_hash_ref() {
2352 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2353 }
2354 if let Some(b) = container.as_blessed_ref() {
2355 let mut data = b.data.write();
2356 if let Some(r) = data.as_hash_ref() {
2357 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2358 }
2359 if let Some(mut map) = data.as_hash_map() {
2360 let v = map.shift_remove(key).unwrap_or(PerlValue::UNDEF);
2361 *data = PerlValue::hash(map);
2362 return Ok(v);
2363 }
2364 return Err(PerlError::runtime(
2365 "delete argument is not a HASH reference",
2366 line,
2367 ));
2368 }
2369 Err(PerlError::runtime(
2370 "delete argument is not a HASH reference",
2371 line,
2372 ))
2373 }
2374
2375 pub(crate) fn exists_arrow_array_element(
2377 &self,
2378 container: PerlValue,
2379 idx: i64,
2380 line: usize,
2381 ) -> PerlResult<bool> {
2382 if let Some(a) = container.as_array_ref() {
2383 let arr = a.read();
2384 let i = if idx < 0 {
2385 (arr.len() as i64 + idx) as usize
2386 } else {
2387 idx as usize
2388 };
2389 return Ok(i < arr.len());
2390 }
2391 Err(PerlError::runtime(
2392 "exists argument is not an ARRAY reference",
2393 line,
2394 ))
2395 }
2396
2397 pub(crate) fn delete_arrow_array_element(
2399 &self,
2400 container: PerlValue,
2401 idx: i64,
2402 line: usize,
2403 ) -> PerlResult<PerlValue> {
2404 if let Some(a) = container.as_array_ref() {
2405 let mut arr = a.write();
2406 let i = if idx < 0 {
2407 (arr.len() as i64 + idx) as usize
2408 } else {
2409 idx as usize
2410 };
2411 if i >= arr.len() {
2412 return Ok(PerlValue::UNDEF);
2413 }
2414 let old = arr.get(i).cloned().unwrap_or(PerlValue::UNDEF);
2415 arr[i] = PerlValue::UNDEF;
2416 return Ok(old);
2417 }
2418 Err(PerlError::runtime(
2419 "delete argument is not an ARRAY reference",
2420 line,
2421 ))
2422 }
2423
2424 pub(crate) fn inc_directories(&self) -> Vec<String> {
2426 let mut v: Vec<String> = self
2427 .scope
2428 .get_array("INC")
2429 .into_iter()
2430 .map(|x| x.to_string())
2431 .filter(|s| !s.is_empty())
2432 .collect();
2433 if v.is_empty() {
2434 v.push(".".to_string());
2435 }
2436 v
2437 }
2438
2439 #[inline]
2440 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
2441 matches!(
2442 name,
2443 "_" | "0"
2444 | "!"
2445 | "@"
2446 | "/"
2447 | "\\"
2448 | ","
2449 | "."
2450 | "__PACKAGE__"
2451 | "$$"
2452 | "|"
2453 | "?"
2454 | "\""
2455 | "&"
2456 | "`"
2457 | "'"
2458 | "+"
2459 | "<"
2460 | ">"
2461 | "("
2462 | ")"
2463 | "]"
2464 | ";"
2465 | "ARGV"
2466 | "%"
2467 | "="
2468 | "-"
2469 | ":"
2470 | "*"
2471 | "INC"
2472 ) || name.chars().all(|c| c.is_ascii_digit())
2473 || name.starts_with('^')
2474 || (name.starts_with('#') && name.len() > 1)
2475 }
2476
2477 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2478 if !self.strict_vars
2479 || Self::strict_scalar_exempt(name)
2480 || name.contains("::")
2481 || self.scope.scalar_binding_exists(name)
2482 {
2483 return Ok(());
2484 }
2485 Err(PerlError::runtime(
2486 format!(
2487 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
2488 name, name
2489 ),
2490 line,
2491 )
2492 .into())
2493 }
2494
2495 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2496 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
2497 return Ok(());
2498 }
2499 Err(PerlError::runtime(
2500 format!(
2501 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
2502 name, name
2503 ),
2504 line,
2505 )
2506 .into())
2507 }
2508
2509 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2510 if !self.strict_vars
2512 || name.contains("::")
2513 || self.scope.hash_binding_exists(name)
2514 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
2515 {
2516 return Ok(());
2517 }
2518 Err(PerlError::runtime(
2519 format!(
2520 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
2521 name, name
2522 ),
2523 line,
2524 )
2525 .into())
2526 }
2527
2528 fn looks_like_version_only(spec: &str) -> bool {
2529 let t = spec.trim();
2530 !t.is_empty()
2531 && !t.contains('/')
2532 && !t.contains('\\')
2533 && !t.contains("::")
2534 && t.chars()
2535 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
2536 && t.chars().any(|c| c.is_ascii_digit())
2537 }
2538
2539 fn module_spec_to_relpath(spec: &str) -> String {
2540 let t = spec.trim();
2541 if t.contains("::") {
2542 format!("{}.pm", t.replace("::", "/"))
2543 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
2544 t.replace('\\', "/")
2545 } else {
2546 format!("{}.pm", t)
2547 }
2548 }
2549
2550 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
2553 if name.contains("::") {
2554 return name.to_string();
2555 }
2556 let pkg = self.current_package();
2557 if pkg.is_empty() || pkg == "main" {
2558 name.to_string()
2559 } else {
2560 format!("{}::{}", pkg, name)
2561 }
2562 }
2563
2564 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
2566 let mut msg = format!("Undefined subroutine &{}", name);
2567 if self.strict_subs {
2568 msg.push_str(
2569 " (strict subs: declare the sub or use a fully qualified name before calling)",
2570 );
2571 }
2572 msg
2573 }
2574
2575 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
2577 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
2578 if self.strict_subs {
2579 msg.push_str(
2580 " (strict subs: declare the sub or use a fully qualified name before calling)",
2581 );
2582 }
2583 msg
2584 }
2585
2586 fn import_alias_key(&self, short: &str) -> String {
2588 self.qualify_sub_key(short)
2589 }
2590
2591 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
2593 if imports.len() == 1 {
2594 match &imports[0].kind {
2595 ExprKind::QW(ws) => return ws.is_empty(),
2596 ExprKind::List(xs) => return xs.is_empty(),
2598 _ => {}
2599 }
2600 }
2601 false
2602 }
2603
2604 fn apply_module_import(
2606 &mut self,
2607 module: &str,
2608 imports: &[Expr],
2609 line: usize,
2610 ) -> PerlResult<()> {
2611 if imports.is_empty() {
2612 return self.import_all_from_module(module, line);
2613 }
2614 if Self::is_explicit_empty_import_list(imports) {
2615 return Ok(());
2616 }
2617 let names = Self::pragma_import_strings(imports, line)?;
2618 if names.is_empty() {
2619 return Ok(());
2620 }
2621 for name in names {
2622 self.import_one_symbol(module, &name, line)?;
2623 }
2624 Ok(())
2625 }
2626
2627 fn import_all_from_module(&mut self, module: &str, line: usize) -> PerlResult<()> {
2628 if module == "List::Util" {
2629 crate::list_util::ensure_list_util(self);
2630 }
2631 if let Some(lists) = self.module_export_lists.get(module) {
2632 let export: Vec<String> = lists.export.clone();
2633 for short in export {
2634 self.import_named_sub(module, &short, line)?;
2635 }
2636 return Ok(());
2637 }
2638 let prefix = format!("{}::", module);
2640 let keys: Vec<String> = self
2641 .subs
2642 .keys()
2643 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
2644 .cloned()
2645 .collect();
2646 for k in keys {
2647 let short = k[prefix.len()..].to_string();
2648 if let Some(sub) = self.subs.get(&k).cloned() {
2649 let alias = self.import_alias_key(&short);
2650 self.subs.insert(alias, sub);
2651 }
2652 }
2653 Ok(())
2654 }
2655
2656 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> PerlResult<()> {
2658 if module == "List::Util" {
2659 crate::list_util::ensure_list_util(self);
2660 }
2661 let qual = format!("{}::{}", module, short);
2662 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
2663 PerlError::runtime(
2664 format!(
2665 "`{}` is not defined in module `{}` (expected `{}`)",
2666 short, module, qual
2667 ),
2668 line,
2669 )
2670 })?;
2671 let alias = self.import_alias_key(short);
2672 self.subs.insert(alias, sub);
2673 Ok(())
2674 }
2675
2676 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> PerlResult<()> {
2677 if let Some(lists) = self.module_export_lists.get(module) {
2678 let allowed: HashSet<&str> = lists
2679 .export
2680 .iter()
2681 .map(|s| s.as_str())
2682 .chain(lists.export_ok.iter().map(|s| s.as_str()))
2683 .collect();
2684 if !allowed.contains(export) {
2685 return Err(PerlError::runtime(
2686 format!(
2687 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
2688 export, module
2689 ),
2690 line,
2691 ));
2692 }
2693 }
2694 self.import_named_sub(module, export, line)
2695 }
2696
2697 fn record_exporter_our_array_name(&mut self, name: &str, items: &[PerlValue]) {
2699 if name != "EXPORT" && name != "EXPORT_OK" {
2700 return;
2701 }
2702 let pkg = self.current_package();
2703 if pkg.is_empty() || pkg == "main" {
2704 return;
2705 }
2706 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
2707 let ent = self.module_export_lists.entry(pkg).or_default();
2708 if name == "EXPORT" {
2709 ent.export = names;
2710 } else {
2711 ent.export_ok = names;
2712 }
2713 }
2714
2715 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
2719 let key = self.qualify_sub_key(name);
2720 let Some(sub) = self.subs.get(&key).cloned() else {
2721 return;
2722 };
2723 let captured = self.scope.capture();
2724 let closure_env = if captured.is_empty() {
2725 None
2726 } else {
2727 Some(captured)
2728 };
2729 let mut new_sub = (*sub).clone();
2730 new_sub.closure_env = closure_env;
2731 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
2732 self.subs.insert(key, Arc::new(new_sub));
2733 }
2734
2735 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<PerlSub>> {
2736 if let Some(s) = self.subs.get(name) {
2737 return Some(s.clone());
2738 }
2739 if !name.contains("::") {
2740 let pkg = self.current_package();
2741 if !pkg.is_empty() && pkg != "main" {
2742 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
2743 q.push_str(&pkg);
2744 q.push_str("::");
2745 q.push_str(name);
2746 return self.subs.get(&q).cloned();
2747 }
2748 }
2749 None
2750 }
2751
2752 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
2755 if let Some(first) = imports.first() {
2756 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
2757 return &imports[1..];
2758 }
2759 }
2760 imports
2761 }
2762
2763 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> PerlResult<Vec<String>> {
2765 let mut out = Vec::new();
2766 for e in imports {
2767 match &e.kind {
2768 ExprKind::String(s) => out.push(s.clone()),
2769 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
2770 ExprKind::Integer(n) => out.push(n.to_string()),
2771 ExprKind::InterpolatedString(parts) => {
2774 let mut s = String::new();
2775 for p in parts {
2776 match p {
2777 StringPart::Literal(l) => s.push_str(l),
2778 StringPart::ScalarVar(v) => {
2779 s.push('$');
2780 s.push_str(v);
2781 }
2782 StringPart::ArrayVar(v) => {
2783 s.push('@');
2784 s.push_str(v);
2785 }
2786 _ => {
2787 return Err(PerlError::runtime(
2788 "pragma import must be a compile-time string, qw(), or integer",
2789 e.line.max(default_line),
2790 ));
2791 }
2792 }
2793 }
2794 out.push(s);
2795 }
2796 _ => {
2797 return Err(PerlError::runtime(
2798 "pragma import must be a compile-time string, qw(), or integer",
2799 e.line.max(default_line),
2800 ));
2801 }
2802 }
2803 }
2804 Ok(out)
2805 }
2806
2807 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2808 if imports.is_empty() {
2809 self.strict_refs = true;
2810 self.strict_subs = true;
2811 self.strict_vars = true;
2812 return Ok(());
2813 }
2814 let names = Self::pragma_import_strings(imports, line)?;
2815 for name in names {
2816 match name.as_str() {
2817 "refs" => self.strict_refs = true,
2818 "subs" => self.strict_subs = true,
2819 "vars" => self.strict_vars = true,
2820 _ => {
2821 return Err(PerlError::runtime(
2822 format!("Unknown strict mode `{}`", name),
2823 line,
2824 ));
2825 }
2826 }
2827 }
2828 Ok(())
2829 }
2830
2831 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2832 if imports.is_empty() {
2833 self.strict_refs = false;
2834 self.strict_subs = false;
2835 self.strict_vars = false;
2836 return Ok(());
2837 }
2838 let names = Self::pragma_import_strings(imports, line)?;
2839 for name in names {
2840 match name.as_str() {
2841 "refs" => self.strict_refs = false,
2842 "subs" => self.strict_subs = false,
2843 "vars" => self.strict_vars = false,
2844 _ => {
2845 return Err(PerlError::runtime(
2846 format!("Unknown strict mode `{}`", name),
2847 line,
2848 ));
2849 }
2850 }
2851 }
2852 Ok(())
2853 }
2854
2855 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2856 let items = Self::pragma_import_strings(imports, line)?;
2857 if items.is_empty() {
2858 return Err(PerlError::runtime(
2859 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
2860 line,
2861 ));
2862 }
2863 for item in items {
2864 let s = item.trim();
2865 if let Some(rest) = s.strip_prefix(':') {
2866 self.apply_feature_bundle(rest, line)?;
2867 } else {
2868 self.apply_feature_name(s, true, line)?;
2869 }
2870 }
2871 Ok(())
2872 }
2873
2874 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2875 if imports.is_empty() {
2876 self.feature_bits = 0;
2877 return Ok(());
2878 }
2879 let items = Self::pragma_import_strings(imports, line)?;
2880 for item in items {
2881 let s = item.trim();
2882 if let Some(rest) = s.strip_prefix(':') {
2883 self.clear_feature_bundle(rest);
2884 } else {
2885 self.apply_feature_name(s, false, line)?;
2886 }
2887 }
2888 Ok(())
2889 }
2890
2891 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> PerlResult<()> {
2892 let key = v.trim();
2893 match key {
2894 "5.10" | "5.010" | "5.10.0" => {
2895 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
2896 }
2897 "5.12" | "5.012" | "5.12.0" => {
2898 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
2899 }
2900 _ => {
2901 return Err(PerlError::runtime(
2902 format!("unsupported feature bundle :{}", key),
2903 line,
2904 ));
2905 }
2906 }
2907 Ok(())
2908 }
2909
2910 fn clear_feature_bundle(&mut self, v: &str) {
2911 let key = v.trim();
2912 if matches!(
2913 key,
2914 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
2915 ) {
2916 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
2917 }
2918 }
2919
2920 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> PerlResult<()> {
2921 let bit = match name {
2922 "say" => FEAT_SAY,
2923 "state" => FEAT_STATE,
2924 "switch" => FEAT_SWITCH,
2925 "unicode_strings" => FEAT_UNICODE_STRINGS,
2926 "postderef"
2930 | "postderef_qq"
2931 | "evalbytes"
2932 | "current_sub"
2933 | "fc"
2934 | "lexical_subs"
2935 | "signatures"
2936 | "refaliasing"
2937 | "bitwise"
2938 | "isa"
2939 | "indirect"
2940 | "multidimensional"
2941 | "bareword_filehandles"
2942 | "try"
2943 | "defer"
2944 | "extra_paired_delimiters"
2945 | "module_true"
2946 | "class"
2947 | "array_base" => return Ok(()),
2948 _ => {
2949 return Err(PerlError::runtime(
2950 format!("unknown feature `{}`", name),
2951 line,
2952 ));
2953 }
2954 };
2955 if enable {
2956 self.feature_bits |= bit;
2957 } else {
2958 self.feature_bits &= !bit;
2959 }
2960 Ok(())
2961 }
2962
2963 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> PerlResult<PerlValue> {
2965 let t = spec.trim();
2966 if t.is_empty() {
2967 return Err(PerlError::runtime("require: empty argument", line));
2968 }
2969 match t {
2970 "strict" => {
2971 self.apply_use_strict(&[], line)?;
2972 return Ok(PerlValue::integer(1));
2973 }
2974 "utf8" => {
2975 self.utf8_pragma = true;
2976 return Ok(PerlValue::integer(1));
2977 }
2978 "feature" | "v5" => {
2979 return Ok(PerlValue::integer(1));
2980 }
2981 "warnings" => {
2982 self.warnings = true;
2983 return Ok(PerlValue::integer(1));
2984 }
2985 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
2986 return Ok(PerlValue::integer(1));
2987 }
2988 _ => {}
2989 }
2990 let p = Path::new(t);
2991 if p.is_absolute() {
2992 return self.require_absolute_path(p, line);
2993 }
2994 if t.starts_with("./") || t.starts_with("../") {
2995 return self.require_relative_path(p, line);
2996 }
2997 if Self::looks_like_version_only(t) {
2998 return Ok(PerlValue::integer(1));
2999 }
3000 let relpath = Self::module_spec_to_relpath(t);
3001 self.require_from_inc(&relpath, line)
3002 }
3003
3004 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> PerlResult<()> {
3006 let v = self.scope.get_hash_element("^HOOK", key);
3007 if v.is_undef() {
3008 return Ok(());
3009 }
3010 let Some(sub) = v.as_code_ref() else {
3011 return Ok(());
3012 };
3013 let r = self.call_sub(
3014 sub.as_ref(),
3015 vec![PerlValue::string(path.to_string())],
3016 WantarrayCtx::Scalar,
3017 line,
3018 );
3019 match r {
3020 Ok(_) => Ok(()),
3021 Err(FlowOrError::Error(e)) => Err(e),
3022 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3023 Err(FlowOrError::Flow(other)) => Err(PerlError::runtime(
3024 format!(
3025 "require hook {:?} returned unexpected control flow: {:?}",
3026 key, other
3027 ),
3028 line,
3029 )),
3030 }
3031 }
3032
3033 fn require_absolute_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3034 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3035 let key = canon.to_string_lossy().into_owned();
3036 if self.scope.exists_hash_element("INC", &key) {
3037 return Ok(PerlValue::integer(1));
3038 }
3039 self.invoke_require_hook("require__before", &key, line)?;
3040 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3041 PerlError::runtime(
3042 format!("Can't open {} for reading: {}", canon.display(), e),
3043 line,
3044 )
3045 })?;
3046 let code = crate::data_section::strip_perl_end_marker(&code);
3047 self.scope
3048 .set_hash_element("INC", &key, PerlValue::string(key.clone()))?;
3049 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3050 let r = crate::parse_and_run_string_in_file(code, self, &key);
3051 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3052 r?;
3053 self.invoke_require_hook("require__after", &key, line)?;
3054 Ok(PerlValue::integer(1))
3055 }
3056
3057 fn require_relative_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
3058 if !path.exists() {
3059 return Err(PerlError::runtime(
3060 format!(
3061 "Can't locate {} (relative path does not exist)",
3062 path.display()
3063 ),
3064 line,
3065 ));
3066 }
3067 self.require_absolute_path(path, line)
3068 }
3069
3070 fn require_from_inc(&mut self, relpath: &str, line: usize) -> PerlResult<PerlValue> {
3071 if self.scope.exists_hash_element("INC", relpath) {
3072 return Ok(PerlValue::integer(1));
3073 }
3074 self.invoke_require_hook("require__before", relpath, line)?;
3075
3076 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3078 let code = crate::data_section::strip_perl_end_marker(&code);
3079 self.scope.set_hash_element(
3080 "INC",
3081 relpath,
3082 PerlValue::string(format!("(virtual)/{}", relpath)),
3083 )?;
3084 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3085 let r = crate::parse_and_run_string_in_file(code, self, relpath);
3086 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3087 r?;
3088 self.invoke_require_hook("require__after", relpath, line)?;
3089 return Ok(PerlValue::integer(1));
3090 }
3091
3092 for dir in self.inc_directories() {
3093 let full = Path::new(&dir).join(relpath);
3094 if full.is_file() {
3095 let code = read_file_text_perl_compat(&full).map_err(|e| {
3096 PerlError::runtime(
3097 format!("Can't open {} for reading: {}", full.display(), e),
3098 line,
3099 )
3100 })?;
3101 let code = crate::data_section::strip_perl_end_marker(&code);
3102 let abs = full.canonicalize().unwrap_or(full);
3103 let abs_s = abs.to_string_lossy().into_owned();
3104 self.scope
3105 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3106 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3107 let r = crate::parse_and_run_string_in_file(code, self, &abs_s);
3108 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3109 r?;
3110 self.invoke_require_hook("require__after", relpath, line)?;
3111 return Ok(PerlValue::integer(1));
3112 }
3113 }
3114 Err(PerlError::runtime(
3115 format!(
3116 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3117 relpath
3118 ),
3119 line,
3120 ))
3121 }
3122
3123 pub fn register_virtual_module(&mut self, path: String, source: String) {
3125 self.virtual_modules.insert(path, source);
3126 }
3127
3128 pub(crate) fn exec_use_stmt(
3130 &mut self,
3131 module: &str,
3132 imports: &[Expr],
3133 line: usize,
3134 ) -> PerlResult<()> {
3135 match module {
3136 "strict" => self.apply_use_strict(imports, line),
3137 "utf8" => {
3138 if !imports.is_empty() {
3139 return Err(PerlError::runtime("use utf8 takes no arguments", line));
3140 }
3141 self.utf8_pragma = true;
3142 Ok(())
3143 }
3144 "feature" => self.apply_use_feature(imports, line),
3145 "v5" => Ok(()),
3146 "warnings" => {
3147 self.warnings = true;
3148 Ok(())
3149 }
3150 "English" => {
3151 self.english_enabled = true;
3152 let args = Self::pragma_import_strings(imports, line)?;
3153 let no_match = args.iter().any(|a| a == "-no_match_vars");
3154 if !no_match {
3158 self.english_match_vars_ever_enabled = true;
3159 }
3160 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3161 Ok(())
3162 }
3163 "Env" => self.apply_use_env(imports, line),
3164 "open" => self.apply_use_open(imports, line),
3165 "constant" => self.apply_use_constant(imports, line),
3166 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3167 _ => {
3168 self.require_execute(module, line)?;
3169 let imports = Self::imports_after_leading_use_version(imports);
3170 self.apply_module_import(module, imports, line)?;
3171 Ok(())
3172 }
3173 }
3174 }
3175
3176 pub(crate) fn exec_no_stmt(
3178 &mut self,
3179 module: &str,
3180 imports: &[Expr],
3181 line: usize,
3182 ) -> PerlResult<()> {
3183 match module {
3184 "strict" => self.apply_no_strict(imports, line),
3185 "utf8" => {
3186 if !imports.is_empty() {
3187 return Err(PerlError::runtime("no utf8 takes no arguments", line));
3188 }
3189 self.utf8_pragma = false;
3190 Ok(())
3191 }
3192 "feature" => self.apply_no_feature(imports, line),
3193 "v5" => Ok(()),
3194 "warnings" => {
3195 self.warnings = false;
3196 Ok(())
3197 }
3198 "English" => {
3199 self.english_enabled = false;
3200 if !self.english_match_vars_ever_enabled {
3203 self.english_no_match_vars = false;
3204 }
3205 Ok(())
3206 }
3207 "open" => {
3208 self.open_pragma_utf8 = false;
3209 Ok(())
3210 }
3211 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3212 _ => Ok(()),
3213 }
3214 }
3215
3216 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3218 let names = Self::pragma_import_strings(imports, line)?;
3219 for n in names {
3220 let key = n.trim_start_matches('@');
3221 if key.eq_ignore_ascii_case("PATH") {
3222 let path_env = std::env::var("PATH").unwrap_or_default();
3223 let path_vec: Vec<PerlValue> = std::env::split_paths(&path_env)
3224 .map(|p| PerlValue::string(p.to_string_lossy().into_owned()))
3225 .collect();
3226 let aname = self.stash_array_name_for_package("PATH");
3227 self.scope.declare_array(&aname, path_vec);
3228 }
3229 }
3230 Ok(())
3231 }
3232
3233 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3235 let items = Self::pragma_import_strings(imports, line)?;
3236 for item in items {
3237 let s = item.trim();
3238 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3239 self.open_pragma_utf8 = true;
3240 continue;
3241 }
3242 if let Some(rest) = s.strip_prefix(":encoding(") {
3243 if let Some(inner) = rest.strip_suffix(')') {
3244 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3245 self.open_pragma_utf8 = true;
3246 }
3247 }
3248 }
3249 }
3250 Ok(())
3251 }
3252
3253 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3255 if imports.is_empty() {
3256 return Ok(());
3257 }
3258 if imports.len() == 1 {
3260 match &imports[0].kind {
3261 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
3262 _ => {}
3263 }
3264 }
3265 for imp in imports {
3266 match &imp.kind {
3267 ExprKind::List(items) => {
3268 if items.len() % 2 != 0 {
3269 return Err(PerlError::runtime(
3270 format!(
3271 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
3272 items.len()
3273 ),
3274 line,
3275 ));
3276 }
3277 let mut i = 0;
3278 while i < items.len() {
3279 let name = match &items[i].kind {
3280 ExprKind::String(s) => s.clone(),
3281 _ => {
3282 return Err(PerlError::runtime(
3283 "use constant: constant name must be a string literal",
3284 line,
3285 ));
3286 }
3287 };
3288 let val = match self.eval_expr(&items[i + 1]) {
3289 Ok(v) => v,
3290 Err(FlowOrError::Error(e)) => return Err(e),
3291 Err(FlowOrError::Flow(_)) => {
3292 return Err(PerlError::runtime(
3293 "use constant: unexpected control flow in initializer",
3294 line,
3295 ));
3296 }
3297 };
3298 self.install_constant_sub(&name, &val, line)?;
3299 i += 2;
3300 }
3301 }
3302 _ => {
3303 return Err(PerlError::runtime(
3304 "use constant: expected list of NAME => VALUE pairs",
3305 line,
3306 ));
3307 }
3308 }
3309 }
3310 Ok(())
3311 }
3312
3313 fn install_constant_sub(&mut self, name: &str, val: &PerlValue, line: usize) -> PerlResult<()> {
3314 let key = self.qualify_sub_key(name);
3315 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
3316 let body = vec![Statement {
3317 label: None,
3318 kind: StmtKind::Return(Some(ret_expr)),
3319 line,
3320 }];
3321 self.subs.insert(
3322 key.clone(),
3323 Arc::new(PerlSub {
3324 name: key,
3325 params: vec![],
3326 body,
3327 prototype: None,
3328 closure_env: None,
3329 fib_like: None,
3330 }),
3331 );
3332 Ok(())
3333 }
3334
3335 fn perl_value_to_const_literal_expr(&self, v: &PerlValue, line: usize) -> PerlResult<Expr> {
3337 if v.is_undef() {
3338 return Ok(Expr {
3339 kind: ExprKind::Undef,
3340 line,
3341 });
3342 }
3343 if let Some(n) = v.as_integer() {
3344 return Ok(Expr {
3345 kind: ExprKind::Integer(n),
3346 line,
3347 });
3348 }
3349 if let Some(f) = v.as_float() {
3350 return Ok(Expr {
3351 kind: ExprKind::Float(f),
3352 line,
3353 });
3354 }
3355 if let Some(s) = v.as_str() {
3356 return Ok(Expr {
3357 kind: ExprKind::String(s),
3358 line,
3359 });
3360 }
3361 if let Some(arr) = v.as_array_vec() {
3362 let mut elems = Vec::with_capacity(arr.len());
3363 for e in &arr {
3364 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3365 }
3366 return Ok(Expr {
3367 kind: ExprKind::ArrayRef(elems),
3368 line,
3369 });
3370 }
3371 if let Some(h) = v.as_hash_map() {
3372 let mut pairs = Vec::with_capacity(h.len());
3373 for (k, vv) in h.iter() {
3374 pairs.push((
3375 Expr {
3376 kind: ExprKind::String(k.clone()),
3377 line,
3378 },
3379 self.perl_value_to_const_literal_expr(vv, line)?,
3380 ));
3381 }
3382 return Ok(Expr {
3383 kind: ExprKind::HashRef(pairs),
3384 line,
3385 });
3386 }
3387 if let Some(aref) = v.as_array_ref() {
3388 let arr = aref.read();
3389 let mut elems = Vec::with_capacity(arr.len());
3390 for e in arr.iter() {
3391 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3392 }
3393 return Ok(Expr {
3394 kind: ExprKind::ArrayRef(elems),
3395 line,
3396 });
3397 }
3398 if let Some(href) = v.as_hash_ref() {
3399 let h = href.read();
3400 let mut pairs = Vec::with_capacity(h.len());
3401 for (k, vv) in h.iter() {
3402 pairs.push((
3403 Expr {
3404 kind: ExprKind::String(k.clone()),
3405 line,
3406 },
3407 self.perl_value_to_const_literal_expr(vv, line)?,
3408 ));
3409 }
3410 return Ok(Expr {
3411 kind: ExprKind::HashRef(pairs),
3412 line,
3413 });
3414 }
3415 Err(PerlError::runtime(
3416 format!("use constant: unsupported value type ({v:?})"),
3417 line,
3418 ))
3419 }
3420
3421 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> PerlResult<()> {
3423 if crate::list_util::program_needs_list_util(program) {
3424 crate::list_util::ensure_list_util(self);
3425 }
3426 for stmt in &program.statements {
3427 match &stmt.kind {
3428 StmtKind::Package { name } => {
3429 let _ = self
3430 .scope
3431 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
3432 }
3433 StmtKind::SubDecl {
3434 name,
3435 params,
3436 body,
3437 prototype,
3438 } => {
3439 let key = self.qualify_sub_key(name);
3440 let mut sub = PerlSub {
3441 name: name.clone(),
3442 params: params.clone(),
3443 body: body.clone(),
3444 closure_env: None,
3445 prototype: prototype.clone(),
3446 fib_like: None,
3447 };
3448 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
3449 self.subs.insert(key, Arc::new(sub));
3450 }
3451 StmtKind::UsePerlVersion { .. } => {}
3452 StmtKind::Use { module, imports } => {
3453 self.exec_use_stmt(module, imports, stmt.line)?;
3454 }
3455 StmtKind::UseOverload { pairs } => {
3456 self.install_use_overload_pairs(pairs);
3457 }
3458 StmtKind::FormatDecl { name, lines } => {
3459 self.install_format_decl(name, lines, stmt.line)?;
3460 }
3461 StmtKind::No { module, imports } => {
3462 self.exec_no_stmt(module, imports, stmt.line)?;
3463 }
3464 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
3465 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
3466 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
3467 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
3468 StmtKind::End(block) => self.end_blocks.push(block.clone()),
3469 _ => {}
3470 }
3471 }
3472 Ok(())
3473 }
3474
3475 pub fn install_data_handle(&mut self, data: Vec<u8>) {
3477 self.input_handles.insert(
3478 "DATA".to_string(),
3479 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
3480 );
3481 }
3482
3483 pub(crate) fn open_builtin_execute(
3489 &mut self,
3490 handle_name: String,
3491 mode_s: String,
3492 file_opt: Option<String>,
3493 line: usize,
3494 ) -> PerlResult<PerlValue> {
3495 let (actual_mode, path) = if let Some(f) = file_opt {
3500 (mode_s, f)
3501 } else {
3502 let trimmed = mode_s.trim();
3503 if let Some(rest) = trimmed.strip_prefix('|') {
3504 ("|-".to_string(), rest.trim_start().to_string())
3505 } else if trimmed.ends_with('|') {
3506 let mut cmd = trimmed.to_string();
3507 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
3509 } else if let Some(rest) = trimmed.strip_prefix(">>") {
3510 (">>".to_string(), rest.trim().to_string())
3511 } else if let Some(rest) = trimmed.strip_prefix('>') {
3512 (">".to_string(), rest.trim().to_string())
3513 } else if let Some(rest) = trimmed.strip_prefix('<') {
3514 ("<".to_string(), rest.trim().to_string())
3515 } else {
3516 ("<".to_string(), trimmed.to_string())
3517 }
3518 };
3519 let handle_return = handle_name.clone();
3520 match actual_mode.as_str() {
3521 "-|" => {
3522 let mut cmd = piped_shell_command(&path);
3523 cmd.stdout(Stdio::piped());
3524 let mut child = cmd.spawn().map_err(|e| {
3525 self.apply_io_error_to_errno(&e);
3526 PerlError::runtime(format!("Can't open pipe from command: {}", e), line)
3527 })?;
3528 let stdout = child
3529 .stdout
3530 .take()
3531 .ok_or_else(|| PerlError::runtime("pipe: child has no stdout", line))?;
3532 self.input_handles
3533 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
3534 self.pipe_children.insert(handle_name, child);
3535 }
3536 "|-" => {
3537 let mut cmd = piped_shell_command(&path);
3538 cmd.stdin(Stdio::piped());
3539 let mut child = cmd.spawn().map_err(|e| {
3540 self.apply_io_error_to_errno(&e);
3541 PerlError::runtime(format!("Can't open pipe to command: {}", e), line)
3542 })?;
3543 let stdin = child
3544 .stdin
3545 .take()
3546 .ok_or_else(|| PerlError::runtime("pipe: child has no stdin", line))?;
3547 self.output_handles
3548 .insert(handle_name.clone(), Box::new(stdin));
3549 self.pipe_children.insert(handle_name, child);
3550 }
3551 "<" => {
3552 let file = std::fs::File::open(&path).map_err(|e| {
3553 self.apply_io_error_to_errno(&e);
3554 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3555 })?;
3556 let shared = Arc::new(Mutex::new(file));
3557 self.io_file_slots
3558 .insert(handle_name.clone(), Arc::clone(&shared));
3559 self.input_handles.insert(
3560 handle_name.clone(),
3561 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
3562 );
3563 }
3564 ">" => {
3565 let file = std::fs::File::create(&path).map_err(|e| {
3566 self.apply_io_error_to_errno(&e);
3567 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3568 })?;
3569 let shared = Arc::new(Mutex::new(file));
3570 self.io_file_slots
3571 .insert(handle_name.clone(), Arc::clone(&shared));
3572 self.output_handles.insert(
3573 handle_name.clone(),
3574 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3575 );
3576 }
3577 ">>" => {
3578 let file = std::fs::OpenOptions::new()
3579 .append(true)
3580 .create(true)
3581 .open(&path)
3582 .map_err(|e| {
3583 self.apply_io_error_to_errno(&e);
3584 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3585 })?;
3586 let shared = Arc::new(Mutex::new(file));
3587 self.io_file_slots
3588 .insert(handle_name.clone(), Arc::clone(&shared));
3589 self.output_handles.insert(
3590 handle_name.clone(),
3591 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3592 );
3593 }
3594 _ => {
3595 return Err(PerlError::runtime(
3596 format!("Unknown open mode '{}'", actual_mode),
3597 line,
3598 ));
3599 }
3600 }
3601 Ok(PerlValue::io_handle(handle_return))
3602 }
3603
3604 pub(crate) fn eval_chunk_by_builtin(
3608 &mut self,
3609 key_spec: &Expr,
3610 list_expr: &Expr,
3611 ctx: WantarrayCtx,
3612 line: usize,
3613 ) -> ExecResult {
3614 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
3615 let chunks = match &key_spec.kind {
3616 ExprKind::CodeRef { .. } => {
3617 let cr = self.eval_expr(key_spec)?;
3618 let Some(sub) = cr.as_code_ref() else {
3619 return Err(PerlError::runtime(
3620 "group_by/chunk_by: first argument must be { BLOCK }",
3621 line,
3622 )
3623 .into());
3624 };
3625 let sub = sub.clone();
3626 let mut chunks: Vec<PerlValue> = Vec::new();
3627 let mut run: Vec<PerlValue> = Vec::new();
3628 let mut prev_key: Option<PerlValue> = None;
3629 for item in list {
3630 self.scope.set_topic(item.clone());
3631 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
3632 Ok(k) => k,
3633 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
3634 Err(FlowOrError::Flow(Flow::Return(v))) => v,
3635 Err(_) => PerlValue::UNDEF,
3636 };
3637 match &prev_key {
3638 None => {
3639 run.push(item);
3640 prev_key = Some(key);
3641 }
3642 Some(pk) => {
3643 if key.str_eq(pk) {
3644 run.push(item);
3645 } else {
3646 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3647 std::mem::take(&mut run),
3648 ))));
3649 run.push(item);
3650 prev_key = Some(key);
3651 }
3652 }
3653 }
3654 }
3655 if !run.is_empty() {
3656 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3657 }
3658 chunks
3659 }
3660 _ => {
3661 let mut chunks: Vec<PerlValue> = Vec::new();
3662 let mut run: Vec<PerlValue> = Vec::new();
3663 let mut prev_key: Option<PerlValue> = None;
3664 for item in list {
3665 self.scope.set_topic(item.clone());
3666 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
3667 match &prev_key {
3668 None => {
3669 run.push(item);
3670 prev_key = Some(key);
3671 }
3672 Some(pk) => {
3673 if key.str_eq(pk) {
3674 run.push(item);
3675 } else {
3676 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3677 std::mem::take(&mut run),
3678 ))));
3679 run.push(item);
3680 prev_key = Some(key);
3681 }
3682 }
3683 }
3684 }
3685 if !run.is_empty() {
3686 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3687 }
3688 chunks
3689 }
3690 };
3691 Ok(match ctx {
3692 WantarrayCtx::List => PerlValue::array(chunks),
3693 WantarrayCtx::Scalar => PerlValue::integer(chunks.len() as i64),
3694 WantarrayCtx::Void => PerlValue::UNDEF,
3695 })
3696 }
3697
3698 pub(crate) fn list_higher_order_block_builtin(
3700 &mut self,
3701 name: &str,
3702 args: &[PerlValue],
3703 line: usize,
3704 ) -> PerlResult<PerlValue> {
3705 match self.list_higher_order_block_builtin_exec(name, args, line) {
3706 Ok(v) => Ok(v),
3707 Err(FlowOrError::Error(e)) => Err(e),
3708 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
3709 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
3710 format!("{name}: unsupported control flow in block"),
3711 line,
3712 )),
3713 }
3714 }
3715
3716 fn list_higher_order_block_builtin_exec(
3717 &mut self,
3718 name: &str,
3719 args: &[PerlValue],
3720 line: usize,
3721 ) -> ExecResult {
3722 if args.is_empty() {
3723 return Err(
3724 PerlError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
3725 );
3726 }
3727 let Some(sub) = args[0].as_code_ref() else {
3728 return Err(PerlError::runtime(
3729 format!("{name}: first argument must be {{ BLOCK }}"),
3730 line,
3731 )
3732 .into());
3733 };
3734 let sub = sub.clone();
3735 let items: Vec<PerlValue> = args[1..].to_vec();
3736 if matches!(name, "tap" | "peek") && items.len() == 1 {
3737 if let Some(p) = items[0].as_pipeline() {
3738 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
3739 return Ok(PerlValue::pipeline(Arc::clone(&p)));
3740 }
3741 let v = &items[0];
3742 if v.is_iterator() || v.as_array_vec().is_some() {
3743 let source = crate::map_stream::into_pull_iter(v.clone());
3744 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
3745 return Ok(PerlValue::iterator(Arc::new(
3746 crate::map_stream::TapIterator::new(
3747 source,
3748 sub,
3749 self.subs.clone(),
3750 capture,
3751 atomic_arrays,
3752 atomic_hashes,
3753 ),
3754 )));
3755 }
3756 }
3757 let wa = self.wantarray_kind;
3762 match name {
3763 "take_while" => {
3764 let mut out = Vec::new();
3765 for item in items {
3766 self.scope_push_hook();
3767 self.scope.set_topic(item.clone());
3768 let pred = self.exec_block(&sub.body)?;
3769 self.scope_pop_hook();
3770 if !pred.is_true() {
3771 break;
3772 }
3773 out.push(item);
3774 }
3775 Ok(match wa {
3776 WantarrayCtx::List => PerlValue::array(out),
3777 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3778 WantarrayCtx::Void => PerlValue::UNDEF,
3779 })
3780 }
3781 "drop_while" | "skip_while" => {
3782 let mut i = 0usize;
3783 while i < items.len() {
3784 self.scope_push_hook();
3785 self.scope.set_topic(items[i].clone());
3786 let pred = self.exec_block(&sub.body)?;
3787 self.scope_pop_hook();
3788 if !pred.is_true() {
3789 break;
3790 }
3791 i += 1;
3792 }
3793 let rest = items[i..].to_vec();
3794 Ok(match wa {
3795 WantarrayCtx::List => PerlValue::array(rest),
3796 WantarrayCtx::Scalar => PerlValue::integer(rest.len() as i64),
3797 WantarrayCtx::Void => PerlValue::UNDEF,
3798 })
3799 }
3800 "reject" => {
3801 let mut out = Vec::new();
3802 for item in items {
3803 self.scope_push_hook();
3804 self.scope.set_topic(item.clone());
3805 let pred = self.exec_block(&sub.body)?;
3806 self.scope_pop_hook();
3807 if !pred.is_true() {
3808 out.push(item);
3809 }
3810 }
3811 Ok(match wa {
3812 WantarrayCtx::List => PerlValue::array(out),
3813 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3814 WantarrayCtx::Void => PerlValue::UNDEF,
3815 })
3816 }
3817 "tap" | "peek" => {
3818 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
3819 Ok(match wa {
3820 WantarrayCtx::List => PerlValue::array(items),
3821 WantarrayCtx::Scalar => PerlValue::integer(items.len() as i64),
3822 WantarrayCtx::Void => PerlValue::UNDEF,
3823 })
3824 }
3825 "partition" => {
3826 let mut yes = Vec::new();
3827 let mut no = Vec::new();
3828 for item in items {
3829 self.scope.set_topic(item.clone());
3830 let pred = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3831 if pred.is_true() {
3832 yes.push(item);
3833 } else {
3834 no.push(item);
3835 }
3836 }
3837 let yes_ref = PerlValue::array_ref(Arc::new(RwLock::new(yes)));
3838 let no_ref = PerlValue::array_ref(Arc::new(RwLock::new(no)));
3839 Ok(match wa {
3840 WantarrayCtx::List => PerlValue::array(vec![yes_ref, no_ref]),
3841 WantarrayCtx::Scalar => PerlValue::integer(2),
3842 WantarrayCtx::Void => PerlValue::UNDEF,
3843 })
3844 }
3845 "min_by" => {
3846 let mut best: Option<(PerlValue, PerlValue)> = None;
3847 for item in items {
3848 self.scope.set_topic(item.clone());
3849 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3850 best = Some(match best {
3851 None => (item, key),
3852 Some((bv, bk)) => {
3853 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
3854 (item, key)
3855 } else {
3856 (bv, bk)
3857 }
3858 }
3859 });
3860 }
3861 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
3862 }
3863 "max_by" => {
3864 let mut best: Option<(PerlValue, PerlValue)> = None;
3865 for item in items {
3866 self.scope.set_topic(item.clone());
3867 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3868 best = Some(match best {
3869 None => (item, key),
3870 Some((bv, bk)) => {
3871 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
3872 (item, key)
3873 } else {
3874 (bv, bk)
3875 }
3876 }
3877 });
3878 }
3879 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
3880 }
3881 "zip_with" => {
3882 let flat: Vec<PerlValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
3885 let refs: Vec<Vec<PerlValue>> = flat
3886 .iter()
3887 .map(|el| {
3888 if let Some(ar) = el.as_array_ref() {
3889 ar.read().clone()
3890 } else if let Some(name) = el.as_array_binding_name() {
3891 self.scope.get_array(&name)
3892 } else {
3893 vec![el.clone()]
3894 }
3895 })
3896 .collect();
3897 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
3898 let mut out = Vec::with_capacity(max_len);
3899 for i in 0..max_len {
3900 let pair: Vec<PerlValue> = refs
3901 .iter()
3902 .map(|l| l.get(i).cloned().unwrap_or(PerlValue::UNDEF))
3903 .collect();
3904 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
3905 out.push(result);
3906 }
3907 Ok(match wa {
3908 WantarrayCtx::List => PerlValue::array(out),
3909 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3910 WantarrayCtx::Void => PerlValue::UNDEF,
3911 })
3912 }
3913 "count_by" => {
3914 let mut counts = indexmap::IndexMap::new();
3915 for item in items {
3916 self.scope.set_topic(item.clone());
3917 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3918 let k = key.to_string();
3919 let entry = counts.entry(k).or_insert(PerlValue::integer(0));
3920 *entry = PerlValue::integer(entry.to_int() + 1);
3921 }
3922 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(counts))))
3923 }
3924 _ => Err(PerlError::runtime(
3925 format!("internal: unknown list block builtin `{name}`"),
3926 line,
3927 )
3928 .into()),
3929 }
3930 }
3931
3932 pub(crate) fn builtin_rmdir_execute(
3934 &mut self,
3935 args: &[PerlValue],
3936 _line: usize,
3937 ) -> PerlResult<PerlValue> {
3938 let mut count = 0i64;
3939 for a in args {
3940 let p = a.to_string();
3941 if p.is_empty() {
3942 continue;
3943 }
3944 if std::fs::remove_dir(&p).is_ok() {
3945 count += 1;
3946 }
3947 }
3948 Ok(PerlValue::integer(count))
3949 }
3950
3951 pub(crate) fn builtin_touch_execute(
3953 &mut self,
3954 args: &[PerlValue],
3955 _line: usize,
3956 ) -> PerlResult<PerlValue> {
3957 let paths: Vec<String> = args.iter().map(|v| v.to_string()).collect();
3958 Ok(PerlValue::integer(crate::perl_fs::touch_paths(&paths)))
3959 }
3960
3961 pub(crate) fn builtin_utime_execute(
3963 &mut self,
3964 args: &[PerlValue],
3965 line: usize,
3966 ) -> PerlResult<PerlValue> {
3967 if args.len() < 3 {
3968 return Err(PerlError::runtime(
3969 "utime requires at least three arguments (atime, mtime, files...)",
3970 line,
3971 ));
3972 }
3973 let at = args[0].to_int();
3974 let mt = args[1].to_int();
3975 let paths: Vec<String> = args.iter().skip(2).map(|v| v.to_string()).collect();
3976 let n = crate::perl_fs::utime_paths(at, mt, &paths);
3977 #[cfg(not(unix))]
3978 if !paths.is_empty() && n == 0 {
3979 return Err(PerlError::runtime(
3980 "utime is not supported on this platform",
3981 line,
3982 ));
3983 }
3984 Ok(PerlValue::integer(n))
3985 }
3986
3987 pub(crate) fn builtin_umask_execute(
3989 &mut self,
3990 args: &[PerlValue],
3991 line: usize,
3992 ) -> PerlResult<PerlValue> {
3993 #[cfg(unix)]
3994 {
3995 let _ = line;
3996 if args.is_empty() {
3997 let cur = unsafe { libc::umask(0) };
3998 unsafe { libc::umask(cur) };
3999 return Ok(PerlValue::integer(cur as i64));
4000 }
4001 let new_m = args[0].to_int() as libc::mode_t;
4002 let old = unsafe { libc::umask(new_m) };
4003 Ok(PerlValue::integer(old as i64))
4004 }
4005 #[cfg(not(unix))]
4006 {
4007 let _ = args;
4008 Err(PerlError::runtime(
4009 "umask is not supported on this platform",
4010 line,
4011 ))
4012 }
4013 }
4014
4015 pub(crate) fn builtin_getcwd_execute(
4017 &mut self,
4018 args: &[PerlValue],
4019 line: usize,
4020 ) -> PerlResult<PerlValue> {
4021 if !args.is_empty() {
4022 return Err(PerlError::runtime("getcwd takes no arguments", line));
4023 }
4024 match std::env::current_dir() {
4025 Ok(p) => Ok(PerlValue::string(p.to_string_lossy().into_owned())),
4026 Err(e) => {
4027 self.apply_io_error_to_errno(&e);
4028 Ok(PerlValue::UNDEF)
4029 }
4030 }
4031 }
4032
4033 pub(crate) fn builtin_realpath_execute(
4035 &mut self,
4036 args: &[PerlValue],
4037 line: usize,
4038 ) -> PerlResult<PerlValue> {
4039 let path = args
4040 .first()
4041 .ok_or_else(|| PerlError::runtime("realpath: need path", line))?
4042 .to_string();
4043 if path.is_empty() {
4044 return Err(PerlError::runtime("realpath: need path", line));
4045 }
4046 match crate::perl_fs::realpath_resolved(&path) {
4047 Ok(s) => Ok(PerlValue::string(s)),
4048 Err(e) => {
4049 self.apply_io_error_to_errno(&e);
4050 Ok(PerlValue::UNDEF)
4051 }
4052 }
4053 }
4054
4055 pub(crate) fn builtin_pipe_execute(
4057 &mut self,
4058 args: &[PerlValue],
4059 line: usize,
4060 ) -> PerlResult<PerlValue> {
4061 if args.len() != 2 {
4062 return Err(PerlError::runtime(
4063 "pipe requires exactly two arguments",
4064 line,
4065 ));
4066 }
4067 #[cfg(unix)]
4068 {
4069 use std::fs::File;
4070 use std::os::unix::io::FromRawFd;
4071
4072 let read_name = args[0].to_string();
4073 let write_name = args[1].to_string();
4074 if read_name.is_empty() || write_name.is_empty() {
4075 return Err(PerlError::runtime("pipe: invalid handle name", line));
4076 }
4077 let mut fds = [0i32; 2];
4078 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
4079 let e = std::io::Error::last_os_error();
4080 self.apply_io_error_to_errno(&e);
4081 return Ok(PerlValue::integer(0));
4082 }
4083 let read_file = unsafe { File::from_raw_fd(fds[0]) };
4084 let write_file = unsafe { File::from_raw_fd(fds[1]) };
4085
4086 let read_shared = Arc::new(Mutex::new(read_file));
4087 let write_shared = Arc::new(Mutex::new(write_file));
4088
4089 self.close_builtin_execute(read_name.clone()).ok();
4090 self.close_builtin_execute(write_name.clone()).ok();
4091
4092 self.io_file_slots
4093 .insert(read_name.clone(), Arc::clone(&read_shared));
4094 self.input_handles.insert(
4095 read_name,
4096 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
4097 );
4098
4099 self.io_file_slots
4100 .insert(write_name.clone(), Arc::clone(&write_shared));
4101 self.output_handles
4102 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
4103
4104 Ok(PerlValue::integer(1))
4105 }
4106 #[cfg(not(unix))]
4107 {
4108 let _ = args;
4109 Err(PerlError::runtime(
4110 "pipe is not supported on this platform",
4111 line,
4112 ))
4113 }
4114 }
4115
4116 pub(crate) fn close_builtin_execute(&mut self, name: String) -> PerlResult<PerlValue> {
4117 self.output_handles.remove(&name);
4118 self.input_handles.remove(&name);
4119 self.io_file_slots.remove(&name);
4120 if let Some(mut child) = self.pipe_children.remove(&name) {
4121 if let Ok(st) = child.wait() {
4122 self.record_child_exit_status(st);
4123 }
4124 }
4125 Ok(PerlValue::integer(1))
4126 }
4127
4128 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
4129 self.input_handles.contains_key(name)
4130 }
4131
4132 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
4136 self.line_mode_eof_pending
4137 }
4138
4139 pub(crate) fn eof_builtin_execute(
4143 &self,
4144 args: &[PerlValue],
4145 line: usize,
4146 ) -> PerlResult<PerlValue> {
4147 match args.len() {
4148 0 => Ok(PerlValue::integer(if self.eof_without_arg_is_true() {
4149 1
4150 } else {
4151 0
4152 })),
4153 1 => {
4154 let name = args[0].to_string();
4155 let at_eof = !self.has_input_handle(&name);
4156 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
4157 }
4158 _ => Err(PerlError::runtime("eof: too many arguments", line)),
4159 }
4160 }
4161
4162 pub(crate) fn study_return_value(s: &str) -> PerlValue {
4165 if s.is_empty() {
4166 PerlValue::string(String::new())
4167 } else {
4168 PerlValue::integer(1)
4169 }
4170 }
4171
4172 pub(crate) fn readline_builtin_execute(
4173 &mut self,
4174 handle: Option<&str>,
4175 ) -> PerlResult<PerlValue> {
4176 if handle.is_none() {
4178 let argv = self.scope.get_array("ARGV");
4179 if !argv.is_empty() {
4180 loop {
4181 if self.diamond_reader.is_none() {
4182 while self.diamond_next_idx < argv.len() {
4183 let path = argv[self.diamond_next_idx].to_string();
4184 self.diamond_next_idx += 1;
4185 match File::open(&path) {
4186 Ok(f) => {
4187 self.argv_current_file = path;
4188 self.diamond_reader = Some(BufReader::new(f));
4189 break;
4190 }
4191 Err(e) => {
4192 self.apply_io_error_to_errno(&e);
4193 }
4194 }
4195 }
4196 if self.diamond_reader.is_none() {
4197 return Ok(PerlValue::UNDEF);
4198 }
4199 }
4200 let mut line_str = String::new();
4201 let read_result: Result<usize, io::Error> =
4202 if let Some(reader) = self.diamond_reader.as_mut() {
4203 if self.open_pragma_utf8 {
4204 let mut buf = Vec::new();
4205 reader.read_until(b'\n', &mut buf).inspect(|n| {
4206 if *n > 0 {
4207 line_str = String::from_utf8_lossy(&buf).into_owned();
4208 }
4209 })
4210 } else {
4211 let mut buf = Vec::new();
4212 match reader.read_until(b'\n', &mut buf) {
4213 Ok(n) => {
4214 if n > 0 {
4215 line_str =
4216 crate::perl_decode::decode_utf8_or_latin1_read_until(
4217 &buf,
4218 );
4219 }
4220 Ok(n)
4221 }
4222 Err(e) => Err(e),
4223 }
4224 }
4225 } else {
4226 unreachable!()
4227 };
4228 match read_result {
4229 Ok(0) => {
4230 self.diamond_reader = None;
4231 continue;
4232 }
4233 Ok(_) => {
4234 self.bump_line_for_handle(&self.argv_current_file.clone());
4235 return Ok(PerlValue::string(line_str));
4236 }
4237 Err(e) => {
4238 self.apply_io_error_to_errno(&e);
4239 self.diamond_reader = None;
4240 continue;
4241 }
4242 }
4243 }
4244 } else {
4245 self.argv_current_file.clear();
4246 }
4247 }
4248
4249 let handle_name = handle.unwrap_or("STDIN");
4250 let mut line_str = String::new();
4251 if handle_name == "STDIN" {
4252 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
4253 self.last_stdin_die_bracket = if handle.is_none() {
4254 "<>".to_string()
4255 } else {
4256 "<STDIN>".to_string()
4257 };
4258 self.bump_line_for_handle("STDIN");
4259 return Ok(PerlValue::string(queued));
4260 }
4261 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4262 let mut buf = Vec::new();
4263 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
4264 if *n > 0 {
4265 line_str = String::from_utf8_lossy(&buf).into_owned();
4266 }
4267 })
4268 } else {
4269 let mut buf = Vec::new();
4270 let mut lock = io::stdin().lock();
4271 match lock.read_until(b'\n', &mut buf) {
4272 Ok(n) => {
4273 if n > 0 {
4274 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4275 }
4276 Ok(n)
4277 }
4278 Err(e) => Err(e),
4279 }
4280 };
4281 match r {
4282 Ok(0) => Ok(PerlValue::UNDEF),
4283 Ok(_) => {
4284 self.last_stdin_die_bracket = if handle.is_none() {
4285 "<>".to_string()
4286 } else {
4287 "<STDIN>".to_string()
4288 };
4289 self.bump_line_for_handle("STDIN");
4290 Ok(PerlValue::string(line_str))
4291 }
4292 Err(e) => {
4293 self.apply_io_error_to_errno(&e);
4294 Ok(PerlValue::UNDEF)
4295 }
4296 }
4297 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
4298 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4299 let mut buf = Vec::new();
4300 reader.read_until(b'\n', &mut buf).inspect(|n| {
4301 if *n > 0 {
4302 line_str = String::from_utf8_lossy(&buf).into_owned();
4303 }
4304 })
4305 } else {
4306 let mut buf = Vec::new();
4307 match reader.read_until(b'\n', &mut buf) {
4308 Ok(n) => {
4309 if n > 0 {
4310 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4311 }
4312 Ok(n)
4313 }
4314 Err(e) => Err(e),
4315 }
4316 };
4317 match r {
4318 Ok(0) => Ok(PerlValue::UNDEF),
4319 Ok(_) => {
4320 self.bump_line_for_handle(handle_name);
4321 Ok(PerlValue::string(line_str))
4322 }
4323 Err(e) => {
4324 self.apply_io_error_to_errno(&e);
4325 Ok(PerlValue::UNDEF)
4326 }
4327 }
4328 } else {
4329 Ok(PerlValue::UNDEF)
4330 }
4331 }
4332
4333 pub(crate) fn readline_builtin_execute_list(
4335 &mut self,
4336 handle: Option<&str>,
4337 ) -> PerlResult<PerlValue> {
4338 let mut lines = Vec::new();
4339 loop {
4340 let v = self.readline_builtin_execute(handle)?;
4341 if v.is_undef() {
4342 break;
4343 }
4344 lines.push(v);
4345 }
4346 Ok(PerlValue::array(lines))
4347 }
4348
4349 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> PerlValue {
4350 match std::fs::read_dir(path) {
4351 Ok(rd) => {
4352 let entries: Vec<String> = rd
4353 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
4354 .collect();
4355 self.dir_handles
4356 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
4357 PerlValue::integer(1)
4358 }
4359 Err(e) => {
4360 self.apply_io_error_to_errno(&e);
4361 PerlValue::integer(0)
4362 }
4363 }
4364 }
4365
4366 pub(crate) fn readdir_handle(&mut self, handle: &str) -> PerlValue {
4367 if let Some(dh) = self.dir_handles.get_mut(handle) {
4368 if dh.pos < dh.entries.len() {
4369 let s = dh.entries[dh.pos].clone();
4370 dh.pos += 1;
4371 PerlValue::string(s)
4372 } else {
4373 PerlValue::UNDEF
4374 }
4375 } else {
4376 PerlValue::UNDEF
4377 }
4378 }
4379
4380 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> PerlValue {
4382 if let Some(dh) = self.dir_handles.get_mut(handle) {
4383 let rest: Vec<PerlValue> = dh.entries[dh.pos..]
4384 .iter()
4385 .cloned()
4386 .map(PerlValue::string)
4387 .collect();
4388 dh.pos = dh.entries.len();
4389 PerlValue::array(rest)
4390 } else {
4391 PerlValue::array(Vec::new())
4392 }
4393 }
4394
4395 pub(crate) fn closedir_handle(&mut self, handle: &str) -> PerlValue {
4396 PerlValue::integer(if self.dir_handles.remove(handle).is_some() {
4397 1
4398 } else {
4399 0
4400 })
4401 }
4402
4403 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> PerlValue {
4404 if let Some(dh) = self.dir_handles.get_mut(handle) {
4405 dh.pos = 0;
4406 PerlValue::integer(1)
4407 } else {
4408 PerlValue::integer(0)
4409 }
4410 }
4411
4412 pub(crate) fn telldir_handle(&mut self, handle: &str) -> PerlValue {
4413 self.dir_handles
4414 .get(handle)
4415 .map(|dh| PerlValue::integer(dh.pos as i64))
4416 .unwrap_or(PerlValue::UNDEF)
4417 }
4418
4419 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> PerlValue {
4420 if let Some(dh) = self.dir_handles.get_mut(handle) {
4421 dh.pos = pos.min(dh.entries.len());
4422 PerlValue::integer(1)
4423 } else {
4424 PerlValue::integer(0)
4425 }
4426 }
4427
4428 #[inline]
4433 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
4434 crate::special_vars::is_regex_match_scalar_name(name)
4435 }
4436
4437 #[inline]
4441 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
4442 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
4443 self.regex_capture_scope_fresh = false;
4444 }
4445 }
4446
4447 pub(crate) fn apply_regex_captures(
4448 &mut self,
4449 haystack: &str,
4450 offset: usize,
4451 re: &PerlCompiledRegex,
4452 caps: &PerlCaptures<'_>,
4453 capture_all: CaptureAllMode,
4454 ) -> Result<(), FlowOrError> {
4455 let m0 = caps.get(0).expect("regex capture 0");
4456 let s0 = offset + m0.start;
4457 let e0 = offset + m0.end;
4458 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
4459 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
4460 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
4461 let mut last_paren = String::new();
4462 for i in 1..caps.len() {
4463 if let Some(m) = caps.get(i) {
4464 last_paren = m.text.to_string();
4465 }
4466 }
4467 self.last_paren_match = last_paren;
4468 self.last_subpattern_name = String::new();
4469 for n in re.capture_names().flatten() {
4470 if caps.name(n).is_some() {
4471 self.last_subpattern_name = n.to_string();
4472 }
4473 }
4474 self.scope
4475 .set_scalar("&", PerlValue::string(self.last_match.clone()))?;
4476 self.scope
4477 .set_scalar("`", PerlValue::string(self.prematch.clone()))?;
4478 self.scope
4479 .set_scalar("'", PerlValue::string(self.postmatch.clone()))?;
4480 self.scope
4481 .set_scalar("+", PerlValue::string(self.last_paren_match.clone()))?;
4482 for i in 1..caps.len() {
4483 if let Some(m) = caps.get(i) {
4484 self.scope
4485 .set_scalar(&i.to_string(), PerlValue::string(m.text.to_string()))?;
4486 }
4487 }
4488 let mut start_arr = vec![PerlValue::integer(s0 as i64)];
4489 let mut end_arr = vec![PerlValue::integer(e0 as i64)];
4490 for i in 1..caps.len() {
4491 if let Some(m) = caps.get(i) {
4492 start_arr.push(PerlValue::integer((offset + m.start) as i64));
4493 end_arr.push(PerlValue::integer((offset + m.end) as i64));
4494 } else {
4495 start_arr.push(PerlValue::integer(-1));
4496 end_arr.push(PerlValue::integer(-1));
4497 }
4498 }
4499 self.scope.set_array("-", start_arr)?;
4500 self.scope.set_array("+", end_arr)?;
4501 let mut named = IndexMap::new();
4502 for name in re.capture_names().flatten() {
4503 if let Some(m) = caps.name(name) {
4504 named.insert(name.to_string(), PerlValue::string(m.text.to_string()));
4505 }
4506 }
4507 self.scope.set_hash("+", named.clone())?;
4508 let mut named_minus = IndexMap::new();
4510 for (name, val) in &named {
4511 named_minus.insert(
4512 name.clone(),
4513 PerlValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
4514 );
4515 }
4516 self.scope.set_hash("-", named_minus)?;
4517 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
4518 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
4519 match capture_all {
4520 CaptureAllMode::Empty => {
4521 self.scope.set_array("^CAPTURE_ALL", vec![])?;
4522 }
4523 CaptureAllMode::Append => {
4524 let mut rows = self.scope.get_array("^CAPTURE_ALL");
4525 rows.push(PerlValue::array(cap_flat));
4526 self.scope.set_array("^CAPTURE_ALL", rows)?;
4527 }
4528 CaptureAllMode::Skip => {}
4529 }
4530 Ok(())
4531 }
4532
4533 pub(crate) fn clear_flip_flop_state(&mut self) {
4534 self.flip_flop_active.clear();
4535 self.flip_flop_exclusive_left_line.clear();
4536 self.flip_flop_sequence.clear();
4537 self.flip_flop_last_dot.clear();
4538 self.flip_flop_tree.clear();
4539 }
4540
4541 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
4542 self.flip_flop_active.resize(slots as usize, false);
4543 self.flip_flop_active.fill(false);
4544 self.flip_flop_exclusive_left_line
4545 .resize(slots as usize, None);
4546 self.flip_flop_exclusive_left_line.fill(None);
4547 self.flip_flop_sequence.resize(slots as usize, 0);
4548 self.flip_flop_sequence.fill(0);
4549 self.flip_flop_last_dot.resize(slots as usize, None);
4550 self.flip_flop_last_dot.fill(None);
4551 }
4552
4553 #[inline]
4557 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
4558 if self.last_readline_handle.is_empty() {
4559 self.line_number
4560 } else {
4561 *self
4562 .handle_line_numbers
4563 .get(&self.last_readline_handle)
4564 .unwrap_or(&0)
4565 }
4566 }
4567
4568 pub(crate) fn scalar_flip_flop_eval(
4577 &mut self,
4578 left: i64,
4579 right: i64,
4580 slot: usize,
4581 exclusive: bool,
4582 ) -> PerlResult<PerlValue> {
4583 if self.flip_flop_active.len() <= slot {
4584 self.flip_flop_active.resize(slot + 1, false);
4585 }
4586 if self.flip_flop_exclusive_left_line.len() <= slot {
4587 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4588 }
4589 if self.flip_flop_sequence.len() <= slot {
4590 self.flip_flop_sequence.resize(slot + 1, 0);
4591 }
4592 if self.flip_flop_last_dot.len() <= slot {
4593 self.flip_flop_last_dot.resize(slot + 1, None);
4594 }
4595 let dot = self.scalar_flipflop_dot_line();
4596 let active = &mut self.flip_flop_active[slot];
4597 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4598 let seq = &mut self.flip_flop_sequence[slot];
4599 let last_dot = &mut self.flip_flop_last_dot[slot];
4600 if !*active {
4601 if dot == left {
4602 *active = true;
4603 *seq = 1;
4604 *last_dot = Some(dot);
4605 if exclusive {
4606 *excl_left = Some(dot);
4607 } else {
4608 *excl_left = None;
4609 if dot == right {
4610 *active = false;
4611 return Ok(PerlValue::string(format!("{}E0", *seq)));
4612 }
4613 }
4614 return Ok(PerlValue::string(seq.to_string()));
4615 }
4616 *last_dot = Some(dot);
4617 return Ok(PerlValue::string(String::new()));
4618 }
4619 if *last_dot != Some(dot) {
4622 *seq += 1;
4623 *last_dot = Some(dot);
4624 }
4625 let cur_seq = *seq;
4626 if let Some(ll) = *excl_left {
4627 if dot == right && dot > ll {
4628 *active = false;
4629 *excl_left = None;
4630 *seq = 0;
4631 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4632 }
4633 } else if dot == right {
4634 *active = false;
4635 *seq = 0;
4636 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4637 }
4638 Ok(PerlValue::string(cur_seq.to_string()))
4639 }
4640
4641 fn regex_flip_flop_transition(
4642 active: &mut bool,
4643 excl_left: &mut Option<i64>,
4644 exclusive: bool,
4645 dot: i64,
4646 left_m: bool,
4647 right_m: bool,
4648 ) -> i64 {
4649 if !*active {
4650 if left_m {
4651 *active = true;
4652 if exclusive {
4653 *excl_left = Some(dot);
4654 } else {
4655 *excl_left = None;
4656 if right_m {
4657 *active = false;
4658 }
4659 }
4660 return 1;
4661 }
4662 return 0;
4663 }
4664 if let Some(ll) = *excl_left {
4665 if right_m && dot > ll {
4666 *active = false;
4667 *excl_left = None;
4668 }
4669 } else if right_m {
4670 *active = false;
4671 }
4672 1
4673 }
4674
4675 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
4680 &mut self,
4681 left_pat: &str,
4682 left_flags: &str,
4683 right_pat: &str,
4684 right_flags: &str,
4685 slot: usize,
4686 exclusive: bool,
4687 line: usize,
4688 ) -> PerlResult<PerlValue> {
4689 let dot = self.scalar_flipflop_dot_line();
4690 let subject = self.scope.get_scalar("_").to_string();
4691 let left_re = self
4692 .compile_regex(left_pat, left_flags, line)
4693 .map_err(|e| match e {
4694 FlowOrError::Error(err) => err,
4695 FlowOrError::Flow(_) => {
4696 PerlError::runtime("unexpected flow in regex flip-flop", line)
4697 }
4698 })?;
4699 let right_re = self
4700 .compile_regex(right_pat, right_flags, line)
4701 .map_err(|e| match e {
4702 FlowOrError::Error(err) => err,
4703 FlowOrError::Flow(_) => {
4704 PerlError::runtime("unexpected flow in regex flip-flop", line)
4705 }
4706 })?;
4707 let left_m = left_re.is_match(&subject);
4708 let right_m = right_re.is_match(&subject);
4709 if self.flip_flop_active.len() <= slot {
4710 self.flip_flop_active.resize(slot + 1, false);
4711 }
4712 if self.flip_flop_exclusive_left_line.len() <= slot {
4713 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4714 }
4715 let active = &mut self.flip_flop_active[slot];
4716 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4717 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4718 active, excl_left, exclusive, dot, left_m, right_m,
4719 )))
4720 }
4721
4722 pub(crate) fn regex_flip_flop_eval_dynamic_right(
4724 &mut self,
4725 left_pat: &str,
4726 left_flags: &str,
4727 slot: usize,
4728 exclusive: bool,
4729 line: usize,
4730 right_m: bool,
4731 ) -> PerlResult<PerlValue> {
4732 let dot = self.scalar_flipflop_dot_line();
4733 let subject = self.scope.get_scalar("_").to_string();
4734 let left_re = self
4735 .compile_regex(left_pat, left_flags, line)
4736 .map_err(|e| match e {
4737 FlowOrError::Error(err) => err,
4738 FlowOrError::Flow(_) => {
4739 PerlError::runtime("unexpected flow in regex flip-flop", line)
4740 }
4741 })?;
4742 let left_m = left_re.is_match(&subject);
4743 if self.flip_flop_active.len() <= slot {
4744 self.flip_flop_active.resize(slot + 1, false);
4745 }
4746 if self.flip_flop_exclusive_left_line.len() <= slot {
4747 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4748 }
4749 let active = &mut self.flip_flop_active[slot];
4750 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4751 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4752 active, excl_left, exclusive, dot, left_m, right_m,
4753 )))
4754 }
4755
4756 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
4758 &mut self,
4759 left_pat: &str,
4760 left_flags: &str,
4761 slot: usize,
4762 exclusive: bool,
4763 line: usize,
4764 rhs_line: i64,
4765 ) -> PerlResult<PerlValue> {
4766 let dot = self.scalar_flipflop_dot_line();
4767 let subject = self.scope.get_scalar("_").to_string();
4768 let left_re = self
4769 .compile_regex(left_pat, left_flags, line)
4770 .map_err(|e| match e {
4771 FlowOrError::Error(err) => err,
4772 FlowOrError::Flow(_) => {
4773 PerlError::runtime("unexpected flow in regex flip-flop", line)
4774 }
4775 })?;
4776 let left_m = left_re.is_match(&subject);
4777 let right_m = dot == rhs_line;
4778 if self.flip_flop_active.len() <= slot {
4779 self.flip_flop_active.resize(slot + 1, false);
4780 }
4781 if self.flip_flop_exclusive_left_line.len() <= slot {
4782 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4783 }
4784 let active = &mut self.flip_flop_active[slot];
4785 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4786 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4787 active, excl_left, exclusive, dot, left_m, right_m,
4788 )))
4789 }
4790
4791 pub(crate) fn regex_eof_flip_flop_eval(
4796 &mut self,
4797 left_pat: &str,
4798 left_flags: &str,
4799 slot: usize,
4800 exclusive: bool,
4801 line: usize,
4802 ) -> PerlResult<PerlValue> {
4803 let dot = self.scalar_flipflop_dot_line();
4804 let subject = self.scope.get_scalar("_").to_string();
4805 let left_re = self
4806 .compile_regex(left_pat, left_flags, line)
4807 .map_err(|e| match e {
4808 FlowOrError::Error(err) => err,
4809 FlowOrError::Flow(_) => {
4810 PerlError::runtime("unexpected flow in regex/eof flip-flop", line)
4811 }
4812 })?;
4813 let left_m = left_re.is_match(&subject);
4814 let right_m = self.eof_without_arg_is_true();
4815 if self.flip_flop_active.len() <= slot {
4816 self.flip_flop_active.resize(slot + 1, false);
4817 }
4818 if self.flip_flop_exclusive_left_line.len() <= slot {
4819 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4820 }
4821 let active = &mut self.flip_flop_active[slot];
4822 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4823 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4824 active, excl_left, exclusive, dot, left_m, right_m,
4825 )))
4826 }
4827
4828 pub(crate) fn chomp_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
4830 let mut s = val.to_string();
4831 let removed = if s.ends_with('\n') {
4832 s.pop();
4833 1i64
4834 } else {
4835 0i64
4836 };
4837 self.assign_value(target, PerlValue::string(s))?;
4838 Ok(PerlValue::integer(removed))
4839 }
4840
4841 pub(crate) fn chop_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
4843 let mut s = val.to_string();
4844 let chopped = s
4845 .pop()
4846 .map(|c| PerlValue::string(c.to_string()))
4847 .unwrap_or(PerlValue::UNDEF);
4848 self.assign_value(target, PerlValue::string(s))?;
4849 Ok(chopped)
4850 }
4851
4852 pub(crate) fn regex_match_execute(
4854 &mut self,
4855 s: String,
4856 pattern: &str,
4857 flags: &str,
4858 scalar_g: bool,
4859 pos_key: &str,
4860 line: usize,
4861 ) -> ExecResult {
4862 if !flags.contains('g') && !scalar_g {
4870 let memo_hit = {
4871 if let Some(ref mem) = self.regex_match_memo {
4872 mem.pattern == pattern
4873 && mem.flags == flags
4874 && mem.multiline == self.multiline_match
4875 && mem.haystack == s
4876 } else {
4877 false
4878 }
4879 };
4880 if memo_hit {
4881 if self.regex_capture_scope_fresh {
4882 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
4883 }
4884 let (memo_s, memo_result) = {
4887 let mem = self.regex_match_memo.as_ref().expect("memo");
4888 (mem.haystack.clone(), mem.result.clone())
4889 };
4890 let re = self.compile_regex(pattern, flags, line)?;
4891 if let Some(caps) = re.captures(&memo_s) {
4892 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
4893 }
4894 self.regex_capture_scope_fresh = true;
4895 return Ok(memo_result);
4896 }
4897 }
4898 let re = self.compile_regex(pattern, flags, line)?;
4899 if flags.contains('g') && scalar_g {
4900 let key = pos_key.to_string();
4901 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
4902 if start == 0 {
4903 self.scope.set_array("^CAPTURE_ALL", vec![])?;
4904 }
4905 if start > s.len() {
4906 self.regex_pos.insert(key, None);
4907 return Ok(PerlValue::integer(0));
4908 }
4909 let sub = s.get(start..).unwrap_or("");
4910 if let Some(caps) = re.captures(sub) {
4911 let overall = caps.get(0).expect("capture 0");
4912 let abs_end = start + overall.end;
4913 self.regex_pos.insert(key, Some(abs_end));
4914 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
4915 Ok(PerlValue::integer(1))
4916 } else {
4917 self.regex_pos.insert(key, None);
4918 Ok(PerlValue::integer(0))
4919 }
4920 } else if flags.contains('g') {
4921 let mut rows = Vec::new();
4922 let mut last_caps: Option<PerlCaptures<'_>> = None;
4923 for caps in re.captures_iter(&s) {
4924 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
4925 &caps,
4926 )));
4927 last_caps = Some(caps);
4928 }
4929 self.scope.set_array("^CAPTURE_ALL", rows)?;
4930 let matches: Vec<PerlValue> = match &*re {
4931 PerlCompiledRegex::Rust(r) => r
4932 .find_iter(&s)
4933 .map(|m| PerlValue::string(m.as_str().to_string()))
4934 .collect(),
4935 PerlCompiledRegex::Fancy(r) => r
4936 .find_iter(&s)
4937 .filter_map(|m| m.ok())
4938 .map(|m| PerlValue::string(m.as_str().to_string()))
4939 .collect(),
4940 PerlCompiledRegex::Pcre2(r) => r
4941 .find_iter(s.as_bytes())
4942 .filter_map(|m| m.ok())
4943 .map(|m| {
4944 let t = s.get(m.start()..m.end()).unwrap_or("");
4945 PerlValue::string(t.to_string())
4946 })
4947 .collect(),
4948 };
4949 if matches.is_empty() {
4950 Ok(PerlValue::integer(0))
4951 } else {
4952 if let Some(caps) = last_caps {
4953 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
4954 }
4955 Ok(PerlValue::array(matches))
4956 }
4957 } else if let Some(caps) = re.captures(&s) {
4958 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
4959 let result = PerlValue::integer(1);
4960 self.regex_match_memo = Some(RegexMatchMemo {
4961 pattern: pattern.to_string(),
4962 flags: flags.to_string(),
4963 multiline: self.multiline_match,
4964 haystack: s,
4965 result: result.clone(),
4966 });
4967 self.regex_capture_scope_fresh = true;
4968 Ok(result)
4969 } else {
4970 let result = PerlValue::integer(0);
4971 self.regex_match_memo = Some(RegexMatchMemo {
4973 pattern: pattern.to_string(),
4974 flags: flags.to_string(),
4975 multiline: self.multiline_match,
4976 haystack: s,
4977 result: result.clone(),
4978 });
4979 Ok(result)
4982 }
4983 }
4984
4985 pub(crate) fn expand_env_braces_in_subst(
4989 &mut self,
4990 raw: &str,
4991 line: usize,
4992 ) -> PerlResult<String> {
4993 self.materialize_env_if_needed();
4994 let mut out = String::new();
4995 let mut rest = raw;
4996 while let Some(idx) = rest.find("$ENV{") {
4997 out.push_str(&rest[..idx]);
4998 let after = &rest[idx + 5..];
4999 let end = after
5000 .find('}')
5001 .ok_or_else(|| PerlError::runtime("Unclosed $ENV{...} in s///", line))?;
5002 let key = &after[..end];
5003 let val = self.scope.get_hash_element("ENV", key);
5004 out.push_str(&val.to_string());
5005 rest = &after[end + 1..];
5006 }
5007 out.push_str(rest);
5008 Ok(out)
5009 }
5010
5011 pub(crate) fn regex_subst_execute(
5017 &mut self,
5018 s: String,
5019 pattern: &str,
5020 replacement: &str,
5021 flags: &str,
5022 target: &Expr,
5023 line: usize,
5024 ) -> ExecResult {
5025 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
5026 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
5027 let re = self.compile_regex(&pattern, &re_flags, line)?;
5028 if flags.contains('e') {
5029 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
5030 }
5031 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
5032 let replacement = self.interpolate_replacement_string(&replacement);
5033 let replacement = normalize_replacement_backrefs(&replacement);
5034 let last_caps = if flags.contains('g') {
5035 let mut rows = Vec::new();
5036 let mut last = None;
5037 for caps in re.captures_iter(&s) {
5038 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5039 &caps,
5040 )));
5041 last = Some(caps);
5042 }
5043 self.scope.set_array("^CAPTURE_ALL", rows)?;
5044 last
5045 } else {
5046 re.captures(&s)
5047 };
5048 if let Some(caps) = last_caps {
5049 let mode = if flags.contains('g') {
5050 CaptureAllMode::Skip
5051 } else {
5052 CaptureAllMode::Empty
5053 };
5054 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
5055 }
5056 let (new_s, count) = if flags.contains('g') {
5057 let count = re.find_iter_count(&s);
5058 (re.replace_all(&s, replacement.as_str()), count)
5059 } else {
5060 let count = if re.is_match(&s) { 1 } else { 0 };
5061 (re.replace(&s, replacement.as_str()), count)
5062 };
5063 if flags.contains('r') {
5064 Ok(PerlValue::string(new_s))
5066 } else {
5067 self.assign_value(target, PerlValue::string(new_s))?;
5068 Ok(PerlValue::integer(count as i64))
5069 }
5070 }
5071
5072 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
5075 let prep_source = |raw: &str| -> String {
5076 let mut code = raw.trim().to_string();
5077 if !code.ends_with(';') {
5078 code.push(';');
5079 }
5080 code
5081 };
5082 let mut cur = prep_source(replacement);
5083 let mut last = PerlValue::UNDEF;
5084 for round in 0..e_count {
5085 last = crate::parse_and_run_string(&cur, self)?;
5086 if round + 1 < e_count {
5087 cur = prep_source(&last.to_string());
5088 }
5089 }
5090 Ok(last)
5091 }
5092
5093 fn regex_subst_execute_eval(
5094 &mut self,
5095 s: String,
5096 re: &PerlCompiledRegex,
5097 replacement: &str,
5098 flags: &str,
5099 target: &Expr,
5100 line: usize,
5101 ) -> ExecResult {
5102 let e_count = flags.chars().filter(|c| *c == 'e').count();
5103 if e_count == 0 {
5104 return Err(PerlError::runtime("s///e: internal error (no e flag)", line).into());
5105 }
5106
5107 if flags.contains('g') {
5108 let mut rows = Vec::new();
5109 let mut out = String::new();
5110 let mut last = 0usize;
5111 let mut count = 0usize;
5112 for caps in re.captures_iter(&s) {
5113 let m0 = caps.get(0).expect("regex capture 0");
5114 out.push_str(&s[last..m0.start]);
5115 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5116 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5117 out.push_str(&repl_val.to_string());
5118 last = m0.end;
5119 count += 1;
5120 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5121 &caps,
5122 )));
5123 }
5124 self.scope.set_array("^CAPTURE_ALL", rows)?;
5125 out.push_str(&s[last..]);
5126 if flags.contains('r') {
5127 return Ok(PerlValue::string(out));
5128 }
5129 self.assign_value(target, PerlValue::string(out))?;
5130 return Ok(PerlValue::integer(count as i64));
5131 }
5132 if let Some(caps) = re.captures(&s) {
5133 let m0 = caps.get(0).expect("regex capture 0");
5134 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5135 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5136 let mut out = String::new();
5137 out.push_str(&s[..m0.start]);
5138 out.push_str(&repl_val.to_string());
5139 out.push_str(&s[m0.end..]);
5140 if flags.contains('r') {
5141 return Ok(PerlValue::string(out));
5142 }
5143 self.assign_value(target, PerlValue::string(out))?;
5144 return Ok(PerlValue::integer(1));
5145 }
5146 if flags.contains('r') {
5147 return Ok(PerlValue::string(s));
5148 }
5149 self.assign_value(target, PerlValue::string(s))?;
5150 Ok(PerlValue::integer(0))
5151 }
5152
5153 pub(crate) fn regex_transliterate_execute(
5155 &mut self,
5156 s: String,
5157 from: &str,
5158 to: &str,
5159 flags: &str,
5160 target: &Expr,
5161 line: usize,
5162 ) -> ExecResult {
5163 let _ = line;
5164 let from_chars = Self::tr_expand_ranges(from);
5165 let to_chars = Self::tr_expand_ranges(to);
5166 let delete_mode = flags.contains('d');
5167 let mut count = 0i64;
5168 let new_s: String = s
5169 .chars()
5170 .filter_map(|c| {
5171 if let Some(pos) = from_chars.iter().position(|&fc| fc == c) {
5172 count += 1;
5173 if delete_mode {
5174 if pos < to_chars.len() {
5176 Some(to_chars[pos])
5177 } else {
5178 None }
5180 } else {
5181 Some(to_chars.get(pos).or(to_chars.last()).copied().unwrap_or(c))
5183 }
5184 } else {
5185 Some(c)
5186 }
5187 })
5188 .collect();
5189 if flags.contains('r') {
5190 Ok(PerlValue::string(new_s))
5192 } else {
5193 self.assign_value(target, PerlValue::string(new_s))?;
5194 Ok(PerlValue::integer(count))
5195 }
5196 }
5197
5198 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
5201 let raw: Vec<char> = spec.chars().collect();
5202 let mut out = Vec::with_capacity(raw.len());
5203 let mut i = 0;
5204 while i < raw.len() {
5205 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
5206 let start = raw[i] as u32;
5207 let end = raw[i + 2] as u32;
5208 for code in start..=end {
5209 if let Some(c) = char::from_u32(code) {
5210 out.push(c);
5211 }
5212 }
5213 i += 3;
5214 } else {
5215 out.push(raw[i]);
5216 i += 1;
5217 }
5218 }
5219 out
5220 }
5221
5222 pub(crate) fn splice_builtin_execute(
5224 &mut self,
5225 args: &[PerlValue],
5226 line: usize,
5227 ) -> PerlResult<PerlValue> {
5228 if args.is_empty() {
5229 return Err(PerlError::runtime("splice: missing array", line));
5230 }
5231 let arr_name = args[0].to_string();
5232 let arr_len = self.scope.array_len(&arr_name);
5233 let offset_val = args
5234 .get(1)
5235 .cloned()
5236 .unwrap_or_else(|| PerlValue::integer(0));
5237 let length_val = match args.get(2) {
5238 None => PerlValue::UNDEF,
5239 Some(v) => v.clone(),
5240 };
5241 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
5242 let rep_vals: Vec<PerlValue> = args.iter().skip(3).cloned().collect();
5243 let arr = self.scope.get_array_mut(&arr_name)?;
5244 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
5245 for (i, v) in rep_vals.into_iter().enumerate() {
5246 arr.insert(off + i, v);
5247 }
5248 Ok(match self.wantarray_kind {
5249 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
5250 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
5251 })
5252 }
5253
5254 pub(crate) fn unshift_builtin_execute(
5256 &mut self,
5257 args: &[PerlValue],
5258 line: usize,
5259 ) -> PerlResult<PerlValue> {
5260 if args.is_empty() {
5261 return Err(PerlError::runtime("unshift: missing array", line));
5262 }
5263 let arr_name = args[0].to_string();
5264 let mut flat_vals: Vec<PerlValue> = Vec::new();
5265 for a in args.iter().skip(1) {
5266 if let Some(items) = a.as_array_vec() {
5267 flat_vals.extend(items);
5268 } else {
5269 flat_vals.push(a.clone());
5270 }
5271 }
5272 let arr = self.scope.get_array_mut(&arr_name)?;
5273 for (i, v) in flat_vals.into_iter().enumerate() {
5274 arr.insert(i, v);
5275 }
5276 Ok(PerlValue::integer(arr.len() as i64))
5277 }
5278
5279 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
5282 if upper == 0.0 {
5283 self.rand_rng.gen_range(0.0..1.0)
5284 } else if upper > 0.0 {
5285 self.rand_rng.gen_range(0.0..upper)
5286 } else {
5287 self.rand_rng.gen_range(upper..0.0)
5288 }
5289 }
5290
5291 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
5293 let n = if let Some(s) = seed {
5294 s as i64
5295 } else {
5296 std::time::SystemTime::now()
5297 .duration_since(std::time::UNIX_EPOCH)
5298 .map(|d| d.as_secs() as i64)
5299 .unwrap_or(1)
5300 };
5301 let mag = n.unsigned_abs();
5302 self.rand_rng = StdRng::seed_from_u64(mag);
5303 n.abs()
5304 }
5305
5306 pub fn set_file(&mut self, file: &str) {
5307 self.file = file.to_string();
5308 }
5309
5310 pub fn repl_completion_names(&self) -> Vec<String> {
5312 let mut v = self.scope.repl_binding_names();
5313 v.extend(self.subs.keys().cloned());
5314 v.sort();
5315 v.dedup();
5316 v
5317 }
5318
5319 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
5321 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
5322 subs.sort();
5323 let mut classes: HashSet<String> = HashSet::new();
5324 for k in &subs {
5325 if let Some((pkg, rest)) = k.split_once("::") {
5326 if !rest.contains("::") {
5327 classes.insert(pkg.to_string());
5328 }
5329 }
5330 }
5331 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
5332 for bn in self.scope.repl_binding_names() {
5333 if let Some(r) = bn.strip_prefix('$') {
5334 let v = self.scope.get_scalar(r);
5335 if let Some(b) = v.as_blessed_ref() {
5336 blessed_scalars.insert(r.to_string(), b.class.clone());
5337 classes.insert(b.class.clone());
5338 }
5339 }
5340 }
5341 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
5342 for c in classes {
5343 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
5344 }
5345 ReplCompletionSnapshot {
5346 subs,
5347 blessed_scalars,
5348 isa_for_class,
5349 }
5350 }
5351
5352 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
5353 if n == 0 {
5354 return Err(FlowOrError::Error(PerlError::runtime(
5355 "bench: iteration count must be positive",
5356 line,
5357 )));
5358 }
5359 let warmup = (n / 10).clamp(1, 10);
5360 for _ in 0..warmup {
5361 self.exec_block(body)?;
5362 }
5363 let mut samples = Vec::with_capacity(n);
5364 for _ in 0..n {
5365 let start = std::time::Instant::now();
5366 self.exec_block(body)?;
5367 samples.push(start.elapsed().as_secs_f64() * 1000.0);
5368 }
5369 let mut sorted = samples.clone();
5370 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
5371 let min_ms = sorted[0];
5372 let mean = samples.iter().sum::<f64>() / n as f64;
5373 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
5374 .saturating_sub(1)
5375 .min(n - 1);
5376 let p99_ms = sorted[p99_idx];
5377 Ok(PerlValue::string(format!(
5378 "bench: n={} warmup={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
5379 n, warmup, min_ms, mean, p99_ms
5380 )))
5381 }
5382
5383 pub fn execute(&mut self, program: &Program) -> PerlResult<PerlValue> {
5384 if self.line_mode_skip_main {
5387 return self.execute_tree(program);
5388 }
5389 if let Some(result) = crate::try_vm_execute(program, self) {
5392 return result;
5393 }
5394
5395 self.execute_tree(program)
5397 }
5398
5399 pub fn run_end_blocks(&mut self) -> PerlResult<()> {
5401 self.global_phase = "END".to_string();
5402 let ends = std::mem::take(&mut self.end_blocks);
5403 for block in &ends {
5404 self.exec_block(block).map_err(|e| match e {
5405 FlowOrError::Error(e) => e,
5406 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in END", 0),
5407 })?;
5408 }
5409 Ok(())
5410 }
5411
5412 pub fn run_global_teardown(&mut self) -> PerlResult<()> {
5415 self.global_phase = "DESTRUCT".to_string();
5416 self.drain_pending_destroys(0)
5417 }
5418
5419 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> PerlResult<()> {
5421 loop {
5422 let batch = crate::pending_destroy::take_queue();
5423 if batch.is_empty() {
5424 break;
5425 }
5426 for (class, payload) in batch {
5427 let fq = format!("{}::DESTROY", class);
5428 let Some(sub) = self.subs.get(&fq).cloned() else {
5429 continue;
5430 };
5431 let inv = PerlValue::blessed(Arc::new(
5432 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
5433 ));
5434 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
5435 Ok(_) => {}
5436 Err(FlowOrError::Error(e)) => return Err(e),
5437 Err(FlowOrError::Flow(Flow::Return(_))) => {}
5438 Err(FlowOrError::Flow(other)) => {
5439 return Err(PerlError::runtime(
5440 format!("DESTROY: unexpected control flow ({other:?})"),
5441 line,
5442 ));
5443 }
5444 }
5445 }
5446 }
5447 Ok(())
5448 }
5449
5450 pub fn execute_tree(&mut self, program: &Program) -> PerlResult<PerlValue> {
5452 self.global_phase = "RUN".to_string();
5454 self.clear_flip_flop_state();
5455 self.prepare_program_top_level(program)?;
5457
5458 let begins = std::mem::take(&mut self.begin_blocks);
5460 if !begins.is_empty() {
5461 self.global_phase = "START".to_string();
5462 }
5463 for block in &begins {
5464 self.exec_block(block).map_err(|e| match e {
5465 FlowOrError::Error(e) => e,
5466 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in BEGIN", 0),
5467 })?;
5468 }
5469
5470 let ucs = std::mem::take(&mut self.unit_check_blocks);
5473 for block in ucs.iter().rev() {
5474 self.exec_block(block).map_err(|e| match e {
5475 FlowOrError::Error(e) => e,
5476 FlowOrError::Flow(_) => {
5477 PerlError::runtime("Unexpected flow control in UNITCHECK", 0)
5478 }
5479 })?;
5480 }
5481
5482 let checks = std::mem::take(&mut self.check_blocks);
5484 if !checks.is_empty() {
5485 self.global_phase = "CHECK".to_string();
5486 }
5487 for block in checks.iter().rev() {
5488 self.exec_block(block).map_err(|e| match e {
5489 FlowOrError::Error(e) => e,
5490 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in CHECK", 0),
5491 })?;
5492 }
5493
5494 let inits = std::mem::take(&mut self.init_blocks);
5496 if !inits.is_empty() {
5497 self.global_phase = "INIT".to_string();
5498 }
5499 for block in &inits {
5500 self.exec_block(block).map_err(|e| match e {
5501 FlowOrError::Error(e) => e,
5502 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in INIT", 0),
5503 })?;
5504 }
5505
5506 self.global_phase = "RUN".to_string();
5507
5508 if self.line_mode_skip_main {
5509 return Ok(PerlValue::UNDEF);
5512 }
5513
5514 let mut last = PerlValue::UNDEF;
5516 for stmt in &program.statements {
5517 match &stmt.kind {
5518 StmtKind::Begin(_)
5519 | StmtKind::UnitCheck(_)
5520 | StmtKind::Check(_)
5521 | StmtKind::Init(_)
5522 | StmtKind::End(_)
5523 | StmtKind::UsePerlVersion { .. }
5524 | StmtKind::Use { .. }
5525 | StmtKind::No { .. }
5526 | StmtKind::FormatDecl { .. } => continue,
5527 _ => {
5528 match self.exec_statement(stmt) {
5529 Ok(val) => last = val,
5530 Err(FlowOrError::Error(e)) => {
5531 self.global_phase = "END".to_string();
5533 let ends = std::mem::take(&mut self.end_blocks);
5534 for block in &ends {
5535 let _ = self.exec_block(block);
5536 }
5537 return Err(e);
5538 }
5539 Err(FlowOrError::Flow(Flow::Return(v))) => {
5540 last = v;
5541 break;
5542 }
5543 Err(FlowOrError::Flow(_)) => {}
5544 }
5545 }
5546 }
5547 }
5548
5549 self.global_phase = "END".to_string();
5551 let ends = std::mem::take(&mut self.end_blocks);
5552 for block in &ends {
5553 let _ = self.exec_block(block);
5554 }
5555
5556 self.drain_pending_destroys(0)?;
5557 Ok(last)
5558 }
5559
5560 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
5561 self.exec_block_with_tail(block, WantarrayCtx::Void)
5562 }
5563
5564 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5567 let uses_goto = block
5568 .iter()
5569 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
5570 if uses_goto {
5571 self.scope_push_hook();
5572 let r = self.exec_block_with_goto_tail(block, tail);
5573 self.scope_pop_hook();
5574 r
5575 } else {
5576 self.scope_push_hook();
5577 let result = self.exec_block_no_scope_with_tail(block, tail);
5578 self.scope_pop_hook();
5579 result
5580 }
5581 }
5582
5583 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5584 let mut map: HashMap<String, usize> = HashMap::new();
5585 for (i, s) in block.iter().enumerate() {
5586 if let Some(l) = &s.label {
5587 map.insert(l.clone(), i);
5588 }
5589 }
5590 let mut pc = 0usize;
5591 let mut last = PerlValue::UNDEF;
5592 let last_idx = block.len().saturating_sub(1);
5593 while pc < block.len() {
5594 if let StmtKind::Goto { target } = &block[pc].kind {
5595 let line = block[pc].line;
5596 let name = self.eval_expr(target)?.to_string();
5597 pc = *map.get(&name).ok_or_else(|| {
5598 FlowOrError::Error(PerlError::runtime(
5599 format!("goto: unknown label {}", name),
5600 line,
5601 ))
5602 })?;
5603 continue;
5604 }
5605 let v = if pc == last_idx {
5606 match &block[pc].kind {
5607 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
5608 _ => self.exec_statement(&block[pc])?,
5609 }
5610 } else {
5611 self.exec_statement(&block[pc])?
5612 };
5613 last = v;
5614 pc += 1;
5615 }
5616 Ok(last)
5617 }
5618
5619 #[inline]
5622 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
5623 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
5624 }
5625
5626 pub(crate) fn exec_block_no_scope_with_tail(
5627 &mut self,
5628 block: &Block,
5629 tail: WantarrayCtx,
5630 ) -> ExecResult {
5631 if block.is_empty() {
5632 return Ok(PerlValue::UNDEF);
5633 }
5634 let last_i = block.len() - 1;
5635 for (i, stmt) in block.iter().enumerate() {
5636 if i < last_i {
5637 self.exec_statement(stmt)?;
5638 } else {
5639 return match &stmt.kind {
5640 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
5641 _ => self.exec_statement(stmt),
5642 };
5643 }
5644 }
5645 Ok(PerlValue::UNDEF)
5646 }
5647
5648 pub(crate) fn spawn_async_block(&self, block: &Block) -> PerlValue {
5650 use parking_lot::Mutex as ParkMutex;
5651
5652 let block = block.clone();
5653 let subs = self.subs.clone();
5654 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5655 let result = Arc::new(ParkMutex::new(None));
5656 let join = Arc::new(ParkMutex::new(None));
5657 let result2 = result.clone();
5658 let h = std::thread::spawn(move || {
5659 let mut interp = Interpreter::new();
5660 interp.subs = subs;
5661 interp.scope.restore_capture(&scalars);
5662 interp.scope.restore_atomics(&aar, &ahash);
5663 interp.enable_parallel_guard();
5664 let r = match interp.exec_block(&block) {
5665 Ok(v) => Ok(v),
5666 Err(FlowOrError::Error(e)) => Err(e),
5667 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5668 Err(PerlError::runtime("yield inside async/spawn block", 0))
5669 }
5670 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5671 };
5672 *result2.lock() = Some(r);
5673 });
5674 *join.lock() = Some(h);
5675 PerlValue::async_task(Arc::new(PerlAsyncTask { result, join }))
5676 }
5677
5678 pub(crate) fn eval_timeout_block(
5680 &mut self,
5681 body: &Block,
5682 secs: f64,
5683 line: usize,
5684 ) -> ExecResult {
5685 use std::sync::mpsc::channel;
5686 use std::time::Duration;
5687
5688 let block = body.clone();
5689 let subs = self.subs.clone();
5690 let struct_defs = self.struct_defs.clone();
5691 let enum_defs = self.enum_defs.clone();
5692 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5693 self.materialize_env_if_needed();
5694 let env = self.env.clone();
5695 let argv = self.argv.clone();
5696 let inc = self.scope.get_array("INC");
5697 let (tx, rx) = channel::<PerlResult<PerlValue>>();
5698 let _handle = std::thread::spawn(move || {
5699 let mut interp = Interpreter::new();
5700 interp.subs = subs;
5701 interp.struct_defs = struct_defs;
5702 interp.enum_defs = enum_defs;
5703 interp.env = env.clone();
5704 interp.argv = argv.clone();
5705 interp.scope.declare_array(
5706 "ARGV",
5707 argv.iter().map(|s| PerlValue::string(s.clone())).collect(),
5708 );
5709 for (k, v) in env {
5710 interp
5711 .scope
5712 .set_hash_element("ENV", &k, v)
5713 .expect("set ENV in timeout thread");
5714 }
5715 interp.scope.declare_array("INC", inc);
5716 interp.scope.restore_capture(&scalars);
5717 interp.scope.restore_atomics(&aar, &ahash);
5718 interp.enable_parallel_guard();
5719 let out: PerlResult<PerlValue> = match interp.exec_block(&block) {
5720 Ok(v) => Ok(v),
5721 Err(FlowOrError::Error(e)) => Err(e),
5722 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5723 Err(PerlError::runtime("yield inside eval_timeout block", 0))
5724 }
5725 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5726 };
5727 let _ = tx.send(out);
5728 });
5729 let dur = Duration::from_secs_f64(secs.max(0.0));
5730 match rx.recv_timeout(dur) {
5731 Ok(Ok(v)) => Ok(v),
5732 Ok(Err(e)) => Err(FlowOrError::Error(e)),
5733 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(PerlError::runtime(
5734 format!(
5735 "eval_timeout: exceeded {} second(s) (worker continues in background)",
5736 secs
5737 ),
5738 line,
5739 )
5740 .into()),
5741 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(PerlError::runtime(
5742 "eval_timeout: worker thread panicked or disconnected",
5743 line,
5744 )
5745 .into()),
5746 }
5747 }
5748
5749 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
5750 let mut last = PerlValue::UNDEF;
5751 for stmt in body {
5752 match &stmt.kind {
5753 StmtKind::When { cond, body: wb } => {
5754 if self.when_matches(cond)? {
5755 return self.exec_block_smart(wb);
5756 }
5757 }
5758 StmtKind::DefaultCase { body: db } => {
5759 return self.exec_block_smart(db);
5760 }
5761 _ => {
5762 last = self.exec_statement(stmt)?;
5763 }
5764 }
5765 }
5766 Ok(last)
5767 }
5768
5769 pub(crate) fn exec_given_with_topic_value(
5771 &mut self,
5772 topic: PerlValue,
5773 body: &Block,
5774 ) -> ExecResult {
5775 self.scope_push_hook();
5776 self.scope.declare_scalar("_", topic);
5777 self.english_note_lexical_scalar("_");
5778 let r = self.exec_given_body(body);
5779 self.scope_pop_hook();
5780 r
5781 }
5782
5783 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
5784 let t = self.eval_expr(topic)?;
5785 self.exec_given_with_topic_value(t, body)
5786 }
5787
5788 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5790 let topic = self.scope.get_scalar("_");
5791 let line = cond.line;
5792 match &cond.kind {
5793 ExprKind::Regex(pattern, flags) => {
5794 let re = self.compile_regex(pattern, flags, line)?;
5795 let s = topic.to_string();
5796 Ok(re.is_match(&s))
5797 }
5798 ExprKind::String(s) => Ok(topic.to_string() == *s),
5799 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
5800 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
5801 _ => {
5802 let c = self.eval_expr(cond)?;
5803 Ok(self.smartmatch_when(&topic, &c))
5804 }
5805 }
5806 }
5807
5808 fn smartmatch_when(&self, topic: &PerlValue, c: &PerlValue) -> bool {
5809 if let Some(re) = c.as_regex() {
5810 return re.is_match(&topic.to_string());
5811 }
5812 topic.to_string() == c.to_string()
5813 }
5814
5815 pub(crate) fn eval_boolean_rvalue_condition(
5817 &mut self,
5818 cond: &Expr,
5819 ) -> Result<bool, FlowOrError> {
5820 match &cond.kind {
5821 ExprKind::Regex(pattern, flags) => {
5822 let topic = self.scope.get_scalar("_");
5823 let line = cond.line;
5824 let s = topic.to_string();
5825 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
5826 Ok(v.is_true())
5827 }
5828 ExprKind::ReadLine(_) => {
5830 let v = self.eval_expr(cond)?;
5831 self.scope.set_topic(v.clone());
5832 Ok(!v.is_undef())
5833 }
5834 _ => {
5835 let v = self.eval_expr(cond)?;
5836 Ok(v.is_true())
5837 }
5838 }
5839 }
5840
5841 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5843 self.eval_boolean_rvalue_condition(cond)
5844 }
5845
5846 pub(crate) fn eval_algebraic_match(
5847 &mut self,
5848 subject: &Expr,
5849 arms: &[MatchArm],
5850 line: usize,
5851 ) -> ExecResult {
5852 let val = self.eval_algebraic_match_subject(subject, line)?;
5853 self.eval_algebraic_match_with_subject_value(val, arms, line)
5854 }
5855
5856 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
5858 match &subject.kind {
5859 ExprKind::ArrayVar(name) => {
5860 self.check_strict_array_var(name, line)?;
5861 let aname = self.stash_array_name_for_package(name);
5862 Ok(PerlValue::array_binding_ref(aname))
5863 }
5864 ExprKind::HashVar(name) => {
5865 self.check_strict_hash_var(name, line)?;
5866 self.touch_env_hash(name);
5867 Ok(PerlValue::hash_binding_ref(name.clone()))
5868 }
5869 _ => self.eval_expr(subject),
5870 }
5871 }
5872
5873 pub(crate) fn eval_algebraic_match_with_subject_value(
5875 &mut self,
5876 val: PerlValue,
5877 arms: &[MatchArm],
5878 line: usize,
5879 ) -> ExecResult {
5880 if let Some(e) = val.as_enum_inst() {
5882 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
5883 if !has_catchall {
5884 let covered: Vec<String> = arms
5885 .iter()
5886 .filter_map(|a| {
5887 if let MatchPattern::Value(expr) = &a.pattern {
5888 if let ExprKind::FuncCall { name, .. } = &expr.kind {
5889 return name.rsplit_once("::").map(|(_, v)| v.to_string());
5890 }
5891 }
5892 None
5893 })
5894 .collect();
5895 let missing: Vec<&str> = e
5896 .def
5897 .variants
5898 .iter()
5899 .filter(|v| !covered.contains(&v.name))
5900 .map(|v| v.name.as_str())
5901 .collect();
5902 if !missing.is_empty() {
5903 return Err(PerlError::runtime(
5904 format!(
5905 "non-exhaustive match on enum `{}`: missing variant(s) {}",
5906 e.def.name,
5907 missing.join(", ")
5908 ),
5909 line,
5910 )
5911 .into());
5912 }
5913 }
5914 }
5915 for arm in arms {
5916 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
5917 let re = self.compile_regex(pattern, flags, line)?;
5918 let s = val.to_string();
5919 if let Some(caps) = re.captures(&s) {
5920 self.scope_push_hook();
5921 self.scope.declare_scalar("_", val.clone());
5922 self.english_note_lexical_scalar("_");
5923 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
5924 let guard_ok = if let Some(g) = &arm.guard {
5925 self.eval_expr(g)?.is_true()
5926 } else {
5927 true
5928 };
5929 if !guard_ok {
5930 self.scope_pop_hook();
5931 continue;
5932 }
5933 let out = self.eval_expr(&arm.body);
5934 self.scope_pop_hook();
5935 return out;
5936 }
5937 continue;
5938 }
5939 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
5940 self.scope_push_hook();
5941 self.scope.declare_scalar("_", val.clone());
5942 self.english_note_lexical_scalar("_");
5943 for b in bindings {
5944 match b {
5945 PatternBinding::Scalar(name, v) => {
5946 self.scope.declare_scalar(&name, v);
5947 self.english_note_lexical_scalar(&name);
5948 }
5949 PatternBinding::Array(name, elems) => {
5950 self.scope.declare_array(&name, elems);
5951 }
5952 }
5953 }
5954 let guard_ok = if let Some(g) = &arm.guard {
5955 self.eval_expr(g)?.is_true()
5956 } else {
5957 true
5958 };
5959 if !guard_ok {
5960 self.scope_pop_hook();
5961 continue;
5962 }
5963 let out = self.eval_expr(&arm.body);
5964 self.scope_pop_hook();
5965 return out;
5966 }
5967 }
5968 Err(PerlError::runtime(
5969 "match: no arm matched the value (add a `_` catch-all)",
5970 line,
5971 )
5972 .into())
5973 }
5974
5975 fn parse_duration_seconds(pv: &PerlValue) -> Option<f64> {
5976 let s = pv.to_string();
5977 let s = s.trim();
5978 if let Some(rest) = s.strip_suffix("ms") {
5979 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
5980 }
5981 if let Some(rest) = s.strip_suffix('s') {
5982 return rest.trim().parse::<f64>().ok();
5983 }
5984 if let Some(rest) = s.strip_suffix('m') {
5985 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
5986 }
5987 s.parse::<f64>().ok()
5988 }
5989
5990 fn eval_retry_block(
5991 &mut self,
5992 body: &Block,
5993 times: &Expr,
5994 backoff: RetryBackoff,
5995 _line: usize,
5996 ) -> ExecResult {
5997 let max = self.eval_expr(times)?.to_int().max(1) as usize;
5998 let base_ms: u64 = 10;
5999 let mut attempt = 0usize;
6000 loop {
6001 attempt += 1;
6002 match self.exec_block(body) {
6003 Ok(v) => return Ok(v),
6004 Err(FlowOrError::Error(e)) => {
6005 if attempt >= max {
6006 return Err(FlowOrError::Error(e));
6007 }
6008 let delay_ms = match backoff {
6009 RetryBackoff::None => 0,
6010 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
6011 RetryBackoff::Exponential => {
6012 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
6013 }
6014 };
6015 if delay_ms > 0 {
6016 std::thread::sleep(Duration::from_millis(delay_ms));
6017 }
6018 }
6019 Err(e) => return Err(e),
6020 }
6021 }
6022 }
6023
6024 fn eval_rate_limit_block(
6025 &mut self,
6026 slot: u32,
6027 max: &Expr,
6028 window: &Expr,
6029 body: &Block,
6030 _line: usize,
6031 ) -> ExecResult {
6032 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
6033 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
6034 .filter(|s| *s > 0.0)
6035 .unwrap_or(1.0);
6036 let window_d = Duration::from_secs_f64(window_sec);
6037 let slot = slot as usize;
6038 while self.rate_limit_slots.len() <= slot {
6039 self.rate_limit_slots.push(VecDeque::new());
6040 }
6041 {
6042 let dq = &mut self.rate_limit_slots[slot];
6043 loop {
6044 let now = Instant::now();
6045 while let Some(t0) = dq.front().copied() {
6046 if now.duration_since(t0) >= window_d {
6047 dq.pop_front();
6048 } else {
6049 break;
6050 }
6051 }
6052 if dq.len() < max_n || max_n == 0 {
6053 break;
6054 }
6055 let t0 = dq.front().copied().unwrap();
6056 let wait = window_d.saturating_sub(now.duration_since(t0));
6057 if wait.is_zero() {
6058 dq.pop_front();
6059 continue;
6060 }
6061 std::thread::sleep(wait);
6062 }
6063 dq.push_back(Instant::now());
6064 }
6065 self.exec_block(body)
6066 }
6067
6068 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
6069 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
6070 .filter(|s| *s > 0.0)
6071 .unwrap_or(1.0);
6072 loop {
6073 match self.exec_block(body) {
6074 Ok(_) => {}
6075 Err(e) => return Err(e),
6076 }
6077 std::thread::sleep(Duration::from_secs_f64(sec));
6078 }
6079 }
6080
6081 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> PerlResult<PerlValue> {
6083 let pair = |value: PerlValue, more: i64| {
6084 PerlValue::array_ref(Arc::new(RwLock::new(vec![value, PerlValue::integer(more)])))
6085 };
6086 let mut exhausted = gen.exhausted.lock();
6087 if *exhausted {
6088 return Ok(pair(PerlValue::UNDEF, 0));
6089 }
6090 let mut pc = gen.pc.lock();
6091 let mut scope_started = gen.scope_started.lock();
6092 if *pc >= gen.block.len() {
6093 if *scope_started {
6094 self.scope_pop_hook();
6095 *scope_started = false;
6096 }
6097 *exhausted = true;
6098 return Ok(pair(PerlValue::UNDEF, 0));
6099 }
6100 if !*scope_started {
6101 self.scope_push_hook();
6102 *scope_started = true;
6103 }
6104 self.in_generator = true;
6105 while *pc < gen.block.len() {
6106 let stmt = &gen.block[*pc];
6107 match self.exec_statement(stmt) {
6108 Ok(_) => {
6109 *pc += 1;
6110 }
6111 Err(FlowOrError::Flow(Flow::Yield(v))) => {
6112 *pc += 1;
6113 self.in_generator = false;
6114 if *scope_started {
6117 self.scope_pop_hook();
6118 *scope_started = false;
6119 }
6120 return Ok(pair(v, 1));
6121 }
6122 Err(e) => {
6123 self.in_generator = false;
6124 if *scope_started {
6125 self.scope_pop_hook();
6126 *scope_started = false;
6127 }
6128 return Err(match e {
6129 FlowOrError::Error(ee) => ee,
6130 FlowOrError::Flow(Flow::Yield(_)) => {
6131 unreachable!("yield handled above")
6132 }
6133 FlowOrError::Flow(flow) => PerlError::runtime(
6134 format!("unexpected control flow in generator: {:?}", flow),
6135 0,
6136 ),
6137 });
6138 }
6139 }
6140 }
6141 self.in_generator = false;
6142 if *scope_started {
6143 self.scope_pop_hook();
6144 *scope_started = false;
6145 }
6146 *exhausted = true;
6147 Ok(pair(PerlValue::UNDEF, 0))
6148 }
6149
6150 fn match_pattern_try(
6151 &mut self,
6152 subject: &PerlValue,
6153 pattern: &MatchPattern,
6154 line: usize,
6155 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6156 match pattern {
6157 MatchPattern::Any => Ok(Some(vec![])),
6158 MatchPattern::Regex { .. } => {
6159 unreachable!("regex arms are handled in eval_algebraic_match")
6160 }
6161 MatchPattern::Value(expr) => {
6162 if self.match_pattern_value_alternation(subject, expr, line)? {
6163 Ok(Some(vec![]))
6164 } else {
6165 Ok(None)
6166 }
6167 }
6168 MatchPattern::Array(elems) => {
6169 let Some(arr) = self.match_subject_as_array(subject) else {
6170 return Ok(None);
6171 };
6172 self.match_array_pattern_elems(&arr, elems, line)
6173 }
6174 MatchPattern::Hash(pairs) => {
6175 let Some(h) = self.match_subject_as_hash(subject) else {
6176 return Ok(None);
6177 };
6178 self.match_hash_pattern_pairs(&h, pairs, line)
6179 }
6180 MatchPattern::OptionSome(name) => {
6181 let Some(arr) = self.match_subject_as_array(subject) else {
6182 return Ok(None);
6183 };
6184 if arr.len() < 2 {
6185 return Ok(None);
6186 }
6187 if !arr[1].is_true() {
6188 return Ok(None);
6189 }
6190 Ok(Some(vec![PatternBinding::Scalar(
6191 name.clone(),
6192 arr[0].clone(),
6193 )]))
6194 }
6195 }
6196 }
6197
6198 fn match_pattern_value_alternation(
6201 &mut self,
6202 subject: &PerlValue,
6203 expr: &Expr,
6204 line: usize,
6205 ) -> Result<bool, FlowOrError> {
6206 if let ExprKind::BinOp {
6207 left,
6208 op: BinOp::BitOr,
6209 right,
6210 } = &expr.kind
6211 {
6212 if self.match_pattern_value_alternation(subject, left, line)? {
6213 return Ok(true);
6214 }
6215 return self.match_pattern_value_alternation(subject, right, line);
6216 }
6217 let pv = self.eval_expr(expr)?;
6218 Ok(self.smartmatch_when(subject, &pv))
6219 }
6220
6221 fn match_subject_as_array(&self, v: &PerlValue) -> Option<Vec<PerlValue>> {
6223 if let Some(a) = v.as_array_vec() {
6224 return Some(a);
6225 }
6226 if let Some(r) = v.as_array_ref() {
6227 return Some(r.read().clone());
6228 }
6229 if let Some(name) = v.as_array_binding_name() {
6230 return Some(self.scope.get_array(&name));
6231 }
6232 None
6233 }
6234
6235 fn match_subject_as_hash(&mut self, v: &PerlValue) -> Option<IndexMap<String, PerlValue>> {
6236 if let Some(h) = v.as_hash_map() {
6237 return Some(h);
6238 }
6239 if let Some(r) = v.as_hash_ref() {
6240 return Some(r.read().clone());
6241 }
6242 if let Some(name) = v.as_hash_binding_name() {
6243 self.touch_env_hash(&name);
6244 return Some(self.scope.get_hash(&name));
6245 }
6246 None
6247 }
6248
6249 pub(crate) fn hash_slice_deref_values(
6252 &mut self,
6253 container: &PerlValue,
6254 key_values: &[PerlValue],
6255 line: usize,
6256 ) -> Result<PerlValue, FlowOrError> {
6257 let h = if let Some(m) = self.match_subject_as_hash(container) {
6258 m
6259 } else {
6260 return Err(PerlError::runtime(
6261 "Hash slice dereference needs a hash or hash reference value",
6262 line,
6263 )
6264 .into());
6265 };
6266 let mut result = Vec::new();
6267 for kv in key_values {
6268 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6269 vv.iter().map(|x| x.to_string()).collect()
6270 } else {
6271 vec![kv.to_string()]
6272 };
6273 for k in key_strings {
6274 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6275 }
6276 }
6277 Ok(PerlValue::array(result))
6278 }
6279
6280 pub(crate) fn assign_hash_slice_one_key(
6283 &mut self,
6284 container: PerlValue,
6285 key: &str,
6286 val: PerlValue,
6287 line: usize,
6288 ) -> Result<PerlValue, FlowOrError> {
6289 if let Some(r) = container.as_hash_ref() {
6290 r.write().insert(key.to_string(), val);
6291 return Ok(PerlValue::UNDEF);
6292 }
6293 if let Some(name) = container.as_hash_binding_name() {
6294 self.touch_env_hash(&name);
6295 self.scope
6296 .set_hash_element(&name, key, val)
6297 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6298 return Ok(PerlValue::UNDEF);
6299 }
6300 if let Some(s) = container.as_str() {
6301 self.touch_env_hash(&s);
6302 if self.strict_refs {
6303 return Err(PerlError::runtime(
6304 format!(
6305 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6306 s
6307 ),
6308 line,
6309 )
6310 .into());
6311 }
6312 self.scope
6313 .set_hash_element(&s, key, val)
6314 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6315 return Ok(PerlValue::UNDEF);
6316 }
6317 Err(PerlError::runtime(
6318 "Hash slice assignment needs a hash or hash reference value",
6319 line,
6320 )
6321 .into())
6322 }
6323
6324 pub(crate) fn assign_named_hash_slice(
6327 &mut self,
6328 hash: &str,
6329 key_values: Vec<PerlValue>,
6330 val: PerlValue,
6331 line: usize,
6332 ) -> Result<PerlValue, FlowOrError> {
6333 self.touch_env_hash(hash);
6334 let mut ks: Vec<String> = Vec::new();
6335 for kv in key_values {
6336 if let Some(vv) = kv.as_array_vec() {
6337 ks.extend(vv.iter().map(|x| x.to_string()));
6338 } else {
6339 ks.push(kv.to_string());
6340 }
6341 }
6342 if ks.is_empty() {
6343 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6344 }
6345 let items = val.to_list();
6346 for (i, k) in ks.iter().enumerate() {
6347 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6348 self.scope
6349 .set_hash_element(hash, k, v)
6350 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6351 }
6352 Ok(PerlValue::UNDEF)
6353 }
6354
6355 pub(crate) fn assign_hash_slice_deref(
6357 &mut self,
6358 container: PerlValue,
6359 key_values: Vec<PerlValue>,
6360 val: PerlValue,
6361 line: usize,
6362 ) -> Result<PerlValue, FlowOrError> {
6363 let mut ks: Vec<String> = Vec::new();
6364 for kv in key_values {
6365 if let Some(vv) = kv.as_array_vec() {
6366 ks.extend(vv.iter().map(|x| x.to_string()));
6367 } else {
6368 ks.push(kv.to_string());
6369 }
6370 }
6371 if ks.is_empty() {
6372 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6373 }
6374 let items = val.to_list();
6375 if let Some(r) = container.as_hash_ref() {
6376 let mut h = r.write();
6377 for (i, k) in ks.iter().enumerate() {
6378 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6379 h.insert(k.clone(), v);
6380 }
6381 return Ok(PerlValue::UNDEF);
6382 }
6383 if let Some(name) = container.as_hash_binding_name() {
6384 self.touch_env_hash(&name);
6385 for (i, k) in ks.iter().enumerate() {
6386 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6387 self.scope
6388 .set_hash_element(&name, k, v)
6389 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6390 }
6391 return Ok(PerlValue::UNDEF);
6392 }
6393 if let Some(s) = container.as_str() {
6394 if self.strict_refs {
6395 return Err(PerlError::runtime(
6396 format!(
6397 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6398 s
6399 ),
6400 line,
6401 )
6402 .into());
6403 }
6404 self.touch_env_hash(&s);
6405 for (i, k) in ks.iter().enumerate() {
6406 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6407 self.scope
6408 .set_hash_element(&s, k, v)
6409 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6410 }
6411 return Ok(PerlValue::UNDEF);
6412 }
6413 Err(PerlError::runtime(
6414 "Hash slice assignment needs a hash or hash reference value",
6415 line,
6416 )
6417 .into())
6418 }
6419
6420 pub(crate) fn compound_assign_hash_slice_deref(
6423 &mut self,
6424 container: PerlValue,
6425 key_values: Vec<PerlValue>,
6426 op: BinOp,
6427 rhs: PerlValue,
6428 line: usize,
6429 ) -> Result<PerlValue, FlowOrError> {
6430 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6431 let last_old = old_list
6432 .to_list()
6433 .last()
6434 .cloned()
6435 .unwrap_or(PerlValue::UNDEF);
6436 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6437 let mut ks: Vec<String> = Vec::new();
6438 for kv in &key_values {
6439 if let Some(vv) = kv.as_array_vec() {
6440 ks.extend(vv.iter().map(|x| x.to_string()));
6441 } else {
6442 ks.push(kv.to_string());
6443 }
6444 }
6445 if ks.is_empty() {
6446 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6447 }
6448 let last_key = ks.last().expect("non-empty ks");
6449 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6450 Ok(new_val)
6451 }
6452
6453 pub(crate) fn hash_slice_deref_inc_dec(
6459 &mut self,
6460 container: PerlValue,
6461 key_values: Vec<PerlValue>,
6462 kind: u8,
6463 line: usize,
6464 ) -> Result<PerlValue, FlowOrError> {
6465 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6466 let last_old = old_list
6467 .to_list()
6468 .last()
6469 .cloned()
6470 .unwrap_or(PerlValue::UNDEF);
6471 let new_val = if kind & 1 == 0 {
6472 PerlValue::integer(last_old.to_int() + 1)
6473 } else {
6474 PerlValue::integer(last_old.to_int() - 1)
6475 };
6476 let mut ks: Vec<String> = Vec::new();
6477 for kv in &key_values {
6478 if let Some(vv) = kv.as_array_vec() {
6479 ks.extend(vv.iter().map(|x| x.to_string()));
6480 } else {
6481 ks.push(kv.to_string());
6482 }
6483 }
6484 let last_key = ks.last().ok_or_else(|| {
6485 PerlError::runtime("Hash slice increment needs at least one key", line)
6486 })?;
6487 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6488 Ok(if kind < 2 { new_val } else { last_old })
6489 }
6490
6491 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[PerlValue]) -> PerlValue {
6492 self.touch_env_hash(hash);
6493 let h = self.scope.get_hash(hash);
6494 let mut result = Vec::new();
6495 for kv in key_values {
6496 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6497 vv.iter().map(|x| x.to_string()).collect()
6498 } else {
6499 vec![kv.to_string()]
6500 };
6501 for k in key_strings {
6502 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6503 }
6504 }
6505 PerlValue::array(result)
6506 }
6507
6508 pub(crate) fn compound_assign_named_hash_slice(
6510 &mut self,
6511 hash: &str,
6512 key_values: Vec<PerlValue>,
6513 op: BinOp,
6514 rhs: PerlValue,
6515 line: usize,
6516 ) -> Result<PerlValue, FlowOrError> {
6517 let old_list = self.hash_slice_named_values(hash, &key_values);
6518 let last_old = old_list
6519 .to_list()
6520 .last()
6521 .cloned()
6522 .unwrap_or(PerlValue::UNDEF);
6523 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6524 let mut ks: Vec<String> = Vec::new();
6525 for kv in &key_values {
6526 if let Some(vv) = kv.as_array_vec() {
6527 ks.extend(vv.iter().map(|x| x.to_string()));
6528 } else {
6529 ks.push(kv.to_string());
6530 }
6531 }
6532 if ks.is_empty() {
6533 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6534 }
6535 let last_key = ks.last().expect("non-empty ks");
6536 let container = PerlValue::string(hash.to_string());
6537 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6538 Ok(new_val)
6539 }
6540
6541 pub(crate) fn named_hash_slice_inc_dec(
6543 &mut self,
6544 hash: &str,
6545 key_values: Vec<PerlValue>,
6546 kind: u8,
6547 line: usize,
6548 ) -> Result<PerlValue, FlowOrError> {
6549 let old_list = self.hash_slice_named_values(hash, &key_values);
6550 let last_old = old_list
6551 .to_list()
6552 .last()
6553 .cloned()
6554 .unwrap_or(PerlValue::UNDEF);
6555 let new_val = if kind & 1 == 0 {
6556 PerlValue::integer(last_old.to_int() + 1)
6557 } else {
6558 PerlValue::integer(last_old.to_int() - 1)
6559 };
6560 let mut ks: Vec<String> = Vec::new();
6561 for kv in &key_values {
6562 if let Some(vv) = kv.as_array_vec() {
6563 ks.extend(vv.iter().map(|x| x.to_string()));
6564 } else {
6565 ks.push(kv.to_string());
6566 }
6567 }
6568 let last_key = ks.last().ok_or_else(|| {
6569 PerlError::runtime("Hash slice increment needs at least one key", line)
6570 })?;
6571 let container = PerlValue::string(hash.to_string());
6572 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6573 Ok(if kind < 2 { new_val } else { last_old })
6574 }
6575
6576 fn match_array_pattern_elems(
6577 &mut self,
6578 arr: &[PerlValue],
6579 elems: &[MatchArrayElem],
6580 line: usize,
6581 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6582 let has_rest = elems
6583 .iter()
6584 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
6585 let mut binds: Vec<PatternBinding> = Vec::new();
6586 let mut idx = 0usize;
6587 for (i, elem) in elems.iter().enumerate() {
6588 match elem {
6589 MatchArrayElem::Rest => {
6590 if i != elems.len() - 1 {
6591 return Err(PerlError::runtime(
6592 "internal: `*` must be last in array match pattern",
6593 line,
6594 )
6595 .into());
6596 }
6597 return Ok(Some(binds));
6598 }
6599 MatchArrayElem::RestBind(name) => {
6600 if i != elems.len() - 1 {
6601 return Err(PerlError::runtime(
6602 "internal: `@name` rest bind must be last in array match pattern",
6603 line,
6604 )
6605 .into());
6606 }
6607 let tail = arr[idx..].to_vec();
6608 binds.push(PatternBinding::Array(name.clone(), tail));
6609 return Ok(Some(binds));
6610 }
6611 MatchArrayElem::CaptureScalar(name) => {
6612 if idx >= arr.len() {
6613 return Ok(None);
6614 }
6615 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
6616 idx += 1;
6617 }
6618 MatchArrayElem::Expr(e) => {
6619 if idx >= arr.len() {
6620 return Ok(None);
6621 }
6622 let expected = self.eval_expr(e)?;
6623 if !self.smartmatch_when(&arr[idx], &expected) {
6624 return Ok(None);
6625 }
6626 idx += 1;
6627 }
6628 }
6629 }
6630 if !has_rest && idx != arr.len() {
6631 return Ok(None);
6632 }
6633 Ok(Some(binds))
6634 }
6635
6636 fn match_hash_pattern_pairs(
6637 &mut self,
6638 h: &IndexMap<String, PerlValue>,
6639 pairs: &[MatchHashPair],
6640 _line: usize,
6641 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6642 let mut binds = Vec::new();
6643 for pair in pairs {
6644 match pair {
6645 MatchHashPair::KeyOnly { key } => {
6646 let ks = self.eval_expr(key)?.to_string();
6647 if !h.contains_key(&ks) {
6648 return Ok(None);
6649 }
6650 }
6651 MatchHashPair::Capture { key, name } => {
6652 let ks = self.eval_expr(key)?.to_string();
6653 let Some(v) = h.get(&ks) else {
6654 return Ok(None);
6655 };
6656 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
6657 }
6658 }
6659 }
6660 Ok(Some(binds))
6661 }
6662
6663 #[inline]
6665 fn block_needs_scope(block: &Block) -> bool {
6666 block.iter().any(|s| match &s.kind {
6667 StmtKind::My(_)
6668 | StmtKind::Our(_)
6669 | StmtKind::Local(_)
6670 | StmtKind::State(_)
6671 | StmtKind::LocalExpr { .. } => true,
6672 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
6673 _ => false,
6674 })
6675 }
6676
6677 #[inline]
6679 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
6680 if Self::block_needs_scope(block) {
6681 self.exec_block(block)
6682 } else {
6683 self.exec_block_no_scope(block)
6684 }
6685 }
6686
6687 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
6688 let t0 = self.profiler.is_some().then(std::time::Instant::now);
6689 let r = self.exec_statement_inner(stmt);
6690 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
6691 prof.on_line(&self.file, stmt.line, t0.elapsed());
6692 }
6693 r
6694 }
6695
6696 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
6697 if let Err(e) = crate::perl_signal::poll(self) {
6698 return Err(FlowOrError::Error(e));
6699 }
6700 if let Err(e) = self.drain_pending_destroys(stmt.line) {
6701 return Err(FlowOrError::Error(e));
6702 }
6703 match &stmt.kind {
6704 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
6705 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
6706 StmtKind::If {
6707 condition,
6708 body,
6709 elsifs,
6710 else_block,
6711 } => {
6712 if self.eval_boolean_rvalue_condition(condition)? {
6713 return self.exec_block(body);
6714 }
6715 for (c, b) in elsifs {
6716 if self.eval_boolean_rvalue_condition(c)? {
6717 return self.exec_block(b);
6718 }
6719 }
6720 if let Some(eb) = else_block {
6721 return self.exec_block(eb);
6722 }
6723 Ok(PerlValue::UNDEF)
6724 }
6725 StmtKind::Unless {
6726 condition,
6727 body,
6728 else_block,
6729 } => {
6730 if !self.eval_boolean_rvalue_condition(condition)? {
6731 return self.exec_block(body);
6732 }
6733 if let Some(eb) = else_block {
6734 return self.exec_block(eb);
6735 }
6736 Ok(PerlValue::UNDEF)
6737 }
6738 StmtKind::While {
6739 condition,
6740 body,
6741 label,
6742 continue_block,
6743 } => {
6744 'outer: loop {
6745 if !self.eval_boolean_rvalue_condition(condition)? {
6746 break;
6747 }
6748 'inner: loop {
6749 match self.exec_block_smart(body) {
6750 Ok(_) => break 'inner,
6751 Err(FlowOrError::Flow(Flow::Last(ref l)))
6752 if l == label || l.is_none() =>
6753 {
6754 break 'outer;
6755 }
6756 Err(FlowOrError::Flow(Flow::Next(ref l)))
6757 if l == label || l.is_none() =>
6758 {
6759 if let Some(cb) = continue_block {
6760 let _ = self.exec_block_smart(cb);
6761 }
6762 continue 'outer;
6763 }
6764 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6765 if l == label || l.is_none() =>
6766 {
6767 continue 'inner;
6768 }
6769 Err(e) => return Err(e),
6770 }
6771 }
6772 if let Some(cb) = continue_block {
6773 let _ = self.exec_block_smart(cb);
6774 }
6775 }
6776 Ok(PerlValue::UNDEF)
6777 }
6778 StmtKind::Until {
6779 condition,
6780 body,
6781 label,
6782 continue_block,
6783 } => {
6784 'outer: loop {
6785 if self.eval_boolean_rvalue_condition(condition)? {
6786 break;
6787 }
6788 'inner: loop {
6789 match self.exec_block(body) {
6790 Ok(_) => break 'inner,
6791 Err(FlowOrError::Flow(Flow::Last(ref l)))
6792 if l == label || l.is_none() =>
6793 {
6794 break 'outer;
6795 }
6796 Err(FlowOrError::Flow(Flow::Next(ref l)))
6797 if l == label || l.is_none() =>
6798 {
6799 if let Some(cb) = continue_block {
6800 let _ = self.exec_block_smart(cb);
6801 }
6802 continue 'outer;
6803 }
6804 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6805 if l == label || l.is_none() =>
6806 {
6807 continue 'inner;
6808 }
6809 Err(e) => return Err(e),
6810 }
6811 }
6812 if let Some(cb) = continue_block {
6813 let _ = self.exec_block_smart(cb);
6814 }
6815 }
6816 Ok(PerlValue::UNDEF)
6817 }
6818 StmtKind::DoWhile { body, condition } => {
6819 loop {
6820 self.exec_block(body)?;
6821 if !self.eval_boolean_rvalue_condition(condition)? {
6822 break;
6823 }
6824 }
6825 Ok(PerlValue::UNDEF)
6826 }
6827 StmtKind::For {
6828 init,
6829 condition,
6830 step,
6831 body,
6832 label,
6833 continue_block,
6834 } => {
6835 self.scope_push_hook();
6836 if let Some(init) = init {
6837 self.exec_statement(init)?;
6838 }
6839 'outer: loop {
6840 if let Some(cond) = condition {
6841 if !self.eval_boolean_rvalue_condition(cond)? {
6842 break;
6843 }
6844 }
6845 'inner: loop {
6846 match self.exec_block_smart(body) {
6847 Ok(_) => break 'inner,
6848 Err(FlowOrError::Flow(Flow::Last(ref l)))
6849 if l == label || l.is_none() =>
6850 {
6851 break 'outer;
6852 }
6853 Err(FlowOrError::Flow(Flow::Next(ref l)))
6854 if l == label || l.is_none() =>
6855 {
6856 if let Some(cb) = continue_block {
6857 let _ = self.exec_block_smart(cb);
6858 }
6859 if let Some(step) = step {
6860 self.eval_expr(step)?;
6861 }
6862 continue 'outer;
6863 }
6864 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6865 if l == label || l.is_none() =>
6866 {
6867 continue 'inner;
6868 }
6869 Err(e) => {
6870 self.scope_pop_hook();
6871 return Err(e);
6872 }
6873 }
6874 }
6875 if let Some(cb) = continue_block {
6876 let _ = self.exec_block_smart(cb);
6877 }
6878 if let Some(step) = step {
6879 self.eval_expr(step)?;
6880 }
6881 }
6882 self.scope_pop_hook();
6883 Ok(PerlValue::UNDEF)
6884 }
6885 StmtKind::Foreach {
6886 var,
6887 list,
6888 body,
6889 label,
6890 continue_block,
6891 } => {
6892 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
6893 let items = list_val.to_list();
6894 self.scope_push_hook();
6895 self.scope.declare_scalar(var, PerlValue::UNDEF);
6896 self.english_note_lexical_scalar(var);
6897 let mut i = 0usize;
6898 'outer: while i < items.len() {
6899 self.scope
6900 .set_scalar(var, items[i].clone())
6901 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
6902 'inner: loop {
6903 match self.exec_block_smart(body) {
6904 Ok(_) => break 'inner,
6905 Err(FlowOrError::Flow(Flow::Last(ref l)))
6906 if l == label || l.is_none() =>
6907 {
6908 break 'outer;
6909 }
6910 Err(FlowOrError::Flow(Flow::Next(ref l)))
6911 if l == label || l.is_none() =>
6912 {
6913 if let Some(cb) = continue_block {
6914 let _ = self.exec_block_smart(cb);
6915 }
6916 i += 1;
6917 continue 'outer;
6918 }
6919 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6920 if l == label || l.is_none() =>
6921 {
6922 continue 'inner;
6923 }
6924 Err(e) => {
6925 self.scope_pop_hook();
6926 return Err(e);
6927 }
6928 }
6929 }
6930 if let Some(cb) = continue_block {
6931 let _ = self.exec_block_smart(cb);
6932 }
6933 i += 1;
6934 }
6935 self.scope_pop_hook();
6936 Ok(PerlValue::UNDEF)
6937 }
6938 StmtKind::SubDecl {
6939 name,
6940 params,
6941 body,
6942 prototype,
6943 } => {
6944 let key = self.qualify_sub_key(name);
6945 let captured = self.scope.capture();
6946 let closure_env = if captured.is_empty() {
6947 None
6948 } else {
6949 Some(captured)
6950 };
6951 let mut sub = PerlSub {
6952 name: name.clone(),
6953 params: params.clone(),
6954 body: body.clone(),
6955 closure_env,
6956 prototype: prototype.clone(),
6957 fib_like: None,
6958 };
6959 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
6960 self.subs.insert(key, Arc::new(sub));
6961 Ok(PerlValue::UNDEF)
6962 }
6963 StmtKind::StructDecl { def } => {
6964 if self.struct_defs.contains_key(&def.name) {
6965 return Err(PerlError::runtime(
6966 format!("duplicate struct `{}`", def.name),
6967 stmt.line,
6968 )
6969 .into());
6970 }
6971 self.struct_defs
6972 .insert(def.name.clone(), Arc::new(def.clone()));
6973 Ok(PerlValue::UNDEF)
6974 }
6975 StmtKind::EnumDecl { def } => {
6976 if self.enum_defs.contains_key(&def.name) {
6977 return Err(PerlError::runtime(
6978 format!("duplicate enum `{}`", def.name),
6979 stmt.line,
6980 )
6981 .into());
6982 }
6983 self.enum_defs
6984 .insert(def.name.clone(), Arc::new(def.clone()));
6985 Ok(PerlValue::UNDEF)
6986 }
6987 StmtKind::ClassDecl { def } => {
6988 if self.class_defs.contains_key(&def.name) {
6989 return Err(PerlError::runtime(
6990 format!("duplicate class `{}`", def.name),
6991 stmt.line,
6992 )
6993 .into());
6994 }
6995 for parent_name in &def.extends {
6997 if let Some(parent_def) = self.class_defs.get(parent_name) {
6998 if parent_def.is_final {
6999 return Err(PerlError::runtime(
7000 format!("cannot extend final class `{}`", parent_name),
7001 stmt.line,
7002 )
7003 .into());
7004 }
7005 for m in &def.methods {
7007 if let Some(parent_method) = parent_def.method(&m.name) {
7008 if parent_method.is_final {
7009 return Err(PerlError::runtime(
7010 format!(
7011 "cannot override final method `{}` from class `{}`",
7012 m.name, parent_name
7013 ),
7014 stmt.line,
7015 )
7016 .into());
7017 }
7018 }
7019 }
7020 }
7021 }
7022 let mut def = def.clone();
7024 for trait_name in &def.implements.clone() {
7025 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
7026 for required in trait_def.required_methods() {
7027 let has_method = def.methods.iter().any(|m| m.name == required.name);
7028 if !has_method {
7029 return Err(PerlError::runtime(
7030 format!(
7031 "class `{}` implements trait `{}` but does not define required method `{}`",
7032 def.name, trait_name, required.name
7033 ),
7034 stmt.line,
7035 )
7036 .into());
7037 }
7038 }
7039 for tm in &trait_def.methods {
7041 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
7042 def.methods.push(tm.clone());
7043 }
7044 }
7045 }
7046 }
7047 if !def.is_abstract {
7050 for parent_name in &def.extends.clone() {
7051 if let Some(parent_def) = self.class_defs.get(parent_name) {
7052 if parent_def.is_abstract {
7053 for m in &parent_def.methods {
7054 if m.body.is_none()
7055 && !def.methods.iter().any(|dm| dm.name == m.name)
7056 {
7057 return Err(PerlError::runtime(
7058 format!(
7059 "class `{}` must implement abstract method `{}` from `{}`",
7060 def.name, m.name, parent_name
7061 ),
7062 stmt.line,
7063 )
7064 .into());
7065 }
7066 }
7067 }
7068 }
7069 }
7070 }
7071 for sf in &def.static_fields {
7073 let val = if let Some(ref expr) = sf.default {
7074 self.eval_expr(expr)?
7075 } else {
7076 PerlValue::UNDEF
7077 };
7078 let key = format!("{}::{}", def.name, sf.name);
7079 self.scope.declare_scalar(&key, val);
7080 }
7081 for m in &def.methods {
7083 if let Some(ref body) = m.body {
7084 let fq = format!("{}::{}", def.name, m.name);
7085 let sub = Arc::new(PerlSub {
7086 name: fq.clone(),
7087 params: m.params.clone(),
7088 body: body.clone(),
7089 closure_env: None,
7090 prototype: None,
7091 fib_like: None,
7092 });
7093 self.subs.insert(fq, sub);
7094 }
7095 }
7096 if !def.extends.is_empty() {
7098 let isa_key = format!("{}::ISA", def.name);
7099 let parents: Vec<PerlValue> = def
7100 .extends
7101 .iter()
7102 .map(|p| PerlValue::string(p.clone()))
7103 .collect();
7104 self.scope.declare_array(&isa_key, parents);
7105 }
7106 self.class_defs.insert(def.name.clone(), Arc::new(def));
7107 Ok(PerlValue::UNDEF)
7108 }
7109 StmtKind::TraitDecl { def } => {
7110 if self.trait_defs.contains_key(&def.name) {
7111 return Err(PerlError::runtime(
7112 format!("duplicate trait `{}`", def.name),
7113 stmt.line,
7114 )
7115 .into());
7116 }
7117 self.trait_defs
7118 .insert(def.name.clone(), Arc::new(def.clone()));
7119 Ok(PerlValue::UNDEF)
7120 }
7121 StmtKind::My(decls) | StmtKind::Our(decls) => {
7122 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
7123 if decls.len() > 1 && decls[0].initializer.is_some() {
7126 let val = self.eval_expr_ctx(
7127 decls[0].initializer.as_ref().unwrap(),
7128 WantarrayCtx::List,
7129 )?;
7130 let items = val.to_list();
7131 let mut idx = 0;
7132 for decl in decls {
7133 match decl.sigil {
7134 Sigil::Scalar => {
7135 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7136 let skey = if is_our {
7137 self.stash_scalar_name_for_package(&decl.name)
7138 } else {
7139 decl.name.clone()
7140 };
7141 self.scope.declare_scalar_frozen(
7142 &skey,
7143 v,
7144 decl.frozen,
7145 decl.type_annotation.clone(),
7146 )?;
7147 self.english_note_lexical_scalar(&decl.name);
7148 if is_our {
7149 self.note_our_scalar(&decl.name);
7150 }
7151 idx += 1;
7152 }
7153 Sigil::Array => {
7154 let rest: Vec<PerlValue> = items[idx..].to_vec();
7156 idx = items.len();
7157 if is_our {
7158 self.record_exporter_our_array_name(&decl.name, &rest);
7159 }
7160 let aname = self.stash_array_name_for_package(&decl.name);
7161 self.scope.declare_array(&aname, rest);
7162 }
7163 Sigil::Hash => {
7164 let rest: Vec<PerlValue> = items[idx..].to_vec();
7165 idx = items.len();
7166 let mut map = IndexMap::new();
7167 let mut i = 0;
7168 while i + 1 < rest.len() {
7169 map.insert(rest[i].to_string(), rest[i + 1].clone());
7170 i += 2;
7171 }
7172 self.scope.declare_hash(&decl.name, map);
7173 }
7174 Sigil::Typeglob => {
7175 return Err(PerlError::runtime(
7176 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
7177 stmt.line,
7178 )
7179 .into());
7180 }
7181 }
7182 }
7183 } else {
7184 for decl in decls {
7186 let compound_init = decl
7190 .initializer
7191 .as_ref()
7192 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
7193
7194 if compound_init {
7195 match decl.sigil {
7196 Sigil::Typeglob => {
7197 return Err(PerlError::runtime(
7198 "compound assignment on typeglob declaration is not supported",
7199 stmt.line,
7200 )
7201 .into());
7202 }
7203 Sigil::Scalar => {
7204 let skey = if is_our {
7205 self.stash_scalar_name_for_package(&decl.name)
7206 } else {
7207 decl.name.clone()
7208 };
7209 self.scope.declare_scalar_frozen(
7210 &skey,
7211 PerlValue::UNDEF,
7212 decl.frozen,
7213 decl.type_annotation.clone(),
7214 )?;
7215 self.english_note_lexical_scalar(&decl.name);
7216 if is_our {
7217 self.note_our_scalar(&decl.name);
7218 }
7219 let init = decl.initializer.as_ref().unwrap();
7220 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7221 }
7222 Sigil::Array => {
7223 let aname = self.stash_array_name_for_package(&decl.name);
7224 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
7225 let init = decl.initializer.as_ref().unwrap();
7226 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7227 if is_our {
7228 let items = self.scope.get_array(&aname);
7229 self.record_exporter_our_array_name(&decl.name, &items);
7230 }
7231 }
7232 Sigil::Hash => {
7233 self.scope.declare_hash_frozen(
7234 &decl.name,
7235 IndexMap::new(),
7236 decl.frozen,
7237 );
7238 let init = decl.initializer.as_ref().unwrap();
7239 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7240 }
7241 }
7242 continue;
7243 }
7244
7245 let val = if let Some(init) = &decl.initializer {
7246 let ctx = match decl.sigil {
7247 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7248 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7249 };
7250 self.eval_expr_ctx(init, ctx)?
7251 } else {
7252 PerlValue::UNDEF
7253 };
7254 match decl.sigil {
7255 Sigil::Typeglob => {
7256 return Err(PerlError::runtime(
7257 "`my *FH` / typeglob declaration is not supported",
7258 stmt.line,
7259 )
7260 .into());
7261 }
7262 Sigil::Scalar => {
7263 let skey = if is_our {
7264 self.stash_scalar_name_for_package(&decl.name)
7265 } else {
7266 decl.name.clone()
7267 };
7268 self.scope.declare_scalar_frozen(
7269 &skey,
7270 val,
7271 decl.frozen,
7272 decl.type_annotation.clone(),
7273 )?;
7274 self.english_note_lexical_scalar(&decl.name);
7275 if is_our {
7276 self.note_our_scalar(&decl.name);
7277 }
7278 }
7279 Sigil::Array => {
7280 let items = val.to_list();
7281 if is_our {
7282 self.record_exporter_our_array_name(&decl.name, &items);
7283 }
7284 let aname = self.stash_array_name_for_package(&decl.name);
7285 self.scope.declare_array_frozen(&aname, items, decl.frozen);
7286 }
7287 Sigil::Hash => {
7288 let items = val.to_list();
7289 let mut map = IndexMap::new();
7290 let mut i = 0;
7291 while i + 1 < items.len() {
7292 let k = items[i].to_string();
7293 let v = items[i + 1].clone();
7294 map.insert(k, v);
7295 i += 2;
7296 }
7297 self.scope.declare_hash_frozen(&decl.name, map, decl.frozen);
7298 }
7299 }
7300 }
7301 }
7302 Ok(PerlValue::UNDEF)
7303 }
7304 StmtKind::State(decls) => {
7305 for decl in decls {
7308 let state_key = format!("{}:{}", stmt.line, decl.name);
7309 match decl.sigil {
7310 Sigil::Scalar => {
7311 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
7312 self.scope.declare_scalar(&decl.name, prev);
7314 } else {
7315 let val = if let Some(init) = &decl.initializer {
7317 self.eval_expr(init)?
7318 } else {
7319 PerlValue::UNDEF
7320 };
7321 self.state_vars.insert(state_key.clone(), val.clone());
7322 self.scope.declare_scalar(&decl.name, val);
7323 }
7324 if let Some(frame) = self.state_bindings_stack.last_mut() {
7326 frame.push((decl.name.clone(), state_key));
7327 }
7328 }
7329 _ => {
7330 let val = if let Some(init) = &decl.initializer {
7332 self.eval_expr(init)?
7333 } else {
7334 PerlValue::UNDEF
7335 };
7336 match decl.sigil {
7337 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
7338 Sigil::Hash => {
7339 let items = val.to_list();
7340 let mut map = IndexMap::new();
7341 let mut i = 0;
7342 while i + 1 < items.len() {
7343 map.insert(items[i].to_string(), items[i + 1].clone());
7344 i += 2;
7345 }
7346 self.scope.declare_hash(&decl.name, map);
7347 }
7348 _ => {}
7349 }
7350 }
7351 }
7352 }
7353 Ok(PerlValue::UNDEF)
7354 }
7355 StmtKind::Local(decls) => {
7356 if decls.len() > 1 && decls[0].initializer.is_some() {
7357 let val = self.eval_expr_ctx(
7358 decls[0].initializer.as_ref().unwrap(),
7359 WantarrayCtx::List,
7360 )?;
7361 let items = val.to_list();
7362 let mut idx = 0;
7363 for decl in decls {
7364 match decl.sigil {
7365 Sigil::Scalar => {
7366 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7367 idx += 1;
7368 self.scope.local_set_scalar(&decl.name, v)?;
7369 }
7370 Sigil::Array => {
7371 let rest: Vec<PerlValue> = items[idx..].to_vec();
7372 idx = items.len();
7373 self.scope.local_set_array(&decl.name, rest)?;
7374 }
7375 Sigil::Hash => {
7376 let rest: Vec<PerlValue> = items[idx..].to_vec();
7377 idx = items.len();
7378 if decl.name == "ENV" {
7379 self.materialize_env_if_needed();
7380 }
7381 let mut map = IndexMap::new();
7382 let mut i = 0;
7383 while i + 1 < rest.len() {
7384 map.insert(rest[i].to_string(), rest[i + 1].clone());
7385 i += 2;
7386 }
7387 self.scope.local_set_hash(&decl.name, map)?;
7388 }
7389 Sigil::Typeglob => {
7390 return Err(PerlError::runtime(
7391 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
7392 stmt.line,
7393 )
7394 .into());
7395 }
7396 }
7397 }
7398 Ok(val)
7399 } else {
7400 let mut last_val = PerlValue::UNDEF;
7401 for decl in decls {
7402 let val = if let Some(init) = &decl.initializer {
7403 let ctx = match decl.sigil {
7404 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7405 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7406 };
7407 self.eval_expr_ctx(init, ctx)?
7408 } else {
7409 PerlValue::UNDEF
7410 };
7411 last_val = val.clone();
7412 match decl.sigil {
7413 Sigil::Typeglob => {
7414 let old = self.glob_handle_alias.remove(&decl.name);
7415 if let Some(frame) = self.glob_restore_frames.last_mut() {
7416 frame.push((decl.name.clone(), old));
7417 }
7418 if let Some(init) = &decl.initializer {
7419 if let ExprKind::Typeglob(rhs) = &init.kind {
7420 self.glob_handle_alias
7421 .insert(decl.name.clone(), rhs.clone());
7422 } else {
7423 return Err(PerlError::runtime(
7424 "local *GLOB = *OTHER — right side must be a typeglob",
7425 stmt.line,
7426 )
7427 .into());
7428 }
7429 }
7430 }
7431 Sigil::Scalar => {
7432 if Self::is_special_scalar_name_for_set(&decl.name) {
7438 let old = self.get_special_var(&decl.name);
7439 if let Some(frame) = self.special_var_restore_frames.last_mut()
7440 {
7441 frame.push((decl.name.clone(), old));
7442 }
7443 self.set_special_var(&decl.name, &val)
7444 .map_err(|e| e.at_line(stmt.line))?;
7445 }
7446 self.scope.local_set_scalar(&decl.name, val)?;
7447 }
7448 Sigil::Array => {
7449 self.scope.local_set_array(&decl.name, val.to_list())?;
7450 }
7451 Sigil::Hash => {
7452 if decl.name == "ENV" {
7453 self.materialize_env_if_needed();
7454 }
7455 let items = val.to_list();
7456 let mut map = IndexMap::new();
7457 let mut i = 0;
7458 while i + 1 < items.len() {
7459 let k = items[i].to_string();
7460 let v = items[i + 1].clone();
7461 map.insert(k, v);
7462 i += 2;
7463 }
7464 self.scope.local_set_hash(&decl.name, map)?;
7465 }
7466 }
7467 }
7468 Ok(last_val)
7469 }
7470 }
7471 StmtKind::LocalExpr {
7472 target,
7473 initializer,
7474 } => {
7475 let rhs_name = |init: &Expr| -> PerlResult<Option<String>> {
7476 match &init.kind {
7477 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
7478 _ => Err(PerlError::runtime(
7479 "local *GLOB = *OTHER — right side must be a typeglob",
7480 stmt.line,
7481 )),
7482 }
7483 };
7484 match &target.kind {
7485 ExprKind::Typeglob(name) => {
7486 let rhs = if let Some(init) = initializer {
7487 rhs_name(init)?
7488 } else {
7489 None
7490 };
7491 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
7492 return Ok(PerlValue::UNDEF);
7493 }
7494 ExprKind::Deref {
7495 expr,
7496 kind: Sigil::Typeglob,
7497 } => {
7498 let lhs = self.eval_expr(expr)?.to_string();
7499 let rhs = if let Some(init) = initializer {
7500 rhs_name(init)?
7501 } else {
7502 None
7503 };
7504 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7505 return Ok(PerlValue::UNDEF);
7506 }
7507 ExprKind::TypeglobExpr(e) => {
7508 let lhs = self.eval_expr(e)?.to_string();
7509 let rhs = if let Some(init) = initializer {
7510 rhs_name(init)?
7511 } else {
7512 None
7513 };
7514 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7515 return Ok(PerlValue::UNDEF);
7516 }
7517 _ => {}
7518 }
7519 let val = if let Some(init) = initializer {
7520 let ctx = match &target.kind {
7521 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
7522 _ => WantarrayCtx::Scalar,
7523 };
7524 self.eval_expr_ctx(init, ctx)?
7525 } else {
7526 PerlValue::UNDEF
7527 };
7528 match &target.kind {
7529 ExprKind::ScalarVar(name) => {
7530 if Self::is_special_scalar_name_for_set(name) {
7533 let old = self.get_special_var(name);
7534 if let Some(frame) = self.special_var_restore_frames.last_mut() {
7535 frame.push((name.clone(), old));
7536 }
7537 self.set_special_var(name, &val)
7538 .map_err(|e| e.at_line(stmt.line))?;
7539 }
7540 self.scope.local_set_scalar(name, val.clone())?;
7541 }
7542 ExprKind::ArrayVar(name) => {
7543 self.scope.local_set_array(name, val.to_list())?;
7544 }
7545 ExprKind::HashVar(name) => {
7546 if name == "ENV" {
7547 self.materialize_env_if_needed();
7548 }
7549 let items = val.to_list();
7550 let mut map = IndexMap::new();
7551 let mut i = 0;
7552 while i + 1 < items.len() {
7553 map.insert(items[i].to_string(), items[i + 1].clone());
7554 i += 2;
7555 }
7556 self.scope.local_set_hash(name, map)?;
7557 }
7558 ExprKind::HashElement { hash, key } => {
7559 let ks = self.eval_expr(key)?.to_string();
7560 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
7561 }
7562 ExprKind::ArrayElement { array, index } => {
7563 self.check_strict_array_var(array, stmt.line)?;
7564 let aname = self.stash_array_name_for_package(array);
7565 let idx = self.eval_expr(index)?.to_int();
7566 self.scope
7567 .local_set_array_element(&aname, idx, val.clone())?;
7568 }
7569 _ => {
7570 return Err(PerlError::runtime(
7571 format!(
7572 "local on this lvalue is not supported yet ({:?})",
7573 target.kind
7574 ),
7575 stmt.line,
7576 )
7577 .into());
7578 }
7579 }
7580 Ok(val)
7581 }
7582 StmtKind::MySync(decls) => {
7583 for decl in decls {
7584 let val = if let Some(init) = &decl.initializer {
7585 self.eval_expr(init)?
7586 } else {
7587 PerlValue::UNDEF
7588 };
7589 match decl.sigil {
7590 Sigil::Typeglob => {
7591 return Err(PerlError::runtime(
7592 "`mysync` does not support typeglob variables",
7593 stmt.line,
7594 )
7595 .into());
7596 }
7597 Sigil::Scalar => {
7598 let stored = if val.is_mysync_deque_or_heap() {
7601 val
7602 } else {
7603 PerlValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(val)))
7604 };
7605 self.scope.declare_scalar(&decl.name, stored);
7606 }
7607 Sigil::Array => {
7608 self.scope.declare_atomic_array(&decl.name, val.to_list());
7609 }
7610 Sigil::Hash => {
7611 let items = val.to_list();
7612 let mut map = IndexMap::new();
7613 let mut i = 0;
7614 while i + 1 < items.len() {
7615 map.insert(items[i].to_string(), items[i + 1].clone());
7616 i += 2;
7617 }
7618 self.scope.declare_atomic_hash(&decl.name, map);
7619 }
7620 }
7621 }
7622 Ok(PerlValue::UNDEF)
7623 }
7624 StmtKind::Package { name } => {
7625 let _ = self
7627 .scope
7628 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
7629 Ok(PerlValue::UNDEF)
7630 }
7631 StmtKind::UsePerlVersion { .. } => Ok(PerlValue::UNDEF),
7632 StmtKind::Use { .. } => {
7633 Ok(PerlValue::UNDEF)
7635 }
7636 StmtKind::UseOverload { pairs } => {
7637 self.install_use_overload_pairs(pairs);
7638 Ok(PerlValue::UNDEF)
7639 }
7640 StmtKind::No { .. } => {
7641 Ok(PerlValue::UNDEF)
7643 }
7644 StmtKind::Return(val) => {
7645 let v = if let Some(e) = val {
7646 self.eval_expr_ctx(e, self.wantarray_kind)?
7650 } else {
7651 PerlValue::UNDEF
7652 };
7653 Err(Flow::Return(v).into())
7654 }
7655 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
7656 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
7657 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
7658 StmtKind::Block(block) => self.exec_block(block),
7659 StmtKind::Begin(_)
7660 | StmtKind::UnitCheck(_)
7661 | StmtKind::Check(_)
7662 | StmtKind::Init(_)
7663 | StmtKind::End(_) => Ok(PerlValue::UNDEF),
7664 StmtKind::Empty => Ok(PerlValue::UNDEF),
7665 StmtKind::Goto { target } => {
7666 if let ExprKind::SubroutineRef(name) = &target.kind {
7668 return Err(Flow::GotoSub(name.clone()).into());
7669 }
7670 Err(PerlError::runtime("goto reached outside goto-aware block", stmt.line).into())
7671 }
7672 StmtKind::EvalTimeout { timeout, body } => {
7673 let secs = self.eval_expr(timeout)?.to_number();
7674 self.eval_timeout_block(body, secs, stmt.line)
7675 }
7676 StmtKind::Tie {
7677 target,
7678 class,
7679 args,
7680 } => {
7681 let kind = match &target {
7682 TieTarget::Scalar(_) => 0u8,
7683 TieTarget::Array(_) => 1u8,
7684 TieTarget::Hash(_) => 2u8,
7685 };
7686 let name = match &target {
7687 TieTarget::Scalar(s) => s.as_str(),
7688 TieTarget::Array(a) => a.as_str(),
7689 TieTarget::Hash(h) => h.as_str(),
7690 };
7691 let mut vals = vec![self.eval_expr(class)?];
7692 for a in args {
7693 vals.push(self.eval_expr(a)?);
7694 }
7695 self.tie_execute(kind, name, vals, stmt.line)
7696 .map_err(Into::into)
7697 }
7698 StmtKind::TryCatch {
7699 try_block,
7700 catch_var,
7701 catch_block,
7702 finally_block,
7703 } => match self.exec_block(try_block) {
7704 Ok(v) => {
7705 if let Some(fb) = finally_block {
7706 self.exec_block(fb)?;
7707 }
7708 Ok(v)
7709 }
7710 Err(FlowOrError::Error(e)) => {
7711 if matches!(e.kind, ErrorKind::Exit(_)) {
7712 return Err(FlowOrError::Error(e));
7713 }
7714 self.scope_push_hook();
7715 self.scope
7716 .declare_scalar(catch_var, PerlValue::string(e.to_string()));
7717 self.english_note_lexical_scalar(catch_var);
7718 let r = self.exec_block(catch_block);
7719 self.scope_pop_hook();
7720 if let Some(fb) = finally_block {
7721 self.exec_block(fb)?;
7722 }
7723 r
7724 }
7725 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
7726 },
7727 StmtKind::Given { topic, body } => self.exec_given(topic, body),
7728 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(PerlError::runtime(
7729 "when/default may only appear inside a given block",
7730 stmt.line,
7731 )
7732 .into()),
7733 StmtKind::FormatDecl { .. } => {
7734 Ok(PerlValue::UNDEF)
7736 }
7737 StmtKind::Continue(block) => self.exec_block_smart(block),
7738 }
7739 }
7740
7741 #[inline]
7742 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
7743 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
7744 }
7745
7746 pub(crate) fn scalar_compound_assign_scalar_target(
7750 &mut self,
7751 name: &str,
7752 op: BinOp,
7753 rhs: PerlValue,
7754 ) -> Result<PerlValue, PerlError> {
7755 if op == BinOp::Concat {
7756 return self.scope.scalar_concat_inplace(name, &rhs);
7757 }
7758 Ok(self
7759 .scope
7760 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs)))
7761 }
7762
7763 fn compound_scalar_binop(old: &PerlValue, op: BinOp, rhs: &PerlValue) -> PerlValue {
7764 match op {
7765 BinOp::Add => {
7766 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7767 PerlValue::integer(a.wrapping_add(b))
7768 } else {
7769 PerlValue::float(old.to_number() + rhs.to_number())
7770 }
7771 }
7772 BinOp::Sub => {
7773 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7774 PerlValue::integer(a.wrapping_sub(b))
7775 } else {
7776 PerlValue::float(old.to_number() - rhs.to_number())
7777 }
7778 }
7779 BinOp::Mul => {
7780 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7781 PerlValue::integer(a.wrapping_mul(b))
7782 } else {
7783 PerlValue::float(old.to_number() * rhs.to_number())
7784 }
7785 }
7786 BinOp::BitAnd => {
7787 if let Some(s) = crate::value::set_intersection(old, rhs) {
7788 s
7789 } else {
7790 PerlValue::integer(old.to_int() & rhs.to_int())
7791 }
7792 }
7793 BinOp::BitOr => {
7794 if let Some(s) = crate::value::set_union(old, rhs) {
7795 s
7796 } else {
7797 PerlValue::integer(old.to_int() | rhs.to_int())
7798 }
7799 }
7800 BinOp::BitXor => PerlValue::integer(old.to_int() ^ rhs.to_int()),
7801 BinOp::ShiftLeft => PerlValue::integer(old.to_int() << rhs.to_int()),
7802 BinOp::ShiftRight => PerlValue::integer(old.to_int() >> rhs.to_int()),
7803 BinOp::Div => PerlValue::float(old.to_number() / rhs.to_number()),
7804 BinOp::Mod => PerlValue::float(old.to_number() % rhs.to_number()),
7805 BinOp::Pow => PerlValue::float(old.to_number().powf(rhs.to_number())),
7806 BinOp::LogOr => {
7807 if old.is_true() {
7808 old.clone()
7809 } else {
7810 rhs.clone()
7811 }
7812 }
7813 BinOp::DefinedOr => {
7814 if !old.is_undef() {
7815 old.clone()
7816 } else {
7817 rhs.clone()
7818 }
7819 }
7820 BinOp::LogAnd => {
7821 if old.is_true() {
7822 rhs.clone()
7823 } else {
7824 old.clone()
7825 }
7826 }
7827 _ => PerlValue::float(old.to_number() + rhs.to_number()),
7828 }
7829 }
7830
7831 fn eval_hash_slice_key_components(
7835 &mut self,
7836 key_expr: &Expr,
7837 ) -> Result<Vec<String>, FlowOrError> {
7838 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
7839 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
7840 } else {
7841 self.eval_expr(key_expr)?
7842 };
7843 if let Some(vv) = v.as_array_vec() {
7844 Ok(vv.iter().map(|x| x.to_string()).collect())
7845 } else {
7846 Ok(vec![v.to_string()])
7847 }
7848 }
7849
7850 pub(crate) fn symbolic_deref(
7852 &mut self,
7853 val: PerlValue,
7854 kind: Sigil,
7855 line: usize,
7856 ) -> ExecResult {
7857 match kind {
7858 Sigil::Scalar => {
7859 if let Some(name) = val.as_scalar_binding_name() {
7860 return Ok(self.get_special_var(&name));
7861 }
7862 if let Some(r) = val.as_scalar_ref() {
7863 return Ok(r.read().clone());
7864 }
7865 if let Some(r) = val.as_array_ref() {
7867 return Ok(PerlValue::array(r.read().clone()));
7868 }
7869 if let Some(name) = val.as_array_binding_name() {
7870 return Ok(PerlValue::array(self.scope.get_array(&name)));
7871 }
7872 if let Some(r) = val.as_hash_ref() {
7873 return Ok(PerlValue::hash(r.read().clone()));
7874 }
7875 if let Some(name) = val.as_hash_binding_name() {
7876 self.touch_env_hash(&name);
7877 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
7878 }
7879 if let Some(s) = val.as_str() {
7880 if self.strict_refs {
7881 return Err(PerlError::runtime(
7882 format!(
7883 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
7884 s
7885 ),
7886 line,
7887 )
7888 .into());
7889 }
7890 return Ok(self.get_special_var(&s));
7891 }
7892 Err(PerlError::runtime("Can't dereference non-reference as scalar", line).into())
7893 }
7894 Sigil::Array => {
7895 if let Some(r) = val.as_array_ref() {
7896 return Ok(PerlValue::array(r.read().clone()));
7897 }
7898 if let Some(name) = val.as_array_binding_name() {
7899 return Ok(PerlValue::array(self.scope.get_array(&name)));
7900 }
7901 if let Some(s) = val.as_str() {
7902 if self.strict_refs {
7903 return Err(PerlError::runtime(
7904 format!(
7905 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
7906 s
7907 ),
7908 line,
7909 )
7910 .into());
7911 }
7912 return Ok(PerlValue::array(self.scope.get_array(&s)));
7913 }
7914 Err(PerlError::runtime("Can't dereference non-reference as array", line).into())
7915 }
7916 Sigil::Hash => {
7917 if let Some(r) = val.as_hash_ref() {
7918 return Ok(PerlValue::hash(r.read().clone()));
7919 }
7920 if let Some(name) = val.as_hash_binding_name() {
7921 self.touch_env_hash(&name);
7922 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
7923 }
7924 if let Some(s) = val.as_str() {
7925 if self.strict_refs {
7926 return Err(PerlError::runtime(
7927 format!(
7928 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7929 s
7930 ),
7931 line,
7932 )
7933 .into());
7934 }
7935 self.touch_env_hash(&s);
7936 return Ok(PerlValue::hash(self.scope.get_hash(&s)));
7937 }
7938 Err(PerlError::runtime("Can't dereference non-reference as hash", line).into())
7939 }
7940 Sigil::Typeglob => {
7941 if let Some(s) = val.as_str() {
7942 return Ok(PerlValue::string(self.resolve_io_handle_name(&s)));
7943 }
7944 Err(PerlError::runtime("Can't dereference non-reference as typeglob", line).into())
7945 }
7946 }
7947 }
7948
7949 #[inline]
7952 pub(crate) fn peel_array_ref_for_list_join(&self, v: PerlValue) -> PerlValue {
7953 if let Some(r) = v.as_array_ref() {
7954 return PerlValue::array(r.read().clone());
7955 }
7956 v
7957 }
7958
7959 pub(crate) fn make_array_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
7961 if let Some(a) = val.as_array_ref() {
7962 return Ok(PerlValue::array_ref(Arc::clone(&a)));
7963 }
7964 if let Some(name) = val.as_array_binding_name() {
7965 return Ok(PerlValue::array_binding_ref(name));
7966 }
7967 if let Some(s) = val.as_str() {
7968 if self.strict_refs {
7969 return Err(PerlError::runtime(
7970 format!(
7971 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
7972 s
7973 ),
7974 line,
7975 )
7976 .into());
7977 }
7978 return Ok(PerlValue::array_binding_ref(s.to_string()));
7979 }
7980 if let Some(r) = val.as_scalar_ref() {
7981 let inner = r.read().clone();
7982 return self.make_array_ref_alias(inner, line);
7983 }
7984 Err(PerlError::runtime("Can't make array reference from value", line).into())
7985 }
7986
7987 pub(crate) fn make_hash_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
7989 if let Some(h) = val.as_hash_ref() {
7990 return Ok(PerlValue::hash_ref(Arc::clone(&h)));
7991 }
7992 if let Some(name) = val.as_hash_binding_name() {
7993 return Ok(PerlValue::hash_binding_ref(name));
7994 }
7995 if let Some(s) = val.as_str() {
7996 if self.strict_refs {
7997 return Err(PerlError::runtime(
7998 format!(
7999 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8000 s
8001 ),
8002 line,
8003 )
8004 .into());
8005 }
8006 return Ok(PerlValue::hash_binding_ref(s.to_string()));
8007 }
8008 if let Some(r) = val.as_scalar_ref() {
8009 let inner = r.read().clone();
8010 return self.make_hash_ref_alias(inner, line);
8011 }
8012 Err(PerlError::runtime("Can't make hash reference from value", line).into())
8013 }
8014
8015 pub(crate) fn process_case_escapes(s: &str) -> String {
8018 if !s.contains('\\') {
8020 return s.to_string();
8021 }
8022 let mut result = String::with_capacity(s.len());
8023 let mut chars = s.chars().peekable();
8024 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
8028 if c == '\\' {
8029 match chars.peek() {
8030 Some(&'U') => {
8031 chars.next();
8032 mode = Some('U');
8033 continue;
8034 }
8035 Some(&'L') => {
8036 chars.next();
8037 mode = Some('L');
8038 continue;
8039 }
8040 Some(&'Q') => {
8041 chars.next();
8042 mode = Some('Q');
8043 continue;
8044 }
8045 Some(&'E') => {
8046 chars.next();
8047 mode = None;
8048 next_char_mod = None;
8049 continue;
8050 }
8051 Some(&'u') => {
8052 chars.next();
8053 next_char_mod = Some('u');
8054 continue;
8055 }
8056 Some(&'l') => {
8057 chars.next();
8058 next_char_mod = Some('l');
8059 continue;
8060 }
8061 _ => {}
8062 }
8063 }
8064
8065 let ch = c;
8066
8067 if let Some(m) = next_char_mod.take() {
8069 let transformed = match m {
8070 'u' => ch.to_uppercase().next().unwrap_or(ch),
8071 'l' => ch.to_lowercase().next().unwrap_or(ch),
8072 _ => ch,
8073 };
8074 result.push(transformed);
8075 } else {
8076 match mode {
8078 Some('U') => {
8079 for uc in ch.to_uppercase() {
8080 result.push(uc);
8081 }
8082 }
8083 Some('L') => {
8084 for lc in ch.to_lowercase() {
8085 result.push(lc);
8086 }
8087 }
8088 Some('Q') => {
8089 if !ch.is_ascii_alphanumeric() && ch != '_' {
8090 result.push('\\');
8091 }
8092 result.push(ch);
8093 }
8094 None | Some(_) => {
8095 result.push(ch);
8096 }
8097 }
8098 }
8099 }
8100 result
8101 }
8102
8103 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
8104 let line = expr.line;
8105 match &expr.kind {
8106 ExprKind::Integer(n) => Ok(PerlValue::integer(*n)),
8107 ExprKind::Float(f) => Ok(PerlValue::float(*f)),
8108 ExprKind::String(s) => {
8109 let processed = Self::process_case_escapes(s);
8110 Ok(PerlValue::string(processed))
8111 }
8112 ExprKind::Bareword(s) => {
8113 if s == "__PACKAGE__" {
8114 return Ok(PerlValue::string(self.current_package()));
8115 }
8116 if let Some(sub) = self.resolve_sub_by_name(s) {
8117 return self.call_sub(&sub, vec![], ctx, line);
8118 }
8119 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
8121 return r.map_err(Into::into);
8122 }
8123 Ok(PerlValue::string(s.clone()))
8124 }
8125 ExprKind::Undef => Ok(PerlValue::UNDEF),
8126 ExprKind::MagicConst(MagicConstKind::File) => Ok(PerlValue::string(self.file.clone())),
8127 ExprKind::MagicConst(MagicConstKind::Line) => Ok(PerlValue::integer(expr.line as i64)),
8128 ExprKind::MagicConst(MagicConstKind::Sub) => {
8129 if let Some(sub) = self.current_sub_stack.last().cloned() {
8130 Ok(PerlValue::code_ref(sub))
8131 } else {
8132 Ok(PerlValue::UNDEF)
8133 }
8134 }
8135 ExprKind::Regex(pattern, flags) => {
8136 if ctx == WantarrayCtx::Void {
8137 let topic = self.scope.get_scalar("_");
8139 let s = topic.to_string();
8140 self.regex_match_execute(s, pattern, flags, false, "_", line)
8141 } else {
8142 let re = self.compile_regex(pattern, flags, line)?;
8143 Ok(PerlValue::regex(re, pattern.clone(), flags.clone()))
8144 }
8145 }
8146 ExprKind::QW(words) => Ok(PerlValue::array(
8147 words.iter().map(|w| PerlValue::string(w.clone())).collect(),
8148 )),
8149
8150 ExprKind::InterpolatedString(parts) => {
8152 let mut raw_result = String::new();
8153 for part in parts {
8154 match part {
8155 StringPart::Literal(s) => raw_result.push_str(s),
8156 StringPart::ScalarVar(name) => {
8157 self.check_strict_scalar_var(name, line)?;
8158 let val = self.get_special_var(name);
8159 let s = self.stringify_value(val, line)?;
8160 raw_result.push_str(&s);
8161 }
8162 StringPart::ArrayVar(name) => {
8163 self.check_strict_array_var(name, line)?;
8164 let aname = self.stash_array_name_for_package(name);
8165 let arr = self.scope.get_array(&aname);
8166 let mut parts = Vec::with_capacity(arr.len());
8167 for v in &arr {
8168 parts.push(self.stringify_value(v.clone(), line)?);
8169 }
8170 let sep = self.list_separator.clone();
8171 raw_result.push_str(&parts.join(&sep));
8172 }
8173 StringPart::Expr(e) => {
8174 if let ExprKind::ArraySlice { array, .. } = &e.kind {
8175 self.check_strict_array_var(array, line)?;
8176 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8177 let val = self.peel_array_ref_for_list_join(val);
8178 let list = val.to_list();
8179 let sep = self.list_separator.clone();
8180 let mut parts = Vec::with_capacity(list.len());
8181 for v in list {
8182 parts.push(self.stringify_value(v, line)?);
8183 }
8184 raw_result.push_str(&parts.join(&sep));
8185 } else if let ExprKind::Deref {
8186 kind: Sigil::Array, ..
8187 } = &e.kind
8188 {
8189 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8190 let val = self.peel_array_ref_for_list_join(val);
8191 let list = val.to_list();
8192 let sep = self.list_separator.clone();
8193 let mut parts = Vec::with_capacity(list.len());
8194 for v in list {
8195 parts.push(self.stringify_value(v, line)?);
8196 }
8197 raw_result.push_str(&parts.join(&sep));
8198 } else {
8199 let val = self.eval_expr(e)?;
8200 let s = self.stringify_value(val, line)?;
8201 raw_result.push_str(&s);
8202 }
8203 }
8204 }
8205 }
8206 let result = Self::process_case_escapes(&raw_result);
8207 Ok(PerlValue::string(result))
8208 }
8209
8210 ExprKind::ScalarVar(name) => {
8212 self.check_strict_scalar_var(name, line)?;
8213 let stor = self.tree_scalar_storage_name(name);
8214 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
8215 let class = obj
8216 .as_blessed_ref()
8217 .map(|b| b.class.clone())
8218 .unwrap_or_default();
8219 let full = format!("{}::FETCH", class);
8220 if let Some(sub) = self.subs.get(&full).cloned() {
8221 return self.call_sub(&sub, vec![obj], ctx, line);
8222 }
8223 }
8224 Ok(self.get_special_var(&stor))
8225 }
8226 ExprKind::ArrayVar(name) => {
8227 self.check_strict_array_var(name, line)?;
8228 let aname = self.stash_array_name_for_package(name);
8229 let arr = self.scope.get_array(&aname);
8230 if ctx == WantarrayCtx::List {
8231 Ok(PerlValue::array(arr))
8232 } else {
8233 Ok(PerlValue::integer(arr.len() as i64))
8234 }
8235 }
8236 ExprKind::HashVar(name) => {
8237 self.check_strict_hash_var(name, line)?;
8238 self.touch_env_hash(name);
8239 let h = self.scope.get_hash(name);
8240 let pv = PerlValue::hash(h);
8241 if ctx == WantarrayCtx::List {
8242 Ok(pv)
8243 } else {
8244 Ok(pv.scalar_context())
8245 }
8246 }
8247 ExprKind::Typeglob(name) => {
8248 let n = self.resolve_io_handle_name(name);
8249 Ok(PerlValue::string(n))
8250 }
8251 ExprKind::TypeglobExpr(e) => {
8252 let name = self.eval_expr(e)?.to_string();
8253 let n = self.resolve_io_handle_name(&name);
8254 Ok(PerlValue::string(n))
8255 }
8256 ExprKind::ArrayElement { array, index } => {
8257 self.check_strict_array_var(array, line)?;
8258 let idx = self.eval_expr(index)?.to_int();
8259 let aname = self.stash_array_name_for_package(array);
8260 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
8261 let class = obj
8262 .as_blessed_ref()
8263 .map(|b| b.class.clone())
8264 .unwrap_or_default();
8265 let full = format!("{}::FETCH", class);
8266 if let Some(sub) = self.subs.get(&full).cloned() {
8267 let arg_vals = vec![obj, PerlValue::integer(idx)];
8268 return self.call_sub(&sub, arg_vals, ctx, line);
8269 }
8270 }
8271 Ok(self.scope.get_array_element(&aname, idx))
8272 }
8273 ExprKind::HashElement { hash, key } => {
8274 self.check_strict_hash_var(hash, line)?;
8275 let k = self.eval_expr(key)?.to_string();
8276 self.touch_env_hash(hash);
8277 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
8278 let class = obj
8279 .as_blessed_ref()
8280 .map(|b| b.class.clone())
8281 .unwrap_or_default();
8282 let full = format!("{}::FETCH", class);
8283 if let Some(sub) = self.subs.get(&full).cloned() {
8284 let arg_vals = vec![obj, PerlValue::string(k)];
8285 return self.call_sub(&sub, arg_vals, ctx, line);
8286 }
8287 }
8288 Ok(self.scope.get_hash_element(hash, &k))
8289 }
8290 ExprKind::ArraySlice { array, indices } => {
8291 self.check_strict_array_var(array, line)?;
8292 let aname = self.stash_array_name_for_package(array);
8293 let flat = self.flatten_array_slice_index_specs(indices)?;
8294 let mut result = Vec::with_capacity(flat.len());
8295 for idx in flat {
8296 result.push(self.scope.get_array_element(&aname, idx));
8297 }
8298 Ok(PerlValue::array(result))
8299 }
8300 ExprKind::HashSlice { hash, keys } => {
8301 self.check_strict_hash_var(hash, line)?;
8302 self.touch_env_hash(hash);
8303 let mut result = Vec::new();
8304 for key_expr in keys {
8305 for k in self.eval_hash_slice_key_components(key_expr)? {
8306 result.push(self.scope.get_hash_element(hash, &k));
8307 }
8308 }
8309 Ok(PerlValue::array(result))
8310 }
8311 ExprKind::HashSliceDeref { container, keys } => {
8312 let hv = self.eval_expr(container)?;
8313 let mut key_vals = Vec::with_capacity(keys.len());
8314 for key_expr in keys {
8315 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
8316 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8317 } else {
8318 self.eval_expr(key_expr)?
8319 };
8320 key_vals.push(v);
8321 }
8322 self.hash_slice_deref_values(&hv, &key_vals, line)
8323 }
8324 ExprKind::AnonymousListSlice { source, indices } => {
8325 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
8326 let items = list_val.to_list();
8327 let flat = self.flatten_array_slice_index_specs(indices)?;
8328 let mut out = Vec::with_capacity(flat.len());
8329 for idx in flat {
8330 let i = if idx < 0 {
8331 (items.len() as i64 + idx) as usize
8332 } else {
8333 idx as usize
8334 };
8335 out.push(items.get(i).cloned().unwrap_or(PerlValue::UNDEF));
8336 }
8337 let arr = PerlValue::array(out);
8338 if ctx != WantarrayCtx::List {
8339 let v = arr.to_list();
8340 Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF))
8341 } else {
8342 Ok(arr)
8343 }
8344 }
8345
8346 ExprKind::ScalarRef(inner) => match &inner.kind {
8348 ExprKind::ScalarVar(name) => Ok(PerlValue::scalar_binding_ref(name.clone())),
8349 ExprKind::ArrayVar(name) => {
8350 self.check_strict_array_var(name, line)?;
8351 let aname = self.stash_array_name_for_package(name);
8352 Ok(PerlValue::array_binding_ref(aname))
8353 }
8354 ExprKind::HashVar(name) => {
8355 self.check_strict_hash_var(name, line)?;
8356 Ok(PerlValue::hash_binding_ref(name.clone()))
8357 }
8358 ExprKind::Deref {
8359 expr: e,
8360 kind: Sigil::Array,
8361 } => {
8362 let v = self.eval_expr(e)?;
8363 self.make_array_ref_alias(v, line)
8364 }
8365 ExprKind::Deref {
8366 expr: e,
8367 kind: Sigil::Hash,
8368 } => {
8369 let v = self.eval_expr(e)?;
8370 self.make_hash_ref_alias(v, line)
8371 }
8372 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
8373 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8374 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8375 }
8376 ExprKind::HashSliceDeref { .. } => {
8377 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8378 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8379 }
8380 _ => {
8381 let val = self.eval_expr(inner)?;
8382 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8383 }
8384 },
8385 ExprKind::ArrayRef(elems) => {
8386 let mut arr = Vec::with_capacity(elems.len());
8390 for e in elems {
8391 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8392 if let Some(vec) = v.as_array_vec() {
8393 arr.extend(vec);
8394 } else {
8395 arr.push(v);
8396 }
8397 }
8398 Ok(PerlValue::array_ref(Arc::new(RwLock::new(arr))))
8399 }
8400 ExprKind::HashRef(pairs) => {
8401 let mut map = IndexMap::new();
8404 for (k, v) in pairs {
8405 let key_str = self.eval_expr(k)?.to_string();
8406 if key_str == "__HASH_SPREAD__" {
8407 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8409 let items = spread.to_list();
8410 let mut i = 0;
8411 while i + 1 < items.len() {
8412 map.insert(items[i].to_string(), items[i + 1].clone());
8413 i += 2;
8414 }
8415 } else {
8416 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8417 map.insert(key_str, val);
8418 }
8419 }
8420 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map))))
8421 }
8422 ExprKind::CodeRef { params, body } => {
8423 let captured = self.scope.capture();
8424 Ok(PerlValue::code_ref(Arc::new(PerlSub {
8425 name: "__ANON__".to_string(),
8426 params: params.clone(),
8427 body: body.clone(),
8428 closure_env: Some(captured),
8429 prototype: None,
8430 fib_like: None,
8431 })))
8432 }
8433 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
8434 ExprKind::SubroutineCodeRef(name) => {
8435 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
8436 PerlError::runtime(self.undefined_subroutine_resolve_message(name), line)
8437 })?;
8438 Ok(PerlValue::code_ref(sub))
8439 }
8440 ExprKind::DynamicSubCodeRef(expr) => {
8441 let name = self.eval_expr(expr)?.to_string();
8442 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
8443 PerlError::runtime(self.undefined_subroutine_resolve_message(&name), line)
8444 })?;
8445 Ok(PerlValue::code_ref(sub))
8446 }
8447 ExprKind::Deref { expr, kind } => {
8448 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
8449 let val = self.eval_expr(expr)?;
8450 let n = self.array_deref_len(val, line)?;
8451 return Ok(PerlValue::integer(n));
8452 }
8453 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
8454 let val = self.eval_expr(expr)?;
8455 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
8456 return Ok(h.scalar_context());
8457 }
8458 let val = self.eval_expr(expr)?;
8459 self.symbolic_deref(val, *kind, line)
8460 }
8461 ExprKind::ArrowDeref { expr, index, kind } => {
8462 match kind {
8463 DerefKind::Array => {
8464 let container = self.eval_arrow_array_base(expr, line)?;
8465 if let ExprKind::List(indices) = &index.kind {
8466 let mut out = Vec::with_capacity(indices.len());
8467 for ix in indices {
8468 let idx = self.eval_expr(ix)?.to_int();
8469 out.push(self.read_arrow_array_element(
8470 container.clone(),
8471 idx,
8472 line,
8473 )?);
8474 }
8475 let arr = PerlValue::array(out);
8476 if ctx != WantarrayCtx::List {
8477 let v = arr.to_list();
8478 return Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF));
8479 }
8480 return Ok(arr);
8481 }
8482 let idx = self.eval_expr(index)?.to_int();
8483 self.read_arrow_array_element(container, idx, line)
8484 }
8485 DerefKind::Hash => {
8486 let val = self.eval_arrow_hash_base(expr, line)?;
8487 let key = self.eval_expr(index)?.to_string();
8488 self.read_arrow_hash_element(val, key.as_str(), line)
8489 }
8490 DerefKind::Call => {
8491 let val = self.eval_expr(expr)?;
8493 if let ExprKind::List(ref arg_exprs) = index.kind {
8494 let mut args = Vec::new();
8495 for a in arg_exprs {
8496 args.push(self.eval_expr(a)?);
8497 }
8498 if let Some(sub) = val.as_code_ref() {
8499 return self.call_sub(&sub, args, ctx, line);
8500 }
8501 Err(PerlError::runtime("Not a code reference", line).into())
8502 } else {
8503 Err(PerlError::runtime("Invalid call deref", line).into())
8504 }
8505 }
8506 }
8507 }
8508
8509 ExprKind::BinOp { left, op, right } => {
8511 match op {
8513 BinOp::BindMatch => {
8514 let lv = self.eval_expr(left)?;
8515 let rv = self.eval_expr(right)?;
8516 let s = lv.to_string();
8517 let pat = rv.to_string();
8518 return self.regex_match_execute(s, &pat, "", false, "_", line);
8519 }
8520 BinOp::BindNotMatch => {
8521 let lv = self.eval_expr(left)?;
8522 let rv = self.eval_expr(right)?;
8523 let s = lv.to_string();
8524 let pat = rv.to_string();
8525 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
8526 return Ok(PerlValue::integer(if m.is_true() { 0 } else { 1 }));
8527 }
8528 BinOp::LogAnd | BinOp::LogAndWord => {
8529 match &left.kind {
8530 ExprKind::Regex(_, _) => {
8531 if !self.eval_boolean_rvalue_condition(left)? {
8532 return Ok(PerlValue::string(String::new()));
8533 }
8534 }
8535 _ => {
8536 let lv = self.eval_expr(left)?;
8537 if !lv.is_true() {
8538 return Ok(lv);
8539 }
8540 }
8541 }
8542 return match &right.kind {
8543 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8544 if self.eval_boolean_rvalue_condition(right)? {
8545 1
8546 } else {
8547 0
8548 },
8549 )),
8550 _ => self.eval_expr(right),
8551 };
8552 }
8553 BinOp::LogOr | BinOp::LogOrWord => {
8554 match &left.kind {
8555 ExprKind::Regex(_, _) => {
8556 if self.eval_boolean_rvalue_condition(left)? {
8557 return Ok(PerlValue::integer(1));
8558 }
8559 }
8560 _ => {
8561 let lv = self.eval_expr(left)?;
8562 if lv.is_true() {
8563 return Ok(lv);
8564 }
8565 }
8566 }
8567 return match &right.kind {
8568 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8569 if self.eval_boolean_rvalue_condition(right)? {
8570 1
8571 } else {
8572 0
8573 },
8574 )),
8575 _ => self.eval_expr(right),
8576 };
8577 }
8578 BinOp::DefinedOr => {
8579 let lv = self.eval_expr(left)?;
8580 if !lv.is_undef() {
8581 return Ok(lv);
8582 }
8583 return self.eval_expr(right);
8584 }
8585 _ => {}
8586 }
8587 let lv = self.eval_expr(left)?;
8588 let rv = self.eval_expr(right)?;
8589 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
8590 return r;
8591 }
8592 self.eval_binop(*op, &lv, &rv, line)
8593 }
8594
8595 ExprKind::UnaryOp { op, expr } => match op {
8597 UnaryOp::PreIncrement => {
8598 if let ExprKind::ScalarVar(name) = &expr.kind {
8599 self.check_strict_scalar_var(name, line)?;
8600 let n = self.english_scalar_name(name);
8601 return Ok(self
8602 .scope
8603 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() + 1)));
8604 }
8605 if let ExprKind::Deref { kind, .. } = &expr.kind {
8606 if matches!(kind, Sigil::Array | Sigil::Hash) {
8607 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8608 *kind, true, true, line,
8609 ));
8610 }
8611 }
8612 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8613 let href = self.eval_expr(container)?;
8614 let mut key_vals = Vec::with_capacity(keys.len());
8615 for key_expr in keys {
8616 key_vals.push(self.eval_expr(key_expr)?);
8617 }
8618 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
8619 }
8620 if let ExprKind::ArrowDeref {
8621 expr: arr_expr,
8622 index,
8623 kind: DerefKind::Array,
8624 } = &expr.kind
8625 {
8626 if let ExprKind::List(indices) = &index.kind {
8627 let container = self.eval_arrow_array_base(arr_expr, line)?;
8628 let mut idxs = Vec::with_capacity(indices.len());
8629 for ix in indices {
8630 idxs.push(self.eval_expr(ix)?.to_int());
8631 }
8632 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
8633 }
8634 }
8635 let val = self.eval_expr(expr)?;
8636 let new_val = PerlValue::integer(val.to_int() + 1);
8637 self.assign_value(expr, new_val.clone())?;
8638 Ok(new_val)
8639 }
8640 UnaryOp::PreDecrement => {
8641 if let ExprKind::ScalarVar(name) = &expr.kind {
8642 self.check_strict_scalar_var(name, line)?;
8643 let n = self.english_scalar_name(name);
8644 return Ok(self
8645 .scope
8646 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() - 1)));
8647 }
8648 if let ExprKind::Deref { kind, .. } = &expr.kind {
8649 if matches!(kind, Sigil::Array | Sigil::Hash) {
8650 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8651 *kind, true, false, line,
8652 ));
8653 }
8654 }
8655 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8656 let href = self.eval_expr(container)?;
8657 let mut key_vals = Vec::with_capacity(keys.len());
8658 for key_expr in keys {
8659 key_vals.push(self.eval_expr(key_expr)?);
8660 }
8661 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
8662 }
8663 if let ExprKind::ArrowDeref {
8664 expr: arr_expr,
8665 index,
8666 kind: DerefKind::Array,
8667 } = &expr.kind
8668 {
8669 if let ExprKind::List(indices) = &index.kind {
8670 let container = self.eval_arrow_array_base(arr_expr, line)?;
8671 let mut idxs = Vec::with_capacity(indices.len());
8672 for ix in indices {
8673 idxs.push(self.eval_expr(ix)?.to_int());
8674 }
8675 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
8676 }
8677 }
8678 let val = self.eval_expr(expr)?;
8679 let new_val = PerlValue::integer(val.to_int() - 1);
8680 self.assign_value(expr, new_val.clone())?;
8681 Ok(new_val)
8682 }
8683 _ => {
8684 match op {
8685 UnaryOp::LogNot | UnaryOp::LogNotWord => {
8686 if let ExprKind::Regex(pattern, flags) = &expr.kind {
8687 let topic = self.scope.get_scalar("_");
8688 let rl = expr.line;
8689 let s = topic.to_string();
8690 let v =
8691 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
8692 return Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }));
8693 }
8694 }
8695 _ => {}
8696 }
8697 let val = self.eval_expr(expr)?;
8698 match op {
8699 UnaryOp::Negate => {
8700 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
8701 return r;
8702 }
8703 if let Some(n) = val.as_integer() {
8704 Ok(PerlValue::integer(-n))
8705 } else {
8706 Ok(PerlValue::float(-val.to_number()))
8707 }
8708 }
8709 UnaryOp::LogNot => {
8710 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8711 let pv = r?;
8712 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8713 }
8714 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8715 }
8716 UnaryOp::BitNot => Ok(PerlValue::integer(!val.to_int())),
8717 UnaryOp::LogNotWord => {
8718 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8719 let pv = r?;
8720 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8721 }
8722 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8723 }
8724 UnaryOp::Ref => {
8725 if let ExprKind::ScalarVar(name) = &expr.kind {
8726 return Ok(PerlValue::scalar_binding_ref(name.clone()));
8727 }
8728 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8729 }
8730 _ => unreachable!(),
8731 }
8732 }
8733 },
8734
8735 ExprKind::PostfixOp { expr, op } => {
8736 if let ExprKind::ScalarVar(name) = &expr.kind {
8739 self.check_strict_scalar_var(name, line)?;
8740 let n = self.english_scalar_name(name);
8741 let f: fn(&PerlValue) -> PerlValue = match op {
8742 PostfixOp::Increment => |v| PerlValue::integer(v.to_int() + 1),
8743 PostfixOp::Decrement => |v| PerlValue::integer(v.to_int() - 1),
8744 };
8745 return Ok(self.scope.atomic_mutate_post(n, f));
8746 }
8747 if let ExprKind::Deref { kind, .. } = &expr.kind {
8748 if matches!(kind, Sigil::Array | Sigil::Hash) {
8749 let is_inc = matches!(op, PostfixOp::Increment);
8750 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8751 *kind, false, is_inc, line,
8752 ));
8753 }
8754 }
8755 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8756 let href = self.eval_expr(container)?;
8757 let mut key_vals = Vec::with_capacity(keys.len());
8758 for key_expr in keys {
8759 key_vals.push(self.eval_expr(key_expr)?);
8760 }
8761 let kind_byte = match op {
8762 PostfixOp::Increment => 2u8,
8763 PostfixOp::Decrement => 3u8,
8764 };
8765 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
8766 }
8767 if let ExprKind::ArrowDeref {
8768 expr: arr_expr,
8769 index,
8770 kind: DerefKind::Array,
8771 } = &expr.kind
8772 {
8773 if let ExprKind::List(indices) = &index.kind {
8774 let container = self.eval_arrow_array_base(arr_expr, line)?;
8775 let mut idxs = Vec::with_capacity(indices.len());
8776 for ix in indices {
8777 idxs.push(self.eval_expr(ix)?.to_int());
8778 }
8779 let kind_byte = match op {
8780 PostfixOp::Increment => 2u8,
8781 PostfixOp::Decrement => 3u8,
8782 };
8783 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
8784 }
8785 }
8786 let val = self.eval_expr(expr)?;
8787 let old = val.clone();
8788 let new_val = match op {
8789 PostfixOp::Increment => PerlValue::integer(val.to_int() + 1),
8790 PostfixOp::Decrement => PerlValue::integer(val.to_int() - 1),
8791 };
8792 self.assign_value(expr, new_val)?;
8793 Ok(old)
8794 }
8795
8796 ExprKind::Assign { target, value } => {
8798 if let ExprKind::Typeglob(lhs) = &target.kind {
8799 if let ExprKind::Typeglob(rhs) = &value.kind {
8800 self.copy_typeglob_slots(lhs, rhs, line)?;
8801 return self.eval_expr(value);
8802 }
8803 }
8804 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
8805 self.assign_value(target, val.clone())?;
8806 Ok(val)
8807 }
8808 ExprKind::CompoundAssign { target, op, value } => {
8809 if let ExprKind::ScalarVar(name) = &target.kind {
8812 self.check_strict_scalar_var(name, line)?;
8813 let n = self.english_scalar_name(name);
8814 let op = *op;
8815 let rhs = match op {
8816 BinOp::LogOr => {
8817 let old = self.scope.get_scalar(n);
8818 if old.is_true() {
8819 return Ok(old);
8820 }
8821 self.eval_expr(value)?
8822 }
8823 BinOp::DefinedOr => {
8824 let old = self.scope.get_scalar(n);
8825 if !old.is_undef() {
8826 return Ok(old);
8827 }
8828 self.eval_expr(value)?
8829 }
8830 BinOp::LogAnd => {
8831 let old = self.scope.get_scalar(n);
8832 if !old.is_true() {
8833 return Ok(old);
8834 }
8835 self.eval_expr(value)?
8836 }
8837 _ => self.eval_expr(value)?,
8838 };
8839 return Ok(self.scalar_compound_assign_scalar_target(n, op, rhs)?);
8840 }
8841 let rhs = self.eval_expr(value)?;
8842 if let ExprKind::HashElement { hash, key } = &target.kind {
8844 self.check_strict_hash_var(hash, line)?;
8845 let k = self.eval_expr(key)?.to_string();
8846 let op = *op;
8847 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
8848 BinOp::Add => {
8849 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8850 PerlValue::integer(a.wrapping_add(b))
8851 } else {
8852 PerlValue::float(old.to_number() + rhs.to_number())
8853 }
8854 }
8855 BinOp::Sub => {
8856 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8857 PerlValue::integer(a.wrapping_sub(b))
8858 } else {
8859 PerlValue::float(old.to_number() - rhs.to_number())
8860 }
8861 }
8862 BinOp::Concat => {
8863 let mut s = old.to_string();
8864 rhs.append_to(&mut s);
8865 PerlValue::string(s)
8866 }
8867 _ => PerlValue::float(old.to_number() + rhs.to_number()),
8868 })?);
8869 }
8870 if let ExprKind::ArrayElement { array, index } = &target.kind {
8872 self.check_strict_array_var(array, line)?;
8873 let idx = self.eval_expr(index)?.to_int();
8874 let op = *op;
8875 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
8876 BinOp::Add => {
8877 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8878 PerlValue::integer(a.wrapping_add(b))
8879 } else {
8880 PerlValue::float(old.to_number() + rhs.to_number())
8881 }
8882 }
8883 _ => PerlValue::float(old.to_number() + rhs.to_number()),
8884 })?);
8885 }
8886 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
8887 let href = self.eval_expr(container)?;
8888 let mut key_vals = Vec::with_capacity(keys.len());
8889 for key_expr in keys {
8890 key_vals.push(self.eval_expr(key_expr)?);
8891 }
8892 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
8893 }
8894 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
8895 if let ExprKind::Deref {
8896 expr: inner,
8897 kind: Sigil::Array,
8898 } = &source.kind
8899 {
8900 let container = self.eval_arrow_array_base(inner, line)?;
8901 let idxs = self.flatten_array_slice_index_specs(indices)?;
8902 return self
8903 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
8904 }
8905 }
8906 if let ExprKind::ArrowDeref {
8907 expr: arr_expr,
8908 index,
8909 kind: DerefKind::Array,
8910 } = &target.kind
8911 {
8912 if let ExprKind::List(indices) = &index.kind {
8913 let container = self.eval_arrow_array_base(arr_expr, line)?;
8914 let mut idxs = Vec::with_capacity(indices.len());
8915 for ix in indices {
8916 idxs.push(self.eval_expr(ix)?.to_int());
8917 }
8918 return self
8919 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
8920 }
8921 }
8922 let old = self.eval_expr(target)?;
8923 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
8924 self.assign_value(target, new_val.clone())?;
8925 Ok(new_val)
8926 }
8927
8928 ExprKind::Ternary {
8930 condition,
8931 then_expr,
8932 else_expr,
8933 } => {
8934 if self.eval_boolean_rvalue_condition(condition)? {
8935 self.eval_expr(then_expr)
8936 } else {
8937 self.eval_expr(else_expr)
8938 }
8939 }
8940
8941 ExprKind::Range {
8943 from,
8944 to,
8945 exclusive,
8946 } => {
8947 if ctx == WantarrayCtx::List {
8948 let f = self.eval_expr(from)?;
8949 let t = self.eval_expr(to)?;
8950 let list = perl_list_range_expand(f, t);
8951 Ok(PerlValue::array(list))
8952 } else {
8953 let key = std::ptr::from_ref(expr) as usize;
8954 match (&from.kind, &to.kind) {
8955 (
8956 ExprKind::Regex(left_pat, left_flags),
8957 ExprKind::Regex(right_pat, right_flags),
8958 ) => {
8959 let dot = self.scalar_flipflop_dot_line();
8960 let subject = self.scope.get_scalar("_").to_string();
8961 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
8962 |e| match e {
8963 FlowOrError::Error(err) => err,
8964 FlowOrError::Flow(_) => PerlError::runtime(
8965 "unexpected flow in regex flip-flop",
8966 line,
8967 ),
8968 },
8969 )?;
8970 let right_re = self
8971 .compile_regex(right_pat, right_flags, line)
8972 .map_err(|e| match e {
8973 FlowOrError::Error(err) => err,
8974 FlowOrError::Flow(_) => PerlError::runtime(
8975 "unexpected flow in regex flip-flop",
8976 line,
8977 ),
8978 })?;
8979 let left_m = left_re.is_match(&subject);
8980 let right_m = right_re.is_match(&subject);
8981 let st = self.flip_flop_tree.entry(key).or_default();
8982 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
8983 &mut st.active,
8984 &mut st.exclusive_left_line,
8985 *exclusive,
8986 dot,
8987 left_m,
8988 right_m,
8989 )))
8990 }
8991 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
8992 let dot = self.scalar_flipflop_dot_line();
8993 let subject = self.scope.get_scalar("_").to_string();
8994 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
8995 |e| match e {
8996 FlowOrError::Error(err) => err,
8997 FlowOrError::Flow(_) => PerlError::runtime(
8998 "unexpected flow in regex/eof flip-flop",
8999 line,
9000 ),
9001 },
9002 )?;
9003 let left_m = left_re.is_match(&subject);
9004 let right_m = self.eof_without_arg_is_true();
9005 let st = self.flip_flop_tree.entry(key).or_default();
9006 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9007 &mut st.active,
9008 &mut st.exclusive_left_line,
9009 *exclusive,
9010 dot,
9011 left_m,
9012 right_m,
9013 )))
9014 }
9015 (
9016 ExprKind::Regex(left_pat, left_flags),
9017 ExprKind::Integer(_) | ExprKind::Float(_),
9018 ) => {
9019 let dot = self.scalar_flipflop_dot_line();
9020 let right = self.eval_expr(to)?.to_int();
9021 let subject = self.scope.get_scalar("_").to_string();
9022 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9023 |e| match e {
9024 FlowOrError::Error(err) => err,
9025 FlowOrError::Flow(_) => PerlError::runtime(
9026 "unexpected flow in regex flip-flop",
9027 line,
9028 ),
9029 },
9030 )?;
9031 let left_m = left_re.is_match(&subject);
9032 let right_m = dot == right;
9033 let st = self.flip_flop_tree.entry(key).or_default();
9034 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9035 &mut st.active,
9036 &mut st.exclusive_left_line,
9037 *exclusive,
9038 dot,
9039 left_m,
9040 right_m,
9041 )))
9042 }
9043 (ExprKind::Regex(left_pat, left_flags), _) => {
9044 if let ExprKind::Eof(Some(_)) = &to.kind {
9045 return Err(FlowOrError::Error(PerlError::runtime(
9046 "regex flip-flop with eof(HANDLE) is not supported",
9047 line,
9048 )));
9049 }
9050 let dot = self.scalar_flipflop_dot_line();
9051 let subject = self.scope.get_scalar("_").to_string();
9052 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
9053 |e| match e {
9054 FlowOrError::Error(err) => err,
9055 FlowOrError::Flow(_) => PerlError::runtime(
9056 "unexpected flow in regex flip-flop",
9057 line,
9058 ),
9059 },
9060 )?;
9061 let left_m = left_re.is_match(&subject);
9062 let right_m = self.eval_boolean_rvalue_condition(to)?;
9063 let st = self.flip_flop_tree.entry(key).or_default();
9064 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
9065 &mut st.active,
9066 &mut st.exclusive_left_line,
9067 *exclusive,
9068 dot,
9069 left_m,
9070 right_m,
9071 )))
9072 }
9073 _ => {
9074 let left = self.eval_expr(from)?.to_int();
9075 let right = self.eval_expr(to)?.to_int();
9076 let dot = self.scalar_flipflop_dot_line();
9077 let st = self.flip_flop_tree.entry(key).or_default();
9078 if !st.active {
9079 if dot == left {
9080 st.active = true;
9081 if *exclusive {
9082 st.exclusive_left_line = Some(dot);
9083 } else {
9084 st.exclusive_left_line = None;
9085 if dot == right {
9086 st.active = false;
9087 }
9088 }
9089 return Ok(PerlValue::integer(1));
9090 }
9091 return Ok(PerlValue::integer(0));
9092 }
9093 if let Some(ll) = st.exclusive_left_line {
9094 if dot == right && dot > ll {
9095 st.active = false;
9096 st.exclusive_left_line = None;
9097 }
9098 } else if dot == right {
9099 st.active = false;
9100 }
9101 Ok(PerlValue::integer(1))
9102 }
9103 }
9104 }
9105 }
9106
9107 ExprKind::Repeat { expr, count } => {
9109 let val = self.eval_expr(expr)?;
9110 let n = self.eval_expr(count)?.to_int().max(0) as usize;
9111 if let Some(s) = val.as_str() {
9112 Ok(PerlValue::string(s.repeat(n)))
9113 } else if let Some(a) = val.as_array_vec() {
9114 let mut result = Vec::with_capacity(a.len() * n);
9115 for _ in 0..n {
9116 result.extend(a.iter().cloned());
9117 }
9118 Ok(PerlValue::array(result))
9119 } else {
9120 Ok(PerlValue::string(val.to_string().repeat(n)))
9121 }
9122 }
9123
9124 ExprKind::MyExpr { keyword, decls } => {
9129 let stmt_kind = match keyword.as_str() {
9132 "my" => StmtKind::My(decls.clone()),
9133 "our" => StmtKind::Our(decls.clone()),
9134 "state" => StmtKind::State(decls.clone()),
9135 "local" => StmtKind::Local(decls.clone()),
9136 _ => StmtKind::My(decls.clone()),
9137 };
9138 let stmt = Statement {
9139 label: None,
9140 kind: stmt_kind,
9141 line,
9142 };
9143 self.exec_statement(&stmt)?;
9144 let first = decls.first().ok_or_else(|| {
9148 FlowOrError::Error(PerlError::runtime("MyExpr: empty decl list", line))
9149 })?;
9150 Ok(match first.sigil {
9151 Sigil::Scalar => self.scope.get_scalar(&first.name),
9152 Sigil::Array => PerlValue::array(self.scope.get_array(&first.name)),
9153 Sigil::Hash => {
9154 let h = self.scope.get_hash(&first.name);
9155 let mut flat: Vec<PerlValue> = Vec::with_capacity(h.len() * 2);
9156 for (k, v) in h {
9157 flat.push(PerlValue::string(k));
9158 flat.push(v);
9159 }
9160 PerlValue::array(flat)
9161 }
9162 Sigil::Typeglob => PerlValue::UNDEF,
9163 })
9164 }
9165
9166 ExprKind::FuncCall { name, args } => {
9168 if matches!(name.as_str(), "read" | "CORE::read") && args.len() >= 3 {
9170 let fh_val = self.eval_expr(&args[0])?;
9171 let fh = fh_val
9172 .as_io_handle_name()
9173 .unwrap_or_else(|| fh_val.to_string());
9174 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
9175 let offset = if args.len() > 3 {
9176 self.eval_expr(&args[3])?.to_int().max(0) as usize
9177 } else {
9178 0
9179 };
9180 let var_name = match &args[1].kind {
9182 ExprKind::ScalarVar(n) => n.clone(),
9183 _ => self.eval_expr(&args[1])?.to_string(),
9184 };
9185 let mut buf = vec![0u8; len];
9186 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
9187 slot.lock().read(&mut buf).unwrap_or(0)
9188 } else if fh == "STDIN" {
9189 std::io::stdin().read(&mut buf).unwrap_or(0)
9190 } else {
9191 return Err(PerlError::runtime(
9192 format!("read: unopened handle {}", fh),
9193 line,
9194 )
9195 .into());
9196 };
9197 buf.truncate(n);
9198 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
9199 if offset > 0 {
9200 let mut existing = self.scope.get_scalar(&var_name).to_string();
9201 while existing.len() < offset {
9202 existing.push('\0');
9203 }
9204 existing.push_str(&read_str);
9205 let _ = self
9206 .scope
9207 .set_scalar(&var_name, PerlValue::string(existing));
9208 } else {
9209 let _ = self
9210 .scope
9211 .set_scalar(&var_name, PerlValue::string(read_str));
9212 }
9213 return Ok(PerlValue::integer(n as i64));
9214 }
9215 if matches!(name.as_str(), "group_by" | "chunk_by") {
9216 if args.len() != 2 {
9217 return Err(PerlError::runtime(
9218 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
9219 line,
9220 )
9221 .into());
9222 }
9223 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
9224 }
9225 if matches!(name.as_str(), "puniq" | "pfirst" | "pany") {
9226 let mut arg_vals = Vec::with_capacity(args.len());
9227 for a in args {
9228 arg_vals.push(self.eval_expr(a)?);
9229 }
9230 let saved_wa = self.wantarray_kind;
9231 self.wantarray_kind = ctx;
9232 let r = self.eval_par_list_call(name.as_str(), &arg_vals, ctx, line);
9233 self.wantarray_kind = saved_wa;
9234 return r.map_err(Into::into);
9235 }
9236 let arg_vals = if matches!(name.as_str(), "any" | "all" | "none" | "first")
9237 || matches!(
9238 name.as_str(),
9239 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9240 )
9241 || matches!(
9242 name.as_str(),
9243 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
9244 ) {
9245 if args.len() != 2 {
9246 return Err(PerlError::runtime(
9247 format!("{}: expected BLOCK, LIST", name),
9248 line,
9249 )
9250 .into());
9251 }
9252 let cr = self.eval_expr(&args[0])?;
9253 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
9254 let mut v = vec![cr];
9255 v.extend(list_src.to_list());
9256 v
9257 } else if matches!(
9258 name.as_str(),
9259 "zip" | "List::Util::zip" | "List::Util::zip_longest"
9260 ) {
9261 let mut v = Vec::with_capacity(args.len());
9262 for a in args {
9263 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9264 }
9265 v
9266 } else if matches!(
9267 name.as_str(),
9268 "uniq"
9269 | "distinct"
9270 | "uniqstr"
9271 | "uniqint"
9272 | "uniqnum"
9273 | "flatten"
9274 | "set"
9275 | "list_count"
9276 | "list_size"
9277 | "count"
9278 | "size"
9279 | "cnt"
9280 | "with_index"
9281 | "List::Util::uniq"
9282 | "List::Util::uniqstr"
9283 | "List::Util::uniqint"
9284 | "List::Util::uniqnum"
9285 | "shuffle"
9286 | "List::Util::shuffle"
9287 | "sum"
9288 | "sum0"
9289 | "product"
9290 | "min"
9291 | "max"
9292 | "minstr"
9293 | "maxstr"
9294 | "mean"
9295 | "median"
9296 | "mode"
9297 | "stddev"
9298 | "variance"
9299 | "List::Util::sum"
9300 | "List::Util::sum0"
9301 | "List::Util::product"
9302 | "List::Util::min"
9303 | "List::Util::max"
9304 | "List::Util::minstr"
9305 | "List::Util::maxstr"
9306 | "List::Util::mean"
9307 | "List::Util::median"
9308 | "List::Util::mode"
9309 | "List::Util::stddev"
9310 | "List::Util::variance"
9311 | "pairs"
9312 | "unpairs"
9313 | "pairkeys"
9314 | "pairvalues"
9315 | "List::Util::pairs"
9316 | "List::Util::unpairs"
9317 | "List::Util::pairkeys"
9318 | "List::Util::pairvalues"
9319 ) {
9320 let mut list_out = Vec::new();
9324 if args.len() == 1 {
9325 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
9326 } else {
9327 for a in args {
9328 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
9329 }
9330 }
9331 list_out
9332 } else if matches!(
9333 name.as_str(),
9334 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail"
9335 ) {
9336 if args.is_empty() {
9337 return Err(PerlError::runtime(
9338 "take/head/tail/drop/List::Util::head|tail: need LIST..., N or unary N",
9339 line,
9340 )
9341 .into());
9342 }
9343 let mut arg_vals = Vec::with_capacity(args.len());
9344 if args.len() == 1 {
9345 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9347 } else {
9348 for a in &args[..args.len() - 1] {
9349 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9350 }
9351 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
9352 }
9353 arg_vals
9354 } else if matches!(
9355 name.as_str(),
9356 "chunked" | "List::Util::chunked" | "windowed" | "List::Util::windowed"
9357 ) {
9358 let mut list_out = Vec::new();
9359 match args.len() {
9360 0 => {
9361 return Err(PerlError::runtime(
9362 format!("{name}: expected (LIST, N) or unary N after |>"),
9363 line,
9364 )
9365 .into());
9366 }
9367 1 => {
9368 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9370 }
9371 2 => {
9372 list_out.extend(
9373 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
9374 );
9375 list_out.push(self.eval_expr(&args[1])?);
9376 }
9377 _ => {
9378 return Err(PerlError::runtime(
9379 format!(
9380 "{name}: expected exactly (LIST, N); use one list expression then size"
9381 ),
9382 line,
9383 )
9384 .into());
9385 }
9386 }
9387 list_out
9388 } else {
9389 let mut arg_vals = Vec::with_capacity(args.len());
9392 for a in args {
9393 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
9394 if let Some(items) = v.as_array_vec() {
9395 arg_vals.extend(items);
9396 } else {
9397 arg_vals.push(v);
9398 }
9399 }
9400 arg_vals
9401 };
9402 let saved_wa = self.wantarray_kind;
9404 self.wantarray_kind = ctx;
9405 if let Some(sub) = self.resolve_sub_by_name(name) {
9407 self.wantarray_kind = saved_wa;
9408 let args = self.with_topic_default_args(arg_vals);
9409 return self.call_sub(&sub, args, ctx, line);
9410 }
9411 if matches!(
9412 name.as_str(),
9413 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9414 ) {
9415 let r = self.list_higher_order_block_builtin(name.as_str(), &arg_vals, line);
9416 self.wantarray_kind = saved_wa;
9417 return r.map_err(Into::into);
9418 }
9419 if let Some(r) = crate::builtins::try_builtin(self, name.as_str(), &arg_vals, line)
9420 {
9421 self.wantarray_kind = saved_wa;
9422 return r.map_err(Into::into);
9423 }
9424 self.wantarray_kind = saved_wa;
9425 self.call_named_sub(name, arg_vals, line, ctx)
9426 }
9427 ExprKind::IndirectCall {
9428 target,
9429 args,
9430 ampersand: _,
9431 pass_caller_arglist,
9432 } => {
9433 let tval = self.eval_expr(target)?;
9434 let arg_vals = if *pass_caller_arglist {
9435 self.scope.get_array("_")
9436 } else {
9437 let mut v = Vec::with_capacity(args.len());
9438 for a in args {
9439 v.push(self.eval_expr(a)?);
9440 }
9441 v
9442 };
9443 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
9444 }
9445 ExprKind::MethodCall {
9446 object,
9447 method,
9448 args,
9449 super_call,
9450 } => {
9451 let obj = self.eval_expr(object)?;
9452 let mut arg_vals = vec![obj.clone()];
9453 for a in args {
9454 arg_vals.push(self.eval_expr(a)?);
9455 }
9456 if let Some(r) =
9457 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
9458 {
9459 return r.map_err(Into::into);
9460 }
9461 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
9462 return r.map_err(Into::into);
9463 }
9464 let class = if let Some(b) = obj.as_blessed_ref() {
9466 b.class.clone()
9467 } else if let Some(s) = obj.as_str() {
9468 s } else {
9470 return Err(PerlError::runtime("Can't call method on non-object", line).into());
9471 };
9472 if method == "VERSION" && !*super_call {
9473 if let Some(ver) = self.package_version_scalar(class.as_str())? {
9474 return Ok(ver);
9475 }
9476 }
9477 if !*super_call {
9479 match method.as_str() {
9480 "isa" => {
9481 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9482 let mro = self.mro_linearize(&class);
9483 let result = mro.iter().any(|c| c == &target);
9484 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9485 }
9486 "can" => {
9487 let target_method =
9488 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9489 let found = self
9490 .resolve_method_full_name(&class, &target_method, false)
9491 .and_then(|fq| self.subs.get(&fq))
9492 .is_some();
9493 if found {
9494 return Ok(PerlValue::code_ref(Arc::new(PerlSub {
9495 name: target_method,
9496 params: vec![],
9497 body: vec![],
9498 closure_env: None,
9499 prototype: None,
9500 fib_like: None,
9501 })));
9502 } else {
9503 return Ok(PerlValue::UNDEF);
9504 }
9505 }
9506 "DOES" => {
9507 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9508 let mro = self.mro_linearize(&class);
9509 let result = mro.iter().any(|c| c == &target);
9510 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9511 }
9512 _ => {}
9513 }
9514 }
9515 let full_name = self
9516 .resolve_method_full_name(&class, method, *super_call)
9517 .ok_or_else(|| {
9518 PerlError::runtime(
9519 format!(
9520 "Can't locate method \"{}\" for invocant \"{}\"",
9521 method, class
9522 ),
9523 line,
9524 )
9525 })?;
9526 if let Some(sub) = self.subs.get(&full_name).cloned() {
9527 self.call_sub(&sub, arg_vals, ctx, line)
9528 } else if method == "new" && !*super_call {
9529 self.builtin_new(&class, arg_vals, line)
9531 } else if let Some(r) =
9532 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
9533 {
9534 r
9535 } else {
9536 Err(PerlError::runtime(
9537 format!(
9538 "Can't locate method \"{}\" in package \"{}\"",
9539 method, class
9540 ),
9541 line,
9542 )
9543 .into())
9544 }
9545 }
9546
9547 ExprKind::Print { handle, args } => {
9549 self.exec_print(handle.as_deref(), args, false, line)
9550 }
9551 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
9552 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
9553 ExprKind::Die(args) => {
9554 if args.is_empty() {
9555 let current = self.scope.get_scalar("@");
9557 let msg = if current.is_undef() || current.to_string().is_empty() {
9558 let mut m = "Died".to_string();
9559 m.push_str(&self.die_warn_at_suffix(line));
9560 m.push('\n');
9561 m
9562 } else {
9563 current.to_string()
9564 };
9565 return Err(PerlError::die(msg, line).into());
9566 }
9567 if args.len() == 1 {
9569 let v = self.eval_expr(&args[0])?;
9570 if v.as_hash_ref().is_some()
9571 || v.as_blessed_ref().is_some()
9572 || v.as_array_ref().is_some()
9573 || v.as_code_ref().is_some()
9574 {
9575 let msg = v.to_string();
9576 return Err(PerlError::die_with_value(v, msg, line).into());
9577 }
9578 }
9579 let mut msg = String::new();
9580 for a in args {
9581 let v = self.eval_expr(a)?;
9582 msg.push_str(&v.to_string());
9583 }
9584 if msg.is_empty() {
9585 msg = "Died".to_string();
9586 }
9587 if !msg.ends_with('\n') {
9588 msg.push_str(&self.die_warn_at_suffix(line));
9589 msg.push('\n');
9590 }
9591 Err(PerlError::die(msg, line).into())
9592 }
9593 ExprKind::Warn(args) => {
9594 let mut msg = String::new();
9595 for a in args {
9596 let v = self.eval_expr(a)?;
9597 msg.push_str(&v.to_string());
9598 }
9599 if msg.is_empty() {
9600 msg = "Warning: something's wrong".to_string();
9601 }
9602 if !msg.ends_with('\n') {
9603 msg.push_str(&self.die_warn_at_suffix(line));
9604 msg.push('\n');
9605 }
9606 eprint!("{}", msg);
9607 Ok(PerlValue::integer(1))
9608 }
9609
9610 ExprKind::Match {
9612 expr,
9613 pattern,
9614 flags,
9615 scalar_g,
9616 delim: _,
9617 } => {
9618 let val = self.eval_expr(expr)?;
9619 if val.is_iterator() {
9620 let source = crate::map_stream::into_pull_iter(val);
9621 let re = self.compile_regex(pattern, flags, line)?;
9622 let global = flags.contains('g');
9623 if global {
9624 return Ok(PerlValue::iterator(std::sync::Arc::new(
9625 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
9626 )));
9627 } else {
9628 return Ok(PerlValue::iterator(std::sync::Arc::new(
9629 crate::map_stream::MatchStreamIterator::new(source, re),
9630 )));
9631 }
9632 }
9633 let s = val.to_string();
9634 let pos_key = match &expr.kind {
9635 ExprKind::ScalarVar(n) => n.as_str(),
9636 _ => "_",
9637 };
9638 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
9639 }
9640 ExprKind::Substitution {
9641 expr,
9642 pattern,
9643 replacement,
9644 flags,
9645 delim: _,
9646 } => {
9647 let val = self.eval_expr(expr)?;
9648 if val.is_iterator() {
9649 let source = crate::map_stream::into_pull_iter(val);
9650 let re = self.compile_regex(pattern, flags, line)?;
9651 let global = flags.contains('g');
9652 return Ok(PerlValue::iterator(std::sync::Arc::new(
9653 crate::map_stream::SubstStreamIterator::new(
9654 source,
9655 re,
9656 normalize_replacement_backrefs(replacement),
9657 global,
9658 ),
9659 )));
9660 }
9661 let s = val.to_string();
9662 self.regex_subst_execute(
9663 s,
9664 pattern,
9665 replacement.as_str(),
9666 flags.as_str(),
9667 expr,
9668 line,
9669 )
9670 }
9671 ExprKind::Transliterate {
9672 expr,
9673 from,
9674 to,
9675 flags,
9676 delim: _,
9677 } => {
9678 let val = self.eval_expr(expr)?;
9679 if val.is_iterator() {
9680 let source = crate::map_stream::into_pull_iter(val);
9681 return Ok(PerlValue::iterator(std::sync::Arc::new(
9682 crate::map_stream::TransliterateStreamIterator::new(
9683 source, from, to, flags,
9684 ),
9685 )));
9686 }
9687 let s = val.to_string();
9688 self.regex_transliterate_execute(
9689 s,
9690 from.as_str(),
9691 to.as_str(),
9692 flags.as_str(),
9693 expr,
9694 line,
9695 )
9696 }
9697
9698 ExprKind::MapExpr {
9700 block,
9701 list,
9702 flatten_array_refs,
9703 stream,
9704 } => {
9705 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9706 if *stream {
9707 let out =
9708 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
9709 if ctx == WantarrayCtx::List {
9710 return Ok(out);
9711 }
9712 return Ok(PerlValue::integer(out.to_list().len() as i64));
9713 }
9714 let items = list_val.to_list();
9715 if items.len() == 1 {
9716 if let Some(p) = items[0].as_pipeline() {
9717 if *flatten_array_refs {
9718 return Err(PerlError::runtime(
9719 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
9720 line,
9721 )
9722 .into());
9723 }
9724 let sub = self.anon_coderef_from_block(block);
9725 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
9726 return Ok(PerlValue::pipeline(Arc::clone(&p)));
9727 }
9728 }
9729 let mut result = Vec::new();
9734 for item in items {
9735 self.scope.set_topic(item);
9736 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
9737 result.extend(val.map_flatten_outputs(*flatten_array_refs));
9738 }
9739 if ctx == WantarrayCtx::List {
9740 Ok(PerlValue::array(result))
9741 } else {
9742 Ok(PerlValue::integer(result.len() as i64))
9743 }
9744 }
9745 ExprKind::ForEachExpr { block, list } => {
9746 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9747 if list_val.is_iterator() {
9749 let iter = list_val.into_iterator();
9750 let mut count = 0i64;
9751 while let Some(item) = iter.next_item() {
9752 count += 1;
9753 self.scope.set_topic(item);
9754 self.exec_block(block)?;
9755 }
9756 return Ok(PerlValue::integer(count));
9757 }
9758 let items = list_val.to_list();
9759 let count = items.len();
9760 for item in items {
9761 self.scope.set_topic(item);
9762 self.exec_block(block)?;
9763 }
9764 Ok(PerlValue::integer(count as i64))
9765 }
9766 ExprKind::MapExprComma {
9767 expr,
9768 list,
9769 flatten_array_refs,
9770 stream,
9771 } => {
9772 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9773 if *stream {
9774 let out =
9775 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
9776 if ctx == WantarrayCtx::List {
9777 return Ok(out);
9778 }
9779 return Ok(PerlValue::integer(out.to_list().len() as i64));
9780 }
9781 let items = list_val.to_list();
9782 let mut result = Vec::new();
9783 for item in items {
9784 self.scope.set_topic(item.clone());
9785 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
9786 result.extend(val.map_flatten_outputs(*flatten_array_refs));
9787 }
9788 if ctx == WantarrayCtx::List {
9789 Ok(PerlValue::array(result))
9790 } else {
9791 Ok(PerlValue::integer(result.len() as i64))
9792 }
9793 }
9794 ExprKind::GrepExpr {
9795 block,
9796 list,
9797 keyword,
9798 } => {
9799 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9800 if keyword.is_stream() {
9801 let out = self.filter_stream_block_output(list_val, block, line)?;
9802 if ctx == WantarrayCtx::List {
9803 return Ok(out);
9804 }
9805 return Ok(PerlValue::integer(out.to_list().len() as i64));
9806 }
9807 let items = list_val.to_list();
9808 if items.len() == 1 {
9809 if let Some(p) = items[0].as_pipeline() {
9810 let sub = self.anon_coderef_from_block(block);
9811 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
9812 return Ok(PerlValue::pipeline(Arc::clone(&p)));
9813 }
9814 }
9815 let mut result = Vec::new();
9816 for item in items {
9817 self.scope.set_topic(item.clone());
9818 let val = self.exec_block(block)?;
9819 let keep = if let Some(re) = val.as_regex() {
9822 re.is_match(&item.to_string())
9823 } else {
9824 val.is_true()
9825 };
9826 if keep {
9827 result.push(item);
9828 }
9829 }
9830 if ctx == WantarrayCtx::List {
9831 Ok(PerlValue::array(result))
9832 } else {
9833 Ok(PerlValue::integer(result.len() as i64))
9834 }
9835 }
9836 ExprKind::GrepExprComma {
9837 expr,
9838 list,
9839 keyword,
9840 } => {
9841 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9842 if keyword.is_stream() {
9843 let out = self.filter_stream_expr_output(list_val, expr, line)?;
9844 if ctx == WantarrayCtx::List {
9845 return Ok(out);
9846 }
9847 return Ok(PerlValue::integer(out.to_list().len() as i64));
9848 }
9849 let items = list_val.to_list();
9850 let mut result = Vec::new();
9851 for item in items {
9852 self.scope.set_topic(item.clone());
9853 let val = self.eval_expr(expr)?;
9854 let keep = if let Some(re) = val.as_regex() {
9855 re.is_match(&item.to_string())
9856 } else {
9857 val.is_true()
9858 };
9859 if keep {
9860 result.push(item);
9861 }
9862 }
9863 if ctx == WantarrayCtx::List {
9864 Ok(PerlValue::array(result))
9865 } else {
9866 Ok(PerlValue::integer(result.len() as i64))
9867 }
9868 }
9869 ExprKind::SortExpr { cmp, list } => {
9870 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9871 let mut items = list_val.to_list();
9872 match cmp {
9873 Some(SortComparator::Code(code_expr)) => {
9874 let sub = self.eval_expr(code_expr)?;
9875 let Some(sub) = sub.as_code_ref() else {
9876 return Err(PerlError::runtime(
9877 "sort: comparator must be a code reference",
9878 line,
9879 )
9880 .into());
9881 };
9882 let sub = sub.clone();
9883 items.sort_by(|a, b| {
9884 let _ = self.scope.set_scalar("a", a.clone());
9885 let _ = self.scope.set_scalar("b", b.clone());
9886 let _ = self.scope.set_scalar("_0", a.clone());
9887 let _ = self.scope.set_scalar("_1", b.clone());
9888 match self.call_sub(&sub, vec![], ctx, line) {
9889 Ok(v) => {
9890 let n = v.to_int();
9891 if n < 0 {
9892 Ordering::Less
9893 } else if n > 0 {
9894 Ordering::Greater
9895 } else {
9896 Ordering::Equal
9897 }
9898 }
9899 Err(_) => Ordering::Equal,
9900 }
9901 });
9902 }
9903 Some(SortComparator::Block(cmp_block)) => {
9904 if let Some(mode) = detect_sort_block_fast(cmp_block) {
9905 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
9906 } else {
9907 let cmp_block = cmp_block.clone();
9908 items.sort_by(|a, b| {
9909 let _ = self.scope.set_scalar("a", a.clone());
9910 let _ = self.scope.set_scalar("b", b.clone());
9911 let _ = self.scope.set_scalar("_0", a.clone());
9912 let _ = self.scope.set_scalar("_1", b.clone());
9913 match self.exec_block(&cmp_block) {
9914 Ok(v) => {
9915 let n = v.to_int();
9916 if n < 0 {
9917 Ordering::Less
9918 } else if n > 0 {
9919 Ordering::Greater
9920 } else {
9921 Ordering::Equal
9922 }
9923 }
9924 Err(_) => Ordering::Equal,
9925 }
9926 });
9927 }
9928 }
9929 None => {
9930 items.sort_by_key(|a| a.to_string());
9931 }
9932 }
9933 Ok(PerlValue::array(items))
9934 }
9935 ExprKind::ScalarReverse(expr) => {
9936 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
9937 if val.is_iterator() {
9939 return Ok(PerlValue::iterator(Arc::new(
9940 crate::value::ScalarReverseIterator::new(val.into_iterator()),
9941 )));
9942 }
9943 let items = val.to_list();
9944 if items.len() <= 1 {
9945 let s = if items.is_empty() {
9946 String::new()
9947 } else {
9948 items[0].to_string()
9949 };
9950 Ok(PerlValue::string(s.chars().rev().collect()))
9951 } else {
9952 let mut items = items;
9953 items.reverse();
9954 Ok(PerlValue::array(items))
9955 }
9956 }
9957 ExprKind::ReverseExpr(list) => {
9958 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9959 match ctx {
9960 WantarrayCtx::List => {
9961 let mut items = val.to_list();
9962 items.reverse();
9963 Ok(PerlValue::array(items))
9964 }
9965 _ => {
9966 let items = val.to_list();
9967 let s: String = items.iter().map(|v| v.to_string()).collect();
9968 Ok(PerlValue::string(s.chars().rev().collect()))
9969 }
9970 }
9971 }
9972
9973 ExprKind::ParLinesExpr {
9975 path,
9976 callback,
9977 progress,
9978 } => self.eval_par_lines_expr(
9979 path.as_ref(),
9980 callback.as_ref(),
9981 progress.as_deref(),
9982 line,
9983 ),
9984 ExprKind::ParWalkExpr {
9985 path,
9986 callback,
9987 progress,
9988 } => {
9989 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
9990 }
9991 ExprKind::PwatchExpr { path, callback } => {
9992 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
9993 }
9994 ExprKind::PMapExpr {
9995 block,
9996 list,
9997 progress,
9998 flat_outputs,
9999 on_cluster,
10000 stream,
10001 } => {
10002 let show_progress = progress
10003 .as_ref()
10004 .map(|p| self.eval_expr(p))
10005 .transpose()?
10006 .map(|v| v.is_true())
10007 .unwrap_or(false);
10008 let list_val = self.eval_expr(list)?;
10009 if let Some(cluster_e) = on_cluster {
10010 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
10011 return self.eval_pmap_remote(
10012 cluster_val,
10013 list_val,
10014 show_progress,
10015 block,
10016 *flat_outputs,
10017 line,
10018 );
10019 }
10020 if *stream {
10021 let source = crate::map_stream::into_pull_iter(list_val);
10022 let sub = self.anon_coderef_from_block(block);
10023 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
10024 return Ok(PerlValue::iterator(Arc::new(
10025 crate::map_stream::PMapStreamIterator::new(
10026 source,
10027 sub,
10028 self.subs.clone(),
10029 capture,
10030 atomic_arrays,
10031 atomic_hashes,
10032 *flat_outputs,
10033 ),
10034 )));
10035 }
10036 let items = list_val.to_list();
10037 let block = block.clone();
10038 let subs = self.subs.clone();
10039 let (scope_capture, atomic_arrays, atomic_hashes) =
10040 self.scope.capture_with_atomics();
10041 let pmap_progress = PmapProgress::new(show_progress, items.len());
10042
10043 if *flat_outputs {
10044 let mut indexed: Vec<(usize, Vec<PerlValue>)> = items
10045 .into_par_iter()
10046 .enumerate()
10047 .map(|(i, item)| {
10048 let mut local_interp = Interpreter::new();
10049 local_interp.subs = subs.clone();
10050 local_interp.scope.restore_capture(&scope_capture);
10051 local_interp
10052 .scope
10053 .restore_atomics(&atomic_arrays, &atomic_hashes);
10054 local_interp.enable_parallel_guard();
10055 local_interp.scope.set_topic(item);
10056 let val = match local_interp.exec_block(&block) {
10057 Ok(val) => val,
10058 Err(_) => PerlValue::UNDEF,
10059 };
10060 let chunk = val.map_flatten_outputs(true);
10061 pmap_progress.tick();
10062 (i, chunk)
10063 })
10064 .collect();
10065 pmap_progress.finish();
10066 indexed.sort_by_key(|(i, _)| *i);
10067 let results: Vec<PerlValue> =
10068 indexed.into_iter().flat_map(|(_, v)| v).collect();
10069 Ok(PerlValue::array(results))
10070 } else {
10071 let results: Vec<PerlValue> = items
10072 .into_par_iter()
10073 .map(|item| {
10074 let mut local_interp = Interpreter::new();
10075 local_interp.subs = subs.clone();
10076 local_interp.scope.restore_capture(&scope_capture);
10077 local_interp
10078 .scope
10079 .restore_atomics(&atomic_arrays, &atomic_hashes);
10080 local_interp.enable_parallel_guard();
10081 local_interp.scope.set_topic(item);
10082 let val = match local_interp.exec_block(&block) {
10083 Ok(val) => val,
10084 Err(_) => PerlValue::UNDEF,
10085 };
10086 pmap_progress.tick();
10087 val
10088 })
10089 .collect();
10090 pmap_progress.finish();
10091 Ok(PerlValue::array(results))
10092 }
10093 }
10094 ExprKind::PMapChunkedExpr {
10095 chunk_size,
10096 block,
10097 list,
10098 progress,
10099 } => {
10100 let show_progress = progress
10101 .as_ref()
10102 .map(|p| self.eval_expr(p))
10103 .transpose()?
10104 .map(|v| v.is_true())
10105 .unwrap_or(false);
10106 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
10107 let list_val = self.eval_expr(list)?;
10108 let items = list_val.to_list();
10109 let block = block.clone();
10110 let subs = self.subs.clone();
10111 let (scope_capture, atomic_arrays, atomic_hashes) =
10112 self.scope.capture_with_atomics();
10113
10114 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = items
10115 .chunks(chunk_n)
10116 .enumerate()
10117 .map(|(i, c)| (i, c.to_vec()))
10118 .collect();
10119
10120 let n_chunks = indexed_chunks.len();
10121 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
10122
10123 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
10124 .into_par_iter()
10125 .map(|(chunk_idx, chunk)| {
10126 let mut local_interp = Interpreter::new();
10127 local_interp.subs = subs.clone();
10128 local_interp.scope.restore_capture(&scope_capture);
10129 local_interp
10130 .scope
10131 .restore_atomics(&atomic_arrays, &atomic_hashes);
10132 local_interp.enable_parallel_guard();
10133 let mut out = Vec::with_capacity(chunk.len());
10134 for item in chunk {
10135 local_interp.scope.set_topic(item);
10136 match local_interp.exec_block(&block) {
10137 Ok(val) => out.push(val),
10138 Err(_) => out.push(PerlValue::UNDEF),
10139 }
10140 }
10141 pmap_progress.tick();
10142 (chunk_idx, out)
10143 })
10144 .collect();
10145
10146 pmap_progress.finish();
10147 chunk_results.sort_by_key(|(i, _)| *i);
10148 let results: Vec<PerlValue> =
10149 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
10150 Ok(PerlValue::array(results))
10151 }
10152 ExprKind::PGrepExpr {
10153 block,
10154 list,
10155 progress,
10156 stream,
10157 } => {
10158 let show_progress = progress
10159 .as_ref()
10160 .map(|p| self.eval_expr(p))
10161 .transpose()?
10162 .map(|v| v.is_true())
10163 .unwrap_or(false);
10164 let list_val = self.eval_expr(list)?;
10165 if *stream {
10166 let source = crate::map_stream::into_pull_iter(list_val);
10167 let sub = self.anon_coderef_from_block(block);
10168 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
10169 return Ok(PerlValue::iterator(Arc::new(
10170 crate::map_stream::PGrepStreamIterator::new(
10171 source,
10172 sub,
10173 self.subs.clone(),
10174 capture,
10175 atomic_arrays,
10176 atomic_hashes,
10177 ),
10178 )));
10179 }
10180 let items = list_val.to_list();
10181 let block = block.clone();
10182 let subs = self.subs.clone();
10183 let (scope_capture, atomic_arrays, atomic_hashes) =
10184 self.scope.capture_with_atomics();
10185 let pmap_progress = PmapProgress::new(show_progress, items.len());
10186
10187 let results: Vec<PerlValue> = items
10188 .into_par_iter()
10189 .filter_map(|item| {
10190 let mut local_interp = Interpreter::new();
10191 local_interp.subs = subs.clone();
10192 local_interp.scope.restore_capture(&scope_capture);
10193 local_interp
10194 .scope
10195 .restore_atomics(&atomic_arrays, &atomic_hashes);
10196 local_interp.enable_parallel_guard();
10197 local_interp.scope.set_topic(item.clone());
10198 let keep = match local_interp.exec_block(&block) {
10199 Ok(val) => val.is_true(),
10200 Err(_) => false,
10201 };
10202 pmap_progress.tick();
10203 if keep {
10204 Some(item)
10205 } else {
10206 None
10207 }
10208 })
10209 .collect();
10210 pmap_progress.finish();
10211 Ok(PerlValue::array(results))
10212 }
10213 ExprKind::PForExpr {
10214 block,
10215 list,
10216 progress,
10217 } => {
10218 let show_progress = progress
10219 .as_ref()
10220 .map(|p| self.eval_expr(p))
10221 .transpose()?
10222 .map(|v| v.is_true())
10223 .unwrap_or(false);
10224 let list_val = self.eval_expr(list)?;
10225 let items = list_val.to_list();
10226 let block = block.clone();
10227 let subs = self.subs.clone();
10228 let (scope_capture, atomic_arrays, atomic_hashes) =
10229 self.scope.capture_with_atomics();
10230
10231 let pmap_progress = PmapProgress::new(show_progress, items.len());
10232 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10233 items.into_par_iter().for_each(|item| {
10234 if first_err.lock().is_some() {
10235 return;
10236 }
10237 let mut local_interp = Interpreter::new();
10238 local_interp.subs = subs.clone();
10239 local_interp.scope.restore_capture(&scope_capture);
10240 local_interp
10241 .scope
10242 .restore_atomics(&atomic_arrays, &atomic_hashes);
10243 local_interp.enable_parallel_guard();
10244 local_interp.scope.set_topic(item);
10245 match local_interp.exec_block(&block) {
10246 Ok(_) => {}
10247 Err(e) => {
10248 let stryke = match e {
10249 FlowOrError::Error(stryke) => stryke,
10250 FlowOrError::Flow(_) => PerlError::runtime(
10251 "return/last/next/redo not supported inside pfor block",
10252 line,
10253 ),
10254 };
10255 let mut g = first_err.lock();
10256 if g.is_none() {
10257 *g = Some(stryke);
10258 }
10259 }
10260 }
10261 pmap_progress.tick();
10262 });
10263 pmap_progress.finish();
10264 if let Some(e) = first_err.lock().take() {
10265 return Err(FlowOrError::Error(e));
10266 }
10267 Ok(PerlValue::UNDEF)
10268 }
10269 ExprKind::FanExpr {
10270 count,
10271 block,
10272 progress,
10273 capture,
10274 } => {
10275 let show_progress = progress
10276 .as_ref()
10277 .map(|p| self.eval_expr(p))
10278 .transpose()?
10279 .map(|v| v.is_true())
10280 .unwrap_or(false);
10281 let n = match count {
10282 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
10283 None => self.parallel_thread_count(),
10284 };
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
10290 let fan_progress = FanProgress::new(show_progress, n);
10291 if *capture {
10292 if n == 0 {
10293 return Ok(PerlValue::array(Vec::new()));
10294 }
10295 let pairs: Vec<(usize, ExecResult)> = (0..n)
10296 .into_par_iter()
10297 .map(|i| {
10298 fan_progress.start_worker(i);
10299 let mut local_interp = Interpreter::new();
10300 local_interp.subs = subs.clone();
10301 local_interp.suppress_stdout = show_progress;
10302 local_interp.scope.restore_capture(&scope_capture);
10303 local_interp
10304 .scope
10305 .restore_atomics(&atomic_arrays, &atomic_hashes);
10306 local_interp.enable_parallel_guard();
10307 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10308 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10309 let res = local_interp.exec_block(&block);
10310 crate::parallel_trace::fan_worker_set_index(None);
10311 fan_progress.finish_worker(i);
10312 (i, res)
10313 })
10314 .collect();
10315 fan_progress.finish();
10316 let mut pairs = pairs;
10317 pairs.sort_by_key(|(i, _)| *i);
10318 let mut out = Vec::with_capacity(n);
10319 for (_, r) in pairs {
10320 match r {
10321 Ok(v) => out.push(v),
10322 Err(e) => return Err(e),
10323 }
10324 }
10325 return Ok(PerlValue::array(out));
10326 }
10327 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10328 (0..n).into_par_iter().for_each(|i| {
10329 if first_err.lock().is_some() {
10330 return;
10331 }
10332 fan_progress.start_worker(i);
10333 let mut local_interp = Interpreter::new();
10334 local_interp.subs = subs.clone();
10335 local_interp.suppress_stdout = show_progress;
10336 local_interp.scope.restore_capture(&scope_capture);
10337 local_interp
10338 .scope
10339 .restore_atomics(&atomic_arrays, &atomic_hashes);
10340 local_interp.enable_parallel_guard();
10341 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10342 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10343 match local_interp.exec_block(&block) {
10344 Ok(_) => {}
10345 Err(e) => {
10346 let stryke = match e {
10347 FlowOrError::Error(stryke) => stryke,
10348 FlowOrError::Flow(_) => PerlError::runtime(
10349 "return/last/next/redo not supported inside fan block",
10350 line,
10351 ),
10352 };
10353 let mut g = first_err.lock();
10354 if g.is_none() {
10355 *g = Some(stryke);
10356 }
10357 }
10358 }
10359 crate::parallel_trace::fan_worker_set_index(None);
10360 fan_progress.finish_worker(i);
10361 });
10362 fan_progress.finish();
10363 if let Some(e) = first_err.lock().take() {
10364 return Err(FlowOrError::Error(e));
10365 }
10366 Ok(PerlValue::UNDEF)
10367 }
10368 ExprKind::RetryBlock {
10369 body,
10370 times,
10371 backoff,
10372 } => self.eval_retry_block(body, times, *backoff, line),
10373 ExprKind::RateLimitBlock {
10374 slot,
10375 max,
10376 window,
10377 body,
10378 } => self.eval_rate_limit_block(*slot, max, window, body, line),
10379 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
10380 ExprKind::GenBlock { body } => {
10381 let g = Arc::new(PerlGenerator {
10382 block: body.clone(),
10383 pc: Mutex::new(0),
10384 scope_started: Mutex::new(false),
10385 exhausted: Mutex::new(false),
10386 });
10387 Ok(PerlValue::generator(g))
10388 }
10389 ExprKind::Yield(e) => {
10390 if !self.in_generator {
10391 return Err(PerlError::runtime("yield outside gen block", line).into());
10392 }
10393 let v = self.eval_expr(e)?;
10394 Err(FlowOrError::Flow(Flow::Yield(v)))
10395 }
10396 ExprKind::AlgebraicMatch { subject, arms } => {
10397 self.eval_algebraic_match(subject, arms, line)
10398 }
10399 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
10400 Ok(self.spawn_async_block(body))
10401 }
10402 ExprKind::Trace { body } => {
10403 crate::parallel_trace::trace_enter();
10404 let out = self.exec_block(body);
10405 crate::parallel_trace::trace_leave();
10406 out
10407 }
10408 ExprKind::Spinner { message, body } => {
10409 use std::io::Write as _;
10410 let msg = self.eval_expr(message)?.to_string();
10411 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
10412 let done2 = done.clone();
10413 let handle = std::thread::spawn(move || {
10414 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
10415 let mut i = 0;
10416 let stderr = std::io::stderr();
10417 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
10418 {
10419 let stdout = std::io::stdout();
10420 let _stdout_lock = stdout.lock();
10421 let mut err = stderr.lock();
10422 let _ = write!(
10423 err,
10424 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
10425 frames[i % frames.len()],
10426 msg
10427 );
10428 let _ = err.flush();
10429 }
10430 std::thread::sleep(std::time::Duration::from_millis(80));
10431 i += 1;
10432 }
10433 let mut err = stderr.lock();
10434 let _ = write!(err, "\r\x1b[2K");
10435 let _ = err.flush();
10436 });
10437 let result = self.exec_block(body);
10438 done.store(true, std::sync::atomic::Ordering::Relaxed);
10439 let _ = handle.join();
10440 result
10441 }
10442 ExprKind::Timer { body } => {
10443 let start = std::time::Instant::now();
10444 self.exec_block(body)?;
10445 let ms = start.elapsed().as_secs_f64() * 1000.0;
10446 Ok(PerlValue::float(ms))
10447 }
10448 ExprKind::Bench { body, times } => {
10449 let n = self.eval_expr(times)?.to_int();
10450 if n < 0 {
10451 return Err(PerlError::runtime(
10452 "bench: iteration count must be non-negative",
10453 line,
10454 )
10455 .into());
10456 }
10457 self.run_bench_block(body, n as usize, line)
10458 }
10459 ExprKind::Await(expr) => {
10460 let v = self.eval_expr(expr)?;
10461 if let Some(t) = v.as_async_task() {
10462 t.await_result().map_err(FlowOrError::from)
10463 } else {
10464 Ok(v)
10465 }
10466 }
10467 ExprKind::Slurp(e) => {
10468 let path = self.eval_expr(e)?.to_string();
10469 read_file_text_perl_compat(&path)
10470 .map(PerlValue::string)
10471 .map_err(|e| {
10472 FlowOrError::Error(PerlError::runtime(format!("slurp: {}", e), line))
10473 })
10474 }
10475 ExprKind::Capture(e) => {
10476 let cmd = self.eval_expr(e)?.to_string();
10477 let output = Command::new("sh")
10478 .arg("-c")
10479 .arg(&cmd)
10480 .output()
10481 .map_err(|e| {
10482 FlowOrError::Error(PerlError::runtime(format!("capture: {}", e), line))
10483 })?;
10484 self.record_child_exit_status(output.status);
10485 let exitcode = output.status.code().unwrap_or(-1) as i64;
10486 let stdout = decode_utf8_or_latin1(&output.stdout);
10487 let stderr = decode_utf8_or_latin1(&output.stderr);
10488 Ok(PerlValue::capture(Arc::new(CaptureResult {
10489 stdout,
10490 stderr,
10491 exitcode,
10492 })))
10493 }
10494 ExprKind::Qx(e) => {
10495 let cmd = self.eval_expr(e)?.to_string();
10496 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)
10497 }
10498 ExprKind::FetchUrl(e) => {
10499 let url = self.eval_expr(e)?.to_string();
10500 ureq::get(&url)
10501 .call()
10502 .map_err(|e| {
10503 FlowOrError::Error(PerlError::runtime(format!("fetch_url: {}", e), line))
10504 })
10505 .and_then(|r| {
10506 r.into_string().map(PerlValue::string).map_err(|e| {
10507 FlowOrError::Error(PerlError::runtime(
10508 format!("fetch_url: {}", e),
10509 line,
10510 ))
10511 })
10512 })
10513 }
10514 ExprKind::Pchannel { capacity } => {
10515 if let Some(c) = capacity {
10516 let n = self.eval_expr(c)?.to_int().max(1) as usize;
10517 Ok(crate::pchannel::create_bounded_pair(n))
10518 } else {
10519 Ok(crate::pchannel::create_pair())
10520 }
10521 }
10522 ExprKind::PSortExpr {
10523 cmp,
10524 list,
10525 progress,
10526 } => {
10527 let show_progress = progress
10528 .as_ref()
10529 .map(|p| self.eval_expr(p))
10530 .transpose()?
10531 .map(|v| v.is_true())
10532 .unwrap_or(false);
10533 let list_val = self.eval_expr(list)?;
10534 let mut items = list_val.to_list();
10535 let pmap_progress = PmapProgress::new(show_progress, 2);
10536 pmap_progress.tick();
10537 if let Some(cmp_block) = cmp {
10538 if let Some(mode) = detect_sort_block_fast(cmp_block) {
10539 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
10540 } else {
10541 let cmp_block = cmp_block.clone();
10542 let subs = self.subs.clone();
10543 let scope_capture = self.scope.capture();
10544 items.par_sort_by(|a, b| {
10545 let mut local_interp = Interpreter::new();
10546 local_interp.subs = subs.clone();
10547 local_interp.scope.restore_capture(&scope_capture);
10548 let _ = local_interp.scope.set_scalar("a", a.clone());
10549 let _ = local_interp.scope.set_scalar("b", b.clone());
10550 let _ = local_interp.scope.set_scalar("_0", a.clone());
10551 let _ = local_interp.scope.set_scalar("_1", b.clone());
10552 match local_interp.exec_block(&cmp_block) {
10553 Ok(v) => {
10554 let n = v.to_int();
10555 if n < 0 {
10556 std::cmp::Ordering::Less
10557 } else if n > 0 {
10558 std::cmp::Ordering::Greater
10559 } else {
10560 std::cmp::Ordering::Equal
10561 }
10562 }
10563 Err(_) => std::cmp::Ordering::Equal,
10564 }
10565 });
10566 }
10567 } else {
10568 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
10569 }
10570 pmap_progress.tick();
10571 pmap_progress.finish();
10572 Ok(PerlValue::array(items))
10573 }
10574
10575 ExprKind::ReduceExpr { block, list } => {
10576 let list_val = self.eval_expr(list)?;
10577 let items = list_val.to_list();
10578 if items.is_empty() {
10579 return Ok(PerlValue::UNDEF);
10580 }
10581 if items.len() == 1 {
10582 return Ok(items.into_iter().next().unwrap());
10583 }
10584 let block = block.clone();
10585 let subs = self.subs.clone();
10586 let scope_capture = self.scope.capture();
10587 let mut acc = items[0].clone();
10588 for b in items.into_iter().skip(1) {
10589 let mut local_interp = Interpreter::new();
10590 local_interp.subs = subs.clone();
10591 local_interp.scope.restore_capture(&scope_capture);
10592 let _ = local_interp.scope.set_scalar("a", acc.clone());
10593 let _ = local_interp.scope.set_scalar("b", b.clone());
10594 let _ = local_interp.scope.set_scalar("_0", acc);
10595 let _ = local_interp.scope.set_scalar("_1", b);
10596 acc = match local_interp.exec_block(&block) {
10597 Ok(val) => val,
10598 Err(_) => PerlValue::UNDEF,
10599 };
10600 }
10601 Ok(acc)
10602 }
10603
10604 ExprKind::PReduceExpr {
10605 block,
10606 list,
10607 progress,
10608 } => {
10609 let show_progress = progress
10610 .as_ref()
10611 .map(|p| self.eval_expr(p))
10612 .transpose()?
10613 .map(|v| v.is_true())
10614 .unwrap_or(false);
10615 let list_val = self.eval_expr(list)?;
10616 let items = list_val.to_list();
10617 if items.is_empty() {
10618 return Ok(PerlValue::UNDEF);
10619 }
10620 if items.len() == 1 {
10621 return Ok(items.into_iter().next().unwrap());
10622 }
10623 let block = block.clone();
10624 let subs = self.subs.clone();
10625 let scope_capture = self.scope.capture();
10626 let pmap_progress = PmapProgress::new(show_progress, items.len());
10627
10628 let result = items
10629 .into_par_iter()
10630 .map(|x| {
10631 pmap_progress.tick();
10632 x
10633 })
10634 .reduce_with(|a, b| {
10635 let mut local_interp = Interpreter::new();
10636 local_interp.subs = subs.clone();
10637 local_interp.scope.restore_capture(&scope_capture);
10638 let _ = local_interp.scope.set_scalar("a", a.clone());
10639 let _ = local_interp.scope.set_scalar("b", b.clone());
10640 let _ = local_interp.scope.set_scalar("_0", a);
10641 let _ = local_interp.scope.set_scalar("_1", b);
10642 match local_interp.exec_block(&block) {
10643 Ok(val) => val,
10644 Err(_) => PerlValue::UNDEF,
10645 }
10646 });
10647 pmap_progress.finish();
10648 Ok(result.unwrap_or(PerlValue::UNDEF))
10649 }
10650
10651 ExprKind::PReduceInitExpr {
10652 init,
10653 block,
10654 list,
10655 progress,
10656 } => {
10657 let show_progress = progress
10658 .as_ref()
10659 .map(|p| self.eval_expr(p))
10660 .transpose()?
10661 .map(|v| v.is_true())
10662 .unwrap_or(false);
10663 let init_val = self.eval_expr(init)?;
10664 let list_val = self.eval_expr(list)?;
10665 let items = list_val.to_list();
10666 if items.is_empty() {
10667 return Ok(init_val);
10668 }
10669 let block = block.clone();
10670 let subs = self.subs.clone();
10671 let scope_capture = self.scope.capture();
10672 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
10673 if items.len() == 1 {
10674 return Ok(fold_preduce_init_step(
10675 &subs,
10676 cap,
10677 &block,
10678 preduce_init_fold_identity(&init_val),
10679 items.into_iter().next().unwrap(),
10680 ));
10681 }
10682 let pmap_progress = PmapProgress::new(show_progress, items.len());
10683 let result = items
10684 .into_par_iter()
10685 .fold(
10686 || preduce_init_fold_identity(&init_val),
10687 |acc, item| {
10688 pmap_progress.tick();
10689 fold_preduce_init_step(&subs, cap, &block, acc, item)
10690 },
10691 )
10692 .reduce(
10693 || preduce_init_fold_identity(&init_val),
10694 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
10695 );
10696 pmap_progress.finish();
10697 Ok(result)
10698 }
10699
10700 ExprKind::PMapReduceExpr {
10701 map_block,
10702 reduce_block,
10703 list,
10704 progress,
10705 } => {
10706 let show_progress = progress
10707 .as_ref()
10708 .map(|p| self.eval_expr(p))
10709 .transpose()?
10710 .map(|v| v.is_true())
10711 .unwrap_or(false);
10712 let list_val = self.eval_expr(list)?;
10713 let items = list_val.to_list();
10714 if items.is_empty() {
10715 return Ok(PerlValue::UNDEF);
10716 }
10717 let map_block = map_block.clone();
10718 let reduce_block = reduce_block.clone();
10719 let subs = self.subs.clone();
10720 let scope_capture = self.scope.capture();
10721 if items.len() == 1 {
10722 let mut local_interp = Interpreter::new();
10723 local_interp.subs = subs.clone();
10724 local_interp.scope.restore_capture(&scope_capture);
10725 local_interp.scope.set_topic(items[0].clone());
10726 return match local_interp.exec_block_no_scope(&map_block) {
10727 Ok(v) => Ok(v),
10728 Err(_) => Ok(PerlValue::UNDEF),
10729 };
10730 }
10731 let pmap_progress = PmapProgress::new(show_progress, items.len());
10732 let result = items
10733 .into_par_iter()
10734 .map(|item| {
10735 let mut local_interp = Interpreter::new();
10736 local_interp.subs = subs.clone();
10737 local_interp.scope.restore_capture(&scope_capture);
10738 local_interp.scope.set_topic(item);
10739 let val = match local_interp.exec_block_no_scope(&map_block) {
10740 Ok(val) => val,
10741 Err(_) => PerlValue::UNDEF,
10742 };
10743 pmap_progress.tick();
10744 val
10745 })
10746 .reduce_with(|a, b| {
10747 let mut local_interp = Interpreter::new();
10748 local_interp.subs = subs.clone();
10749 local_interp.scope.restore_capture(&scope_capture);
10750 let _ = local_interp.scope.set_scalar("a", a.clone());
10751 let _ = local_interp.scope.set_scalar("b", b.clone());
10752 let _ = local_interp.scope.set_scalar("_0", a);
10753 let _ = local_interp.scope.set_scalar("_1", b);
10754 match local_interp.exec_block_no_scope(&reduce_block) {
10755 Ok(val) => val,
10756 Err(_) => PerlValue::UNDEF,
10757 }
10758 });
10759 pmap_progress.finish();
10760 Ok(result.unwrap_or(PerlValue::UNDEF))
10761 }
10762
10763 ExprKind::PcacheExpr {
10764 block,
10765 list,
10766 progress,
10767 } => {
10768 let show_progress = progress
10769 .as_ref()
10770 .map(|p| self.eval_expr(p))
10771 .transpose()?
10772 .map(|v| v.is_true())
10773 .unwrap_or(false);
10774 let list_val = self.eval_expr(list)?;
10775 let items = list_val.to_list();
10776 let block = block.clone();
10777 let subs = self.subs.clone();
10778 let scope_capture = self.scope.capture();
10779 let cache = &*crate::pcache::GLOBAL_PCACHE;
10780 let pmap_progress = PmapProgress::new(show_progress, items.len());
10781 let results: Vec<PerlValue> = items
10782 .into_par_iter()
10783 .map(|item| {
10784 let k = crate::pcache::cache_key(&item);
10785 if let Some(v) = cache.get(&k) {
10786 pmap_progress.tick();
10787 return v.clone();
10788 }
10789 let mut local_interp = Interpreter::new();
10790 local_interp.subs = subs.clone();
10791 local_interp.scope.restore_capture(&scope_capture);
10792 local_interp.scope.set_topic(item.clone());
10793 let val = match local_interp.exec_block_no_scope(&block) {
10794 Ok(v) => v,
10795 Err(_) => PerlValue::UNDEF,
10796 };
10797 cache.insert(k, val.clone());
10798 pmap_progress.tick();
10799 val
10800 })
10801 .collect();
10802 pmap_progress.finish();
10803 Ok(PerlValue::array(results))
10804 }
10805
10806 ExprKind::PselectExpr { receivers, timeout } => {
10807 let mut rx_vals = Vec::with_capacity(receivers.len());
10808 for r in receivers {
10809 rx_vals.push(self.eval_expr(r)?);
10810 }
10811 let dur = if let Some(t) = timeout.as_ref() {
10812 Some(std::time::Duration::from_secs_f64(
10813 self.eval_expr(t)?.to_number().max(0.0),
10814 ))
10815 } else {
10816 None
10817 };
10818 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
10819 &rx_vals, dur, line,
10820 )?)
10821 }
10822
10823 ExprKind::Push { array, values } => {
10825 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
10826 }
10827 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
10828 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
10829 ExprKind::Unshift { array, values } => {
10830 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
10831 }
10832 ExprKind::Splice {
10833 array,
10834 offset,
10835 length,
10836 replacement,
10837 } => self.eval_splice_expr(
10838 array.as_ref(),
10839 offset.as_deref(),
10840 length.as_deref(),
10841 replacement.as_slice(),
10842 ctx,
10843 line,
10844 ),
10845 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
10846 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
10847 ExprKind::Keys(expr) => {
10848 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10849 let keys = Self::keys_from_value(val, line)?;
10850 if ctx == WantarrayCtx::List {
10851 Ok(keys)
10852 } else {
10853 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
10854 Ok(PerlValue::integer(n as i64))
10855 }
10856 }
10857 ExprKind::Values(expr) => {
10858 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10859 let vals = Self::values_from_value(val, line)?;
10860 if ctx == WantarrayCtx::List {
10861 Ok(vals)
10862 } else {
10863 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
10864 Ok(PerlValue::integer(n as i64))
10865 }
10866 }
10867 ExprKind::Each(_) => {
10868 Ok(PerlValue::array(vec![]))
10870 }
10871
10872 ExprKind::Chomp(expr) => {
10874 let val = self.eval_expr(expr)?;
10875 self.chomp_inplace_execute(val, expr)
10876 }
10877 ExprKind::Chop(expr) => {
10878 let val = self.eval_expr(expr)?;
10879 self.chop_inplace_execute(val, expr)
10880 }
10881 ExprKind::Length(expr) => {
10882 let val = self.eval_expr(expr)?;
10883 Ok(if let Some(a) = val.as_array_vec() {
10884 PerlValue::integer(a.len() as i64)
10885 } else if let Some(h) = val.as_hash_map() {
10886 PerlValue::integer(h.len() as i64)
10887 } else if let Some(b) = val.as_bytes_arc() {
10888 PerlValue::integer(b.len() as i64)
10889 } else {
10890 PerlValue::integer(val.to_string().len() as i64)
10891 })
10892 }
10893 ExprKind::Substr {
10894 string,
10895 offset,
10896 length,
10897 replacement,
10898 } => self.eval_substr_expr(
10899 string.as_ref(),
10900 offset.as_ref(),
10901 length.as_deref(),
10902 replacement.as_deref(),
10903 line,
10904 ),
10905 ExprKind::Index {
10906 string,
10907 substr,
10908 position,
10909 } => {
10910 let s = self.eval_expr(string)?.to_string();
10911 let sub = self.eval_expr(substr)?.to_string();
10912 let pos = if let Some(p) = position {
10913 self.eval_expr(p)?.to_int() as usize
10914 } else {
10915 0
10916 };
10917 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
10918 Ok(PerlValue::integer(result))
10919 }
10920 ExprKind::Rindex {
10921 string,
10922 substr,
10923 position,
10924 } => {
10925 let s = self.eval_expr(string)?.to_string();
10926 let sub = self.eval_expr(substr)?.to_string();
10927 let end = if let Some(p) = position {
10928 self.eval_expr(p)?.to_int() as usize + sub.len()
10929 } else {
10930 s.len()
10931 };
10932 let search = &s[..end.min(s.len())];
10933 let result = search.rfind(&sub).map(|i| i as i64).unwrap_or(-1);
10934 Ok(PerlValue::integer(result))
10935 }
10936 ExprKind::Sprintf { format, args } => {
10937 let fmt = self.eval_expr(format)?.to_string();
10938 let mut arg_vals = Vec::new();
10941 for a in args {
10942 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
10943 if let Some(items) = v.as_array_vec() {
10944 arg_vals.extend(items);
10945 } else {
10946 arg_vals.push(v);
10947 }
10948 }
10949 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
10950 Ok(PerlValue::string(s))
10951 }
10952 ExprKind::JoinExpr { separator, list } => {
10953 let sep = self.eval_expr(separator)?.to_string();
10954 let items = if let ExprKind::List(exprs) = &list.kind {
10958 let saved = self.wantarray_kind;
10959 self.wantarray_kind = WantarrayCtx::List;
10960 let mut vals = Vec::new();
10961 for e in exprs {
10962 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
10963 if let Some(items) = v.as_array_vec() {
10964 vals.extend(items);
10965 } else {
10966 vals.push(v);
10967 }
10968 }
10969 self.wantarray_kind = saved;
10970 vals
10971 } else {
10972 let saved = self.wantarray_kind;
10973 self.wantarray_kind = WantarrayCtx::List;
10974 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10975 self.wantarray_kind = saved;
10976 if let Some(items) = v.as_array_vec() {
10977 items
10978 } else {
10979 vec![v]
10980 }
10981 };
10982 let mut strs = Vec::with_capacity(items.len());
10983 for v in &items {
10984 strs.push(self.stringify_value(v.clone(), line)?);
10985 }
10986 Ok(PerlValue::string(strs.join(&sep)))
10987 }
10988 ExprKind::SplitExpr {
10989 pattern,
10990 string,
10991 limit,
10992 } => {
10993 let pat = self.eval_expr(pattern)?.to_string();
10994 let s = self.eval_expr(string)?.to_string();
10995 let lim = if let Some(l) = limit {
10996 self.eval_expr(l)?.to_int() as usize
10997 } else {
10998 0
10999 };
11000 let re = self.compile_regex(&pat, "", line)?;
11001 let parts: Vec<PerlValue> = if lim > 0 {
11002 re.splitn_strings(&s, lim)
11003 .into_iter()
11004 .map(PerlValue::string)
11005 .collect()
11006 } else {
11007 re.split_strings(&s)
11008 .into_iter()
11009 .map(PerlValue::string)
11010 .collect()
11011 };
11012 Ok(PerlValue::array(parts))
11013 }
11014
11015 ExprKind::Abs(expr) => {
11017 let val = self.eval_expr(expr)?;
11018 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
11019 return r;
11020 }
11021 Ok(PerlValue::float(val.to_number().abs()))
11022 }
11023 ExprKind::Int(expr) => {
11024 let val = self.eval_expr(expr)?;
11025 Ok(PerlValue::integer(val.to_number() as i64))
11026 }
11027 ExprKind::Sqrt(expr) => {
11028 let val = self.eval_expr(expr)?;
11029 Ok(PerlValue::float(val.to_number().sqrt()))
11030 }
11031 ExprKind::Sin(expr) => {
11032 let val = self.eval_expr(expr)?;
11033 Ok(PerlValue::float(val.to_number().sin()))
11034 }
11035 ExprKind::Cos(expr) => {
11036 let val = self.eval_expr(expr)?;
11037 Ok(PerlValue::float(val.to_number().cos()))
11038 }
11039 ExprKind::Atan2 { y, x } => {
11040 let yv = self.eval_expr(y)?.to_number();
11041 let xv = self.eval_expr(x)?.to_number();
11042 Ok(PerlValue::float(yv.atan2(xv)))
11043 }
11044 ExprKind::Exp(expr) => {
11045 let val = self.eval_expr(expr)?;
11046 Ok(PerlValue::float(val.to_number().exp()))
11047 }
11048 ExprKind::Log(expr) => {
11049 let val = self.eval_expr(expr)?;
11050 Ok(PerlValue::float(val.to_number().ln()))
11051 }
11052 ExprKind::Rand(upper) => {
11053 let u = match upper {
11054 Some(e) => self.eval_expr(e)?.to_number(),
11055 None => 1.0,
11056 };
11057 Ok(PerlValue::float(self.perl_rand(u)))
11058 }
11059 ExprKind::Srand(seed) => {
11060 let s = match seed {
11061 Some(e) => Some(self.eval_expr(e)?.to_number()),
11062 None => None,
11063 };
11064 Ok(PerlValue::integer(self.perl_srand(s)))
11065 }
11066 ExprKind::Hex(expr) => {
11067 let val = self.eval_expr(expr)?.to_string();
11068 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
11069 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
11070 Ok(PerlValue::integer(n))
11071 }
11072 ExprKind::Oct(expr) => {
11073 let val = self.eval_expr(expr)?.to_string();
11074 let s = val.trim();
11075 let n = if s.starts_with("0x") || s.starts_with("0X") {
11076 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
11077 } else if s.starts_with("0b") || s.starts_with("0B") {
11078 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
11079 } else if s.starts_with("0o") || s.starts_with("0O") {
11080 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
11081 } else {
11082 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
11083 };
11084 Ok(PerlValue::integer(n))
11085 }
11086
11087 ExprKind::Lc(expr) => Ok(PerlValue::string(
11089 self.eval_expr(expr)?.to_string().to_lowercase(),
11090 )),
11091 ExprKind::Uc(expr) => Ok(PerlValue::string(
11092 self.eval_expr(expr)?.to_string().to_uppercase(),
11093 )),
11094 ExprKind::Lcfirst(expr) => {
11095 let s = self.eval_expr(expr)?.to_string();
11096 let mut chars = s.chars();
11097 let result = match chars.next() {
11098 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
11099 None => String::new(),
11100 };
11101 Ok(PerlValue::string(result))
11102 }
11103 ExprKind::Ucfirst(expr) => {
11104 let s = self.eval_expr(expr)?.to_string();
11105 let mut chars = s.chars();
11106 let result = match chars.next() {
11107 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
11108 None => String::new(),
11109 };
11110 Ok(PerlValue::string(result))
11111 }
11112 ExprKind::Fc(expr) => Ok(PerlValue::string(default_case_fold_str(
11113 &self.eval_expr(expr)?.to_string(),
11114 ))),
11115 ExprKind::Crypt { plaintext, salt } => {
11116 let p = self.eval_expr(plaintext)?.to_string();
11117 let sl = self.eval_expr(salt)?.to_string();
11118 Ok(PerlValue::string(perl_crypt(&p, &sl)))
11119 }
11120 ExprKind::Pos(e) => {
11121 let key = match e {
11122 None => "_".to_string(),
11123 Some(expr) => match &expr.kind {
11124 ExprKind::ScalarVar(n) => n.clone(),
11125 _ => self.eval_expr(expr)?.to_string(),
11126 },
11127 };
11128 Ok(self
11129 .regex_pos
11130 .get(&key)
11131 .copied()
11132 .flatten()
11133 .map(|p| PerlValue::integer(p as i64))
11134 .unwrap_or(PerlValue::UNDEF))
11135 }
11136 ExprKind::Study(expr) => {
11137 let s = self.eval_expr(expr)?.to_string();
11138 Ok(Self::study_return_value(&s))
11139 }
11140
11141 ExprKind::Defined(expr) => {
11143 if let ExprKind::SubroutineRef(name) = &expr.kind {
11145 let exists = self.resolve_sub_by_name(name).is_some();
11146 return Ok(PerlValue::integer(if exists { 1 } else { 0 }));
11147 }
11148 let val = self.eval_expr(expr)?;
11149 Ok(PerlValue::integer(if val.is_undef() { 0 } else { 1 }))
11150 }
11151 ExprKind::Ref(expr) => {
11152 let val = self.eval_expr(expr)?;
11153 Ok(val.ref_type())
11154 }
11155 ExprKind::ScalarContext(expr) => {
11156 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11157 Ok(v.scalar_context())
11158 }
11159
11160 ExprKind::Chr(expr) => {
11162 let n = self.eval_expr(expr)?.to_int() as u32;
11163 Ok(PerlValue::string(
11164 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
11165 ))
11166 }
11167 ExprKind::Ord(expr) => {
11168 let s = self.eval_expr(expr)?.to_string();
11169 Ok(PerlValue::integer(
11170 s.chars().next().map(|c| c as i64).unwrap_or(0),
11171 ))
11172 }
11173
11174 ExprKind::OpenMyHandle { .. } => Err(PerlError::runtime(
11176 "internal: `open my $fh` handle used outside open()",
11177 line,
11178 )
11179 .into()),
11180 ExprKind::Open { handle, mode, file } => {
11181 if let ExprKind::OpenMyHandle { name } = &handle.kind {
11182 self.scope
11183 .declare_scalar_frozen(name, PerlValue::UNDEF, false, None)?;
11184 self.english_note_lexical_scalar(name);
11185 let mode_s = self.eval_expr(mode)?.to_string();
11186 let file_opt = if let Some(f) = file {
11187 Some(self.eval_expr(f)?.to_string())
11188 } else {
11189 None
11190 };
11191 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
11192 self.scope.set_scalar(name, ret.clone())?;
11193 return Ok(ret);
11194 }
11195 let handle_s = self.eval_expr(handle)?.to_string();
11196 let handle_name = self.resolve_io_handle_name(&handle_s);
11197 let mode_s = self.eval_expr(mode)?.to_string();
11198 let file_opt = if let Some(f) = file {
11199 Some(self.eval_expr(f)?.to_string())
11200 } else {
11201 None
11202 };
11203 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
11204 .map_err(Into::into)
11205 }
11206 ExprKind::Close(expr) => {
11207 let s = self.eval_expr(expr)?.to_string();
11208 let name = self.resolve_io_handle_name(&s);
11209 self.close_builtin_execute(name).map_err(Into::into)
11210 }
11211 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
11212 self.readline_builtin_execute_list(handle.as_deref())
11213 } else {
11214 self.readline_builtin_execute(handle.as_deref())
11215 }
11216 .map_err(Into::into),
11217 ExprKind::Eof(expr) => match expr {
11218 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
11219 Some(e) => {
11220 let name = self.eval_expr(e)?;
11221 self.eof_builtin_execute(&[name], line).map_err(Into::into)
11222 }
11223 },
11224
11225 ExprKind::Opendir { handle, path } => {
11226 let h = self.eval_expr(handle)?.to_string();
11227 let p = self.eval_expr(path)?.to_string();
11228 Ok(self.opendir_handle(&h, &p))
11229 }
11230 ExprKind::Readdir(e) => {
11231 let h = self.eval_expr(e)?.to_string();
11232 Ok(if ctx == WantarrayCtx::List {
11233 self.readdir_handle_list(&h)
11234 } else {
11235 self.readdir_handle(&h)
11236 })
11237 }
11238 ExprKind::Closedir(e) => {
11239 let h = self.eval_expr(e)?.to_string();
11240 Ok(self.closedir_handle(&h))
11241 }
11242 ExprKind::Rewinddir(e) => {
11243 let h = self.eval_expr(e)?.to_string();
11244 Ok(self.rewinddir_handle(&h))
11245 }
11246 ExprKind::Telldir(e) => {
11247 let h = self.eval_expr(e)?.to_string();
11248 Ok(self.telldir_handle(&h))
11249 }
11250 ExprKind::Seekdir { handle, position } => {
11251 let h = self.eval_expr(handle)?.to_string();
11252 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
11253 Ok(self.seekdir_handle(&h, pos))
11254 }
11255
11256 ExprKind::FileTest { op, expr } => {
11258 let path = self.eval_expr(expr)?.to_string();
11259 if matches!(op, 'M' | 'A' | 'C') {
11261 #[cfg(unix)]
11262 {
11263 return match crate::perl_fs::filetest_age_days(&path, *op) {
11264 Some(days) => Ok(PerlValue::float(days)),
11265 None => Ok(PerlValue::UNDEF),
11266 };
11267 }
11268 #[cfg(not(unix))]
11269 return Ok(PerlValue::UNDEF);
11270 }
11271 if *op == 's' {
11273 return match std::fs::metadata(&path) {
11274 Ok(m) => Ok(PerlValue::integer(m.len() as i64)),
11275 Err(_) => Ok(PerlValue::UNDEF),
11276 };
11277 }
11278 let result = match op {
11279 'e' => std::path::Path::new(&path).exists(),
11280 'f' => std::path::Path::new(&path).is_file(),
11281 'd' => std::path::Path::new(&path).is_dir(),
11282 'l' => std::path::Path::new(&path).is_symlink(),
11283 #[cfg(unix)]
11284 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
11285 #[cfg(not(unix))]
11286 'r' => std::fs::metadata(&path).is_ok(),
11287 #[cfg(unix)]
11288 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
11289 #[cfg(not(unix))]
11290 'w' => std::fs::metadata(&path).is_ok(),
11291 #[cfg(unix)]
11292 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
11293 #[cfg(not(unix))]
11294 'x' => false,
11295 #[cfg(unix)]
11296 'o' => crate::perl_fs::filetest_owned_effective(&path),
11297 #[cfg(not(unix))]
11298 'o' => false,
11299 #[cfg(unix)]
11300 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
11301 #[cfg(not(unix))]
11302 'R' => false,
11303 #[cfg(unix)]
11304 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
11305 #[cfg(not(unix))]
11306 'W' => false,
11307 #[cfg(unix)]
11308 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
11309 #[cfg(not(unix))]
11310 'X' => false,
11311 #[cfg(unix)]
11312 'O' => crate::perl_fs::filetest_owned_real(&path),
11313 #[cfg(not(unix))]
11314 'O' => false,
11315 'z' => std::fs::metadata(&path)
11316 .map(|m| m.len() == 0)
11317 .unwrap_or(true),
11318 't' => crate::perl_fs::filetest_is_tty(&path),
11319 #[cfg(unix)]
11320 'p' => crate::perl_fs::filetest_is_pipe(&path),
11321 #[cfg(not(unix))]
11322 'p' => false,
11323 #[cfg(unix)]
11324 'S' => crate::perl_fs::filetest_is_socket(&path),
11325 #[cfg(not(unix))]
11326 'S' => false,
11327 #[cfg(unix)]
11328 'b' => crate::perl_fs::filetest_is_block_device(&path),
11329 #[cfg(not(unix))]
11330 'b' => false,
11331 #[cfg(unix)]
11332 'c' => crate::perl_fs::filetest_is_char_device(&path),
11333 #[cfg(not(unix))]
11334 'c' => false,
11335 #[cfg(unix)]
11336 'u' => crate::perl_fs::filetest_is_setuid(&path),
11337 #[cfg(not(unix))]
11338 'u' => false,
11339 #[cfg(unix)]
11340 'g' => crate::perl_fs::filetest_is_setgid(&path),
11341 #[cfg(not(unix))]
11342 'g' => false,
11343 #[cfg(unix)]
11344 'k' => crate::perl_fs::filetest_is_sticky(&path),
11345 #[cfg(not(unix))]
11346 'k' => false,
11347 'T' => crate::perl_fs::filetest_is_text(&path),
11348 'B' => crate::perl_fs::filetest_is_binary(&path),
11349 _ => false,
11350 };
11351 Ok(PerlValue::integer(if result { 1 } else { 0 }))
11352 }
11353
11354 ExprKind::System(args) => {
11356 let mut cmd_args = Vec::new();
11357 for a in args {
11358 cmd_args.push(self.eval_expr(a)?.to_string());
11359 }
11360 if cmd_args.is_empty() {
11361 return Ok(PerlValue::integer(-1));
11362 }
11363 let status = Command::new("sh")
11364 .arg("-c")
11365 .arg(cmd_args.join(" "))
11366 .status();
11367 match status {
11368 Ok(s) => {
11369 self.record_child_exit_status(s);
11370 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
11371 }
11372 Err(e) => {
11373 self.apply_io_error_to_errno(&e);
11374 Ok(PerlValue::integer(-1))
11375 }
11376 }
11377 }
11378 ExprKind::Exec(args) => {
11379 let mut cmd_args = Vec::new();
11380 for a in args {
11381 cmd_args.push(self.eval_expr(a)?.to_string());
11382 }
11383 if cmd_args.is_empty() {
11384 return Ok(PerlValue::integer(-1));
11385 }
11386 let status = Command::new("sh")
11387 .arg("-c")
11388 .arg(cmd_args.join(" "))
11389 .status();
11390 match status {
11391 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
11392 Err(e) => {
11393 self.apply_io_error_to_errno(&e);
11394 Ok(PerlValue::integer(-1))
11395 }
11396 }
11397 }
11398 ExprKind::Eval(expr) => {
11399 self.eval_nesting += 1;
11400 let out = match &expr.kind {
11401 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
11402 Ok(v) => {
11403 self.clear_eval_error();
11404 Ok(v)
11405 }
11406 Err(FlowOrError::Error(e)) => {
11407 self.set_eval_error_from_perl_error(&e);
11408 Ok(PerlValue::UNDEF)
11409 }
11410 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
11411 },
11412 _ => {
11413 let code = self.eval_expr(expr)?.to_string();
11414 match crate::parse_and_run_string(&code, self) {
11416 Ok(v) => {
11417 self.clear_eval_error();
11418 Ok(v)
11419 }
11420 Err(e) => {
11421 self.set_eval_error(e.to_string());
11422 Ok(PerlValue::UNDEF)
11423 }
11424 }
11425 }
11426 };
11427 self.eval_nesting -= 1;
11428 out
11429 }
11430 ExprKind::Do(expr) => match &expr.kind {
11431 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
11432 _ => {
11433 let val = self.eval_expr(expr)?;
11434 let filename = val.to_string();
11435 match read_file_text_perl_compat(&filename) {
11436 Ok(code) => {
11437 let code = crate::data_section::strip_perl_end_marker(&code);
11438 match crate::parse_and_run_string_in_file(code, self, &filename) {
11439 Ok(v) => Ok(v),
11440 Err(e) => {
11441 self.set_eval_error(e.to_string());
11442 Ok(PerlValue::UNDEF)
11443 }
11444 }
11445 }
11446 Err(e) => {
11447 self.apply_io_error_to_errno(&e);
11448 Ok(PerlValue::UNDEF)
11449 }
11450 }
11451 }
11452 },
11453 ExprKind::Require(expr) => {
11454 let spec = self.eval_expr(expr)?.to_string();
11455 self.require_execute(&spec, line)
11456 .map_err(FlowOrError::Error)
11457 }
11458 ExprKind::Exit(code) => {
11459 let c = if let Some(e) = code {
11460 self.eval_expr(e)?.to_int() as i32
11461 } else {
11462 0
11463 };
11464 Err(PerlError::new(ErrorKind::Exit(c), "", line, &self.file).into())
11465 }
11466 ExprKind::Chdir(expr) => {
11467 let path = self.eval_expr(expr)?.to_string();
11468 match std::env::set_current_dir(&path) {
11469 Ok(_) => Ok(PerlValue::integer(1)),
11470 Err(e) => {
11471 self.apply_io_error_to_errno(&e);
11472 Ok(PerlValue::integer(0))
11473 }
11474 }
11475 }
11476 ExprKind::Mkdir { path, mode: _ } => {
11477 let p = self.eval_expr(path)?.to_string();
11478 match std::fs::create_dir(&p) {
11479 Ok(_) => Ok(PerlValue::integer(1)),
11480 Err(e) => {
11481 self.apply_io_error_to_errno(&e);
11482 Ok(PerlValue::integer(0))
11483 }
11484 }
11485 }
11486 ExprKind::Unlink(args) => {
11487 let mut count = 0i64;
11488 for a in args {
11489 let path = self.eval_expr(a)?.to_string();
11490 if std::fs::remove_file(&path).is_ok() {
11491 count += 1;
11492 }
11493 }
11494 Ok(PerlValue::integer(count))
11495 }
11496 ExprKind::Rename { old, new } => {
11497 let o = self.eval_expr(old)?.to_string();
11498 let n = self.eval_expr(new)?.to_string();
11499 Ok(crate::perl_fs::rename_paths(&o, &n))
11500 }
11501 ExprKind::Chmod(args) => {
11502 let mode = self.eval_expr(&args[0])?.to_int();
11503 let mut paths = Vec::new();
11504 for a in &args[1..] {
11505 paths.push(self.eval_expr(a)?.to_string());
11506 }
11507 Ok(PerlValue::integer(crate::perl_fs::chmod_paths(
11508 &paths, mode,
11509 )))
11510 }
11511 ExprKind::Chown(args) => {
11512 let uid = self.eval_expr(&args[0])?.to_int();
11513 let gid = self.eval_expr(&args[1])?.to_int();
11514 let mut paths = Vec::new();
11515 for a in &args[2..] {
11516 paths.push(self.eval_expr(a)?.to_string());
11517 }
11518 Ok(PerlValue::integer(crate::perl_fs::chown_paths(
11519 &paths, uid, gid,
11520 )))
11521 }
11522 ExprKind::Stat(e) => {
11523 let path = self.eval_expr(e)?.to_string();
11524 Ok(crate::perl_fs::stat_path(&path, false))
11525 }
11526 ExprKind::Lstat(e) => {
11527 let path = self.eval_expr(e)?.to_string();
11528 Ok(crate::perl_fs::stat_path(&path, true))
11529 }
11530 ExprKind::Link { old, new } => {
11531 let o = self.eval_expr(old)?.to_string();
11532 let n = self.eval_expr(new)?.to_string();
11533 Ok(crate::perl_fs::link_hard(&o, &n))
11534 }
11535 ExprKind::Symlink { old, new } => {
11536 let o = self.eval_expr(old)?.to_string();
11537 let n = self.eval_expr(new)?.to_string();
11538 Ok(crate::perl_fs::link_sym(&o, &n))
11539 }
11540 ExprKind::Readlink(e) => {
11541 let path = self.eval_expr(e)?.to_string();
11542 Ok(crate::perl_fs::read_link(&path))
11543 }
11544 ExprKind::Files(args) => {
11545 let dir = if args.is_empty() {
11546 ".".to_string()
11547 } else {
11548 self.eval_expr(&args[0])?.to_string()
11549 };
11550 Ok(crate::perl_fs::list_files(&dir))
11551 }
11552 ExprKind::Filesf(args) => {
11553 let dir = if args.is_empty() {
11554 ".".to_string()
11555 } else {
11556 self.eval_expr(&args[0])?.to_string()
11557 };
11558 Ok(crate::perl_fs::list_filesf(&dir))
11559 }
11560 ExprKind::FilesfRecursive(args) => {
11561 let dir = if args.is_empty() {
11562 ".".to_string()
11563 } else {
11564 self.eval_expr(&args[0])?.to_string()
11565 };
11566 Ok(PerlValue::iterator(Arc::new(
11567 crate::value::FsWalkIterator::new(&dir, true),
11568 )))
11569 }
11570 ExprKind::Dirs(args) => {
11571 let dir = if args.is_empty() {
11572 ".".to_string()
11573 } else {
11574 self.eval_expr(&args[0])?.to_string()
11575 };
11576 Ok(crate::perl_fs::list_dirs(&dir))
11577 }
11578 ExprKind::DirsRecursive(args) => {
11579 let dir = if args.is_empty() {
11580 ".".to_string()
11581 } else {
11582 self.eval_expr(&args[0])?.to_string()
11583 };
11584 Ok(PerlValue::iterator(Arc::new(
11585 crate::value::FsWalkIterator::new(&dir, false),
11586 )))
11587 }
11588 ExprKind::SymLinks(args) => {
11589 let dir = if args.is_empty() {
11590 ".".to_string()
11591 } else {
11592 self.eval_expr(&args[0])?.to_string()
11593 };
11594 Ok(crate::perl_fs::list_sym_links(&dir))
11595 }
11596 ExprKind::Sockets(args) => {
11597 let dir = if args.is_empty() {
11598 ".".to_string()
11599 } else {
11600 self.eval_expr(&args[0])?.to_string()
11601 };
11602 Ok(crate::perl_fs::list_sockets(&dir))
11603 }
11604 ExprKind::Pipes(args) => {
11605 let dir = if args.is_empty() {
11606 ".".to_string()
11607 } else {
11608 self.eval_expr(&args[0])?.to_string()
11609 };
11610 Ok(crate::perl_fs::list_pipes(&dir))
11611 }
11612 ExprKind::BlockDevices(args) => {
11613 let dir = if args.is_empty() {
11614 ".".to_string()
11615 } else {
11616 self.eval_expr(&args[0])?.to_string()
11617 };
11618 Ok(crate::perl_fs::list_block_devices(&dir))
11619 }
11620 ExprKind::CharDevices(args) => {
11621 let dir = if args.is_empty() {
11622 ".".to_string()
11623 } else {
11624 self.eval_expr(&args[0])?.to_string()
11625 };
11626 Ok(crate::perl_fs::list_char_devices(&dir))
11627 }
11628 ExprKind::Glob(args) => {
11629 let mut pats = Vec::new();
11630 for a in args {
11631 pats.push(self.eval_expr(a)?.to_string());
11632 }
11633 Ok(crate::perl_fs::glob_patterns(&pats))
11634 }
11635 ExprKind::GlobPar { args, progress } => {
11636 let mut pats = Vec::new();
11637 for a in args {
11638 pats.push(self.eval_expr(a)?.to_string());
11639 }
11640 let show_progress = progress
11641 .as_ref()
11642 .map(|p| self.eval_expr(p))
11643 .transpose()?
11644 .map(|v| v.is_true())
11645 .unwrap_or(false);
11646 if show_progress {
11647 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
11648 } else {
11649 Ok(crate::perl_fs::glob_par_patterns(&pats))
11650 }
11651 }
11652 ExprKind::ParSed { args, progress } => {
11653 let has_progress = progress.is_some();
11654 let mut vals: Vec<PerlValue> = Vec::new();
11655 for a in args {
11656 vals.push(self.eval_expr(a)?);
11657 }
11658 if let Some(p) = progress {
11659 vals.push(self.eval_expr(p.as_ref())?);
11660 }
11661 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
11662 }
11663 ExprKind::Bless { ref_expr, class } => {
11664 let val = self.eval_expr(ref_expr)?;
11665 let class_name = if let Some(c) = class {
11666 self.eval_expr(c)?.to_string()
11667 } else {
11668 self.scope.get_scalar("__PACKAGE__").to_string()
11669 };
11670 Ok(PerlValue::blessed(Arc::new(
11671 crate::value::BlessedRef::new_blessed(class_name, val),
11672 )))
11673 }
11674 ExprKind::Caller(_) => {
11675 Ok(PerlValue::array(vec![
11677 PerlValue::string("main".into()),
11678 PerlValue::string(self.file.clone()),
11679 PerlValue::integer(line as i64),
11680 ]))
11681 }
11682 ExprKind::Wantarray => Ok(match self.wantarray_kind {
11683 WantarrayCtx::Void => PerlValue::UNDEF,
11684 WantarrayCtx::Scalar => PerlValue::integer(0),
11685 WantarrayCtx::List => PerlValue::integer(1),
11686 }),
11687
11688 ExprKind::List(exprs) => {
11689 if ctx == WantarrayCtx::Scalar {
11691 if let Some(last) = exprs.last() {
11692 for e in &exprs[..exprs.len() - 1] {
11694 self.eval_expr(e)?;
11695 }
11696 return self.eval_expr(last);
11697 } else {
11698 return Ok(PerlValue::UNDEF);
11699 }
11700 }
11701 let mut vals = Vec::new();
11702 for e in exprs {
11703 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
11704 if let Some(items) = v.as_array_vec() {
11705 vals.extend(items);
11706 } else {
11707 vals.push(v);
11708 }
11709 }
11710 if vals.len() == 1 {
11711 Ok(vals.pop().unwrap())
11712 } else {
11713 Ok(PerlValue::array(vals))
11714 }
11715 }
11716
11717 ExprKind::PostfixIf { expr, condition } => {
11719 if self.eval_postfix_condition(condition)? {
11720 self.eval_expr(expr)
11721 } else {
11722 Ok(PerlValue::UNDEF)
11723 }
11724 }
11725 ExprKind::PostfixUnless { expr, condition } => {
11726 if !self.eval_postfix_condition(condition)? {
11727 self.eval_expr(expr)
11728 } else {
11729 Ok(PerlValue::UNDEF)
11730 }
11731 }
11732 ExprKind::PostfixWhile { expr, condition } => {
11733 let is_do_block = matches!(
11736 &expr.kind,
11737 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
11738 );
11739 let mut last = PerlValue::UNDEF;
11740 if is_do_block {
11741 loop {
11742 last = self.eval_expr(expr)?;
11743 if !self.eval_postfix_condition(condition)? {
11744 break;
11745 }
11746 }
11747 } else {
11748 loop {
11749 if !self.eval_postfix_condition(condition)? {
11750 break;
11751 }
11752 last = self.eval_expr(expr)?;
11753 }
11754 }
11755 Ok(last)
11756 }
11757 ExprKind::PostfixUntil { expr, condition } => {
11758 let is_do_block = matches!(
11759 &expr.kind,
11760 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
11761 );
11762 let mut last = PerlValue::UNDEF;
11763 if is_do_block {
11764 loop {
11765 last = self.eval_expr(expr)?;
11766 if self.eval_postfix_condition(condition)? {
11767 break;
11768 }
11769 }
11770 } else {
11771 loop {
11772 if self.eval_postfix_condition(condition)? {
11773 break;
11774 }
11775 last = self.eval_expr(expr)?;
11776 }
11777 }
11778 Ok(last)
11779 }
11780 ExprKind::PostfixForeach { expr, list } => {
11781 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
11782 let mut last = PerlValue::UNDEF;
11783 for item in items {
11784 self.scope.set_topic(item);
11785 last = self.eval_expr(expr)?;
11786 }
11787 Ok(last)
11788 }
11789 }
11790 }
11791
11792 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
11795 match op {
11796 BinOp::Add => Some("+"),
11797 BinOp::Sub => Some("-"),
11798 BinOp::Mul => Some("*"),
11799 BinOp::Div => Some("/"),
11800 BinOp::Mod => Some("%"),
11801 BinOp::Pow => Some("**"),
11802 BinOp::Concat => Some("."),
11803 BinOp::StrEq => Some("eq"),
11804 BinOp::NumEq => Some("=="),
11805 BinOp::StrNe => Some("ne"),
11806 BinOp::NumNe => Some("!="),
11807 BinOp::StrLt => Some("lt"),
11808 BinOp::StrGt => Some("gt"),
11809 BinOp::StrLe => Some("le"),
11810 BinOp::StrGe => Some("ge"),
11811 BinOp::NumLt => Some("<"),
11812 BinOp::NumGt => Some(">"),
11813 BinOp::NumLe => Some("<="),
11814 BinOp::NumGe => Some(">="),
11815 BinOp::Spaceship => Some("<=>"),
11816 BinOp::StrCmp => Some("cmp"),
11817 _ => None,
11818 }
11819 }
11820
11821 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
11823 map.get("").or_else(|| map.get("\"\""))
11824 }
11825
11826 pub(crate) fn stringify_value(
11828 &mut self,
11829 v: PerlValue,
11830 line: usize,
11831 ) -> Result<String, FlowOrError> {
11832 if let Some(r) = self.try_overload_stringify(&v, line) {
11833 let pv = r?;
11834 return Ok(pv.to_string());
11835 }
11836 Ok(v.to_string())
11837 }
11838
11839 pub(crate) fn perl_sprintf_stringify(
11841 &mut self,
11842 fmt: &str,
11843 args: &[PerlValue],
11844 line: usize,
11845 ) -> Result<String, FlowOrError> {
11846 perl_sprintf_format_with(fmt, args, |v| self.stringify_value(v.clone(), line))
11847 }
11848
11849 pub(crate) fn render_format_template(
11851 &mut self,
11852 tmpl: &crate::format::FormatTemplate,
11853 line: usize,
11854 ) -> Result<String, FlowOrError> {
11855 use crate::format::{FormatRecord, PictureSegment};
11856 let mut buf = String::new();
11857 for rec in &tmpl.records {
11858 match rec {
11859 FormatRecord::Literal(s) => {
11860 buf.push_str(s);
11861 buf.push('\n');
11862 }
11863 FormatRecord::Picture { segments, exprs } => {
11864 let mut vals: Vec<String> = Vec::new();
11865 for e in exprs {
11866 let v = self.eval_expr(e)?;
11867 vals.push(self.stringify_value(v, line)?);
11868 }
11869 let mut vi = 0usize;
11870 let mut line_out = String::new();
11871 for seg in segments {
11872 match seg {
11873 PictureSegment::Literal(t) => line_out.push_str(t),
11874 PictureSegment::Field {
11875 width,
11876 align,
11877 kind: _,
11878 } => {
11879 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
11880 vi += 1;
11881 line_out.push_str(&crate::format::pad_field(s, *width, *align));
11882 }
11883 }
11884 }
11885 buf.push_str(line_out.trim_end());
11886 buf.push('\n');
11887 }
11888 }
11889 }
11890 Ok(buf)
11891 }
11892
11893 pub(crate) fn resolve_write_output_handle(
11895 &self,
11896 v: &PerlValue,
11897 line: usize,
11898 ) -> PerlResult<String> {
11899 if let Some(n) = v.as_io_handle_name() {
11900 let n = self.resolve_io_handle_name(&n);
11901 if self.is_bound_handle(&n) {
11902 return Ok(n);
11903 }
11904 }
11905 if let Some(s) = v.as_str() {
11906 if self.is_bound_handle(&s) {
11907 return Ok(self.resolve_io_handle_name(&s));
11908 }
11909 }
11910 let s = v.to_string();
11911 if self.is_bound_handle(&s) {
11912 return Ok(self.resolve_io_handle_name(&s));
11913 }
11914 Err(PerlError::runtime(
11915 format!("write: invalid or unopened filehandle {}", s),
11916 line,
11917 ))
11918 }
11919
11920 pub(crate) fn write_format_execute(
11924 &mut self,
11925 args: &[PerlValue],
11926 line: usize,
11927 ) -> PerlResult<PerlValue> {
11928 let handle_name = match args.len() {
11929 0 => self.default_print_handle.clone(),
11930 1 => self.resolve_write_output_handle(&args[0], line)?,
11931 _ => {
11932 return Err(PerlError::runtime("write: too many arguments", line));
11933 }
11934 };
11935 let pkg = self.current_package();
11936 let mut fmt_name = self.scope.get_scalar("~").to_string();
11937 if fmt_name.is_empty() {
11938 fmt_name = "STDOUT".to_string();
11939 }
11940 let key = format!("{}::{}", pkg, fmt_name);
11941 let tmpl = self
11942 .format_templates
11943 .get(&key)
11944 .map(Arc::clone)
11945 .ok_or_else(|| {
11946 PerlError::runtime(
11947 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
11948 line,
11949 )
11950 })?;
11951 let out = self
11952 .render_format_template(&tmpl, line)
11953 .map_err(|e| match e {
11954 FlowOrError::Error(e) => e,
11955 FlowOrError::Flow(_) => PerlError::runtime("write: unexpected control flow", line),
11956 })?;
11957 self.write_formatted_print(handle_name.as_str(), &out, line)?;
11958 Ok(PerlValue::integer(1))
11959 }
11960
11961 pub(crate) fn try_overload_stringify(
11962 &mut self,
11963 v: &PerlValue,
11964 line: usize,
11965 ) -> Option<ExecResult> {
11966 if let Some(c) = v.as_class_inst() {
11968 let method_name = c
11969 .def
11970 .method("stringify")
11971 .or_else(|| c.def.method("\"\""))
11972 .filter(|m| m.body.is_some())?;
11973 let body = method_name.body.clone().unwrap();
11974 let params = method_name.params.clone();
11975 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
11976 }
11977 let br = v.as_blessed_ref()?;
11978 let class = br.class.clone();
11979 let map = self.overload_table.get(&class)?;
11980 let sub_short = Self::overload_stringify_method(map)?;
11981 let fq = format!("{}::{}", class, sub_short);
11982 let sub = self.subs.get(&fq)?.clone();
11983 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
11984 }
11985
11986 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
11988 match key {
11989 "+" => Some("op_add"),
11990 "-" => Some("op_sub"),
11991 "*" => Some("op_mul"),
11992 "/" => Some("op_div"),
11993 "%" => Some("op_mod"),
11994 "**" => Some("op_pow"),
11995 "." => Some("op_concat"),
11996 "==" => Some("op_eq"),
11997 "!=" => Some("op_ne"),
11998 "<" => Some("op_lt"),
11999 ">" => Some("op_gt"),
12000 "<=" => Some("op_le"),
12001 ">=" => Some("op_ge"),
12002 "<=>" => Some("op_spaceship"),
12003 "eq" => Some("op_str_eq"),
12004 "ne" => Some("op_str_ne"),
12005 "lt" => Some("op_str_lt"),
12006 "gt" => Some("op_str_gt"),
12007 "le" => Some("op_str_le"),
12008 "ge" => Some("op_str_ge"),
12009 "cmp" => Some("op_cmp"),
12010 _ => None,
12011 }
12012 }
12013
12014 pub(crate) fn try_overload_binop(
12015 &mut self,
12016 op: BinOp,
12017 lv: &PerlValue,
12018 rv: &PerlValue,
12019 line: usize,
12020 ) -> Option<ExecResult> {
12021 let key = Self::overload_key_for_binop(op)?;
12022 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
12024 (Some(c.def.clone()), lv.clone(), rv.clone())
12025 } else if let Some(c) = rv.as_class_inst() {
12026 (Some(c.def.clone()), rv.clone(), lv.clone())
12027 } else {
12028 (None, lv.clone(), rv.clone())
12029 };
12030 if let Some(ref def) = ci_def {
12031 if let Some(method_name) = Self::overload_method_name_for_key(key) {
12032 if let Some((m, _)) = self.find_class_method(def, method_name) {
12033 if let Some(ref body) = m.body {
12034 let params = m.params.clone();
12035 return Some(self.call_class_method(
12036 body,
12037 ¶ms,
12038 vec![invocant, other],
12039 line,
12040 ));
12041 }
12042 }
12043 }
12044 }
12045 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
12047 (br.class.clone(), lv.clone(), rv.clone())
12048 } else if let Some(br) = rv.as_blessed_ref() {
12049 (br.class.clone(), rv.clone(), lv.clone())
12050 } else {
12051 return None;
12052 };
12053 let map = self.overload_table.get(&class)?;
12054 let sub_short = if let Some(s) = map.get(key) {
12055 s.clone()
12056 } else if let Some(nm) = map.get("nomethod") {
12057 let fq = format!("{}::{}", class, nm);
12058 let sub = self.subs.get(&fq)?.clone();
12059 return Some(self.call_sub(
12060 &sub,
12061 vec![invocant, other, PerlValue::string(key.to_string())],
12062 WantarrayCtx::Scalar,
12063 line,
12064 ));
12065 } else {
12066 return None;
12067 };
12068 let fq = format!("{}::{}", class, sub_short);
12069 let sub = self.subs.get(&fq)?.clone();
12070 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
12071 }
12072
12073 pub(crate) fn try_overload_unary_dispatch(
12075 &mut self,
12076 op_key: &str,
12077 val: &PerlValue,
12078 line: usize,
12079 ) -> Option<ExecResult> {
12080 if let Some(c) = val.as_class_inst() {
12082 let method_name = match op_key {
12083 "neg" => "op_neg",
12084 "bool" => "op_bool",
12085 "abs" => "op_abs",
12086 "0+" => "op_numify",
12087 _ => return None,
12088 };
12089 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
12090 if let Some(ref body) = m.body {
12091 let params = m.params.clone();
12092 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
12093 }
12094 }
12095 return None;
12096 }
12097 let br = val.as_blessed_ref()?;
12099 let class = br.class.clone();
12100 let map = self.overload_table.get(&class)?;
12101 if let Some(s) = map.get(op_key) {
12102 let fq = format!("{}::{}", class, s);
12103 let sub = self.subs.get(&fq)?.clone();
12104 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
12105 }
12106 if let Some(nm) = map.get("nomethod") {
12107 let fq = format!("{}::{}", class, nm);
12108 let sub = self.subs.get(&fq)?.clone();
12109 return Some(self.call_sub(
12110 &sub,
12111 vec![val.clone(), PerlValue::string(op_key.to_string())],
12112 WantarrayCtx::Scalar,
12113 line,
12114 ));
12115 }
12116 None
12117 }
12118
12119 #[inline]
12120 fn eval_binop(
12121 &mut self,
12122 op: BinOp,
12123 lv: &PerlValue,
12124 rv: &PerlValue,
12125 _line: usize,
12126 ) -> ExecResult {
12127 Ok(match op {
12128 BinOp::Add => {
12131 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12132 PerlValue::integer(a.wrapping_add(b))
12133 } else {
12134 PerlValue::float(lv.to_number() + rv.to_number())
12135 }
12136 }
12137 BinOp::Sub => {
12138 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12139 PerlValue::integer(a.wrapping_sub(b))
12140 } else {
12141 PerlValue::float(lv.to_number() - rv.to_number())
12142 }
12143 }
12144 BinOp::Mul => {
12145 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12146 PerlValue::integer(a.wrapping_mul(b))
12147 } else {
12148 PerlValue::float(lv.to_number() * rv.to_number())
12149 }
12150 }
12151 BinOp::Div => {
12152 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12153 if b == 0 {
12154 return Err(PerlError::runtime("Illegal division by zero", _line).into());
12155 }
12156 if a % b == 0 {
12157 PerlValue::integer(a / b)
12158 } else {
12159 PerlValue::float(a as f64 / b as f64)
12160 }
12161 } else {
12162 let d = rv.to_number();
12163 if d == 0.0 {
12164 return Err(PerlError::runtime("Illegal division by zero", _line).into());
12165 }
12166 PerlValue::float(lv.to_number() / d)
12167 }
12168 }
12169 BinOp::Mod => {
12170 let d = rv.to_int();
12171 if d == 0 {
12172 return Err(PerlError::runtime("Illegal modulus zero", _line).into());
12173 }
12174 PerlValue::integer(lv.to_int() % d)
12175 }
12176 BinOp::Pow => {
12177 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12178 let int_pow = (b >= 0)
12179 .then(|| u32::try_from(b).ok())
12180 .flatten()
12181 .and_then(|bu| a.checked_pow(bu))
12182 .map(PerlValue::integer);
12183 int_pow.unwrap_or_else(|| PerlValue::float(lv.to_number().powf(rv.to_number())))
12184 } else {
12185 PerlValue::float(lv.to_number().powf(rv.to_number()))
12186 }
12187 }
12188 BinOp::Concat => {
12189 let mut s = String::new();
12190 lv.append_to(&mut s);
12191 rv.append_to(&mut s);
12192 PerlValue::string(s)
12193 }
12194 BinOp::NumEq => {
12195 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
12197 if a.def.name != b.def.name {
12198 PerlValue::integer(0)
12199 } else {
12200 let av = a.get_values();
12201 let bv = b.get_values();
12202 let eq = av.len() == bv.len()
12203 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
12204 PerlValue::integer(if eq { 1 } else { 0 })
12205 }
12206 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12207 PerlValue::integer(if a == b { 1 } else { 0 })
12208 } else {
12209 PerlValue::integer(if lv.to_number() == rv.to_number() {
12210 1
12211 } else {
12212 0
12213 })
12214 }
12215 }
12216 BinOp::NumNe => {
12217 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12218 PerlValue::integer(if a != b { 1 } else { 0 })
12219 } else {
12220 PerlValue::integer(if lv.to_number() != rv.to_number() {
12221 1
12222 } else {
12223 0
12224 })
12225 }
12226 }
12227 BinOp::NumLt => {
12228 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12229 PerlValue::integer(if a < b { 1 } else { 0 })
12230 } else {
12231 PerlValue::integer(if lv.to_number() < rv.to_number() {
12232 1
12233 } else {
12234 0
12235 })
12236 }
12237 }
12238 BinOp::NumGt => {
12239 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12240 PerlValue::integer(if a > b { 1 } else { 0 })
12241 } else {
12242 PerlValue::integer(if lv.to_number() > rv.to_number() {
12243 1
12244 } else {
12245 0
12246 })
12247 }
12248 }
12249 BinOp::NumLe => {
12250 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12251 PerlValue::integer(if a <= b { 1 } else { 0 })
12252 } else {
12253 PerlValue::integer(if lv.to_number() <= rv.to_number() {
12254 1
12255 } else {
12256 0
12257 })
12258 }
12259 }
12260 BinOp::NumGe => {
12261 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12262 PerlValue::integer(if a >= b { 1 } else { 0 })
12263 } else {
12264 PerlValue::integer(if lv.to_number() >= rv.to_number() {
12265 1
12266 } else {
12267 0
12268 })
12269 }
12270 }
12271 BinOp::Spaceship => {
12272 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12273 PerlValue::integer(if a < b {
12274 -1
12275 } else if a > b {
12276 1
12277 } else {
12278 0
12279 })
12280 } else {
12281 let a = lv.to_number();
12282 let b = rv.to_number();
12283 PerlValue::integer(if a < b {
12284 -1
12285 } else if a > b {
12286 1
12287 } else {
12288 0
12289 })
12290 }
12291 }
12292 BinOp::StrEq => PerlValue::integer(if lv.to_string() == rv.to_string() {
12293 1
12294 } else {
12295 0
12296 }),
12297 BinOp::StrNe => PerlValue::integer(if lv.to_string() != rv.to_string() {
12298 1
12299 } else {
12300 0
12301 }),
12302 BinOp::StrLt => PerlValue::integer(if lv.to_string() < rv.to_string() {
12303 1
12304 } else {
12305 0
12306 }),
12307 BinOp::StrGt => PerlValue::integer(if lv.to_string() > rv.to_string() {
12308 1
12309 } else {
12310 0
12311 }),
12312 BinOp::StrLe => PerlValue::integer(if lv.to_string() <= rv.to_string() {
12313 1
12314 } else {
12315 0
12316 }),
12317 BinOp::StrGe => PerlValue::integer(if lv.to_string() >= rv.to_string() {
12318 1
12319 } else {
12320 0
12321 }),
12322 BinOp::StrCmp => {
12323 let cmp = lv.to_string().cmp(&rv.to_string());
12324 PerlValue::integer(match cmp {
12325 std::cmp::Ordering::Less => -1,
12326 std::cmp::Ordering::Greater => 1,
12327 std::cmp::Ordering::Equal => 0,
12328 })
12329 }
12330 BinOp::BitAnd => {
12331 if let Some(s) = crate::value::set_intersection(lv, rv) {
12332 s
12333 } else {
12334 PerlValue::integer(lv.to_int() & rv.to_int())
12335 }
12336 }
12337 BinOp::BitOr => {
12338 if let Some(s) = crate::value::set_union(lv, rv) {
12339 s
12340 } else {
12341 PerlValue::integer(lv.to_int() | rv.to_int())
12342 }
12343 }
12344 BinOp::BitXor => PerlValue::integer(lv.to_int() ^ rv.to_int()),
12345 BinOp::ShiftLeft => PerlValue::integer(lv.to_int() << rv.to_int()),
12346 BinOp::ShiftRight => PerlValue::integer(lv.to_int() >> rv.to_int()),
12347 BinOp::LogAnd
12349 | BinOp::LogOr
12350 | BinOp::DefinedOr
12351 | BinOp::LogAndWord
12352 | BinOp::LogOrWord => unreachable!(),
12353 BinOp::BindMatch | BinOp::BindNotMatch => {
12354 unreachable!("regex bind handled in eval_expr BinOp arm")
12355 }
12356 })
12357 }
12358
12359 fn err_modify_symbolic_aggregate_deref_inc_dec(
12363 kind: Sigil,
12364 is_pre: bool,
12365 is_inc: bool,
12366 line: usize,
12367 ) -> FlowOrError {
12368 let agg = match kind {
12369 Sigil::Array => "array",
12370 Sigil::Hash => "hash",
12371 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
12372 };
12373 let op = match (is_pre, is_inc) {
12374 (true, true) => "preincrement (++)",
12375 (true, false) => "predecrement (--)",
12376 (false, true) => "postincrement (++)",
12377 (false, false) => "postdecrement (--)",
12378 };
12379 FlowOrError::Error(PerlError::runtime(
12380 format!("Can't modify {agg} dereference in {op}"),
12381 line,
12382 ))
12383 }
12384
12385 pub(crate) fn symbolic_scalar_ref_postfix(
12387 &mut self,
12388 ref_val: PerlValue,
12389 decrement: bool,
12390 line: usize,
12391 ) -> Result<PerlValue, FlowOrError> {
12392 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
12393 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12394 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
12395 Ok(old)
12396 }
12397
12398 pub(crate) fn assign_scalar_ref_deref(
12401 &mut self,
12402 ref_val: PerlValue,
12403 val: PerlValue,
12404 line: usize,
12405 ) -> ExecResult {
12406 if let Some(name) = ref_val.as_scalar_binding_name() {
12407 self.set_special_var(&name, &val)
12408 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12409 return Ok(PerlValue::UNDEF);
12410 }
12411 if let Some(r) = ref_val.as_scalar_ref() {
12412 *r.write() = val;
12413 return Ok(PerlValue::UNDEF);
12414 }
12415 Err(PerlError::runtime("Can't assign to non-scalar reference", line).into())
12416 }
12417
12418 pub(crate) fn assign_symbolic_array_ref_deref(
12420 &mut self,
12421 ref_val: PerlValue,
12422 val: PerlValue,
12423 line: usize,
12424 ) -> ExecResult {
12425 if let Some(a) = ref_val.as_array_ref() {
12426 *a.write() = val.to_list();
12427 return Ok(PerlValue::UNDEF);
12428 }
12429 if let Some(name) = ref_val.as_array_binding_name() {
12430 self.scope
12431 .set_array(&name, val.to_list())
12432 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12433 return Ok(PerlValue::UNDEF);
12434 }
12435 if let Some(s) = ref_val.as_str() {
12436 if self.strict_refs {
12437 return Err(PerlError::runtime(
12438 format!(
12439 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
12440 s
12441 ),
12442 line,
12443 )
12444 .into());
12445 }
12446 self.scope
12447 .set_array(&s, val.to_list())
12448 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12449 return Ok(PerlValue::UNDEF);
12450 }
12451 Err(PerlError::runtime("Can't assign to non-array reference", line).into())
12452 }
12453
12454 pub(crate) fn assign_symbolic_typeglob_ref_deref(
12457 &mut self,
12458 ref_val: PerlValue,
12459 val: PerlValue,
12460 line: usize,
12461 ) -> ExecResult {
12462 let lhs_name = if let Some(s) = ref_val.as_str() {
12463 if self.strict_refs {
12464 return Err(PerlError::runtime(
12465 format!(
12466 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
12467 s
12468 ),
12469 line,
12470 )
12471 .into());
12472 }
12473 s.to_string()
12474 } else {
12475 return Err(
12476 PerlError::runtime("Can't assign to non-glob symbolic reference", line).into(),
12477 );
12478 };
12479 let is_coderef = val.as_code_ref().is_some()
12480 || val
12481 .as_scalar_ref()
12482 .map(|r| r.read().as_code_ref().is_some())
12483 .unwrap_or(false);
12484 if is_coderef {
12485 return self.assign_typeglob_value(&lhs_name, val, line);
12486 }
12487 let rhs_key = val.to_string();
12488 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
12489 .map_err(FlowOrError::Error)?;
12490 Ok(PerlValue::UNDEF)
12491 }
12492
12493 pub(crate) fn assign_symbolic_hash_ref_deref(
12495 &mut self,
12496 ref_val: PerlValue,
12497 val: PerlValue,
12498 line: usize,
12499 ) -> ExecResult {
12500 let items = val.to_list();
12501 let mut map = IndexMap::new();
12502 let mut i = 0;
12503 while i + 1 < items.len() {
12504 map.insert(items[i].to_string(), items[i + 1].clone());
12505 i += 2;
12506 }
12507 if let Some(h) = ref_val.as_hash_ref() {
12508 *h.write() = map;
12509 return Ok(PerlValue::UNDEF);
12510 }
12511 if let Some(name) = ref_val.as_hash_binding_name() {
12512 self.touch_env_hash(&name);
12513 self.scope
12514 .set_hash(&name, map)
12515 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12516 return Ok(PerlValue::UNDEF);
12517 }
12518 if let Some(s) = ref_val.as_str() {
12519 if self.strict_refs {
12520 return Err(PerlError::runtime(
12521 format!(
12522 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
12523 s
12524 ),
12525 line,
12526 )
12527 .into());
12528 }
12529 self.touch_env_hash(&s);
12530 self.scope
12531 .set_hash(&s, map)
12532 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12533 return Ok(PerlValue::UNDEF);
12534 }
12535 Err(PerlError::runtime("Can't assign to non-hash reference", line).into())
12536 }
12537
12538 pub(crate) fn assign_arrow_hash_deref(
12540 &mut self,
12541 container: PerlValue,
12542 key: String,
12543 val: PerlValue,
12544 line: usize,
12545 ) -> ExecResult {
12546 if let Some(b) = container.as_blessed_ref() {
12547 let mut data = b.data.write();
12548 if let Some(r) = data.as_hash_ref() {
12549 r.write().insert(key, val);
12550 return Ok(PerlValue::UNDEF);
12551 }
12552 if let Some(mut map) = data.as_hash_map() {
12553 map.insert(key, val);
12554 *data = PerlValue::hash(map);
12555 return Ok(PerlValue::UNDEF);
12556 }
12557 return Err(PerlError::runtime("Can't assign into non-hash blessed ref", line).into());
12558 }
12559 if let Some(r) = container.as_hash_ref() {
12560 r.write().insert(key, val);
12561 return Ok(PerlValue::UNDEF);
12562 }
12563 if let Some(name) = container.as_hash_binding_name() {
12564 self.touch_env_hash(&name);
12565 self.scope
12566 .set_hash_element(&name, &key, val)
12567 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12568 return Ok(PerlValue::UNDEF);
12569 }
12570 Err(PerlError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
12571 }
12572
12573 pub(crate) fn eval_arrow_array_base(
12576 &mut self,
12577 expr: &Expr,
12578 _line: usize,
12579 ) -> Result<PerlValue, FlowOrError> {
12580 match &expr.kind {
12581 ExprKind::Deref {
12582 expr: inner,
12583 kind: Sigil::Array | Sigil::Scalar,
12584 } => self.eval_expr(inner),
12585 _ => self.eval_expr(expr),
12586 }
12587 }
12588
12589 pub(crate) fn eval_arrow_hash_base(
12591 &mut self,
12592 expr: &Expr,
12593 _line: usize,
12594 ) -> Result<PerlValue, FlowOrError> {
12595 match &expr.kind {
12596 ExprKind::Deref {
12597 expr: inner,
12598 kind: Sigil::Scalar,
12599 } => self.eval_expr(inner),
12600 _ => self.eval_expr(expr),
12601 }
12602 }
12603
12604 pub(crate) fn read_arrow_array_element(
12606 &self,
12607 container: PerlValue,
12608 idx: i64,
12609 line: usize,
12610 ) -> Result<PerlValue, FlowOrError> {
12611 if let Some(a) = container.as_array_ref() {
12612 let arr = a.read();
12613 let i = if idx < 0 {
12614 (arr.len() as i64 + idx) as usize
12615 } else {
12616 idx as usize
12617 };
12618 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12619 }
12620 if let Some(name) = container.as_array_binding_name() {
12621 return Ok(self.scope.get_array_element(&name, idx));
12622 }
12623 if let Some(arr) = container.as_array_vec() {
12624 let i = if idx < 0 {
12625 (arr.len() as i64 + idx) as usize
12626 } else {
12627 idx as usize
12628 };
12629 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12630 }
12631 if let Some(b) = container.as_blessed_ref() {
12635 let inner = b.data.read().clone();
12636 if let Some(a) = inner.as_array_ref() {
12637 let arr = a.read();
12638 let i = if idx < 0 {
12639 (arr.len() as i64 + idx) as usize
12640 } else {
12641 idx as usize
12642 };
12643 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12644 }
12645 }
12646 Err(PerlError::runtime("Can't use arrow deref on non-array-ref", line).into())
12647 }
12648
12649 pub(crate) fn read_arrow_hash_element(
12651 &mut self,
12652 container: PerlValue,
12653 key: &str,
12654 line: usize,
12655 ) -> Result<PerlValue, FlowOrError> {
12656 if let Some(r) = container.as_hash_ref() {
12657 let h = r.read();
12658 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12659 }
12660 if let Some(name) = container.as_hash_binding_name() {
12661 self.touch_env_hash(&name);
12662 return Ok(self.scope.get_hash_element(&name, key));
12663 }
12664 if let Some(b) = container.as_blessed_ref() {
12665 let data = b.data.read();
12666 if let Some(v) = data.hash_get(key) {
12667 return Ok(v);
12668 }
12669 if let Some(r) = data.as_hash_ref() {
12670 let h = r.read();
12671 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12672 }
12673 return Err(PerlError::runtime(
12674 "Can't access hash field on non-hash blessed ref",
12675 line,
12676 )
12677 .into());
12678 }
12679 if let Some(s) = container.as_struct_inst() {
12681 if let Some(idx) = s.def.field_index(key) {
12682 return Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF));
12683 }
12684 return Err(PerlError::runtime(
12685 format!("struct {} has no field `{}`", s.def.name, key),
12686 line,
12687 )
12688 .into());
12689 }
12690 if let Some(c) = container.as_class_inst() {
12692 if let Some(idx) = c.def.field_index(key) {
12693 return Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF));
12694 }
12695 return Err(PerlError::runtime(
12696 format!("class {} has no field `{}`", c.def.name, key),
12697 line,
12698 )
12699 .into());
12700 }
12701 Err(PerlError::runtime("Can't use arrow deref on non-hash-ref", line).into())
12702 }
12703
12704 pub(crate) fn arrow_array_postfix(
12706 &mut self,
12707 container: PerlValue,
12708 idx: i64,
12709 decrement: bool,
12710 line: usize,
12711 ) -> Result<PerlValue, FlowOrError> {
12712 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
12713 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12714 self.assign_arrow_array_deref(container, idx, new_val, line)?;
12715 Ok(old)
12716 }
12717
12718 pub(crate) fn arrow_hash_postfix(
12720 &mut self,
12721 container: PerlValue,
12722 key: String,
12723 decrement: bool,
12724 line: usize,
12725 ) -> Result<PerlValue, FlowOrError> {
12726 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
12727 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12728 self.assign_arrow_hash_deref(container, key, new_val, line)?;
12729 Ok(old)
12730 }
12731
12732 pub(crate) fn resolve_bareword_rvalue(
12740 &mut self,
12741 name: &str,
12742 want: WantarrayCtx,
12743 line: usize,
12744 ) -> Result<PerlValue, FlowOrError> {
12745 if name == "__PACKAGE__" {
12746 return Ok(PerlValue::string(self.current_package()));
12747 }
12748 if let Some(sub) = self.resolve_sub_by_name(name) {
12749 return self.call_sub(&sub, vec![], want, line);
12750 }
12751 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
12753 return r.map_err(Into::into);
12754 }
12755 Ok(PerlValue::string(name.to_string()))
12756 }
12757
12758 pub(crate) fn arrow_array_slice_values(
12762 &mut self,
12763 container: PerlValue,
12764 indices: &[i64],
12765 line: usize,
12766 ) -> Result<PerlValue, FlowOrError> {
12767 let mut out = Vec::with_capacity(indices.len());
12768 for &idx in indices {
12769 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
12770 out.push(v);
12771 }
12772 Ok(PerlValue::array(out))
12773 }
12774
12775 pub(crate) fn assign_arrow_array_slice(
12779 &mut self,
12780 container: PerlValue,
12781 indices: Vec<i64>,
12782 val: PerlValue,
12783 line: usize,
12784 ) -> Result<PerlValue, FlowOrError> {
12785 if indices.is_empty() {
12786 return Err(PerlError::runtime("assign to empty array slice", line).into());
12787 }
12788 let vals = val.to_list();
12789 for (i, idx) in indices.iter().enumerate() {
12790 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
12791 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
12792 }
12793 Ok(PerlValue::UNDEF)
12794 }
12795
12796 pub(crate) fn flatten_array_slice_index_specs(
12798 &mut self,
12799 indices: &[Expr],
12800 ) -> Result<Vec<i64>, FlowOrError> {
12801 let mut out = Vec::new();
12802 for idx_expr in indices {
12803 let v = if matches!(idx_expr.kind, ExprKind::Range { .. }) {
12804 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
12805 } else {
12806 self.eval_expr(idx_expr)?
12807 };
12808 if let Some(list) = v.as_array_vec() {
12809 for idx in list {
12810 out.push(idx.to_int());
12811 }
12812 } else {
12813 out.push(v.to_int());
12814 }
12815 }
12816 Ok(out)
12817 }
12818
12819 pub(crate) fn assign_named_array_slice(
12821 &mut self,
12822 stash_array_name: &str,
12823 indices: Vec<i64>,
12824 val: PerlValue,
12825 line: usize,
12826 ) -> Result<PerlValue, FlowOrError> {
12827 if indices.is_empty() {
12828 return Err(PerlError::runtime("assign to empty array slice", line).into());
12829 }
12830 let vals = val.to_list();
12831 for (i, idx) in indices.iter().enumerate() {
12832 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
12833 self.scope
12834 .set_array_element(stash_array_name, *idx, v)
12835 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12836 }
12837 Ok(PerlValue::UNDEF)
12838 }
12839
12840 pub(crate) fn compound_assign_arrow_array_slice(
12843 &mut self,
12844 container: PerlValue,
12845 indices: Vec<i64>,
12846 op: BinOp,
12847 rhs: PerlValue,
12848 line: usize,
12849 ) -> Result<PerlValue, FlowOrError> {
12850 if indices.is_empty() {
12851 return Err(PerlError::runtime("assign to empty array slice", line).into());
12852 }
12853 let last_idx = *indices.last().expect("non-empty indices");
12854 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
12855 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
12856 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
12857 Ok(new_val)
12858 }
12859
12860 pub(crate) fn arrow_array_slice_inc_dec(
12865 &mut self,
12866 container: PerlValue,
12867 indices: Vec<i64>,
12868 kind: u8,
12869 line: usize,
12870 ) -> Result<PerlValue, FlowOrError> {
12871 if indices.is_empty() {
12872 return Err(
12873 PerlError::runtime("array slice increment needs at least one index", line).into(),
12874 );
12875 }
12876 let last_idx = *indices.last().expect("non-empty indices");
12877 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
12878 let new_val = if kind & 1 == 0 {
12879 PerlValue::integer(last_old.to_int() + 1)
12880 } else {
12881 PerlValue::integer(last_old.to_int() - 1)
12882 };
12883 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
12884 Ok(if kind < 2 { new_val } else { last_old })
12885 }
12886
12887 pub(crate) fn named_array_slice_inc_dec(
12890 &mut self,
12891 stash_array_name: &str,
12892 indices: Vec<i64>,
12893 kind: u8,
12894 line: usize,
12895 ) -> Result<PerlValue, FlowOrError> {
12896 let last_idx = *indices.last().ok_or_else(|| {
12897 PerlError::runtime("array slice increment needs at least one index", line)
12898 })?;
12899 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
12900 let new_val = if kind & 1 == 0 {
12901 PerlValue::integer(last_old.to_int() + 1)
12902 } else {
12903 PerlValue::integer(last_old.to_int() - 1)
12904 };
12905 self.scope
12906 .set_array_element(stash_array_name, last_idx, new_val.clone())
12907 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12908 Ok(if kind < 2 { new_val } else { last_old })
12909 }
12910
12911 pub(crate) fn compound_assign_named_array_slice(
12913 &mut self,
12914 stash_array_name: &str,
12915 indices: Vec<i64>,
12916 op: BinOp,
12917 rhs: PerlValue,
12918 line: usize,
12919 ) -> Result<PerlValue, FlowOrError> {
12920 if indices.is_empty() {
12921 return Err(PerlError::runtime("assign to empty array slice", line).into());
12922 }
12923 let last_idx = *indices.last().expect("non-empty indices");
12924 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
12925 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
12926 self.scope
12927 .set_array_element(stash_array_name, last_idx, new_val.clone())
12928 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12929 Ok(new_val)
12930 }
12931
12932 pub(crate) fn assign_arrow_array_deref(
12934 &mut self,
12935 container: PerlValue,
12936 idx: i64,
12937 val: PerlValue,
12938 line: usize,
12939 ) -> ExecResult {
12940 if let Some(a) = container.as_array_ref() {
12941 let mut arr = a.write();
12942 let i = if idx < 0 {
12943 (arr.len() as i64 + idx) as usize
12944 } else {
12945 idx as usize
12946 };
12947 if i >= arr.len() {
12948 arr.resize(i + 1, PerlValue::UNDEF);
12949 }
12950 arr[i] = val;
12951 return Ok(PerlValue::UNDEF);
12952 }
12953 if let Some(name) = container.as_array_binding_name() {
12954 self.scope
12955 .set_array_element(&name, idx, val)
12956 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12957 return Ok(PerlValue::UNDEF);
12958 }
12959 Err(PerlError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
12960 }
12961
12962 pub(crate) fn assign_typeglob_value(
12964 &mut self,
12965 name: &str,
12966 val: PerlValue,
12967 line: usize,
12968 ) -> ExecResult {
12969 let sub = if let Some(c) = val.as_code_ref() {
12970 Some(c)
12971 } else if let Some(r) = val.as_scalar_ref() {
12972 r.read().as_code_ref().map(|c| Arc::clone(&c))
12973 } else {
12974 None
12975 };
12976 if let Some(sub) = sub {
12977 let lhs_sub = self.qualify_typeglob_sub_key(name);
12978 self.subs.insert(lhs_sub, sub);
12979 return Ok(PerlValue::UNDEF);
12980 }
12981 Err(PerlError::runtime(
12982 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
12983 line,
12984 )
12985 .into())
12986 }
12987
12988 fn assign_value(&mut self, target: &Expr, val: PerlValue) -> ExecResult {
12989 match &target.kind {
12990 ExprKind::ScalarVar(name) => {
12991 let stor = self.tree_scalar_storage_name(name);
12992 if self.scope.is_scalar_frozen(&stor) {
12993 return Err(FlowOrError::Error(PerlError::runtime(
12994 format!("Modification of a frozen value: ${}", name),
12995 target.line,
12996 )));
12997 }
12998 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
12999 let class = obj
13000 .as_blessed_ref()
13001 .map(|b| b.class.clone())
13002 .unwrap_or_default();
13003 let full = format!("{}::STORE", class);
13004 if let Some(sub) = self.subs.get(&full).cloned() {
13005 let arg_vals = vec![obj, val];
13006 return match self.call_sub(
13007 &sub,
13008 arg_vals,
13009 WantarrayCtx::Scalar,
13010 target.line,
13011 ) {
13012 Ok(_) => Ok(PerlValue::UNDEF),
13013 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13014 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13015 };
13016 }
13017 }
13018 self.set_special_var(&stor, &val)
13019 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
13020 Ok(PerlValue::UNDEF)
13021 }
13022 ExprKind::ArrayVar(name) => {
13023 if self.scope.is_array_frozen(name) {
13024 return Err(PerlError::runtime(
13025 format!("Modification of a frozen value: @{}", name),
13026 target.line,
13027 )
13028 .into());
13029 }
13030 if self.strict_vars
13031 && !name.contains("::")
13032 && !self.scope.array_binding_exists(name)
13033 {
13034 return Err(PerlError::runtime(
13035 format!(
13036 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
13037 name, name
13038 ),
13039 target.line,
13040 )
13041 .into());
13042 }
13043 self.scope.set_array(name, val.to_list())?;
13044 Ok(PerlValue::UNDEF)
13045 }
13046 ExprKind::HashVar(name) => {
13047 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
13048 {
13049 return Err(PerlError::runtime(
13050 format!(
13051 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13052 name, name
13053 ),
13054 target.line,
13055 )
13056 .into());
13057 }
13058 let items = val.to_list();
13059 let mut map = IndexMap::new();
13060 let mut i = 0;
13061 while i + 1 < items.len() {
13062 map.insert(items[i].to_string(), items[i + 1].clone());
13063 i += 2;
13064 }
13065 self.scope.set_hash(name, map)?;
13066 Ok(PerlValue::UNDEF)
13067 }
13068 ExprKind::ArrayElement { array, index } => {
13069 if self.strict_vars
13070 && !array.contains("::")
13071 && !self.scope.array_binding_exists(array)
13072 {
13073 return Err(PerlError::runtime(
13074 format!(
13075 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
13076 array, array
13077 ),
13078 target.line,
13079 )
13080 .into());
13081 }
13082 if self.scope.is_array_frozen(array) {
13083 return Err(PerlError::runtime(
13084 format!("Modification of a frozen value: @{}", array),
13085 target.line,
13086 )
13087 .into());
13088 }
13089 let idx = self.eval_expr(index)?.to_int();
13090 let aname = self.stash_array_name_for_package(array);
13091 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
13092 let class = obj
13093 .as_blessed_ref()
13094 .map(|b| b.class.clone())
13095 .unwrap_or_default();
13096 let full = format!("{}::STORE", class);
13097 if let Some(sub) = self.subs.get(&full).cloned() {
13098 let arg_vals = vec![obj, PerlValue::integer(idx), val];
13099 return match self.call_sub(
13100 &sub,
13101 arg_vals,
13102 WantarrayCtx::Scalar,
13103 target.line,
13104 ) {
13105 Ok(_) => Ok(PerlValue::UNDEF),
13106 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13107 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13108 };
13109 }
13110 }
13111 self.scope.set_array_element(&aname, idx, val)?;
13112 Ok(PerlValue::UNDEF)
13113 }
13114 ExprKind::ArraySlice { array, indices } => {
13115 if indices.is_empty() {
13116 return Err(
13117 PerlError::runtime("assign to empty array slice", target.line).into(),
13118 );
13119 }
13120 self.check_strict_array_var(array, target.line)?;
13121 if self.scope.is_array_frozen(array) {
13122 return Err(PerlError::runtime(
13123 format!("Modification of a frozen value: @{}", array),
13124 target.line,
13125 )
13126 .into());
13127 }
13128 let aname = self.stash_array_name_for_package(array);
13129 let flat = self.flatten_array_slice_index_specs(indices)?;
13130 self.assign_named_array_slice(&aname, flat, val, target.line)
13131 }
13132 ExprKind::HashElement { hash, key } => {
13133 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
13134 {
13135 return Err(PerlError::runtime(
13136 format!(
13137 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13138 hash, hash
13139 ),
13140 target.line,
13141 )
13142 .into());
13143 }
13144 if self.scope.is_hash_frozen(hash) {
13145 return Err(PerlError::runtime(
13146 format!("Modification of a frozen value: %%{}", hash),
13147 target.line,
13148 )
13149 .into());
13150 }
13151 let k = self.eval_expr(key)?.to_string();
13152 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
13153 let class = obj
13154 .as_blessed_ref()
13155 .map(|b| b.class.clone())
13156 .unwrap_or_default();
13157 let full = format!("{}::STORE", class);
13158 if let Some(sub) = self.subs.get(&full).cloned() {
13159 let arg_vals = vec![obj, PerlValue::string(k), val];
13160 return match self.call_sub(
13161 &sub,
13162 arg_vals,
13163 WantarrayCtx::Scalar,
13164 target.line,
13165 ) {
13166 Ok(_) => Ok(PerlValue::UNDEF),
13167 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
13168 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
13169 };
13170 }
13171 }
13172 self.scope.set_hash_element(hash, &k, val)?;
13173 Ok(PerlValue::UNDEF)
13174 }
13175 ExprKind::HashSlice { hash, keys } => {
13176 if keys.is_empty() {
13177 return Err(
13178 PerlError::runtime("assign to empty hash slice", target.line).into(),
13179 );
13180 }
13181 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
13182 {
13183 return Err(PerlError::runtime(
13184 format!(
13185 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
13186 hash, hash
13187 ),
13188 target.line,
13189 )
13190 .into());
13191 }
13192 if self.scope.is_hash_frozen(hash) {
13193 return Err(PerlError::runtime(
13194 format!("Modification of a frozen value: %%{}", hash),
13195 target.line,
13196 )
13197 .into());
13198 }
13199 let mut key_vals = Vec::with_capacity(keys.len());
13200 for key_expr in keys {
13201 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
13202 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
13203 } else {
13204 self.eval_expr(key_expr)?
13205 };
13206 key_vals.push(v);
13207 }
13208 self.assign_named_hash_slice(hash, key_vals, val, target.line)
13209 }
13210 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
13211 ExprKind::TypeglobExpr(e) => {
13212 let name = self.eval_expr(e)?.to_string();
13213 let synthetic = Expr {
13214 kind: ExprKind::Typeglob(name),
13215 line: target.line,
13216 };
13217 self.assign_value(&synthetic, val)
13218 }
13219 ExprKind::AnonymousListSlice { source, indices } => {
13220 if let ExprKind::Deref {
13221 expr: inner,
13222 kind: Sigil::Array,
13223 } = &source.kind
13224 {
13225 let container = self.eval_arrow_array_base(inner, target.line)?;
13226 let vals = val.to_list();
13227 let n = indices.len().min(vals.len());
13228 for i in 0..n {
13229 let idx = self.eval_expr(&indices[i])?.to_int();
13230 self.assign_arrow_array_deref(
13231 container.clone(),
13232 idx,
13233 vals[i].clone(),
13234 target.line,
13235 )?;
13236 }
13237 return Ok(PerlValue::UNDEF);
13238 }
13239 Err(
13240 PerlError::runtime("assign to list slice: unsupported base", target.line)
13241 .into(),
13242 )
13243 }
13244 ExprKind::ArrowDeref {
13245 expr,
13246 index,
13247 kind: DerefKind::Hash,
13248 } => {
13249 let key = self.eval_expr(index)?.to_string();
13250 let container = self.eval_expr(expr)?;
13251 self.assign_arrow_hash_deref(container, key, val, target.line)
13252 }
13253 ExprKind::ArrowDeref {
13254 expr,
13255 index,
13256 kind: DerefKind::Array,
13257 } => {
13258 let container = self.eval_arrow_array_base(expr, target.line)?;
13259 if let ExprKind::List(indices) = &index.kind {
13260 let vals = val.to_list();
13261 let n = indices.len().min(vals.len());
13262 for i in 0..n {
13263 let idx = self.eval_expr(&indices[i])?.to_int();
13264 self.assign_arrow_array_deref(
13265 container.clone(),
13266 idx,
13267 vals[i].clone(),
13268 target.line,
13269 )?;
13270 }
13271 return Ok(PerlValue::UNDEF);
13272 }
13273 let idx = self.eval_expr(index)?.to_int();
13274 self.assign_arrow_array_deref(container, idx, val, target.line)
13275 }
13276 ExprKind::HashSliceDeref { container, keys } => {
13277 let href = self.eval_expr(container)?;
13278 let mut key_vals = Vec::with_capacity(keys.len());
13279 for key_expr in keys {
13280 key_vals.push(self.eval_expr(key_expr)?);
13281 }
13282 self.assign_hash_slice_deref(href, key_vals, val, target.line)
13283 }
13284 ExprKind::Deref {
13285 expr,
13286 kind: Sigil::Scalar,
13287 } => {
13288 let ref_val = self.eval_expr(expr)?;
13289 self.assign_scalar_ref_deref(ref_val, val, target.line)
13290 }
13291 ExprKind::Deref {
13292 expr,
13293 kind: Sigil::Array,
13294 } => {
13295 let ref_val = self.eval_expr(expr)?;
13296 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
13297 }
13298 ExprKind::Deref {
13299 expr,
13300 kind: Sigil::Hash,
13301 } => {
13302 let ref_val = self.eval_expr(expr)?;
13303 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
13304 }
13305 ExprKind::Deref {
13306 expr,
13307 kind: Sigil::Typeglob,
13308 } => {
13309 let ref_val = self.eval_expr(expr)?;
13310 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
13311 }
13312 ExprKind::Pos(inner) => {
13313 let key = match inner {
13314 None => "_".to_string(),
13315 Some(expr) => match &expr.kind {
13316 ExprKind::ScalarVar(n) => n.clone(),
13317 _ => self.eval_expr(expr)?.to_string(),
13318 },
13319 };
13320 if val.is_undef() {
13321 self.regex_pos.insert(key, None);
13322 } else {
13323 let u = val.to_int().max(0) as usize;
13324 self.regex_pos.insert(key, Some(u));
13325 }
13326 Ok(PerlValue::UNDEF)
13327 }
13328 ExprKind::List(targets) => {
13331 let items = val.to_list();
13332 for (i, t) in targets.iter().enumerate() {
13333 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
13334 self.assign_value(t, v)?;
13335 }
13336 Ok(PerlValue::UNDEF)
13337 }
13338 ExprKind::Assign { target, .. } => self.assign_value(target, val),
13341 _ => Ok(PerlValue::UNDEF),
13342 }
13343 }
13344
13345 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
13347 (name.starts_with('#') && name.len() > 1)
13348 || name.starts_with('^')
13349 || matches!(
13350 name,
13351 "$$" | "0"
13352 | "!"
13353 | "@"
13354 | "/"
13355 | "\\"
13356 | ","
13357 | "."
13358 | "]"
13359 | ";"
13360 | "ARGV"
13361 | "^I"
13362 | "^D"
13363 | "^P"
13364 | "^S"
13365 | "^W"
13366 | "^O"
13367 | "^T"
13368 | "^V"
13369 | "^E"
13370 | "^H"
13371 | "^WARNING_BITS"
13372 | "^GLOBAL_PHASE"
13373 | "^MATCH"
13374 | "^PREMATCH"
13375 | "^POSTMATCH"
13376 | "^LAST_SUBMATCH_RESULT"
13377 | "<"
13378 | ">"
13379 | "("
13380 | ")"
13381 | "?"
13382 | "|"
13383 | "\""
13384 | "+"
13385 | "%"
13386 | "="
13387 | "-"
13388 | ":"
13389 | "*"
13390 | "INC"
13391 )
13392 || crate::english::is_known_alias(name)
13393 }
13394
13395 #[inline]
13400 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
13401 if !self.english_enabled {
13402 return name;
13403 }
13404 if self
13405 .english_lexical_scalars
13406 .iter()
13407 .any(|s| s.contains(name))
13408 {
13409 return name;
13410 }
13411 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
13412 return short;
13413 }
13414 name
13415 }
13416
13417 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
13419 name.starts_with('^')
13420 || matches!(
13421 name,
13422 "0" | "/"
13423 | "\\"
13424 | ","
13425 | ";"
13426 | "\""
13427 | "%"
13428 | "="
13429 | "-"
13430 | ":"
13431 | "*"
13432 | "INC"
13433 | "^I"
13434 | "^D"
13435 | "^P"
13436 | "^W"
13437 | "^H"
13438 | "^WARNING_BITS"
13439 | "$$"
13440 | "]"
13441 | "^S"
13442 | "ARGV"
13443 | "|"
13444 | "+"
13445 | "?"
13446 | "!"
13447 | "@"
13448 | "."
13449 )
13450 || crate::english::is_known_alias(name)
13451 }
13452
13453 pub(crate) fn get_special_var(&self, name: &str) -> PerlValue {
13454 let name = if !crate::compat_mode() {
13456 match name {
13457 "NR" => ".",
13458 "RS" => "/",
13459 "OFS" => ",",
13460 "ORS" => "\\",
13461 "NF" => {
13462 let len = self.scope.array_len("F");
13463 return PerlValue::integer(len as i64);
13464 }
13465 _ => self.english_scalar_name(name),
13466 }
13467 } else {
13468 self.english_scalar_name(name)
13469 };
13470 match name {
13471 "$$" => PerlValue::integer(std::process::id() as i64),
13472 "_" => self.scope.get_scalar("_"),
13473 "^MATCH" => PerlValue::string(self.last_match.clone()),
13474 "^PREMATCH" => PerlValue::string(self.prematch.clone()),
13475 "^POSTMATCH" => PerlValue::string(self.postmatch.clone()),
13476 "^LAST_SUBMATCH_RESULT" => PerlValue::string(self.last_paren_match.clone()),
13477 "0" => PerlValue::string(self.program_name.clone()),
13478 "!" => PerlValue::errno_dual(self.errno_code, self.errno.clone()),
13479 "@" => {
13480 if let Some(ref v) = self.eval_error_value {
13481 v.clone()
13482 } else {
13483 PerlValue::errno_dual(self.eval_error_code, self.eval_error.clone())
13484 }
13485 }
13486 "/" => match &self.irs {
13487 Some(s) => PerlValue::string(s.clone()),
13488 None => PerlValue::UNDEF,
13489 },
13490 "\\" => PerlValue::string(self.ors.clone()),
13491 "," => PerlValue::string(self.ofs.clone()),
13492 "." => {
13493 if self.last_readline_handle.is_empty() {
13495 if self.line_number == 0 {
13496 PerlValue::UNDEF
13497 } else {
13498 PerlValue::integer(self.line_number)
13499 }
13500 } else {
13501 PerlValue::integer(
13502 *self
13503 .handle_line_numbers
13504 .get(&self.last_readline_handle)
13505 .unwrap_or(&0),
13506 )
13507 }
13508 }
13509 "]" => PerlValue::float(perl_bracket_version()),
13510 ";" => PerlValue::string(self.subscript_sep.clone()),
13511 "ARGV" => PerlValue::string(self.argv_current_file.clone()),
13512 "^I" => PerlValue::string(self.inplace_edit.clone()),
13513 "^D" => PerlValue::integer(self.debug_flags),
13514 "^P" => PerlValue::integer(self.perl_debug_flags),
13515 "^S" => PerlValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
13516 "^W" => PerlValue::integer(if self.warnings { 1 } else { 0 }),
13517 "^O" => PerlValue::string(perl_osname()),
13518 "^T" => PerlValue::integer(self.script_start_time),
13519 "^V" => PerlValue::string(perl_version_v_string()),
13520 "^E" => PerlValue::string(extended_os_error_string()),
13521 "^H" => PerlValue::integer(self.compile_hints),
13522 "^WARNING_BITS" => PerlValue::integer(self.warning_bits),
13523 "^GLOBAL_PHASE" => PerlValue::string(self.global_phase.clone()),
13524 "<" | ">" => PerlValue::integer(unix_id_for_special(name)),
13525 "(" | ")" => PerlValue::string(unix_group_list_for_special(name)),
13526 "?" => PerlValue::integer(self.child_exit_status),
13527 "|" => PerlValue::integer(if self.output_autoflush { 1 } else { 0 }),
13528 "\"" => PerlValue::string(self.list_separator.clone()),
13529 "+" => PerlValue::string(self.last_paren_match.clone()),
13530 "%" => PerlValue::integer(self.format_page_number),
13531 "=" => PerlValue::integer(self.format_lines_per_page),
13532 "-" => PerlValue::integer(self.format_lines_left),
13533 ":" => PerlValue::string(self.format_line_break_chars.clone()),
13534 "*" => PerlValue::integer(if self.multiline_match { 1 } else { 0 }),
13535 "^" => PerlValue::string(self.format_top_name.clone()),
13536 "INC" => PerlValue::integer(self.inc_hook_index),
13537 "^A" => PerlValue::string(self.accumulator_format.clone()),
13538 "^C" => PerlValue::integer(if self.sigint_pending_caret.replace(false) {
13539 1
13540 } else {
13541 0
13542 }),
13543 "^F" => PerlValue::integer(self.max_system_fd),
13544 "^L" => PerlValue::string(self.formfeed_string.clone()),
13545 "^M" => PerlValue::string(self.emergency_memory.clone()),
13546 "^N" => PerlValue::string(self.last_subpattern_name.clone()),
13547 "^X" => PerlValue::string(self.executable_path.clone()),
13548 "^TAINT" | "^TAINTED" => PerlValue::integer(0),
13550 "^UNICODE" => PerlValue::integer(if self.utf8_pragma { 1 } else { 0 }),
13551 "^OPEN" => PerlValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
13552 "^UTF8LOCALE" => PerlValue::integer(0),
13553 "^UTF8CACHE" => PerlValue::integer(-1),
13554 _ if name.starts_with('^') && name.len() > 1 => self
13555 .special_caret_scalars
13556 .get(name)
13557 .cloned()
13558 .unwrap_or(PerlValue::UNDEF),
13559 _ if name.starts_with('#') && name.len() > 1 => {
13560 let arr = &name[1..];
13561 let aname = self.stash_array_name_for_package(arr);
13562 let len = self.scope.array_len(&aname);
13563 PerlValue::integer(len as i64 - 1)
13564 }
13565 _ => self.scope.get_scalar(name),
13566 }
13567 }
13568
13569 pub(crate) fn set_special_var(&mut self, name: &str, val: &PerlValue) -> Result<(), PerlError> {
13570 let name = self.english_scalar_name(name);
13571 match name {
13572 "!" => {
13573 let code = val.to_int() as i32;
13574 self.errno_code = code;
13575 self.errno = if code == 0 {
13576 String::new()
13577 } else {
13578 std::io::Error::from_raw_os_error(code).to_string()
13579 };
13580 }
13581 "@" => {
13582 if let Some((code, msg)) = val.errno_dual_parts() {
13583 self.eval_error_code = code;
13584 self.eval_error = msg;
13585 } else {
13586 self.eval_error = val.to_string();
13587 let mut code = val.to_int() as i32;
13588 if code == 0 && !self.eval_error.is_empty() {
13589 code = 1;
13590 }
13591 self.eval_error_code = code;
13592 }
13593 }
13594 "." => {
13595 let n = val.to_int();
13598 if self.last_readline_handle.is_empty() {
13599 self.line_number = n;
13600 } else {
13601 self.handle_line_numbers
13602 .insert(self.last_readline_handle.clone(), n);
13603 }
13604 }
13605 "0" => self.program_name = val.to_string(),
13606 "/" => {
13607 self.irs = if val.is_undef() {
13608 None
13609 } else {
13610 Some(val.to_string())
13611 }
13612 }
13613 "\\" => self.ors = val.to_string(),
13614 "," => self.ofs = val.to_string(),
13615 ";" => self.subscript_sep = val.to_string(),
13616 "\"" => self.list_separator = val.to_string(),
13617 "%" => self.format_page_number = val.to_int(),
13618 "=" => self.format_lines_per_page = val.to_int(),
13619 "-" => self.format_lines_left = val.to_int(),
13620 ":" => self.format_line_break_chars = val.to_string(),
13621 "*" => self.multiline_match = val.to_int() != 0,
13622 "^" => self.format_top_name = val.to_string(),
13623 "INC" => self.inc_hook_index = val.to_int(),
13624 "^A" => self.accumulator_format = val.to_string(),
13625 "^F" => self.max_system_fd = val.to_int(),
13626 "^L" => self.formfeed_string = val.to_string(),
13627 "^M" => self.emergency_memory = val.to_string(),
13628 "^I" => self.inplace_edit = val.to_string(),
13629 "^D" => self.debug_flags = val.to_int(),
13630 "^P" => self.perl_debug_flags = val.to_int(),
13631 "^W" => self.warnings = val.to_int() != 0,
13632 "^H" => self.compile_hints = val.to_int(),
13633 "^WARNING_BITS" => self.warning_bits = val.to_int(),
13634 "|" => {
13635 self.output_autoflush = val.to_int() != 0;
13636 if self.output_autoflush {
13637 let _ = io::stdout().flush();
13638 }
13639 }
13640 "$$"
13642 | "]"
13643 | "^S"
13644 | "ARGV"
13645 | "?"
13646 | "^O"
13647 | "^T"
13648 | "^V"
13649 | "^E"
13650 | "^GLOBAL_PHASE"
13651 | "^MATCH"
13652 | "^PREMATCH"
13653 | "^POSTMATCH"
13654 | "^LAST_SUBMATCH_RESULT"
13655 | "^C"
13656 | "^N"
13657 | "^X"
13658 | "^TAINT"
13659 | "^TAINTED"
13660 | "^UNICODE"
13661 | "^UTF8LOCALE"
13662 | "^UTF8CACHE"
13663 | "+"
13664 | "<"
13665 | ">"
13666 | "("
13667 | ")" => {}
13668 _ if name.starts_with('^') && name.len() > 1 => {
13669 self.special_caret_scalars
13670 .insert(name.to_string(), val.clone());
13671 }
13672 _ => self.scope.set_scalar(name, val.clone())?,
13673 }
13674 Ok(())
13675 }
13676
13677 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
13678 match &expr.kind {
13679 ExprKind::ArrayVar(name) => Ok(name.clone()),
13680 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(PerlError::runtime("Expected array", expr.line).into()),
13682 }
13683 }
13684
13685 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
13687 match &expr.kind {
13688 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
13689 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
13690 _ => expr,
13691 }
13692 }
13693
13694 fn try_eval_array_deref_container(
13696 &mut self,
13697 expr: &Expr,
13698 ) -> Result<Option<PerlValue>, FlowOrError> {
13699 let e = Self::peel_array_builtin_operand(expr);
13700 if let ExprKind::Deref {
13701 expr: inner,
13702 kind: Sigil::Array,
13703 } = &e.kind
13704 {
13705 return Ok(Some(self.eval_expr(inner)?));
13706 }
13707 Ok(None)
13708 }
13709
13710 fn current_package(&self) -> String {
13712 let s = self.scope.get_scalar("__PACKAGE__").to_string();
13713 if s.is_empty() {
13714 "main".to_string()
13715 } else {
13716 s
13717 }
13718 }
13719
13720 pub(crate) fn package_version_scalar(
13723 &mut self,
13724 package: &str,
13725 ) -> PerlResult<Option<PerlValue>> {
13726 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
13727 let _ = self
13728 .scope
13729 .set_scalar("__PACKAGE__", PerlValue::string(package.to_string()));
13730 let ver = self.get_special_var("VERSION");
13731 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
13732 Ok(if ver.is_undef() { None } else { Some(ver) })
13733 }
13734
13735 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<PerlSub>> {
13737 let root = if start_package.is_empty() {
13738 "main"
13739 } else {
13740 start_package
13741 };
13742 for pkg in self.mro_linearize(root) {
13743 let key = if pkg == "main" {
13744 "AUTOLOAD".to_string()
13745 } else {
13746 format!("{}::AUTOLOAD", pkg)
13747 };
13748 if let Some(s) = self.subs.get(&key) {
13749 return Some(s.clone());
13750 }
13751 }
13752 None
13753 }
13754
13755 pub(crate) fn try_autoload_call(
13760 &mut self,
13761 missing_name: &str,
13762 args: Vec<PerlValue>,
13763 line: usize,
13764 want: WantarrayCtx,
13765 method_invocant_class: Option<&str>,
13766 ) -> Option<ExecResult> {
13767 let pkg = self.current_package();
13768 let full = if missing_name.contains("::") {
13769 missing_name.to_string()
13770 } else {
13771 format!("{}::{}", pkg, missing_name)
13772 };
13773 let start_pkg = method_invocant_class.unwrap_or_else(|| {
13774 full.rsplit_once("::")
13775 .map(|(p, _)| p)
13776 .filter(|p| !p.is_empty())
13777 .unwrap_or("main")
13778 });
13779 let sub = self.resolve_autoload_sub(start_pkg)?;
13780 if let Err(e) = self
13781 .scope
13782 .set_scalar("AUTOLOAD", PerlValue::string(full.clone()))
13783 {
13784 return Some(Err(e.into()));
13785 }
13786 Some(self.call_sub(&sub, args, want, line))
13787 }
13788
13789 pub(crate) fn with_topic_default_args(&self, args: Vec<PerlValue>) -> Vec<PerlValue> {
13790 if args.is_empty() {
13791 vec![self.scope.get_scalar("_").clone()]
13792 } else {
13793 args
13794 }
13795 }
13796
13797 pub(crate) fn dispatch_indirect_call(
13800 &mut self,
13801 target: PerlValue,
13802 arg_vals: Vec<PerlValue>,
13803 want: WantarrayCtx,
13804 line: usize,
13805 ) -> ExecResult {
13806 if let Some(sub) = target.as_code_ref() {
13807 return self.call_sub(&sub, arg_vals, want, line);
13808 }
13809 if let Some(name) = target.as_str() {
13810 return self.call_named_sub(&name, arg_vals, line, want);
13811 }
13812 Err(PerlError::runtime("Can't use non-code reference as a subroutine", line).into())
13813 }
13814
13815 pub(crate) fn call_bare_list_util(
13821 &mut self,
13822 name: &str,
13823 args: Vec<PerlValue>,
13824 line: usize,
13825 want: WantarrayCtx,
13826 ) -> ExecResult {
13827 crate::list_util::ensure_list_util(self);
13828 let fq = match name {
13829 "uniq" | "distinct" | "uq" => "List::Util::uniq",
13830 "uniqstr" => "List::Util::uniqstr",
13831 "uniqint" => "List::Util::uniqint",
13832 "uniqnum" => "List::Util::uniqnum",
13833 "shuffle" | "shuf" => "List::Util::shuffle",
13834 "sample" => "List::Util::sample",
13835 "chunked" | "chk" => "List::Util::chunked",
13836 "windowed" | "win" => "List::Util::windowed",
13837 "zip" | "zp" => "List::Util::zip",
13838 "zip_longest" => "List::Util::zip_longest",
13839 "zip_shortest" => "List::Util::zip_shortest",
13840 "mesh" => "List::Util::mesh",
13841 "mesh_longest" => "List::Util::mesh_longest",
13842 "mesh_shortest" => "List::Util::mesh_shortest",
13843 "any" => "List::Util::any",
13844 "all" => "List::Util::all",
13845 "none" => "List::Util::none",
13846 "notall" => "List::Util::notall",
13847 "first" | "fst" => "List::Util::first",
13848 "reduce" | "rd" => "List::Util::reduce",
13849 "reductions" => "List::Util::reductions",
13850 "sum" => "List::Util::sum",
13851 "sum0" => "List::Util::sum0",
13852 "product" => "List::Util::product",
13853 "min" => "List::Util::min",
13854 "max" => "List::Util::max",
13855 "minstr" => "List::Util::minstr",
13856 "maxstr" => "List::Util::maxstr",
13857 "mean" => "List::Util::mean",
13858 "median" | "med" => "List::Util::median",
13859 "mode" => "List::Util::mode",
13860 "stddev" | "std" => "List::Util::stddev",
13861 "variance" | "var" => "List::Util::variance",
13862 "pairs" => "List::Util::pairs",
13863 "unpairs" => "List::Util::unpairs",
13864 "pairkeys" => "List::Util::pairkeys",
13865 "pairvalues" => "List::Util::pairvalues",
13866 "pairgrep" => "List::Util::pairgrep",
13867 "pairmap" => "List::Util::pairmap",
13868 "pairfirst" => "List::Util::pairfirst",
13869 _ => {
13870 return Err(PerlError::runtime(
13871 format!("internal: not a bare list-util alias: {name}"),
13872 line,
13873 )
13874 .into());
13875 }
13876 };
13877 let Some(sub) = self.subs.get(fq).cloned() else {
13878 return Err(PerlError::runtime(
13879 format!("internal: missing native stub for {fq}"),
13880 line,
13881 )
13882 .into());
13883 };
13884 let args = self.with_topic_default_args(args);
13885 self.call_sub(&sub, args, want, line)
13886 }
13887
13888 fn call_named_sub(
13889 &mut self,
13890 name: &str,
13891 args: Vec<PerlValue>,
13892 line: usize,
13893 want: WantarrayCtx,
13894 ) -> ExecResult {
13895 if let Some(sub) = self.resolve_sub_by_name(name) {
13896 let args = self.with_topic_default_args(args);
13897 return self.call_sub(&sub, args, want, line);
13898 }
13899 match name {
13900 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
13901 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
13902 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
13903 | "none" | "notall" | "first" | "fst" | "reduce" | "rd" | "reductions" | "sum"
13904 | "sum0" | "product" | "min" | "max" | "minstr" | "maxstr" | "mean" | "median"
13905 | "med" | "mode" | "stddev" | "std" | "variance" | "var" | "pairs" | "unpairs"
13906 | "pairkeys" | "pairvalues" | "pairgrep" | "pairmap" | "pairfirst" => {
13907 self.call_bare_list_util(name, args, line, want)
13908 }
13909 "deque" => {
13910 if !args.is_empty() {
13911 return Err(PerlError::runtime("deque() takes no arguments", line).into());
13912 }
13913 Ok(PerlValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
13914 }
13915 "defer__internal" => {
13916 if args.len() != 1 {
13917 return Err(PerlError::runtime(
13918 "defer__internal expects one coderef argument",
13919 line,
13920 )
13921 .into());
13922 }
13923 self.scope.push_defer(args[0].clone());
13924 Ok(PerlValue::UNDEF)
13925 }
13926 "heap" => {
13927 if args.len() != 1 {
13928 return Err(
13929 PerlError::runtime("heap() expects one comparator sub", line).into(),
13930 );
13931 }
13932 if let Some(sub) = args[0].as_code_ref() {
13933 Ok(PerlValue::heap(Arc::new(Mutex::new(PerlHeap {
13934 items: Vec::new(),
13935 cmp: Arc::clone(&sub),
13936 }))))
13937 } else {
13938 Err(PerlError::runtime("heap() requires a code reference", line).into())
13939 }
13940 }
13941 "pipeline" => {
13942 let mut items = Vec::new();
13943 for v in args {
13944 if let Some(a) = v.as_array_vec() {
13945 items.extend(a);
13946 } else {
13947 items.push(v);
13948 }
13949 }
13950 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
13951 source: items,
13952 ops: Vec::new(),
13953 has_scalar_terminal: false,
13954 par_stream: false,
13955 streaming: false,
13956 streaming_workers: 0,
13957 streaming_buffer: 256,
13958 }))))
13959 }
13960 "par_pipeline" => {
13961 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
13962 return crate::par_pipeline::run_par_pipeline(self, &args, line)
13963 .map_err(Into::into);
13964 }
13965 Ok(self.builtin_par_pipeline_stream(&args, line)?)
13966 }
13967 "par_pipeline_stream" => {
13968 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
13969 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
13970 .map_err(Into::into);
13971 }
13972 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
13973 }
13974 "ppool" => {
13975 if args.len() != 1 {
13976 return Err(PerlError::runtime(
13977 "ppool() expects one argument (worker count)",
13978 line,
13979 )
13980 .into());
13981 }
13982 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
13983 }
13984 "barrier" => {
13985 if args.len() != 1 {
13986 return Err(PerlError::runtime(
13987 "barrier() expects one argument (party count)",
13988 line,
13989 )
13990 .into());
13991 }
13992 let n = args[0].to_int().max(1) as usize;
13993 Ok(PerlValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
13994 }
13995 "cluster" => {
13996 let items = if args.len() == 1 {
13997 args[0].to_list()
13998 } else {
13999 args.to_vec()
14000 };
14001 let c = RemoteCluster::from_list_args(&items)
14002 .map_err(|msg| PerlError::runtime(msg, line))?;
14003 Ok(PerlValue::remote_cluster(Arc::new(c)))
14004 }
14005 _ => {
14006 if let Some(method_name) = name.strip_prefix("static::") {
14008 let self_val = self.scope.get_scalar("self");
14009 if let Some(c) = self_val.as_class_inst() {
14010 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
14011 if let Some(ref body) = m.body {
14012 let params = m.params.clone();
14013 let mut call_args = vec![self_val.clone()];
14014 call_args.extend(args);
14015 return match self.call_class_method(body, ¶ms, call_args, line)
14016 {
14017 Ok(v) => Ok(v),
14018 Err(FlowOrError::Error(e)) => Err(e.into()),
14019 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14020 Err(e) => Err(e),
14021 };
14022 }
14023 }
14024 return Err(PerlError::runtime(
14025 format!(
14026 "static::{} — method not found on class {}",
14027 method_name, c.def.name
14028 ),
14029 line,
14030 )
14031 .into());
14032 }
14033 return Err(PerlError::runtime(
14034 "static:: can only be used inside a class method",
14035 line,
14036 )
14037 .into());
14038 }
14039 if let Some(def) = self.struct_defs.get(name).cloned() {
14041 return self.struct_construct(&def, args, line);
14042 }
14043 if let Some(def) = self.class_defs.get(name).cloned() {
14045 return self.class_construct(&def, args, line);
14046 }
14047 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
14049 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
14050 return self.enum_construct(&def, variant_name, args, line);
14051 }
14052 }
14053 if let Some((class_name, member_name)) = name.rsplit_once("::") {
14055 if let Some(def) = self.class_defs.get(class_name).cloned() {
14056 if let Some(m) = def.method(member_name) {
14058 if m.is_static {
14059 if let Some(ref body) = m.body {
14060 let params = m.params.clone();
14061 return match self.call_static_class_method(
14062 body,
14063 ¶ms,
14064 args.clone(),
14065 line,
14066 ) {
14067 Ok(v) => Ok(v),
14068 Err(FlowOrError::Error(e)) => Err(e.into()),
14069 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14070 Err(e) => Err(e),
14071 };
14072 }
14073 }
14074 }
14075 if def.static_fields.iter().any(|sf| sf.name == member_name) {
14077 let key = format!("{}::{}", class_name, member_name);
14078 match args.len() {
14079 0 => {
14080 let val = self.scope.get_scalar(&key);
14081 return Ok(val);
14082 }
14083 1 => {
14084 let _ = self.scope.set_scalar(&key, args[0].clone());
14085 return Ok(args[0].clone());
14086 }
14087 _ => {
14088 return Err(PerlError::runtime(
14089 format!(
14090 "static field `{}::{}` takes 0 or 1 arguments",
14091 class_name, member_name
14092 ),
14093 line,
14094 )
14095 .into());
14096 }
14097 }
14098 }
14099 }
14100 }
14101 let args = self.with_topic_default_args(args);
14102 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
14103 return r;
14104 }
14105 Err(PerlError::runtime(self.undefined_subroutine_call_message(name), line).into())
14106 }
14107 }
14108 }
14109
14110 pub(crate) fn struct_construct(
14112 &mut self,
14113 def: &Arc<StructDef>,
14114 args: Vec<PerlValue>,
14115 line: usize,
14116 ) -> ExecResult {
14117 let is_named = args.len() >= 2
14120 && args.len().is_multiple_of(2)
14121 && args.iter().step_by(2).all(|v| {
14122 let s = v.to_string();
14123 def.field_index(&s).is_some()
14124 });
14125
14126 let provided = if is_named {
14127 let mut pairs = Vec::new();
14129 let mut i = 0;
14130 while i + 1 < args.len() {
14131 let k = args[i].to_string();
14132 let v = args[i + 1].clone();
14133 pairs.push((k, v));
14134 i += 2;
14135 }
14136 pairs
14137 } else {
14138 def.fields
14140 .iter()
14141 .zip(args.iter())
14142 .map(|(f, v)| (f.name.clone(), v.clone()))
14143 .collect()
14144 };
14145
14146 let mut defaults = Vec::with_capacity(def.fields.len());
14148 for field in &def.fields {
14149 if let Some(ref expr) = field.default {
14150 let val = self.eval_expr(expr)?;
14151 defaults.push(Some(val));
14152 } else {
14153 defaults.push(None);
14154 }
14155 }
14156
14157 Ok(crate::native_data::struct_new_with_defaults(
14158 def, &provided, &defaults, line,
14159 )?)
14160 }
14161
14162 pub(crate) fn class_construct(
14164 &mut self,
14165 def: &Arc<ClassDef>,
14166 args: Vec<PerlValue>,
14167 _line: usize,
14168 ) -> ExecResult {
14169 use crate::value::ClassInstance;
14170
14171 if def.is_abstract {
14173 return Err(PerlError::runtime(
14174 format!("cannot instantiate abstract class `{}`", def.name),
14175 _line,
14176 )
14177 .into());
14178 }
14179
14180 let all_fields = self.collect_class_fields(def);
14182
14183 let is_named = args.len() >= 2
14185 && args.len().is_multiple_of(2)
14186 && args.iter().step_by(2).all(|v| {
14187 let s = v.to_string();
14188 all_fields.iter().any(|(name, _, _)| name == &s)
14189 });
14190
14191 let provided: Vec<(String, PerlValue)> = if is_named {
14192 let mut pairs = Vec::new();
14193 let mut i = 0;
14194 while i + 1 < args.len() {
14195 let k = args[i].to_string();
14196 let v = args[i + 1].clone();
14197 pairs.push((k, v));
14198 i += 2;
14199 }
14200 pairs
14201 } else {
14202 all_fields
14203 .iter()
14204 .zip(args.iter())
14205 .map(|((name, _, _), v)| (name.clone(), v.clone()))
14206 .collect()
14207 };
14208
14209 let mut values = Vec::with_capacity(all_fields.len());
14211 for (name, default, ty) in &all_fields {
14212 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
14213 val.clone()
14214 } else if let Some(ref expr) = default {
14215 self.eval_expr(expr)?
14216 } else {
14217 PerlValue::UNDEF
14218 };
14219 ty.check_value(&val).map_err(|msg| {
14220 PerlError::type_error(
14221 format!("class {} field `{}`: {}", def.name, name, msg),
14222 _line,
14223 )
14224 })?;
14225 values.push(val);
14226 }
14227
14228 let instance = PerlValue::class_inst(Arc::new(ClassInstance::new(Arc::clone(def), values)));
14229
14230 let build_chain = self.collect_build_chain(def);
14232 if !build_chain.is_empty() {
14233 for (body, params) in &build_chain {
14234 let call_args = vec![instance.clone()];
14235 match self.call_class_method(body, params, call_args, _line) {
14236 Ok(_) => {}
14237 Err(FlowOrError::Flow(Flow::Return(_))) => {}
14238 Err(e) => return Err(e),
14239 }
14240 }
14241 }
14242
14243 Ok(instance)
14244 }
14245
14246 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14248 let mut chain = Vec::new();
14249 for parent_name in &def.extends {
14251 if let Some(parent_def) = self.class_defs.get(parent_name) {
14252 chain.extend(self.collect_build_chain(parent_def));
14253 }
14254 }
14255 if let Some(m) = def.method("BUILD") {
14257 if let Some(ref body) = m.body {
14258 chain.push((body.clone(), m.params.clone()));
14259 }
14260 }
14261 chain
14262 }
14263
14264 fn collect_class_fields(
14267 &self,
14268 def: &ClassDef,
14269 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
14270 self.collect_class_fields_full(def)
14271 .into_iter()
14272 .map(|(name, default, ty, _, _)| (name, default, ty))
14273 .collect()
14274 }
14275
14276 fn collect_class_fields_full(
14278 &self,
14279 def: &ClassDef,
14280 ) -> Vec<(
14281 String,
14282 Option<Expr>,
14283 crate::ast::PerlTypeName,
14284 crate::ast::Visibility,
14285 String,
14286 )> {
14287 let mut all_fields = Vec::new();
14288
14289 for parent_name in &def.extends {
14290 if let Some(parent_def) = self.class_defs.get(parent_name) {
14291 let parent_fields = self.collect_class_fields_full(parent_def);
14292 all_fields.extend(parent_fields);
14293 }
14294 }
14295
14296 for field in &def.fields {
14297 all_fields.push((
14298 field.name.clone(),
14299 field.default.clone(),
14300 field.ty.clone(),
14301 field.visibility,
14302 def.name.clone(),
14303 ));
14304 }
14305
14306 all_fields
14307 }
14308
14309 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
14311 for parent_name in &def.extends {
14313 if let Some(parent_def) = self.class_defs.get(parent_name) {
14314 self.collect_class_method_names(parent_def, names);
14315 }
14316 }
14317 for m in &def.methods {
14319 if !m.is_static && !names.contains(&m.name) {
14320 names.push(m.name.clone());
14321 }
14322 }
14323 }
14324
14325 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14327 let mut chain = Vec::new();
14328 if let Some(m) = def.method("DESTROY") {
14330 if let Some(ref body) = m.body {
14331 chain.push((body.clone(), m.params.clone()));
14332 }
14333 }
14334 for parent_name in &def.extends {
14336 if let Some(parent_def) = self.class_defs.get(parent_name) {
14337 chain.extend(self.collect_destroy_chain(parent_def));
14338 }
14339 }
14340 chain
14341 }
14342
14343 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
14345 if let Some(def) = self.class_defs.get(child) {
14346 for parent in &def.extends {
14347 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
14348 return true;
14349 }
14350 }
14351 }
14352 false
14353 }
14354
14355 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
14357 if let Some(m) = def.method(method) {
14359 return Some((m.clone(), def.name.clone()));
14360 }
14361 for parent_name in &def.extends {
14363 if let Some(parent_def) = self.class_defs.get(parent_name) {
14364 if let Some(result) = self.find_class_method(parent_def, method) {
14365 return Some(result);
14366 }
14367 }
14368 }
14369 None
14370 }
14371
14372 pub(crate) fn enum_construct(
14374 &mut self,
14375 def: &Arc<EnumDef>,
14376 variant_name: &str,
14377 args: Vec<PerlValue>,
14378 line: usize,
14379 ) -> ExecResult {
14380 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
14381 FlowOrError::Error(PerlError::runtime(
14382 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
14383 line,
14384 ))
14385 })?;
14386 let variant = &def.variants[variant_idx];
14387 let data = if variant.ty.is_some() {
14388 if args.is_empty() {
14389 return Err(PerlError::runtime(
14390 format!(
14391 "enum variant `{}::{}` requires data",
14392 def.name, variant_name
14393 ),
14394 line,
14395 )
14396 .into());
14397 }
14398 if args.len() == 1 {
14399 args.into_iter().next().unwrap()
14400 } else {
14401 PerlValue::array(args)
14402 }
14403 } else {
14404 if !args.is_empty() {
14405 return Err(PerlError::runtime(
14406 format!(
14407 "enum variant `{}::{}` does not take data",
14408 def.name, variant_name
14409 ),
14410 line,
14411 )
14412 .into());
14413 }
14414 PerlValue::UNDEF
14415 };
14416 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
14417 Ok(PerlValue::enum_inst(Arc::new(inst)))
14418 }
14419
14420 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
14422 matches!(name, "STDIN" | "STDOUT" | "STDERR")
14423 || self.input_handles.contains_key(name)
14424 || self.output_handles.contains_key(name)
14425 || self.io_file_slots.contains_key(name)
14426 || self.pipe_children.contains_key(name)
14427 }
14428
14429 pub(crate) fn io_handle_method(
14431 &mut self,
14432 name: &str,
14433 method: &str,
14434 args: &[PerlValue],
14435 line: usize,
14436 ) -> PerlResult<PerlValue> {
14437 match method {
14438 "print" => self.io_handle_print(name, args, false, line),
14439 "say" => self.io_handle_print(name, args, true, line),
14440 "printf" => self.io_handle_printf(name, args, line),
14441 "getline" | "readline" => {
14442 if !args.is_empty() {
14443 return Err(PerlError::runtime(
14444 format!("{}: too many arguments", method),
14445 line,
14446 ));
14447 }
14448 self.readline_builtin_execute(Some(name))
14449 }
14450 "close" => {
14451 if !args.is_empty() {
14452 return Err(PerlError::runtime("close: too many arguments", line));
14453 }
14454 self.close_builtin_execute(name.to_string())
14455 }
14456 "eof" => {
14457 if !args.is_empty() {
14458 return Err(PerlError::runtime("eof: too many arguments", line));
14459 }
14460 let at_eof = !self.has_input_handle(name);
14461 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
14462 }
14463 "getc" => {
14464 if !args.is_empty() {
14465 return Err(PerlError::runtime("getc: too many arguments", line));
14466 }
14467 match crate::builtins::try_builtin(
14468 self,
14469 "getc",
14470 &[PerlValue::string(name.to_string())],
14471 line,
14472 ) {
14473 Some(r) => r,
14474 None => Err(PerlError::runtime("getc: not available", line)),
14475 }
14476 }
14477 "binmode" => match crate::builtins::try_builtin(
14478 self,
14479 "binmode",
14480 &[PerlValue::string(name.to_string())],
14481 line,
14482 ) {
14483 Some(r) => r,
14484 None => Err(PerlError::runtime("binmode: not available", line)),
14485 },
14486 "fileno" => match crate::builtins::try_builtin(
14487 self,
14488 "fileno",
14489 &[PerlValue::string(name.to_string())],
14490 line,
14491 ) {
14492 Some(r) => r,
14493 None => Err(PerlError::runtime("fileno: not available", line)),
14494 },
14495 "flush" => {
14496 if !args.is_empty() {
14497 return Err(PerlError::runtime("flush: too many arguments", line));
14498 }
14499 self.io_handle_flush(name, line)
14500 }
14501 _ => Err(PerlError::runtime(
14502 format!("Unknown method for filehandle: {}", method),
14503 line,
14504 )),
14505 }
14506 }
14507
14508 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> PerlResult<PerlValue> {
14509 match handle_name {
14510 "STDOUT" => {
14511 let _ = IoWrite::flush(&mut io::stdout());
14512 }
14513 "STDERR" => {
14514 let _ = IoWrite::flush(&mut io::stderr());
14515 }
14516 name => {
14517 if let Some(writer) = self.output_handles.get_mut(name) {
14518 let _ = IoWrite::flush(&mut *writer);
14519 } else {
14520 return Err(PerlError::runtime(
14521 format!("flush on unopened filehandle {}", name),
14522 line,
14523 ));
14524 }
14525 }
14526 }
14527 Ok(PerlValue::integer(1))
14528 }
14529
14530 fn io_handle_print(
14531 &mut self,
14532 handle_name: &str,
14533 args: &[PerlValue],
14534 newline: bool,
14535 line: usize,
14536 ) -> PerlResult<PerlValue> {
14537 if newline && (self.feature_bits & FEAT_SAY) == 0 {
14538 return Err(PerlError::runtime(
14539 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
14540 line,
14541 ));
14542 }
14543 let mut output = String::new();
14544 if args.is_empty() {
14545 output.push_str(&self.scope.get_scalar("_").to_string());
14547 } else {
14548 for (i, val) in args.iter().enumerate() {
14549 if i > 0 && !self.ofs.is_empty() {
14550 output.push_str(&self.ofs);
14551 }
14552 output.push_str(&val.to_string());
14553 }
14554 }
14555 if newline {
14556 output.push('\n');
14557 }
14558 output.push_str(&self.ors);
14559
14560 self.write_formatted_print(handle_name, &output, line)?;
14561 Ok(PerlValue::integer(1))
14562 }
14563
14564 pub(crate) fn write_formatted_print(
14567 &mut self,
14568 handle_name: &str,
14569 output: &str,
14570 line: usize,
14571 ) -> PerlResult<()> {
14572 match handle_name {
14573 "STDOUT" => {
14574 if !self.suppress_stdout {
14575 print!("{}", output);
14576 if self.output_autoflush {
14577 let _ = io::stdout().flush();
14578 }
14579 }
14580 }
14581 "STDERR" => {
14582 eprint!("{}", output);
14583 let _ = io::stderr().flush();
14584 }
14585 name => {
14586 if let Some(writer) = self.output_handles.get_mut(name) {
14587 let _ = writer.write_all(output.as_bytes());
14588 if self.output_autoflush {
14589 let _ = writer.flush();
14590 }
14591 } else {
14592 return Err(PerlError::runtime(
14593 format!("print on unopened filehandle {}", name),
14594 line,
14595 ));
14596 }
14597 }
14598 }
14599 Ok(())
14600 }
14601
14602 fn io_handle_printf(
14603 &mut self,
14604 handle_name: &str,
14605 args: &[PerlValue],
14606 line: usize,
14607 ) -> PerlResult<PerlValue> {
14608 let (fmt, rest): (String, &[PerlValue]) = if args.is_empty() {
14609 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
14610 Ok(s) => s,
14611 Err(FlowOrError::Error(e)) => return Err(e),
14612 Err(FlowOrError::Flow(_)) => {
14613 return Err(PerlError::runtime(
14614 "printf: unexpected control flow in sprintf",
14615 line,
14616 ));
14617 }
14618 };
14619 (s, &[])
14620 } else {
14621 (args[0].to_string(), &args[1..])
14622 };
14623 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
14624 Ok(s) => s,
14625 Err(FlowOrError::Error(e)) => return Err(e),
14626 Err(FlowOrError::Flow(_)) => {
14627 return Err(PerlError::runtime(
14628 "printf: unexpected control flow in sprintf",
14629 line,
14630 ));
14631 }
14632 };
14633 match handle_name {
14634 "STDOUT" => {
14635 if !self.suppress_stdout {
14636 print!("{}", output);
14637 if self.output_autoflush {
14638 let _ = IoWrite::flush(&mut io::stdout());
14639 }
14640 }
14641 }
14642 "STDERR" => {
14643 eprint!("{}", output);
14644 let _ = IoWrite::flush(&mut io::stderr());
14645 }
14646 name => {
14647 if let Some(writer) = self.output_handles.get_mut(name) {
14648 let _ = writer.write_all(output.as_bytes());
14649 if self.output_autoflush {
14650 let _ = writer.flush();
14651 }
14652 } else {
14653 return Err(PerlError::runtime(
14654 format!("printf on unopened filehandle {}", name),
14655 line,
14656 ));
14657 }
14658 }
14659 }
14660 Ok(PerlValue::integer(1))
14661 }
14662
14663 pub(crate) fn try_native_method(
14665 &mut self,
14666 receiver: &PerlValue,
14667 method: &str,
14668 args: &[PerlValue],
14669 line: usize,
14670 ) -> Option<PerlResult<PerlValue>> {
14671 if let Some(name) = receiver.as_io_handle_name() {
14672 return Some(self.io_handle_method(&name, method, args, line));
14673 }
14674 if let Some(ref s) = receiver.as_str() {
14675 if self.is_bound_handle(s) {
14676 return Some(self.io_handle_method(s, method, args, line));
14677 }
14678 }
14679 if let Some(c) = receiver.as_sqlite_conn() {
14680 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
14681 }
14682 if let Some(s) = receiver.as_struct_inst() {
14683 if let Some(idx) = s.def.field_index(method) {
14685 match args.len() {
14686 0 => {
14687 return Some(Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF)));
14688 }
14689 1 => {
14690 let field = &s.def.fields[idx];
14691 let new_val = args[0].clone();
14692 if let Err(msg) = field.ty.check_value(&new_val) {
14693 return Some(Err(PerlError::type_error(
14694 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
14695 line,
14696 )));
14697 }
14698 s.set_field(idx, new_val.clone());
14699 return Some(Ok(new_val));
14700 }
14701 _ => {
14702 return Some(Err(PerlError::runtime(
14703 format!(
14704 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
14705 method,
14706 args.len()
14707 ),
14708 line,
14709 )));
14710 }
14711 }
14712 }
14713 match method {
14715 "with" => {
14716 let mut new_values = s.get_values();
14718 let mut i = 0;
14719 while i + 1 < args.len() {
14720 let k = args[i].to_string();
14721 let v = args[i + 1].clone();
14722 if let Some(idx) = s.def.field_index(&k) {
14723 let field = &s.def.fields[idx];
14724 if let Err(msg) = field.ty.check_value(&v) {
14725 return Some(Err(PerlError::type_error(
14726 format!(
14727 "struct {} field `{}`: {}",
14728 s.def.name, field.name, msg
14729 ),
14730 line,
14731 )));
14732 }
14733 new_values[idx] = v;
14734 } else {
14735 return Some(Err(PerlError::runtime(
14736 format!("struct {}: unknown field `{}`", s.def.name, k),
14737 line,
14738 )));
14739 }
14740 i += 2;
14741 }
14742 return Some(Ok(PerlValue::struct_inst(Arc::new(
14743 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
14744 ))));
14745 }
14746 "to_hash" => {
14747 if !args.is_empty() {
14749 return Some(Err(PerlError::runtime(
14750 "struct to_hash takes no arguments",
14751 line,
14752 )));
14753 }
14754 let mut map = IndexMap::new();
14755 let values = s.get_values();
14756 for (i, field) in s.def.fields.iter().enumerate() {
14757 map.insert(field.name.clone(), values[i].clone());
14758 }
14759 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
14760 }
14761 "fields" => {
14762 if !args.is_empty() {
14764 return Some(Err(PerlError::runtime(
14765 "struct fields takes no arguments",
14766 line,
14767 )));
14768 }
14769 let names: Vec<PerlValue> = s
14770 .def
14771 .fields
14772 .iter()
14773 .map(|f| PerlValue::string(f.name.clone()))
14774 .collect();
14775 return Some(Ok(PerlValue::array(names)));
14776 }
14777 "clone" => {
14778 if !args.is_empty() {
14780 return Some(Err(PerlError::runtime(
14781 "struct clone takes no arguments",
14782 line,
14783 )));
14784 }
14785 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
14786 return Some(Ok(PerlValue::struct_inst(Arc::new(
14787 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
14788 ))));
14789 }
14790 _ => {}
14791 }
14792 if let Some(m) = s.def.method(method) {
14794 let body = m.body.clone();
14795 let params = m.params.clone();
14796 let mut call_args = vec![receiver.clone()];
14798 call_args.extend(args.iter().cloned());
14799 return Some(
14800 match self.call_struct_method(&body, ¶ms, call_args, line) {
14801 Ok(v) => Ok(v),
14802 Err(FlowOrError::Error(e)) => Err(e),
14803 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14804 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
14805 "unexpected control flow in struct method",
14806 line,
14807 )),
14808 },
14809 );
14810 }
14811 return None;
14812 }
14813 if let Some(c) = receiver.as_class_inst() {
14815 let all_fields_full = self.collect_class_fields_full(&c.def);
14817 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
14818 .iter()
14819 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
14820 .collect();
14821
14822 if let Some(idx) = all_fields_full
14824 .iter()
14825 .position(|(name, _, _, _, _)| name == method)
14826 {
14827 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
14828
14829 match vis {
14831 crate::ast::Visibility::Private => {
14832 let caller_class = self
14834 .scope
14835 .get_scalar("self")
14836 .as_class_inst()
14837 .map(|ci| ci.def.name.clone());
14838 if caller_class.as_deref() != Some(owner_class.as_str()) {
14839 return Some(Err(PerlError::runtime(
14840 format!("field `{}` of class {} is private", method, owner_class),
14841 line,
14842 )));
14843 }
14844 }
14845 crate::ast::Visibility::Protected => {
14846 let caller_class = self
14848 .scope
14849 .get_scalar("self")
14850 .as_class_inst()
14851 .map(|ci| ci.def.name.clone());
14852 let allowed = caller_class.as_deref().is_some_and(|caller| {
14853 caller == owner_class || self.class_inherits_from(caller, owner_class)
14854 });
14855 if !allowed {
14856 return Some(Err(PerlError::runtime(
14857 format!("field `{}` of class {} is protected", method, owner_class),
14858 line,
14859 )));
14860 }
14861 }
14862 crate::ast::Visibility::Public => {}
14863 }
14864
14865 match args.len() {
14866 0 => {
14867 return Some(Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF)));
14868 }
14869 1 => {
14870 let new_val = args[0].clone();
14871 if let Err(msg) = ty.check_value(&new_val) {
14872 return Some(Err(PerlError::type_error(
14873 format!("class {} field `{}`: {}", c.def.name, method, msg),
14874 line,
14875 )));
14876 }
14877 c.set_field(idx, new_val.clone());
14878 return Some(Ok(new_val));
14879 }
14880 _ => {
14881 return Some(Err(PerlError::runtime(
14882 format!(
14883 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
14884 method,
14885 args.len()
14886 ),
14887 line,
14888 )));
14889 }
14890 }
14891 }
14892 match method {
14894 "with" => {
14895 let mut new_values = c.get_values();
14896 let mut i = 0;
14897 while i + 1 < args.len() {
14898 let k = args[i].to_string();
14899 let v = args[i + 1].clone();
14900 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
14901 let (_, _, ref ty) = all_fields[idx];
14902 if let Err(msg) = ty.check_value(&v) {
14903 return Some(Err(PerlError::type_error(
14904 format!("class {} field `{}`: {}", c.def.name, k, msg),
14905 line,
14906 )));
14907 }
14908 new_values[idx] = v;
14909 } else {
14910 return Some(Err(PerlError::runtime(
14911 format!("class {}: unknown field `{}`", c.def.name, k),
14912 line,
14913 )));
14914 }
14915 i += 2;
14916 }
14917 return Some(Ok(PerlValue::class_inst(Arc::new(
14918 crate::value::ClassInstance::new(Arc::clone(&c.def), new_values),
14919 ))));
14920 }
14921 "to_hash" => {
14922 if !args.is_empty() {
14923 return Some(Err(PerlError::runtime(
14924 "class to_hash takes no arguments",
14925 line,
14926 )));
14927 }
14928 let mut map = IndexMap::new();
14929 let values = c.get_values();
14930 for (i, (name, _, _)) in all_fields.iter().enumerate() {
14931 if let Some(v) = values.get(i) {
14932 map.insert(name.clone(), v.clone());
14933 }
14934 }
14935 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
14936 }
14937 "fields" => {
14938 if !args.is_empty() {
14939 return Some(Err(PerlError::runtime(
14940 "class fields takes no arguments",
14941 line,
14942 )));
14943 }
14944 let names: Vec<PerlValue> = all_fields
14945 .iter()
14946 .map(|(name, _, _)| PerlValue::string(name.clone()))
14947 .collect();
14948 return Some(Ok(PerlValue::array(names)));
14949 }
14950 "clone" => {
14951 if !args.is_empty() {
14952 return Some(Err(PerlError::runtime(
14953 "class clone takes no arguments",
14954 line,
14955 )));
14956 }
14957 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
14958 return Some(Ok(PerlValue::class_inst(Arc::new(
14959 crate::value::ClassInstance::new(Arc::clone(&c.def), new_values),
14960 ))));
14961 }
14962 "isa" => {
14963 if args.len() != 1 {
14964 return Some(Err(PerlError::runtime("isa requires one argument", line)));
14965 }
14966 let class_name = args[0].to_string();
14967 let is_a = c.def.name == class_name || c.def.extends.contains(&class_name);
14968 return Some(Ok(if is_a {
14969 PerlValue::integer(1)
14970 } else {
14971 PerlValue::string(String::new())
14972 }));
14973 }
14974 "does" => {
14975 if args.len() != 1 {
14976 return Some(Err(PerlError::runtime("does requires one argument", line)));
14977 }
14978 let trait_name = args[0].to_string();
14979 let implements = c.def.implements.contains(&trait_name);
14980 return Some(Ok(if implements {
14981 PerlValue::integer(1)
14982 } else {
14983 PerlValue::string(String::new())
14984 }));
14985 }
14986 "methods" => {
14987 if !args.is_empty() {
14988 return Some(Err(PerlError::runtime("methods takes no arguments", line)));
14989 }
14990 let mut names = Vec::new();
14991 self.collect_class_method_names(&c.def, &mut names);
14992 let values: Vec<PerlValue> = names.into_iter().map(PerlValue::string).collect();
14993 return Some(Ok(PerlValue::array(values)));
14994 }
14995 "superclass" => {
14996 if !args.is_empty() {
14997 return Some(Err(PerlError::runtime(
14998 "superclass takes no arguments",
14999 line,
15000 )));
15001 }
15002 let parents: Vec<PerlValue> = c
15003 .def
15004 .extends
15005 .iter()
15006 .map(|s| PerlValue::string(s.clone()))
15007 .collect();
15008 return Some(Ok(PerlValue::array(parents)));
15009 }
15010 "destroy" => {
15011 let destroy_chain = self.collect_destroy_chain(&c.def);
15013 for (body, params) in &destroy_chain {
15014 let call_args = vec![receiver.clone()];
15015 match self.call_class_method(body, params, call_args, line) {
15016 Ok(_) => {}
15017 Err(FlowOrError::Flow(Flow::Return(_))) => {}
15018 Err(FlowOrError::Error(e)) => return Some(Err(e)),
15019 Err(_) => {}
15020 }
15021 }
15022 return Some(Ok(PerlValue::UNDEF));
15023 }
15024 _ => {}
15025 }
15026 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
15028 match m.visibility {
15030 crate::ast::Visibility::Private => {
15031 let caller_class = self
15032 .scope
15033 .get_scalar("self")
15034 .as_class_inst()
15035 .map(|ci| ci.def.name.clone());
15036 if caller_class.as_deref() != Some(owner_class.as_str()) {
15037 return Some(Err(PerlError::runtime(
15038 format!("method `{}` of class {} is private", method, owner_class),
15039 line,
15040 )));
15041 }
15042 }
15043 crate::ast::Visibility::Protected => {
15044 let caller_class = self
15045 .scope
15046 .get_scalar("self")
15047 .as_class_inst()
15048 .map(|ci| ci.def.name.clone());
15049 let allowed = caller_class.as_deref().is_some_and(|caller| {
15050 caller == owner_class.as_str()
15051 || self.class_inherits_from(caller, owner_class)
15052 });
15053 if !allowed {
15054 return Some(Err(PerlError::runtime(
15055 format!(
15056 "method `{}` of class {} is protected",
15057 method, owner_class
15058 ),
15059 line,
15060 )));
15061 }
15062 }
15063 crate::ast::Visibility::Public => {}
15064 }
15065 if let Some(ref body) = m.body {
15066 let params = m.params.clone();
15067 let mut call_args = vec![receiver.clone()];
15068 call_args.extend(args.iter().cloned());
15069 return Some(
15070 match self.call_class_method(body, ¶ms, call_args, line) {
15071 Ok(v) => Ok(v),
15072 Err(FlowOrError::Error(e)) => Err(e),
15073 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15074 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
15075 "unexpected control flow in class method",
15076 line,
15077 )),
15078 },
15079 );
15080 }
15081 }
15082 return None;
15083 }
15084 if let Some(d) = receiver.as_dataframe() {
15085 return Some(self.dataframe_method(d, method, args, line));
15086 }
15087 if let Some(s) = crate::value::set_payload(receiver) {
15088 return Some(self.set_method(s, method, args, line));
15089 }
15090 if let Some(d) = receiver.as_deque() {
15091 return Some(self.deque_method(d, method, args, line));
15092 }
15093 if let Some(h) = receiver.as_heap_pq() {
15094 return Some(self.heap_method(h, method, args, line));
15095 }
15096 if let Some(p) = receiver.as_pipeline() {
15097 return Some(self.pipeline_method(p, method, args, line));
15098 }
15099 if let Some(c) = receiver.as_capture() {
15100 return Some(self.capture_method(c, method, args, line));
15101 }
15102 if let Some(p) = receiver.as_ppool() {
15103 return Some(self.ppool_method(p, method, args, line));
15104 }
15105 if let Some(b) = receiver.as_barrier() {
15106 return Some(self.barrier_method(b, method, args, line));
15107 }
15108 if let Some(g) = receiver.as_generator() {
15109 if method == "next" {
15110 if !args.is_empty() {
15111 return Some(Err(PerlError::runtime(
15112 "generator->next takes no arguments",
15113 line,
15114 )));
15115 }
15116 return Some(self.generator_next(&g));
15117 }
15118 return None;
15119 }
15120 if let Some(arc) = receiver.as_atomic_arc() {
15121 let inner = arc.lock().clone();
15122 if let Some(d) = inner.as_deque() {
15123 return Some(self.deque_method(d, method, args, line));
15124 }
15125 if let Some(h) = inner.as_heap_pq() {
15126 return Some(self.heap_method(h, method, args, line));
15127 }
15128 }
15129 None
15130 }
15131
15132 fn dataframe_method(
15134 &mut self,
15135 d: Arc<Mutex<PerlDataFrame>>,
15136 method: &str,
15137 args: &[PerlValue],
15138 line: usize,
15139 ) -> PerlResult<PerlValue> {
15140 match method {
15141 "nrow" | "nrows" => {
15142 if !args.is_empty() {
15143 return Err(PerlError::runtime(
15144 format!("dataframe {} takes no arguments", method),
15145 line,
15146 ));
15147 }
15148 Ok(PerlValue::integer(d.lock().nrows() as i64))
15149 }
15150 "ncol" | "ncols" => {
15151 if !args.is_empty() {
15152 return Err(PerlError::runtime(
15153 format!("dataframe {} takes no arguments", method),
15154 line,
15155 ));
15156 }
15157 Ok(PerlValue::integer(d.lock().ncols() as i64))
15158 }
15159 "filter" => {
15160 if args.len() != 1 {
15161 return Err(PerlError::runtime(
15162 "dataframe filter expects 1 argument (sub)",
15163 line,
15164 ));
15165 }
15166 let Some(sub) = args[0].as_code_ref() else {
15167 return Err(PerlError::runtime(
15168 "dataframe filter expects a code reference",
15169 line,
15170 ));
15171 };
15172 let df_guard = d.lock();
15173 let n = df_guard.nrows();
15174 let mut keep = vec![false; n];
15175 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
15176 let row = df_guard.row_hashref(r);
15177 self.scope_push_hook();
15178 self.scope.set_topic(row);
15179 if let Some(ref env) = sub.closure_env {
15180 self.scope.restore_capture(env);
15181 }
15182 let pass = match self.exec_block_no_scope(&sub.body) {
15183 Ok(v) => v.is_true(),
15184 Err(_) => false,
15185 };
15186 self.scope_pop_hook();
15187 *row_keep = pass;
15188 }
15189 let columns = df_guard.columns.clone();
15190 let cols: Vec<Vec<PerlValue>> = (0..df_guard.ncols())
15191 .map(|i| {
15192 let mut out = Vec::new();
15193 for (r, pass_row) in keep.iter().enumerate().take(n) {
15194 if *pass_row {
15195 out.push(df_guard.cols[i][r].clone());
15196 }
15197 }
15198 out
15199 })
15200 .collect();
15201 let group_by = df_guard.group_by.clone();
15202 drop(df_guard);
15203 let new_df = PerlDataFrame {
15204 columns,
15205 cols,
15206 group_by,
15207 };
15208 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15209 }
15210 "group_by" => {
15211 if args.len() != 1 {
15212 return Err(PerlError::runtime(
15213 "dataframe group_by expects 1 column name",
15214 line,
15215 ));
15216 }
15217 let key = args[0].to_string();
15218 let inner = d.lock();
15219 if inner.col_index(&key).is_none() {
15220 return Err(PerlError::runtime(
15221 format!("dataframe group_by: unknown column \"{}\"", key),
15222 line,
15223 ));
15224 }
15225 let new_df = PerlDataFrame {
15226 columns: inner.columns.clone(),
15227 cols: inner.cols.clone(),
15228 group_by: Some(key),
15229 };
15230 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15231 }
15232 "sum" => {
15233 if args.len() != 1 {
15234 return Err(PerlError::runtime(
15235 "dataframe sum expects 1 column name",
15236 line,
15237 ));
15238 }
15239 let col_name = args[0].to_string();
15240 let inner = d.lock();
15241 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
15242 PerlError::runtime(
15243 format!("dataframe sum: unknown column \"{}\"", col_name),
15244 line,
15245 )
15246 })?;
15247 match &inner.group_by {
15248 Some(gcol) => {
15249 let gi = inner.col_index(gcol).ok_or_else(|| {
15250 PerlError::runtime(
15251 format!("dataframe sum: unknown group column \"{}\"", gcol),
15252 line,
15253 )
15254 })?;
15255 let mut acc: IndexMap<String, f64> = IndexMap::new();
15256 for r in 0..inner.nrows() {
15257 let k = inner.cols[gi][r].to_string();
15258 let v = inner.cols[val_idx][r].to_number();
15259 *acc.entry(k).or_insert(0.0) += v;
15260 }
15261 let keys: Vec<String> = acc.keys().cloned().collect();
15262 let sums: Vec<f64> = acc.values().copied().collect();
15263 let cols = vec![
15264 keys.into_iter().map(PerlValue::string).collect(),
15265 sums.into_iter().map(PerlValue::float).collect(),
15266 ];
15267 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
15268 let out = PerlDataFrame {
15269 columns,
15270 cols,
15271 group_by: None,
15272 };
15273 Ok(PerlValue::dataframe(Arc::new(Mutex::new(out))))
15274 }
15275 None => {
15276 let total: f64 = (0..inner.nrows())
15277 .map(|r| inner.cols[val_idx][r].to_number())
15278 .sum();
15279 Ok(PerlValue::float(total))
15280 }
15281 }
15282 }
15283 _ => Err(PerlError::runtime(
15284 format!("Unknown method for dataframe: {}", method),
15285 line,
15286 )),
15287 }
15288 }
15289
15290 fn set_method(
15292 &self,
15293 s: Arc<crate::value::PerlSet>,
15294 method: &str,
15295 args: &[PerlValue],
15296 line: usize,
15297 ) -> PerlResult<PerlValue> {
15298 match method {
15299 "has" | "contains" | "member" => {
15300 if args.len() != 1 {
15301 return Err(PerlError::runtime(
15302 "set->has expects one argument (element)",
15303 line,
15304 ));
15305 }
15306 let k = crate::value::set_member_key(&args[0]);
15307 Ok(PerlValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
15308 }
15309 "size" | "len" | "count" => {
15310 if !args.is_empty() {
15311 return Err(PerlError::runtime("set->size takes no arguments", line));
15312 }
15313 Ok(PerlValue::integer(s.len() as i64))
15314 }
15315 "values" | "list" | "elements" => {
15316 if !args.is_empty() {
15317 return Err(PerlError::runtime("set->values takes no arguments", line));
15318 }
15319 Ok(PerlValue::array(s.values().cloned().collect()))
15320 }
15321 _ => Err(PerlError::runtime(
15322 format!("Unknown method for set: {}", method),
15323 line,
15324 )),
15325 }
15326 }
15327
15328 fn deque_method(
15329 &mut self,
15330 d: Arc<Mutex<VecDeque<PerlValue>>>,
15331 method: &str,
15332 args: &[PerlValue],
15333 line: usize,
15334 ) -> PerlResult<PerlValue> {
15335 match method {
15336 "push_back" => {
15337 if args.len() != 1 {
15338 return Err(PerlError::runtime("push_back expects 1 argument", line));
15339 }
15340 d.lock().push_back(args[0].clone());
15341 Ok(PerlValue::integer(d.lock().len() as i64))
15342 }
15343 "push_front" => {
15344 if args.len() != 1 {
15345 return Err(PerlError::runtime("push_front expects 1 argument", line));
15346 }
15347 d.lock().push_front(args[0].clone());
15348 Ok(PerlValue::integer(d.lock().len() as i64))
15349 }
15350 "pop_back" => Ok(d.lock().pop_back().unwrap_or(PerlValue::UNDEF)),
15351 "pop_front" => Ok(d.lock().pop_front().unwrap_or(PerlValue::UNDEF)),
15352 "size" | "len" => Ok(PerlValue::integer(d.lock().len() as i64)),
15353 _ => Err(PerlError::runtime(
15354 format!("Unknown method for deque: {}", method),
15355 line,
15356 )),
15357 }
15358 }
15359
15360 fn heap_method(
15361 &mut self,
15362 h: Arc<Mutex<PerlHeap>>,
15363 method: &str,
15364 args: &[PerlValue],
15365 line: usize,
15366 ) -> PerlResult<PerlValue> {
15367 match method {
15368 "push" => {
15369 if args.len() != 1 {
15370 return Err(PerlError::runtime("heap push expects 1 argument", line));
15371 }
15372 let mut g = h.lock();
15373 let n = g.items.len();
15374 g.items.push(args[0].clone());
15375 let cmp = g.cmp.clone();
15376 drop(g);
15377 let mut g = h.lock();
15378 self.heap_sift_up(&mut g.items, &cmp, n);
15379 Ok(PerlValue::integer(g.items.len() as i64))
15380 }
15381 "pop" => {
15382 let mut g = h.lock();
15383 if g.items.is_empty() {
15384 return Ok(PerlValue::UNDEF);
15385 }
15386 let cmp = g.cmp.clone();
15387 let n = g.items.len();
15388 g.items.swap(0, n - 1);
15389 let v = g.items.pop().unwrap();
15390 if !g.items.is_empty() {
15391 self.heap_sift_down(&mut g.items, &cmp, 0);
15392 }
15393 Ok(v)
15394 }
15395 "peek" => Ok(h.lock().items.first().cloned().unwrap_or(PerlValue::UNDEF)),
15396 _ => Err(PerlError::runtime(
15397 format!("Unknown method for heap: {}", method),
15398 line,
15399 )),
15400 }
15401 }
15402
15403 fn ppool_method(
15404 &mut self,
15405 pool: PerlPpool,
15406 method: &str,
15407 args: &[PerlValue],
15408 line: usize,
15409 ) -> PerlResult<PerlValue> {
15410 match method {
15411 "submit" => pool.submit(self, args, line),
15412 "collect" => {
15413 if !args.is_empty() {
15414 return Err(PerlError::runtime("collect() takes no arguments", line));
15415 }
15416 pool.collect(line)
15417 }
15418 _ => Err(PerlError::runtime(
15419 format!("Unknown method for ppool: {}", method),
15420 line,
15421 )),
15422 }
15423 }
15424
15425 fn barrier_method(
15426 &self,
15427 barrier: PerlBarrier,
15428 method: &str,
15429 args: &[PerlValue],
15430 line: usize,
15431 ) -> PerlResult<PerlValue> {
15432 match method {
15433 "wait" => {
15434 if !args.is_empty() {
15435 return Err(PerlError::runtime("wait() takes no arguments", line));
15436 }
15437 let _ = barrier.0.wait();
15438 Ok(PerlValue::integer(1))
15439 }
15440 _ => Err(PerlError::runtime(
15441 format!("Unknown method for barrier: {}", method),
15442 line,
15443 )),
15444 }
15445 }
15446
15447 fn capture_method(
15448 &self,
15449 c: Arc<CaptureResult>,
15450 method: &str,
15451 args: &[PerlValue],
15452 line: usize,
15453 ) -> PerlResult<PerlValue> {
15454 if !args.is_empty() {
15455 return Err(PerlError::runtime(
15456 format!("capture: {} takes no arguments", method),
15457 line,
15458 ));
15459 }
15460 match method {
15461 "stdout" => Ok(PerlValue::string(c.stdout.clone())),
15462 "stderr" => Ok(PerlValue::string(c.stderr.clone())),
15463 "exitcode" => Ok(PerlValue::integer(c.exitcode)),
15464 "failed" => Ok(PerlValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
15465 _ => Err(PerlError::runtime(
15466 format!("Unknown method for capture: {}", method),
15467 line,
15468 )),
15469 }
15470 }
15471
15472 pub(crate) fn builtin_par_pipeline_stream(
15473 &mut self,
15474 args: &[PerlValue],
15475 _line: usize,
15476 ) -> PerlResult<PerlValue> {
15477 let mut items = Vec::new();
15478 for v in args {
15479 if let Some(a) = v.as_array_vec() {
15480 items.extend(a);
15481 } else {
15482 items.push(v.clone());
15483 }
15484 }
15485 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15486 source: items,
15487 ops: Vec::new(),
15488 has_scalar_terminal: false,
15489 par_stream: true,
15490 streaming: false,
15491 streaming_workers: 0,
15492 streaming_buffer: 256,
15493 }))))
15494 }
15495
15496 pub(crate) fn builtin_par_pipeline_stream_new(
15499 &mut self,
15500 args: &[PerlValue],
15501 _line: usize,
15502 ) -> PerlResult<PerlValue> {
15503 let mut items = Vec::new();
15504 let mut workers: usize = 0;
15505 let mut buffer: usize = 256;
15506 let mut i = 0;
15508 while i < args.len() {
15509 let s = args[i].to_string();
15510 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
15511 let val = args[i + 1].to_int().max(1) as usize;
15512 if s == "workers" {
15513 workers = val;
15514 } else {
15515 buffer = val;
15516 }
15517 i += 2;
15518 } else if let Some(a) = args[i].as_array_vec() {
15519 items.extend(a);
15520 i += 1;
15521 } else {
15522 items.push(args[i].clone());
15523 i += 1;
15524 }
15525 }
15526 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15527 source: items,
15528 ops: Vec::new(),
15529 has_scalar_terminal: false,
15530 par_stream: false,
15531 streaming: true,
15532 streaming_workers: workers,
15533 streaming_buffer: buffer,
15534 }))))
15535 }
15536
15537 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<PerlSub> {
15539 let line = 1usize;
15540 let body = vec![Statement {
15541 label: None,
15542 kind: StmtKind::Expression(Expr {
15543 kind: ExprKind::BinOp {
15544 left: Box::new(Expr {
15545 kind: ExprKind::ScalarVar("_".into()),
15546 line,
15547 }),
15548 op: BinOp::Mul,
15549 right: Box::new(Expr {
15550 kind: ExprKind::Integer(k),
15551 line,
15552 }),
15553 },
15554 line,
15555 }),
15556 line,
15557 }];
15558 Arc::new(PerlSub {
15559 name: "__pipeline_int_mul__".into(),
15560 params: vec![],
15561 body,
15562 closure_env: None,
15563 prototype: None,
15564 fib_like: None,
15565 })
15566 }
15567
15568 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<PerlSub> {
15569 let captured = self.scope.capture();
15570 Arc::new(PerlSub {
15571 name: "__ANON__".into(),
15572 params: vec![],
15573 body: block.clone(),
15574 closure_env: Some(captured),
15575 prototype: None,
15576 fib_like: None,
15577 })
15578 }
15579
15580 pub(crate) fn builtin_collect_execute(
15581 &mut self,
15582 args: &[PerlValue],
15583 line: usize,
15584 ) -> PerlResult<PerlValue> {
15585 if args.is_empty() {
15586 return Err(PerlError::runtime(
15587 "collect() expects at least one argument",
15588 line,
15589 ));
15590 }
15591 if args.len() == 1 {
15594 if let Some(p) = args[0].as_pipeline() {
15595 return self.pipeline_collect(&p, line);
15596 }
15597 return Ok(PerlValue::array(args[0].to_list()));
15598 }
15599 Ok(PerlValue::array(args.to_vec()))
15600 }
15601
15602 pub(crate) fn pipeline_push(
15603 &self,
15604 p: &Arc<Mutex<PipelineInner>>,
15605 op: PipelineOp,
15606 line: usize,
15607 ) -> PerlResult<()> {
15608 let mut g = p.lock();
15609 if g.has_scalar_terminal {
15610 return Err(PerlError::runtime(
15611 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
15612 line,
15613 ));
15614 }
15615 if matches!(
15616 &op,
15617 PipelineOp::PReduce { .. }
15618 | PipelineOp::PReduceInit { .. }
15619 | PipelineOp::PMapReduce { .. }
15620 ) {
15621 g.has_scalar_terminal = true;
15622 }
15623 g.ops.push(op);
15624 Ok(())
15625 }
15626
15627 fn pipeline_parse_sub_progress(
15628 args: &[PerlValue],
15629 line: usize,
15630 name: &str,
15631 ) -> PerlResult<(Arc<PerlSub>, bool)> {
15632 if args.is_empty() {
15633 return Err(PerlError::runtime(
15634 format!("pipeline {}: expects at least 1 argument (code ref)", name),
15635 line,
15636 ));
15637 }
15638 let Some(sub) = args[0].as_code_ref() else {
15639 return Err(PerlError::runtime(
15640 format!("pipeline {}: first argument must be a code reference", name),
15641 line,
15642 ));
15643 };
15644 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
15645 if args.len() > 2 {
15646 return Err(PerlError::runtime(
15647 format!(
15648 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
15649 name
15650 ),
15651 line,
15652 ));
15653 }
15654 Ok((sub, progress))
15655 }
15656
15657 pub(crate) fn pipeline_method(
15658 &mut self,
15659 p: Arc<Mutex<PipelineInner>>,
15660 method: &str,
15661 args: &[PerlValue],
15662 line: usize,
15663 ) -> PerlResult<PerlValue> {
15664 match method {
15665 "filter" | "f" | "grep" => {
15666 if args.len() != 1 {
15667 return Err(PerlError::runtime(
15668 "pipeline filter/grep expects 1 argument (sub)",
15669 line,
15670 ));
15671 }
15672 let Some(sub) = args[0].as_code_ref() else {
15673 return Err(PerlError::runtime(
15674 "pipeline filter/grep expects a code reference",
15675 line,
15676 ));
15677 };
15678 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
15679 Ok(PerlValue::pipeline(Arc::clone(&p)))
15680 }
15681 "map" => {
15682 if args.len() != 1 {
15683 return Err(PerlError::runtime(
15684 "pipeline map expects 1 argument (sub)",
15685 line,
15686 ));
15687 }
15688 let Some(sub) = args[0].as_code_ref() else {
15689 return Err(PerlError::runtime(
15690 "pipeline map expects a code reference",
15691 line,
15692 ));
15693 };
15694 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
15695 Ok(PerlValue::pipeline(Arc::clone(&p)))
15696 }
15697 "tap" | "peek" => {
15698 if args.len() != 1 {
15699 return Err(PerlError::runtime(
15700 "pipeline tap/peek expects 1 argument (sub)",
15701 line,
15702 ));
15703 }
15704 let Some(sub) = args[0].as_code_ref() else {
15705 return Err(PerlError::runtime(
15706 "pipeline tap/peek expects a code reference",
15707 line,
15708 ));
15709 };
15710 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
15711 Ok(PerlValue::pipeline(Arc::clone(&p)))
15712 }
15713 "take" => {
15714 if args.len() != 1 {
15715 return Err(PerlError::runtime("pipeline take expects 1 argument", line));
15716 }
15717 let n = args[0].to_int();
15718 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
15719 Ok(PerlValue::pipeline(Arc::clone(&p)))
15720 }
15721 "pmap" => {
15722 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
15723 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
15724 Ok(PerlValue::pipeline(Arc::clone(&p)))
15725 }
15726 "pgrep" => {
15727 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
15728 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
15729 Ok(PerlValue::pipeline(Arc::clone(&p)))
15730 }
15731 "pfor" => {
15732 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
15733 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
15734 Ok(PerlValue::pipeline(Arc::clone(&p)))
15735 }
15736 "pmap_chunked" => {
15737 if args.len() < 2 {
15738 return Err(PerlError::runtime(
15739 "pipeline pmap_chunked expects chunk size and a code reference",
15740 line,
15741 ));
15742 }
15743 let chunk = args[0].to_int().max(1);
15744 let Some(sub) = args[1].as_code_ref() else {
15745 return Err(PerlError::runtime(
15746 "pipeline pmap_chunked: second argument must be a code reference",
15747 line,
15748 ));
15749 };
15750 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15751 if args.len() > 3 {
15752 return Err(PerlError::runtime(
15753 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
15754 line,
15755 ));
15756 }
15757 self.pipeline_push(
15758 &p,
15759 PipelineOp::PMapChunked {
15760 chunk,
15761 sub,
15762 progress,
15763 },
15764 line,
15765 )?;
15766 Ok(PerlValue::pipeline(Arc::clone(&p)))
15767 }
15768 "psort" => {
15769 let (cmp, progress) = match args.len() {
15770 0 => (None, false),
15771 1 => {
15772 if let Some(s) = args[0].as_code_ref() {
15773 (Some(s), false)
15774 } else {
15775 (None, args[0].is_true())
15776 }
15777 }
15778 2 => {
15779 let Some(s) = args[0].as_code_ref() else {
15780 return Err(PerlError::runtime(
15781 "pipeline psort: with two arguments, the first must be a comparator sub",
15782 line,
15783 ));
15784 };
15785 (Some(s), args[1].is_true())
15786 }
15787 _ => {
15788 return Err(PerlError::runtime(
15789 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
15790 line,
15791 ));
15792 }
15793 };
15794 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
15795 Ok(PerlValue::pipeline(Arc::clone(&p)))
15796 }
15797 "pcache" => {
15798 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
15799 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
15800 Ok(PerlValue::pipeline(Arc::clone(&p)))
15801 }
15802 "preduce" => {
15803 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
15804 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
15805 Ok(PerlValue::pipeline(Arc::clone(&p)))
15806 }
15807 "preduce_init" => {
15808 if args.len() < 2 {
15809 return Err(PerlError::runtime(
15810 "pipeline preduce_init expects init value and a code reference",
15811 line,
15812 ));
15813 }
15814 let init = args[0].clone();
15815 let Some(sub) = args[1].as_code_ref() else {
15816 return Err(PerlError::runtime(
15817 "pipeline preduce_init: second argument must be a code reference",
15818 line,
15819 ));
15820 };
15821 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15822 if args.len() > 3 {
15823 return Err(PerlError::runtime(
15824 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
15825 line,
15826 ));
15827 }
15828 self.pipeline_push(
15829 &p,
15830 PipelineOp::PReduceInit {
15831 init,
15832 sub,
15833 progress,
15834 },
15835 line,
15836 )?;
15837 Ok(PerlValue::pipeline(Arc::clone(&p)))
15838 }
15839 "pmap_reduce" => {
15840 if args.len() < 2 {
15841 return Err(PerlError::runtime(
15842 "pipeline pmap_reduce expects map sub and reduce sub",
15843 line,
15844 ));
15845 }
15846 let Some(map) = args[0].as_code_ref() else {
15847 return Err(PerlError::runtime(
15848 "pipeline pmap_reduce: first argument must be a code reference (map)",
15849 line,
15850 ));
15851 };
15852 let Some(reduce) = args[1].as_code_ref() else {
15853 return Err(PerlError::runtime(
15854 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
15855 line,
15856 ));
15857 };
15858 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15859 if args.len() > 3 {
15860 return Err(PerlError::runtime(
15861 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
15862 line,
15863 ));
15864 }
15865 self.pipeline_push(
15866 &p,
15867 PipelineOp::PMapReduce {
15868 map,
15869 reduce,
15870 progress,
15871 },
15872 line,
15873 )?;
15874 Ok(PerlValue::pipeline(Arc::clone(&p)))
15875 }
15876 "collect" => {
15877 if !args.is_empty() {
15878 return Err(PerlError::runtime(
15879 "pipeline collect takes no arguments",
15880 line,
15881 ));
15882 }
15883 self.pipeline_collect(&p, line)
15884 }
15885 _ => {
15886 if let Some(sub) = self.resolve_sub_by_name(method) {
15889 if !args.is_empty() {
15890 return Err(PerlError::runtime(
15891 format!(
15892 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
15893 method
15894 ),
15895 line,
15896 ));
15897 }
15898 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
15899 Ok(PerlValue::pipeline(Arc::clone(&p)))
15900 } else {
15901 Err(PerlError::runtime(
15902 format!("Unknown method for pipeline: {}", method),
15903 line,
15904 ))
15905 }
15906 }
15907 }
15908 }
15909
15910 fn pipeline_parallel_map(
15911 &mut self,
15912 items: Vec<PerlValue>,
15913 sub: &Arc<PerlSub>,
15914 progress: bool,
15915 ) -> Vec<PerlValue> {
15916 let subs = self.subs.clone();
15917 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
15918 let pmap_progress = PmapProgress::new(progress, items.len());
15919 let results: Vec<PerlValue> = items
15920 .into_par_iter()
15921 .map(|item| {
15922 let mut local_interp = Interpreter::new();
15923 local_interp.subs = subs.clone();
15924 local_interp.scope.restore_capture(&scope_capture);
15925 local_interp
15926 .scope
15927 .restore_atomics(&atomic_arrays, &atomic_hashes);
15928 local_interp.enable_parallel_guard();
15929 local_interp.scope.set_topic(item);
15930 local_interp.scope_push_hook();
15931 let val = match local_interp.exec_block_no_scope(&sub.body) {
15932 Ok(val) => val,
15933 Err(_) => PerlValue::UNDEF,
15934 };
15935 local_interp.scope_pop_hook();
15936 pmap_progress.tick();
15937 val
15938 })
15939 .collect();
15940 pmap_progress.finish();
15941 results
15942 }
15943
15944 fn pipeline_par_stream_filter(
15946 &mut self,
15947 items: Vec<PerlValue>,
15948 sub: &Arc<PerlSub>,
15949 ) -> Vec<PerlValue> {
15950 if items.is_empty() {
15951 return items;
15952 }
15953 let subs = self.subs.clone();
15954 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
15955 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
15956 let mut kept: Vec<(usize, PerlValue)> = indexed
15957 .into_par_iter()
15958 .filter_map(|(i, item)| {
15959 let mut local_interp = Interpreter::new();
15960 local_interp.subs = subs.clone();
15961 local_interp.scope.restore_capture(&scope_capture);
15962 local_interp
15963 .scope
15964 .restore_atomics(&atomic_arrays, &atomic_hashes);
15965 local_interp.enable_parallel_guard();
15966 local_interp.scope.set_topic(item.clone());
15967 local_interp.scope_push_hook();
15968 let keep = match local_interp.exec_block_no_scope(&sub.body) {
15969 Ok(val) => val.is_true(),
15970 Err(_) => false,
15971 };
15972 local_interp.scope_pop_hook();
15973 if keep {
15974 Some((i, item))
15975 } else {
15976 None
15977 }
15978 })
15979 .collect();
15980 kept.sort_by_key(|(i, _)| *i);
15981 kept.into_iter().map(|(_, x)| x).collect()
15982 }
15983
15984 fn pipeline_par_stream_map(
15986 &mut self,
15987 items: Vec<PerlValue>,
15988 sub: &Arc<PerlSub>,
15989 ) -> Vec<PerlValue> {
15990 if items.is_empty() {
15991 return items;
15992 }
15993 let subs = self.subs.clone();
15994 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
15995 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
15996 let mut mapped: Vec<(usize, PerlValue)> = indexed
15997 .into_par_iter()
15998 .map(|(i, item)| {
15999 let mut local_interp = Interpreter::new();
16000 local_interp.subs = subs.clone();
16001 local_interp.scope.restore_capture(&scope_capture);
16002 local_interp
16003 .scope
16004 .restore_atomics(&atomic_arrays, &atomic_hashes);
16005 local_interp.enable_parallel_guard();
16006 local_interp.scope.set_topic(item);
16007 local_interp.scope_push_hook();
16008 let val = match local_interp.exec_block_no_scope(&sub.body) {
16009 Ok(val) => val,
16010 Err(_) => PerlValue::UNDEF,
16011 };
16012 local_interp.scope_pop_hook();
16013 (i, val)
16014 })
16015 .collect();
16016 mapped.sort_by_key(|(i, _)| *i);
16017 mapped.into_iter().map(|(_, x)| x).collect()
16018 }
16019
16020 fn pipeline_collect(
16021 &mut self,
16022 p: &Arc<Mutex<PipelineInner>>,
16023 line: usize,
16024 ) -> PerlResult<PerlValue> {
16025 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
16026 let g = p.lock();
16027 (
16028 g.source.clone(),
16029 g.ops.clone(),
16030 g.par_stream,
16031 g.streaming,
16032 g.streaming_workers,
16033 g.streaming_buffer,
16034 )
16035 };
16036 if streaming {
16037 return self.pipeline_collect_streaming(
16038 v,
16039 &ops,
16040 streaming_workers,
16041 streaming_buffer,
16042 line,
16043 );
16044 }
16045 for op in ops {
16046 match op {
16047 PipelineOp::Filter(sub) => {
16048 if par_stream {
16049 v = self.pipeline_par_stream_filter(v, &sub);
16050 } else {
16051 let mut out = Vec::new();
16052 for item in v {
16053 self.scope_push_hook();
16054 self.scope.set_topic(item.clone());
16055 if let Some(ref env) = sub.closure_env {
16056 self.scope.restore_capture(env);
16057 }
16058 let keep = match self.exec_block_no_scope(&sub.body) {
16059 Ok(val) => val.is_true(),
16060 Err(_) => false,
16061 };
16062 self.scope_pop_hook();
16063 if keep {
16064 out.push(item);
16065 }
16066 }
16067 v = out;
16068 }
16069 }
16070 PipelineOp::Map(sub) => {
16071 if par_stream {
16072 v = self.pipeline_par_stream_map(v, &sub);
16073 } else {
16074 let mut out = Vec::new();
16075 for item in v {
16076 self.scope_push_hook();
16077 self.scope.set_topic(item);
16078 if let Some(ref env) = sub.closure_env {
16079 self.scope.restore_capture(env);
16080 }
16081 let mapped = match self.exec_block_no_scope(&sub.body) {
16082 Ok(val) => val,
16083 Err(_) => PerlValue::UNDEF,
16084 };
16085 self.scope_pop_hook();
16086 out.push(mapped);
16087 }
16088 v = out;
16089 }
16090 }
16091 PipelineOp::Tap(sub) => {
16092 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
16093 Ok(_) => {}
16094 Err(FlowOrError::Error(e)) => return Err(e),
16095 Err(FlowOrError::Flow(_)) => {
16096 return Err(PerlError::runtime(
16097 "tap: unsupported control flow in block",
16098 line,
16099 ));
16100 }
16101 }
16102 }
16103 PipelineOp::Take(n) => {
16104 let n = n.max(0) as usize;
16105 if v.len() > n {
16106 v.truncate(n);
16107 }
16108 }
16109 PipelineOp::PMap { sub, progress } => {
16110 v = self.pipeline_parallel_map(v, &sub, progress);
16111 }
16112 PipelineOp::PGrep { sub, progress } => {
16113 let subs = self.subs.clone();
16114 let (scope_capture, atomic_arrays, atomic_hashes) =
16115 self.scope.capture_with_atomics();
16116 let pmap_progress = PmapProgress::new(progress, v.len());
16117 v = v
16118 .into_par_iter()
16119 .filter_map(|item| {
16120 let mut local_interp = Interpreter::new();
16121 local_interp.subs = subs.clone();
16122 local_interp.scope.restore_capture(&scope_capture);
16123 local_interp
16124 .scope
16125 .restore_atomics(&atomic_arrays, &atomic_hashes);
16126 local_interp.enable_parallel_guard();
16127 local_interp.scope.set_topic(item.clone());
16128 local_interp.scope_push_hook();
16129 let keep = match local_interp.exec_block_no_scope(&sub.body) {
16130 Ok(val) => val.is_true(),
16131 Err(_) => false,
16132 };
16133 local_interp.scope_pop_hook();
16134 pmap_progress.tick();
16135 if keep {
16136 Some(item)
16137 } else {
16138 None
16139 }
16140 })
16141 .collect();
16142 pmap_progress.finish();
16143 }
16144 PipelineOp::PFor { sub, progress } => {
16145 let subs = self.subs.clone();
16146 let (scope_capture, atomic_arrays, atomic_hashes) =
16147 self.scope.capture_with_atomics();
16148 let pmap_progress = PmapProgress::new(progress, v.len());
16149 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
16150 v.clone().into_par_iter().for_each(|item| {
16151 if first_err.lock().is_some() {
16152 return;
16153 }
16154 let mut local_interp = Interpreter::new();
16155 local_interp.subs = subs.clone();
16156 local_interp.scope.restore_capture(&scope_capture);
16157 local_interp
16158 .scope
16159 .restore_atomics(&atomic_arrays, &atomic_hashes);
16160 local_interp.enable_parallel_guard();
16161 local_interp.scope.set_topic(item);
16162 local_interp.scope_push_hook();
16163 match local_interp.exec_block_no_scope(&sub.body) {
16164 Ok(_) => {}
16165 Err(e) => {
16166 let stryke = match e {
16167 FlowOrError::Error(stryke) => stryke,
16168 FlowOrError::Flow(_) => PerlError::runtime(
16169 "return/last/next/redo not supported inside pipeline pfor block",
16170 line,
16171 ),
16172 };
16173 let mut g = first_err.lock();
16174 if g.is_none() {
16175 *g = Some(stryke);
16176 }
16177 }
16178 }
16179 local_interp.scope_pop_hook();
16180 pmap_progress.tick();
16181 });
16182 pmap_progress.finish();
16183 let pfor_err = first_err.lock().take();
16184 if let Some(e) = pfor_err {
16185 return Err(e);
16186 }
16187 }
16188 PipelineOp::PMapChunked {
16189 chunk,
16190 sub,
16191 progress,
16192 } => {
16193 let chunk_n = chunk.max(1) as usize;
16194 let subs = self.subs.clone();
16195 let (scope_capture, atomic_arrays, atomic_hashes) =
16196 self.scope.capture_with_atomics();
16197 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = v
16198 .chunks(chunk_n)
16199 .enumerate()
16200 .map(|(i, c)| (i, c.to_vec()))
16201 .collect();
16202 let n_chunks = indexed_chunks.len();
16203 let pmap_progress = PmapProgress::new(progress, n_chunks);
16204 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
16205 .into_par_iter()
16206 .map(|(chunk_idx, chunk)| {
16207 let mut local_interp = Interpreter::new();
16208 local_interp.subs = subs.clone();
16209 local_interp.scope.restore_capture(&scope_capture);
16210 local_interp
16211 .scope
16212 .restore_atomics(&atomic_arrays, &atomic_hashes);
16213 local_interp.enable_parallel_guard();
16214 let mut out = Vec::with_capacity(chunk.len());
16215 for item in chunk {
16216 local_interp.scope.set_topic(item);
16217 local_interp.scope_push_hook();
16218 match local_interp.exec_block_no_scope(&sub.body) {
16219 Ok(val) => {
16220 local_interp.scope_pop_hook();
16221 out.push(val);
16222 }
16223 Err(_) => {
16224 local_interp.scope_pop_hook();
16225 out.push(PerlValue::UNDEF);
16226 }
16227 }
16228 }
16229 pmap_progress.tick();
16230 (chunk_idx, out)
16231 })
16232 .collect();
16233 pmap_progress.finish();
16234 chunk_results.sort_by_key(|(i, _)| *i);
16235 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
16236 }
16237 PipelineOp::PSort { cmp, progress } => {
16238 let pmap_progress = PmapProgress::new(progress, 2);
16239 pmap_progress.tick();
16240 match cmp {
16241 Some(cmp_block) => {
16242 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
16243 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
16244 } else {
16245 let subs = self.subs.clone();
16246 let scope_capture = self.scope.capture();
16247 v.par_sort_by(|a, b| {
16248 let mut local_interp = Interpreter::new();
16249 local_interp.subs = subs.clone();
16250 local_interp.scope.restore_capture(&scope_capture);
16251 local_interp.enable_parallel_guard();
16252 let _ = local_interp.scope.set_scalar("a", a.clone());
16253 let _ = local_interp.scope.set_scalar("b", b.clone());
16254 let _ = local_interp.scope.set_scalar("_0", a.clone());
16255 let _ = local_interp.scope.set_scalar("_1", b.clone());
16256 local_interp.scope_push_hook();
16257 let ord =
16258 match local_interp.exec_block_no_scope(&cmp_block.body) {
16259 Ok(v) => {
16260 let n = v.to_int();
16261 if n < 0 {
16262 std::cmp::Ordering::Less
16263 } else if n > 0 {
16264 std::cmp::Ordering::Greater
16265 } else {
16266 std::cmp::Ordering::Equal
16267 }
16268 }
16269 Err(_) => std::cmp::Ordering::Equal,
16270 };
16271 local_interp.scope_pop_hook();
16272 ord
16273 });
16274 }
16275 }
16276 None => {
16277 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
16278 }
16279 }
16280 pmap_progress.tick();
16281 pmap_progress.finish();
16282 }
16283 PipelineOp::PCache { sub, progress } => {
16284 let subs = self.subs.clone();
16285 let scope_capture = self.scope.capture();
16286 let cache = &*crate::pcache::GLOBAL_PCACHE;
16287 let pmap_progress = PmapProgress::new(progress, v.len());
16288 v = v
16289 .into_par_iter()
16290 .map(|item| {
16291 let k = crate::pcache::cache_key(&item);
16292 if let Some(cached) = cache.get(&k) {
16293 pmap_progress.tick();
16294 return cached.clone();
16295 }
16296 let mut local_interp = Interpreter::new();
16297 local_interp.subs = subs.clone();
16298 local_interp.scope.restore_capture(&scope_capture);
16299 local_interp.enable_parallel_guard();
16300 local_interp.scope.set_topic(item.clone());
16301 local_interp.scope_push_hook();
16302 let val = match local_interp.exec_block_no_scope(&sub.body) {
16303 Ok(v) => v,
16304 Err(_) => PerlValue::UNDEF,
16305 };
16306 local_interp.scope_pop_hook();
16307 cache.insert(k, val.clone());
16308 pmap_progress.tick();
16309 val
16310 })
16311 .collect();
16312 pmap_progress.finish();
16313 }
16314 PipelineOp::PReduce { sub, progress } => {
16315 if v.is_empty() {
16316 return Ok(PerlValue::UNDEF);
16317 }
16318 if v.len() == 1 {
16319 return Ok(v.into_iter().next().unwrap());
16320 }
16321 let block = sub.body.clone();
16322 let subs = self.subs.clone();
16323 let scope_capture = self.scope.capture();
16324 let pmap_progress = PmapProgress::new(progress, v.len());
16325 let result = v
16326 .into_par_iter()
16327 .map(|x| {
16328 pmap_progress.tick();
16329 x
16330 })
16331 .reduce_with(|a, b| {
16332 let mut local_interp = Interpreter::new();
16333 local_interp.subs = subs.clone();
16334 local_interp.scope.restore_capture(&scope_capture);
16335 local_interp.enable_parallel_guard();
16336 let _ = local_interp.scope.set_scalar("a", a.clone());
16337 let _ = local_interp.scope.set_scalar("b", b.clone());
16338 let _ = local_interp.scope.set_scalar("_0", a);
16339 let _ = local_interp.scope.set_scalar("_1", b);
16340 match local_interp.exec_block(&block) {
16341 Ok(val) => val,
16342 Err(_) => PerlValue::UNDEF,
16343 }
16344 });
16345 pmap_progress.finish();
16346 return Ok(result.unwrap_or(PerlValue::UNDEF));
16347 }
16348 PipelineOp::PReduceInit {
16349 init,
16350 sub,
16351 progress,
16352 } => {
16353 if v.is_empty() {
16354 return Ok(init);
16355 }
16356 let block = sub.body.clone();
16357 let subs = self.subs.clone();
16358 let scope_capture = self.scope.capture();
16359 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
16360 if v.len() == 1 {
16361 return Ok(fold_preduce_init_step(
16362 &subs,
16363 cap,
16364 &block,
16365 preduce_init_fold_identity(&init),
16366 v.into_iter().next().unwrap(),
16367 ));
16368 }
16369 let pmap_progress = PmapProgress::new(progress, v.len());
16370 let result = v
16371 .into_par_iter()
16372 .fold(
16373 || preduce_init_fold_identity(&init),
16374 |acc, item| {
16375 pmap_progress.tick();
16376 fold_preduce_init_step(&subs, cap, &block, acc, item)
16377 },
16378 )
16379 .reduce(
16380 || preduce_init_fold_identity(&init),
16381 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
16382 );
16383 pmap_progress.finish();
16384 return Ok(result);
16385 }
16386 PipelineOp::PMapReduce {
16387 map,
16388 reduce,
16389 progress,
16390 } => {
16391 if v.is_empty() {
16392 return Ok(PerlValue::UNDEF);
16393 }
16394 let map_block = map.body.clone();
16395 let reduce_block = reduce.body.clone();
16396 let subs = self.subs.clone();
16397 let scope_capture = self.scope.capture();
16398 if v.len() == 1 {
16399 let mut local_interp = Interpreter::new();
16400 local_interp.subs = subs.clone();
16401 local_interp.scope.restore_capture(&scope_capture);
16402 local_interp.scope.set_topic(v[0].clone());
16403 return match local_interp.exec_block_no_scope(&map_block) {
16404 Ok(val) => Ok(val),
16405 Err(_) => Ok(PerlValue::UNDEF),
16406 };
16407 }
16408 let pmap_progress = PmapProgress::new(progress, v.len());
16409 let result = v
16410 .into_par_iter()
16411 .map(|item| {
16412 let mut local_interp = Interpreter::new();
16413 local_interp.subs = subs.clone();
16414 local_interp.scope.restore_capture(&scope_capture);
16415 local_interp.scope.set_topic(item);
16416 let val = match local_interp.exec_block_no_scope(&map_block) {
16417 Ok(val) => val,
16418 Err(_) => PerlValue::UNDEF,
16419 };
16420 pmap_progress.tick();
16421 val
16422 })
16423 .reduce_with(|a, b| {
16424 let mut local_interp = Interpreter::new();
16425 local_interp.subs = subs.clone();
16426 local_interp.scope.restore_capture(&scope_capture);
16427 let _ = local_interp.scope.set_scalar("a", a.clone());
16428 let _ = local_interp.scope.set_scalar("b", b.clone());
16429 let _ = local_interp.scope.set_scalar("_0", a);
16430 let _ = local_interp.scope.set_scalar("_1", b);
16431 match local_interp.exec_block_no_scope(&reduce_block) {
16432 Ok(val) => val,
16433 Err(_) => PerlValue::UNDEF,
16434 }
16435 });
16436 pmap_progress.finish();
16437 return Ok(result.unwrap_or(PerlValue::UNDEF));
16438 }
16439 }
16440 }
16441 Ok(PerlValue::array(v))
16442 }
16443
16444 fn pipeline_collect_streaming(
16447 &mut self,
16448 source: Vec<PerlValue>,
16449 ops: &[PipelineOp],
16450 workers_per_stage: usize,
16451 buffer: usize,
16452 line: usize,
16453 ) -> PerlResult<PerlValue> {
16454 use crossbeam::channel::{bounded, Receiver, Sender};
16455
16456 for op in ops {
16458 match op {
16459 PipelineOp::PSort { .. }
16460 | PipelineOp::PReduce { .. }
16461 | PipelineOp::PReduceInit { .. }
16462 | PipelineOp::PMapReduce { .. }
16463 | PipelineOp::PMapChunked { .. } => {
16464 return Err(PerlError::runtime(
16465 format!(
16466 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
16467 std::mem::discriminant(op)
16468 ),
16469 line,
16470 ));
16471 }
16472 _ => {}
16473 }
16474 }
16475
16476 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
16479 if streamable_ops.is_empty() {
16480 return Ok(PerlValue::array(source));
16481 }
16482
16483 let n_stages = streamable_ops.len();
16484 let wn = if workers_per_stage > 0 {
16485 workers_per_stage
16486 } else {
16487 self.parallel_thread_count()
16488 };
16489 let subs = self.subs.clone();
16490 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16491
16492 let mut channels: Vec<(Sender<PerlValue>, Receiver<PerlValue>)> =
16497 (0..=n_stages).map(|_| bounded(buffer)).collect();
16498
16499 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
16500 let take_done: Arc<std::sync::atomic::AtomicBool> =
16501 Arc::new(std::sync::atomic::AtomicBool::new(false));
16502
16503 let source_tx = channels[0].0.clone();
16506 let result_rx = channels[n_stages].1.clone();
16507 let results: Arc<Mutex<Vec<PerlValue>>> = Arc::new(Mutex::new(Vec::new()));
16508
16509 std::thread::scope(|scope| {
16510 let result_rx_c = result_rx.clone();
16513 let results_c = Arc::clone(&results);
16514 scope.spawn(move || {
16515 while let Ok(item) = result_rx_c.recv() {
16516 results_c.lock().push(item);
16517 }
16518 });
16519
16520 let err_s = Arc::clone(&err);
16522 let take_done_s = Arc::clone(&take_done);
16523 scope.spawn(move || {
16524 for item in source {
16525 if err_s.lock().is_some()
16526 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
16527 {
16528 break;
16529 }
16530 if source_tx.send(item).is_err() {
16531 break;
16532 }
16533 }
16534 });
16535
16536 for (stage_idx, op) in streamable_ops.iter().enumerate() {
16538 let rx = channels[stage_idx].1.clone();
16539 let tx = channels[stage_idx + 1].0.clone();
16540
16541 for _ in 0..wn {
16542 let rx = rx.clone();
16543 let tx = tx.clone();
16544 let subs = subs.clone();
16545 let capture = capture.clone();
16546 let atomic_arrays = atomic_arrays.clone();
16547 let atomic_hashes = atomic_hashes.clone();
16548 let err_w = Arc::clone(&err);
16549 let take_done_w = Arc::clone(&take_done);
16550
16551 match *op {
16552 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
16553 let sub = Arc::clone(sub);
16554 scope.spawn(move || {
16555 while let Ok(item) = rx.recv() {
16556 if err_w.lock().is_some() {
16557 break;
16558 }
16559 let mut interp = Interpreter::new();
16560 interp.subs = subs.clone();
16561 interp.scope.restore_capture(&capture);
16562 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16563 interp.enable_parallel_guard();
16564 interp.scope.set_topic(item.clone());
16565 interp.scope_push_hook();
16566 let keep = match interp.exec_block_no_scope(&sub.body) {
16567 Ok(val) => val.is_true(),
16568 Err(_) => false,
16569 };
16570 interp.scope_pop_hook();
16571 if keep && tx.send(item).is_err() {
16572 break;
16573 }
16574 }
16575 });
16576 }
16577 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
16578 let sub = Arc::clone(sub);
16579 scope.spawn(move || {
16580 while let Ok(item) = rx.recv() {
16581 if err_w.lock().is_some() {
16582 break;
16583 }
16584 let mut interp = Interpreter::new();
16585 interp.subs = subs.clone();
16586 interp.scope.restore_capture(&capture);
16587 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16588 interp.enable_parallel_guard();
16589 interp.scope.set_topic(item);
16590 interp.scope_push_hook();
16591 let mapped = match interp.exec_block_no_scope(&sub.body) {
16592 Ok(val) => val,
16593 Err(_) => PerlValue::UNDEF,
16594 };
16595 interp.scope_pop_hook();
16596 if tx.send(mapped).is_err() {
16597 break;
16598 }
16599 }
16600 });
16601 }
16602 PipelineOp::Take(n) => {
16603 let limit = (*n).max(0) as usize;
16604 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
16605 let count_w = Arc::clone(&count);
16606 scope.spawn(move || {
16607 while let Ok(item) = rx.recv() {
16608 let prev =
16609 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
16610 if prev >= limit {
16611 take_done_w
16612 .store(true, std::sync::atomic::Ordering::Relaxed);
16613 break;
16614 }
16615 if tx.send(item).is_err() {
16616 break;
16617 }
16618 }
16619 });
16620 break;
16622 }
16623 PipelineOp::PFor { ref sub, .. } => {
16624 let sub = Arc::clone(sub);
16625 scope.spawn(move || {
16626 while let Ok(item) = rx.recv() {
16627 if err_w.lock().is_some() {
16628 break;
16629 }
16630 let mut interp = Interpreter::new();
16631 interp.subs = subs.clone();
16632 interp.scope.restore_capture(&capture);
16633 interp
16634 .scope
16635 .restore_atomics(&atomic_arrays, &atomic_hashes);
16636 interp.enable_parallel_guard();
16637 interp.scope.set_topic(item.clone());
16638 interp.scope_push_hook();
16639 match interp.exec_block_no_scope(&sub.body) {
16640 Ok(_) => {}
16641 Err(e) => {
16642 let msg = match e {
16643 FlowOrError::Error(stryke) => stryke.to_string(),
16644 FlowOrError::Flow(_) => {
16645 "unexpected control flow in par_pipeline_stream pfor".into()
16646 }
16647 };
16648 let mut g = err_w.lock();
16649 if g.is_none() {
16650 *g = Some(msg);
16651 }
16652 interp.scope_pop_hook();
16653 break;
16654 }
16655 }
16656 interp.scope_pop_hook();
16657 if tx.send(item).is_err() {
16658 break;
16659 }
16660 }
16661 });
16662 }
16663 PipelineOp::Tap(ref sub) => {
16664 let sub = Arc::clone(sub);
16665 scope.spawn(move || {
16666 while let Ok(item) = rx.recv() {
16667 if err_w.lock().is_some() {
16668 break;
16669 }
16670 let mut interp = Interpreter::new();
16671 interp.subs = subs.clone();
16672 interp.scope.restore_capture(&capture);
16673 interp
16674 .scope
16675 .restore_atomics(&atomic_arrays, &atomic_hashes);
16676 interp.enable_parallel_guard();
16677 match interp.call_sub(
16678 &sub,
16679 vec![item.clone()],
16680 WantarrayCtx::Void,
16681 line,
16682 )
16683 {
16684 Ok(_) => {}
16685 Err(e) => {
16686 let msg = match e {
16687 FlowOrError::Error(stryke) => stryke.to_string(),
16688 FlowOrError::Flow(_) => {
16689 "unexpected control flow in par_pipeline_stream tap"
16690 .into()
16691 }
16692 };
16693 let mut g = err_w.lock();
16694 if g.is_none() {
16695 *g = Some(msg);
16696 }
16697 break;
16698 }
16699 }
16700 if tx.send(item).is_err() {
16701 break;
16702 }
16703 }
16704 });
16705 }
16706 PipelineOp::PCache { ref sub, .. } => {
16707 let sub = Arc::clone(sub);
16708 scope.spawn(move || {
16709 while let Ok(item) = rx.recv() {
16710 if err_w.lock().is_some() {
16711 break;
16712 }
16713 let k = crate::pcache::cache_key(&item);
16714 let val = if let Some(cached) =
16715 crate::pcache::GLOBAL_PCACHE.get(&k)
16716 {
16717 cached.clone()
16718 } else {
16719 let mut interp = Interpreter::new();
16720 interp.subs = subs.clone();
16721 interp.scope.restore_capture(&capture);
16722 interp
16723 .scope
16724 .restore_atomics(&atomic_arrays, &atomic_hashes);
16725 interp.enable_parallel_guard();
16726 interp.scope.set_topic(item);
16727 interp.scope_push_hook();
16728 let v = match interp.exec_block_no_scope(&sub.body) {
16729 Ok(v) => v,
16730 Err(_) => PerlValue::UNDEF,
16731 };
16732 interp.scope_pop_hook();
16733 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
16734 v
16735 };
16736 if tx.send(val).is_err() {
16737 break;
16738 }
16739 }
16740 });
16741 }
16742 _ => unreachable!(),
16744 }
16745 }
16746 }
16747
16748 channels.clear();
16752 drop(result_rx);
16753 });
16754
16755 if let Some(msg) = err.lock().take() {
16756 return Err(PerlError::runtime(msg, line));
16757 }
16758
16759 let results = std::mem::take(&mut *results.lock());
16760 Ok(PerlValue::array(results))
16761 }
16762
16763 fn heap_compare(&mut self, cmp: &Arc<PerlSub>, a: &PerlValue, b: &PerlValue) -> Ordering {
16764 self.scope_push_hook();
16765 if let Some(ref env) = cmp.closure_env {
16766 self.scope.restore_capture(env);
16767 }
16768 let _ = self.scope.set_scalar("a", a.clone());
16769 let _ = self.scope.set_scalar("b", b.clone());
16770 let _ = self.scope.set_scalar("_0", a.clone());
16771 let _ = self.scope.set_scalar("_1", b.clone());
16772 let ord = match self.exec_block_no_scope(&cmp.body) {
16773 Ok(v) => {
16774 let n = v.to_int();
16775 if n < 0 {
16776 Ordering::Less
16777 } else if n > 0 {
16778 Ordering::Greater
16779 } else {
16780 Ordering::Equal
16781 }
16782 }
16783 Err(_) => Ordering::Equal,
16784 };
16785 self.scope_pop_hook();
16786 ord
16787 }
16788
16789 fn heap_sift_up(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
16790 while i > 0 {
16791 let p = (i - 1) / 2;
16792 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
16793 break;
16794 }
16795 items.swap(i, p);
16796 i = p;
16797 }
16798 }
16799
16800 fn heap_sift_down(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
16801 let n = items.len();
16802 loop {
16803 let mut sm = i;
16804 let l = 2 * i + 1;
16805 let r = 2 * i + 2;
16806 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
16807 sm = l;
16808 }
16809 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
16810 sm = r;
16811 }
16812 if sm == i {
16813 break;
16814 }
16815 items.swap(i, sm);
16816 i = sm;
16817 }
16818 }
16819
16820 fn hash_for_signature_destruct(
16821 &mut self,
16822 v: &PerlValue,
16823 line: usize,
16824 ) -> PerlResult<IndexMap<String, PerlValue>> {
16825 let Some(m) = self.match_subject_as_hash(v) else {
16826 return Err(PerlError::runtime(
16827 format!(
16828 "sub signature hash destruct: expected HASH or HASH reference, got {}",
16829 v.ref_type()
16830 ),
16831 line,
16832 ));
16833 };
16834 Ok(m)
16835 }
16836
16837 pub(crate) fn apply_sub_signature(
16839 &mut self,
16840 sub: &PerlSub,
16841 argv: &[PerlValue],
16842 line: usize,
16843 ) -> PerlResult<()> {
16844 if sub.params.is_empty() {
16845 return Ok(());
16846 }
16847 let mut i = 0usize;
16848 for p in &sub.params {
16849 match p {
16850 SubSigParam::Scalar(name, ty) => {
16851 let val = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
16852 i += 1;
16853 if let Some(t) = ty {
16854 if let Err(e) = t.check_value(&val) {
16855 return Err(PerlError::runtime(
16856 format!("sub parameter ${}: {}", name, e),
16857 line,
16858 ));
16859 }
16860 }
16861 let n = self.english_scalar_name(name);
16862 self.scope.declare_scalar(n, val);
16863 }
16864 SubSigParam::Array(name) => {
16865 let rest: Vec<PerlValue> = argv[i..].to_vec();
16866 i = argv.len();
16867 let aname = self.stash_array_name_for_package(name);
16868 self.scope.declare_array(&aname, rest);
16869 }
16870 SubSigParam::Hash(name) => {
16871 let rest: Vec<PerlValue> = argv[i..].to_vec();
16872 i = argv.len();
16873 let mut map = IndexMap::new();
16874 let mut j = 0;
16875 while j + 1 < rest.len() {
16876 map.insert(rest[j].to_string(), rest[j + 1].clone());
16877 j += 2;
16878 }
16879 self.scope.declare_hash(name, map);
16880 }
16881 SubSigParam::ArrayDestruct(elems) => {
16882 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
16883 i += 1;
16884 let Some(arr) = self.match_subject_as_array(&arg) else {
16885 return Err(PerlError::runtime(
16886 format!(
16887 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
16888 arg.ref_type()
16889 ),
16890 line,
16891 ));
16892 };
16893 let binds = self
16894 .match_array_pattern_elems(&arr, elems, line)
16895 .map_err(|e| match e {
16896 FlowOrError::Error(stryke) => stryke,
16897 FlowOrError::Flow(_) => PerlError::runtime(
16898 "unexpected flow in sub signature array destruct",
16899 line,
16900 ),
16901 })?;
16902 let Some(binds) = binds else {
16903 return Err(PerlError::runtime(
16904 "sub signature array destruct: length or element mismatch",
16905 line,
16906 ));
16907 };
16908 for b in binds {
16909 match b {
16910 PatternBinding::Scalar(name, v) => {
16911 let n = self.english_scalar_name(&name);
16912 self.scope.declare_scalar(n, v);
16913 }
16914 PatternBinding::Array(name, elems) => {
16915 self.scope.declare_array(&name, elems);
16916 }
16917 }
16918 }
16919 }
16920 SubSigParam::HashDestruct(pairs) => {
16921 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
16922 i += 1;
16923 let map = self.hash_for_signature_destruct(&arg, line)?;
16924 for (key, varname) in pairs {
16925 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
16926 let n = self.english_scalar_name(varname);
16927 self.scope.declare_scalar(n, v);
16928 }
16929 }
16930 }
16931 }
16932 Ok(())
16933 }
16934
16935 pub(crate) fn try_hof_dispatch(
16939 &mut self,
16940 sub: &PerlSub,
16941 args: &[PerlValue],
16942 want: WantarrayCtx,
16943 line: usize,
16944 ) -> Option<ExecResult> {
16945 let env = sub.closure_env.as_ref()?;
16946 fn env_get<'a>(env: &'a [(String, PerlValue)], key: &str) -> Option<&'a PerlValue> {
16947 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
16948 }
16949
16950 match sub.name.as_str() {
16951 "__comp__" => {
16953 let fns = env_get(env, "__comp_fns__")?.to_list();
16954 let mut val = args.first().cloned().unwrap_or(PerlValue::UNDEF);
16955 for f in fns.iter().rev() {
16956 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
16957 Ok(v) => val = v,
16958 Err(e) => return Some(Err(e)),
16959 }
16960 }
16961 Some(Ok(val))
16962 }
16963 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
16965 "__juxt__" => {
16967 let fns = env_get(env, "__juxt_fns__")?.to_list();
16968 let mut results = Vec::with_capacity(fns.len());
16969 for f in &fns {
16970 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
16971 Ok(v) => results.push(v),
16972 Err(e) => return Some(Err(e)),
16973 }
16974 }
16975 Some(Ok(PerlValue::array(results)))
16976 }
16977 "__partial__" => {
16979 let fn_val = env_get(env, "__partial_fn__")?.clone();
16980 let bound = env_get(env, "__partial_args__")?.to_list();
16981 let mut all_args = bound;
16982 all_args.extend_from_slice(args);
16983 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
16984 }
16985 "__complement__" => {
16987 let fn_val = env_get(env, "__complement_fn__")?.clone();
16988 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
16989 Ok(v) => Some(Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }))),
16990 Err(e) => Some(Err(e)),
16991 }
16992 }
16993 "__fnil__" => {
16995 let fn_val = env_get(env, "__fnil_fn__")?.clone();
16996 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
16997 let mut patched = args.to_vec();
16998 for (i, d) in defaults.iter().enumerate() {
16999 if i < patched.len() {
17000 if patched[i].is_undef() {
17001 patched[i] = d.clone();
17002 }
17003 } else {
17004 patched.push(d.clone());
17005 }
17006 }
17007 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
17008 }
17009 "__memoize__" => {
17011 let fn_val = env_get(env, "__memoize_fn__")?.clone();
17012 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
17013 let key = args
17014 .iter()
17015 .map(|a| a.to_string())
17016 .collect::<Vec<_>>()
17017 .join("\x00");
17018 if let Some(href) = cache_ref.as_hash_ref() {
17019 if let Some(cached) = href.read().get(&key) {
17020 return Some(Ok(cached.clone()));
17021 }
17022 }
17023 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17024 Ok(v) => {
17025 if let Some(href) = cache_ref.as_hash_ref() {
17026 href.write().insert(key, v.clone());
17027 }
17028 Some(Ok(v))
17029 }
17030 Err(e) => Some(Err(e)),
17031 }
17032 }
17033 "__curry__" => {
17035 let fn_val = env_get(env, "__curry_fn__")?.clone();
17036 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
17037 let bound = env_get(env, "__curry_bound__")?.to_list();
17038 let mut all = bound;
17039 all.extend_from_slice(args);
17040 if all.len() >= arity {
17041 Some(self.dispatch_indirect_call(fn_val, all, want, line))
17042 } else {
17043 let curry_sub = PerlSub {
17044 name: "__curry__".to_string(),
17045 params: vec![],
17046 body: vec![],
17047 closure_env: Some(vec![
17048 ("__curry_fn__".to_string(), fn_val),
17049 (
17050 "__curry_arity__".to_string(),
17051 PerlValue::integer(arity as i64),
17052 ),
17053 ("__curry_bound__".to_string(), PerlValue::array(all)),
17054 ]),
17055 prototype: None,
17056 fib_like: None,
17057 };
17058 Some(Ok(PerlValue::code_ref(Arc::new(curry_sub))))
17059 }
17060 }
17061 "__once__" => {
17063 let cache_ref = env_get(env, "__once_cache__")?.clone();
17064 if let Some(href) = cache_ref.as_hash_ref() {
17065 let r = href.read();
17066 if r.contains_key("done") {
17067 return Some(Ok(r.get("val").cloned().unwrap_or(PerlValue::UNDEF)));
17068 }
17069 }
17070 let fn_val = env_get(env, "__once_fn__")?.clone();
17071 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
17072 Ok(v) => {
17073 if let Some(href) = cache_ref.as_hash_ref() {
17074 let mut w = href.write();
17075 w.insert("done".to_string(), PerlValue::integer(1));
17076 w.insert("val".to_string(), v.clone());
17077 }
17078 Some(Ok(v))
17079 }
17080 Err(e) => Some(Err(e)),
17081 }
17082 }
17083 _ => None,
17084 }
17085 }
17086
17087 pub(crate) fn call_sub(
17088 &mut self,
17089 sub: &PerlSub,
17090 args: Vec<PerlValue>,
17091 want: WantarrayCtx,
17092 _line: usize,
17093 ) -> ExecResult {
17094 self.current_sub_stack.push(Arc::new(sub.clone()));
17096
17097 self.scope_push_hook();
17100 self.scope.declare_array("_", args.clone());
17101 if let Some(ref env) = sub.closure_env {
17102 self.scope.restore_capture(env);
17103 }
17104 self.scope.set_closure_args(&args);
17108 let argv = self.scope.take_sub_underscore().unwrap_or_default();
17110 self.apply_sub_signature(sub, &argv, _line)?;
17111 let saved = self.wantarray_kind;
17112 self.wantarray_kind = want;
17113 if let Some(r) = crate::list_util::native_dispatch(self, sub, &argv, want) {
17114 self.wantarray_kind = saved;
17115 self.scope_pop_hook();
17116 self.current_sub_stack.pop();
17117 return match r {
17118 Ok(v) => Ok(v),
17119 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17120 Err(e) => Err(e),
17121 };
17122 }
17123 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
17124 self.wantarray_kind = saved;
17125 self.scope_pop_hook();
17126 self.current_sub_stack.pop();
17127 return match r {
17128 Ok(v) => Ok(v),
17129 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17130 Err(e) => Err(e),
17131 };
17132 }
17133 if let Some(pat) = sub.fib_like.as_ref() {
17134 if argv.len() == 1 {
17135 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
17136 let t0 = self.profiler.is_some().then(std::time::Instant::now);
17137 if let Some(p) = &mut self.profiler {
17138 p.enter_sub(&sub.name);
17139 }
17140 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
17141 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
17142 p.exit_sub(t0.elapsed());
17143 }
17144 self.wantarray_kind = saved;
17145 self.scope_pop_hook();
17146 self.current_sub_stack.pop();
17147 return Ok(PerlValue::integer(n));
17148 }
17149 }
17150 }
17151 self.scope.declare_array("_", argv.clone());
17152 let t0 = self.profiler.is_some().then(std::time::Instant::now);
17155 if let Some(p) = &mut self.profiler {
17156 p.enter_sub(&sub.name);
17157 }
17158 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
17162 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
17163 p.exit_sub(t0.elapsed());
17164 }
17165 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
17167 Some(self.scope.get_array("_"))
17168 } else {
17169 None
17170 };
17171 self.wantarray_kind = saved;
17172 self.scope_pop_hook();
17173 self.current_sub_stack.pop();
17174 match result {
17175 Ok(v) => Ok(v),
17176 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17177 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
17178 let goto_args = goto_args.unwrap_or_default();
17180 let fqn = if target_name.contains("::") {
17181 target_name.clone()
17182 } else {
17183 format!("{}::{}", self.current_package(), target_name)
17184 };
17185 if let Some(target_sub) = self
17186 .subs
17187 .get(&fqn)
17188 .cloned()
17189 .or_else(|| self.subs.get(&target_name).cloned())
17190 {
17191 self.call_sub(&target_sub, goto_args, want, _line)
17192 } else {
17193 Err(
17194 PerlError::runtime(format!("Undefined subroutine &{}", target_name), _line)
17195 .into(),
17196 )
17197 }
17198 }
17199 Err(FlowOrError::Flow(Flow::Yield(_))) => {
17200 Err(PerlError::runtime("yield is only valid inside gen { }", 0).into())
17201 }
17202 Err(e) => Err(e),
17203 }
17204 }
17205
17206 fn call_struct_method(
17208 &mut self,
17209 body: &Block,
17210 params: &[SubSigParam],
17211 args: Vec<PerlValue>,
17212 line: usize,
17213 ) -> ExecResult {
17214 self.scope_push_hook();
17215 self.scope.declare_array("_", args.clone());
17216 if let Some(self_val) = args.first() {
17218 self.scope.declare_scalar("self", self_val.clone());
17219 }
17220 self.scope.set_closure_args(&args);
17222 let user_args: Vec<PerlValue> = args.iter().skip(1).cloned().collect();
17224 self.apply_params_to_argv(params, &user_args, line)?;
17225 let result = self.exec_block_no_scope(body);
17226 self.scope_pop_hook();
17227 match result {
17228 Ok(v) => Ok(v),
17229 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17230 Err(e) => Err(e),
17231 }
17232 }
17233
17234 pub(crate) fn call_class_method(
17236 &mut self,
17237 body: &Block,
17238 params: &[SubSigParam],
17239 args: Vec<PerlValue>,
17240 line: usize,
17241 ) -> ExecResult {
17242 self.call_class_method_inner(body, params, args, line, false)
17243 }
17244
17245 pub(crate) fn call_static_class_method(
17247 &mut self,
17248 body: &Block,
17249 params: &[SubSigParam],
17250 args: Vec<PerlValue>,
17251 line: usize,
17252 ) -> ExecResult {
17253 self.call_class_method_inner(body, params, args, line, true)
17254 }
17255
17256 fn call_class_method_inner(
17257 &mut self,
17258 body: &Block,
17259 params: &[SubSigParam],
17260 args: Vec<PerlValue>,
17261 line: usize,
17262 is_static: bool,
17263 ) -> ExecResult {
17264 self.scope_push_hook();
17265 self.scope.declare_array("_", args.clone());
17266 if !is_static {
17267 if let Some(self_val) = args.first() {
17269 self.scope.declare_scalar("self", self_val.clone());
17270 }
17271 }
17272 self.scope.set_closure_args(&args);
17274 let user_args: Vec<PerlValue> = if is_static {
17276 args.clone()
17277 } else {
17278 args.iter().skip(1).cloned().collect()
17279 };
17280 self.apply_params_to_argv(params, &user_args, line)?;
17281 let result = self.exec_block_no_scope(body);
17282 self.scope_pop_hook();
17283 match result {
17284 Ok(v) => Ok(v),
17285 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17286 Err(e) => Err(e),
17287 }
17288 }
17289
17290 fn apply_params_to_argv(
17292 &mut self,
17293 params: &[SubSigParam],
17294 argv: &[PerlValue],
17295 line: usize,
17296 ) -> PerlResult<()> {
17297 let mut i = 0;
17298 for param in params {
17299 match param {
17300 SubSigParam::Scalar(name, ty_opt) => {
17301 let v = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17302 i += 1;
17303 if let Some(ty) = ty_opt {
17304 ty.check_value(&v).map_err(|msg| {
17305 PerlError::type_error(
17306 format!("method parameter ${}: {}", name, msg),
17307 line,
17308 )
17309 })?;
17310 }
17311 let n = self.english_scalar_name(name);
17312 self.scope.declare_scalar(n, v);
17313 }
17314 SubSigParam::Array(name) => {
17315 let rest: Vec<PerlValue> = argv[i..].to_vec();
17316 i = argv.len();
17317 let aname = self.stash_array_name_for_package(name);
17318 self.scope.declare_array(&aname, rest);
17319 }
17320 SubSigParam::Hash(name) => {
17321 let rest: Vec<PerlValue> = argv[i..].to_vec();
17322 i = argv.len();
17323 let mut map = IndexMap::new();
17324 let mut j = 0;
17325 while j + 1 < rest.len() {
17326 map.insert(rest[j].to_string(), rest[j + 1].clone());
17327 j += 2;
17328 }
17329 self.scope.declare_hash(name, map);
17330 }
17331 SubSigParam::ArrayDestruct(elems) => {
17332 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17333 i += 1;
17334 let Some(arr) = self.match_subject_as_array(&arg) else {
17335 return Err(PerlError::runtime(
17336 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
17337 line,
17338 ));
17339 };
17340 let binds = self
17341 .match_array_pattern_elems(&arr, elems, line)
17342 .map_err(|e| match e {
17343 FlowOrError::Error(stryke) => stryke,
17344 FlowOrError::Flow(_) => {
17345 PerlError::runtime("unexpected flow in method array destruct", line)
17346 }
17347 })?;
17348 let Some(binds) = binds else {
17349 return Err(PerlError::runtime(
17350 format!(
17351 "method parameter: array destructure failed at position {}",
17352 i
17353 ),
17354 line,
17355 ));
17356 };
17357 for b in binds {
17358 match b {
17359 PatternBinding::Scalar(name, v) => {
17360 let n = self.english_scalar_name(&name);
17361 self.scope.declare_scalar(n, v);
17362 }
17363 PatternBinding::Array(name, elems) => {
17364 self.scope.declare_array(&name, elems);
17365 }
17366 }
17367 }
17368 }
17369 SubSigParam::HashDestruct(pairs) => {
17370 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17371 i += 1;
17372 let map = self.hash_for_signature_destruct(&arg, line)?;
17373 for (key, varname) in pairs {
17374 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
17375 let n = self.english_scalar_name(varname);
17376 self.scope.declare_scalar(n, v);
17377 }
17378 }
17379 }
17380 }
17381 Ok(())
17382 }
17383
17384 fn builtin_new(&mut self, class: &str, args: Vec<PerlValue>, line: usize) -> ExecResult {
17385 if class == "Set" {
17386 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
17387 }
17388 if let Some(def) = self.struct_defs.get(class).cloned() {
17389 let mut provided = Vec::new();
17390 let mut i = 1;
17391 while i + 1 < args.len() {
17392 let k = args[i].to_string();
17393 let v = args[i + 1].clone();
17394 provided.push((k, v));
17395 i += 2;
17396 }
17397 let mut defaults = Vec::with_capacity(def.fields.len());
17398 for field in &def.fields {
17399 if let Some(ref expr) = field.default {
17400 let val = self.eval_expr(expr)?;
17401 defaults.push(Some(val));
17402 } else {
17403 defaults.push(None);
17404 }
17405 }
17406 return Ok(crate::native_data::struct_new_with_defaults(
17407 &def, &provided, &defaults, line,
17408 )?);
17409 }
17410 let mut map = IndexMap::new();
17412 let mut i = 1; while i + 1 < args.len() {
17414 let k = args[i].to_string();
17415 let v = args[i + 1].clone();
17416 map.insert(k, v);
17417 i += 2;
17418 }
17419 Ok(PerlValue::blessed(Arc::new(
17420 crate::value::BlessedRef::new_blessed(class.to_string(), PerlValue::hash(map)),
17421 )))
17422 }
17423
17424 fn exec_print(
17425 &mut self,
17426 handle: Option<&str>,
17427 args: &[Expr],
17428 newline: bool,
17429 line: usize,
17430 ) -> ExecResult {
17431 if newline && (self.feature_bits & FEAT_SAY) == 0 {
17432 return Err(PerlError::runtime(
17433 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
17434 line,
17435 )
17436 .into());
17437 }
17438 let mut output = String::new();
17439 if args.is_empty() {
17440 let topic = self.scope.get_scalar("_").clone();
17442 let s = self.stringify_value(topic, line)?;
17443 output.push_str(&s);
17444 } else {
17445 for (i, a) in args.iter().enumerate() {
17448 if i > 0 {
17449 output.push_str(&self.ofs);
17450 }
17451 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17452 for item in val.to_list() {
17453 let s = self.stringify_value(item, line)?;
17454 output.push_str(&s);
17455 }
17456 }
17457 }
17458 if newline {
17459 output.push('\n');
17460 }
17461 output.push_str(&self.ors);
17462
17463 let handle_name =
17464 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17465 self.write_formatted_print(handle_name.as_str(), &output, line)?;
17466 Ok(PerlValue::integer(1))
17467 }
17468
17469 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
17470 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
17471 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
17473 (s, &[])
17474 } else {
17475 (self.eval_expr(&args[0])?.to_string(), &args[1..])
17476 };
17477 let mut arg_vals = Vec::new();
17481 for a in rest {
17482 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17483 if let Some(items) = v.as_array_vec() {
17484 arg_vals.extend(items);
17485 } else {
17486 arg_vals.push(v);
17487 }
17488 }
17489 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
17490 let handle_name =
17491 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17492 match handle_name.as_str() {
17493 "STDOUT" => {
17494 if !self.suppress_stdout {
17495 print!("{}", output);
17496 if self.output_autoflush {
17497 let _ = io::stdout().flush();
17498 }
17499 }
17500 }
17501 "STDERR" => {
17502 eprint!("{}", output);
17503 let _ = io::stderr().flush();
17504 }
17505 name => {
17506 if let Some(writer) = self.output_handles.get_mut(name) {
17507 let _ = writer.write_all(output.as_bytes());
17508 if self.output_autoflush {
17509 let _ = writer.flush();
17510 }
17511 }
17512 }
17513 }
17514 Ok(PerlValue::integer(1))
17515 }
17516
17517 pub(crate) fn eval_substr_expr(
17519 &mut self,
17520 string: &Expr,
17521 offset: &Expr,
17522 length: Option<&Expr>,
17523 replacement: Option<&Expr>,
17524 _line: usize,
17525 ) -> Result<PerlValue, FlowOrError> {
17526 let s = self.eval_expr(string)?.to_string();
17527 let off = self.eval_expr(offset)?.to_int();
17528 let start = if off < 0 {
17529 (s.len() as i64 + off).max(0) as usize
17530 } else {
17531 off as usize
17532 };
17533 let len = if let Some(l) = length {
17534 let len_val = self.eval_expr(l)?.to_int();
17535 if len_val < 0 {
17536 let remaining = s.len().saturating_sub(start) as i64;
17538 (remaining + len_val).max(0) as usize
17539 } else {
17540 len_val as usize
17541 }
17542 } else {
17543 s.len().saturating_sub(start)
17544 };
17545 let end = start.saturating_add(len).min(s.len());
17546 let result = s.get(start..end).unwrap_or("").to_string();
17547 if let Some(rep) = replacement {
17548 let rep_s = self.eval_expr(rep)?.to_string();
17549 let mut new_s = String::new();
17550 new_s.push_str(&s[..start]);
17551 new_s.push_str(&rep_s);
17552 new_s.push_str(&s[end..]);
17553 self.assign_value(string, PerlValue::string(new_s))?;
17554 }
17555 Ok(PerlValue::string(result))
17556 }
17557
17558 pub(crate) fn eval_push_expr(
17559 &mut self,
17560 array: &Expr,
17561 values: &[Expr],
17562 line: usize,
17563 ) -> Result<PerlValue, FlowOrError> {
17564 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17565 for v in values {
17566 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17567 self.push_array_deref_value(aref.clone(), val, line)?;
17568 }
17569 let len = self.array_deref_len(aref, line)?;
17570 return Ok(PerlValue::integer(len));
17571 }
17572 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17573 if self.scope.is_array_frozen(&arr_name) {
17574 return Err(PerlError::runtime(
17575 format!("Modification of a frozen value: @{}", arr_name),
17576 line,
17577 )
17578 .into());
17579 }
17580 for v in values {
17581 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17582 if let Some(items) = val.as_array_vec() {
17583 for item in items {
17584 self.scope
17585 .push_to_array(&arr_name, item)
17586 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17587 }
17588 } else {
17589 self.scope
17590 .push_to_array(&arr_name, val)
17591 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17592 }
17593 }
17594 let len = self.scope.array_len(&arr_name);
17595 Ok(PerlValue::integer(len as i64))
17596 }
17597
17598 pub(crate) fn eval_pop_expr(
17599 &mut self,
17600 array: &Expr,
17601 line: usize,
17602 ) -> Result<PerlValue, FlowOrError> {
17603 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17604 return self.pop_array_deref(aref, line);
17605 }
17606 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17607 self.scope
17608 .pop_from_array(&arr_name)
17609 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17610 }
17611
17612 pub(crate) fn eval_shift_expr(
17613 &mut self,
17614 array: &Expr,
17615 line: usize,
17616 ) -> Result<PerlValue, FlowOrError> {
17617 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17618 return self.shift_array_deref(aref, line);
17619 }
17620 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17621 self.scope
17622 .shift_from_array(&arr_name)
17623 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17624 }
17625
17626 pub(crate) fn eval_unshift_expr(
17627 &mut self,
17628 array: &Expr,
17629 values: &[Expr],
17630 line: usize,
17631 ) -> Result<PerlValue, FlowOrError> {
17632 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17633 let mut vals = Vec::new();
17634 for v in values {
17635 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17636 if let Some(items) = val.as_array_vec() {
17637 vals.extend(items);
17638 } else {
17639 vals.push(val);
17640 }
17641 }
17642 let len = self.unshift_array_deref_multi(aref, vals, line)?;
17643 return Ok(PerlValue::integer(len));
17644 }
17645 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17646 let mut vals = Vec::new();
17647 for v in values {
17648 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17649 if let Some(items) = val.as_array_vec() {
17650 vals.extend(items);
17651 } else {
17652 vals.push(val);
17653 }
17654 }
17655 let arr = self
17656 .scope
17657 .get_array_mut(&arr_name)
17658 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17659 for (i, v) in vals.into_iter().enumerate() {
17660 arr.insert(i, v);
17661 }
17662 let len = arr.len();
17663 Ok(PerlValue::integer(len as i64))
17664 }
17665
17666 pub(crate) fn push_array_deref_value(
17668 &mut self,
17669 arr_ref: PerlValue,
17670 val: PerlValue,
17671 line: usize,
17672 ) -> Result<(), FlowOrError> {
17673 if let Some(r) = arr_ref.as_array_ref() {
17674 let mut w = r.write();
17675 if let Some(items) = val.as_array_vec() {
17676 w.extend(items.iter().cloned());
17677 } else {
17678 w.push(val);
17679 }
17680 return Ok(());
17681 }
17682 if let Some(name) = arr_ref.as_array_binding_name() {
17683 if let Some(items) = val.as_array_vec() {
17684 for item in items {
17685 self.scope
17686 .push_to_array(&name, item)
17687 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17688 }
17689 } else {
17690 self.scope
17691 .push_to_array(&name, val)
17692 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17693 }
17694 return Ok(());
17695 }
17696 if let Some(s) = arr_ref.as_str() {
17697 if self.strict_refs {
17698 return Err(PerlError::runtime(
17699 format!(
17700 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17701 s
17702 ),
17703 line,
17704 )
17705 .into());
17706 }
17707 let name = s.to_string();
17708 if let Some(items) = val.as_array_vec() {
17709 for item in items {
17710 self.scope
17711 .push_to_array(&name, item)
17712 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17713 }
17714 } else {
17715 self.scope
17716 .push_to_array(&name, val)
17717 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17718 }
17719 return Ok(());
17720 }
17721 Err(PerlError::runtime("push argument is not an ARRAY reference", line).into())
17722 }
17723
17724 pub(crate) fn array_deref_len(
17725 &self,
17726 arr_ref: PerlValue,
17727 line: usize,
17728 ) -> Result<i64, FlowOrError> {
17729 if let Some(r) = arr_ref.as_array_ref() {
17730 return Ok(r.read().len() as i64);
17731 }
17732 if let Some(name) = arr_ref.as_array_binding_name() {
17733 return Ok(self.scope.array_len(&name) as i64);
17734 }
17735 if let Some(s) = arr_ref.as_str() {
17736 if self.strict_refs {
17737 return Err(PerlError::runtime(
17738 format!(
17739 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17740 s
17741 ),
17742 line,
17743 )
17744 .into());
17745 }
17746 return Ok(self.scope.array_len(&s) as i64);
17747 }
17748 Err(PerlError::runtime("argument is not an ARRAY reference", line).into())
17749 }
17750
17751 pub(crate) fn pop_array_deref(
17752 &mut self,
17753 arr_ref: PerlValue,
17754 line: usize,
17755 ) -> Result<PerlValue, FlowOrError> {
17756 if let Some(r) = arr_ref.as_array_ref() {
17757 let mut w = r.write();
17758 return Ok(w.pop().unwrap_or(PerlValue::UNDEF));
17759 }
17760 if let Some(name) = arr_ref.as_array_binding_name() {
17761 return self
17762 .scope
17763 .pop_from_array(&name)
17764 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17765 }
17766 if let Some(s) = arr_ref.as_str() {
17767 if self.strict_refs {
17768 return Err(PerlError::runtime(
17769 format!(
17770 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17771 s
17772 ),
17773 line,
17774 )
17775 .into());
17776 }
17777 return self
17778 .scope
17779 .pop_from_array(&s)
17780 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17781 }
17782 Err(PerlError::runtime("pop argument is not an ARRAY reference", line).into())
17783 }
17784
17785 pub(crate) fn shift_array_deref(
17786 &mut self,
17787 arr_ref: PerlValue,
17788 line: usize,
17789 ) -> Result<PerlValue, FlowOrError> {
17790 if let Some(r) = arr_ref.as_array_ref() {
17791 let mut w = r.write();
17792 return Ok(if w.is_empty() {
17793 PerlValue::UNDEF
17794 } else {
17795 w.remove(0)
17796 });
17797 }
17798 if let Some(name) = arr_ref.as_array_binding_name() {
17799 return self
17800 .scope
17801 .shift_from_array(&name)
17802 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17803 }
17804 if let Some(s) = arr_ref.as_str() {
17805 if self.strict_refs {
17806 return Err(PerlError::runtime(
17807 format!(
17808 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17809 s
17810 ),
17811 line,
17812 )
17813 .into());
17814 }
17815 return self
17816 .scope
17817 .shift_from_array(&s)
17818 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17819 }
17820 Err(PerlError::runtime("shift argument is not an ARRAY reference", line).into())
17821 }
17822
17823 pub(crate) fn unshift_array_deref_multi(
17824 &mut self,
17825 arr_ref: PerlValue,
17826 vals: Vec<PerlValue>,
17827 line: usize,
17828 ) -> Result<i64, FlowOrError> {
17829 let mut flat: Vec<PerlValue> = Vec::new();
17830 for v in vals {
17831 if let Some(items) = v.as_array_vec() {
17832 flat.extend(items);
17833 } else {
17834 flat.push(v);
17835 }
17836 }
17837 if let Some(r) = arr_ref.as_array_ref() {
17838 let mut w = r.write();
17839 for (i, v) in flat.into_iter().enumerate() {
17840 w.insert(i, v);
17841 }
17842 return Ok(w.len() as i64);
17843 }
17844 if let Some(name) = arr_ref.as_array_binding_name() {
17845 let arr = self
17846 .scope
17847 .get_array_mut(&name)
17848 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17849 for (i, v) in flat.into_iter().enumerate() {
17850 arr.insert(i, v);
17851 }
17852 return Ok(arr.len() as i64);
17853 }
17854 if let Some(s) = arr_ref.as_str() {
17855 if self.strict_refs {
17856 return Err(PerlError::runtime(
17857 format!(
17858 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17859 s
17860 ),
17861 line,
17862 )
17863 .into());
17864 }
17865 let name = s.to_string();
17866 let arr = self
17867 .scope
17868 .get_array_mut(&name)
17869 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17870 for (i, v) in flat.into_iter().enumerate() {
17871 arr.insert(i, v);
17872 }
17873 return Ok(arr.len() as i64);
17874 }
17875 Err(PerlError::runtime("unshift argument is not an ARRAY reference", line).into())
17876 }
17877
17878 pub(crate) fn splice_array_deref(
17881 &mut self,
17882 aref: PerlValue,
17883 offset_val: PerlValue,
17884 length_val: PerlValue,
17885 rep_vals: Vec<PerlValue>,
17886 line: usize,
17887 ) -> Result<PerlValue, FlowOrError> {
17888 let ctx = self.wantarray_kind;
17889 if let Some(r) = aref.as_array_ref() {
17890 let arr_len = r.read().len();
17891 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17892 let mut w = r.write();
17893 let removed: Vec<PerlValue> = w.drain(off..end).collect();
17894 for (i, v) in rep_vals.into_iter().enumerate() {
17895 w.insert(off + i, v);
17896 }
17897 return Ok(match ctx {
17898 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17899 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17900 });
17901 }
17902 if let Some(name) = aref.as_array_binding_name() {
17903 let arr_len = self.scope.array_len(&name);
17904 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17905 let arr = self
17906 .scope
17907 .get_array_mut(&name)
17908 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17909 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
17910 for (i, v) in rep_vals.into_iter().enumerate() {
17911 arr.insert(off + i, v);
17912 }
17913 return Ok(match ctx {
17914 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17915 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17916 });
17917 }
17918 if let Some(s) = aref.as_str() {
17919 if self.strict_refs {
17920 return Err(PerlError::runtime(
17921 format!(
17922 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17923 s
17924 ),
17925 line,
17926 )
17927 .into());
17928 }
17929 let arr_len = self.scope.array_len(&s);
17930 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17931 let arr = self
17932 .scope
17933 .get_array_mut(&s)
17934 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17935 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
17936 for (i, v) in rep_vals.into_iter().enumerate() {
17937 arr.insert(off + i, v);
17938 }
17939 return Ok(match ctx {
17940 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17941 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17942 });
17943 }
17944 Err(PerlError::runtime("splice argument is not an ARRAY reference", line).into())
17945 }
17946
17947 pub(crate) fn eval_splice_expr(
17948 &mut self,
17949 array: &Expr,
17950 offset: Option<&Expr>,
17951 length: Option<&Expr>,
17952 replacement: &[Expr],
17953 ctx: WantarrayCtx,
17954 line: usize,
17955 ) -> Result<PerlValue, FlowOrError> {
17956 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17957 let offset_val = if let Some(o) = offset {
17958 self.eval_expr(o)?
17959 } else {
17960 PerlValue::integer(0)
17961 };
17962 let length_val = if let Some(l) = length {
17963 self.eval_expr(l)?
17964 } else {
17965 PerlValue::UNDEF
17966 };
17967 let mut rep_vals = Vec::new();
17968 for r in replacement {
17969 rep_vals.push(self.eval_expr(r)?);
17970 }
17971 let saved = self.wantarray_kind;
17972 self.wantarray_kind = ctx;
17973 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
17974 self.wantarray_kind = saved;
17975 return out;
17976 }
17977 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17978 let arr_len = self.scope.array_len(&arr_name);
17979 let offset_val = if let Some(o) = offset {
17980 self.eval_expr(o)?
17981 } else {
17982 PerlValue::integer(0)
17983 };
17984 let length_val = if let Some(l) = length {
17985 self.eval_expr(l)?
17986 } else {
17987 PerlValue::UNDEF
17988 };
17989 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17990 let mut rep_vals = Vec::new();
17991 for r in replacement {
17992 rep_vals.push(self.eval_expr(r)?);
17993 }
17994 let arr = self
17995 .scope
17996 .get_array_mut(&arr_name)
17997 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17998 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
17999 for (i, v) in rep_vals.into_iter().enumerate() {
18000 arr.insert(off + i, v);
18001 }
18002 Ok(match ctx {
18003 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
18004 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
18005 })
18006 }
18007
18008 pub(crate) fn keys_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
18010 if let Some(h) = val.as_hash_map() {
18011 Ok(PerlValue::array(
18012 h.keys().map(|k| PerlValue::string(k.clone())).collect(),
18013 ))
18014 } else if let Some(r) = val.as_hash_ref() {
18015 Ok(PerlValue::array(
18016 r.read()
18017 .keys()
18018 .map(|k| PerlValue::string(k.clone()))
18019 .collect(),
18020 ))
18021 } else {
18022 Err(PerlError::runtime("keys requires hash", line).into())
18023 }
18024 }
18025
18026 pub(crate) fn eval_keys_expr(
18027 &mut self,
18028 expr: &Expr,
18029 line: usize,
18030 ) -> Result<PerlValue, FlowOrError> {
18031 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
18034 Self::keys_from_value(val, line)
18035 }
18036
18037 pub(crate) fn values_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
18039 if let Some(h) = val.as_hash_map() {
18040 Ok(PerlValue::array(h.values().cloned().collect()))
18041 } else if let Some(r) = val.as_hash_ref() {
18042 Ok(PerlValue::array(r.read().values().cloned().collect()))
18043 } else {
18044 Err(PerlError::runtime("values requires hash", line).into())
18045 }
18046 }
18047
18048 pub(crate) fn eval_values_expr(
18049 &mut self,
18050 expr: &Expr,
18051 line: usize,
18052 ) -> Result<PerlValue, FlowOrError> {
18053 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
18054 Self::values_from_value(val, line)
18055 }
18056
18057 pub(crate) fn eval_delete_operand(
18058 &mut self,
18059 expr: &Expr,
18060 line: usize,
18061 ) -> Result<PerlValue, FlowOrError> {
18062 match &expr.kind {
18063 ExprKind::HashElement { hash, key } => {
18064 let k = self.eval_expr(key)?.to_string();
18065 self.touch_env_hash(hash);
18066 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
18067 let class = obj
18068 .as_blessed_ref()
18069 .map(|b| b.class.clone())
18070 .unwrap_or_default();
18071 let full = format!("{}::DELETE", class);
18072 if let Some(sub) = self.subs.get(&full).cloned() {
18073 return self.call_sub(
18074 &sub,
18075 vec![obj, PerlValue::string(k)],
18076 WantarrayCtx::Scalar,
18077 line,
18078 );
18079 }
18080 }
18081 self.scope
18082 .delete_hash_element(hash, &k)
18083 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18084 }
18085 ExprKind::ArrayElement { array, index } => {
18086 self.check_strict_array_var(array, line)?;
18087 let idx = self.eval_expr(index)?.to_int();
18088 let aname = self.stash_array_name_for_package(array);
18089 self.scope
18090 .delete_array_element(&aname, idx)
18091 .map_err(|e| FlowOrError::Error(e.at_line(line)))
18092 }
18093 ExprKind::ArrowDeref {
18094 expr: inner,
18095 index,
18096 kind: DerefKind::Hash,
18097 } => {
18098 let k = self.eval_expr(index)?.to_string();
18099 let container = self.eval_expr(inner)?;
18100 self.delete_arrow_hash_element(container, &k, line)
18101 .map_err(Into::into)
18102 }
18103 ExprKind::ArrowDeref {
18104 expr: inner,
18105 index,
18106 kind: DerefKind::Array,
18107 } => {
18108 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
18109 return Err(PerlError::runtime(
18110 "delete on array element needs scalar subscript",
18111 line,
18112 )
18113 .into());
18114 }
18115 let container = self.eval_expr(inner)?;
18116 let idx = self.eval_expr(index)?.to_int();
18117 self.delete_arrow_array_element(container, idx, line)
18118 .map_err(Into::into)
18119 }
18120 _ => Err(PerlError::runtime("delete requires hash or array element", line).into()),
18121 }
18122 }
18123
18124 pub(crate) fn eval_exists_operand(
18125 &mut self,
18126 expr: &Expr,
18127 line: usize,
18128 ) -> Result<PerlValue, FlowOrError> {
18129 match &expr.kind {
18130 ExprKind::HashElement { hash, key } => {
18131 let k = self.eval_expr(key)?.to_string();
18132 self.touch_env_hash(hash);
18133 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
18134 let class = obj
18135 .as_blessed_ref()
18136 .map(|b| b.class.clone())
18137 .unwrap_or_default();
18138 let full = format!("{}::EXISTS", class);
18139 if let Some(sub) = self.subs.get(&full).cloned() {
18140 return self.call_sub(
18141 &sub,
18142 vec![obj, PerlValue::string(k)],
18143 WantarrayCtx::Scalar,
18144 line,
18145 );
18146 }
18147 }
18148 Ok(PerlValue::integer(
18149 if self.scope.exists_hash_element(hash, &k) {
18150 1
18151 } else {
18152 0
18153 },
18154 ))
18155 }
18156 ExprKind::ArrayElement { array, index } => {
18157 self.check_strict_array_var(array, line)?;
18158 let idx = self.eval_expr(index)?.to_int();
18159 let aname = self.stash_array_name_for_package(array);
18160 Ok(PerlValue::integer(
18161 if self.scope.exists_array_element(&aname, idx) {
18162 1
18163 } else {
18164 0
18165 },
18166 ))
18167 }
18168 ExprKind::ArrowDeref {
18169 expr: inner,
18170 index,
18171 kind: DerefKind::Hash,
18172 } => {
18173 let k = self.eval_expr(index)?.to_string();
18174 let container = self.eval_expr(inner)?;
18175 let yes = self.exists_arrow_hash_element(container, &k, line)?;
18176 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
18177 }
18178 ExprKind::ArrowDeref {
18179 expr: inner,
18180 index,
18181 kind: DerefKind::Array,
18182 } => {
18183 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
18184 return Err(PerlError::runtime(
18185 "exists on array element needs scalar subscript",
18186 line,
18187 )
18188 .into());
18189 }
18190 let container = self.eval_expr(inner)?;
18191 let idx = self.eval_expr(index)?.to_int();
18192 let yes = self.exists_arrow_array_element(container, idx, line)?;
18193 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
18194 }
18195 _ => Err(PerlError::runtime("exists requires hash or array element", line).into()),
18196 }
18197 }
18198
18199 pub(crate) fn eval_pmap_remote(
18207 &mut self,
18208 cluster_pv: PerlValue,
18209 list_pv: PerlValue,
18210 show_progress: bool,
18211 block: &Block,
18212 flat_outputs: bool,
18213 line: usize,
18214 ) -> Result<PerlValue, FlowOrError> {
18215 let Some(cluster) = cluster_pv.as_remote_cluster() else {
18216 return Err(PerlError::runtime("pmap_on: expected cluster(...) value", line).into());
18217 };
18218 let items = list_pv.to_list();
18219 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18220 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
18221 return Err(PerlError::runtime(
18222 "pmap_on: mysync/atomic capture is not supported for remote workers",
18223 line,
18224 )
18225 .into());
18226 }
18227 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
18228 .map_err(|e| PerlError::runtime(e, line))?;
18229 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
18230 let block_src = crate::fmt::format_block(block);
18231 let item_jsons =
18232 crate::cluster::perl_items_to_json(&items).map_err(|e| PerlError::runtime(e, line))?;
18233
18234 let pmap_progress = PmapProgress::new(show_progress, items.len());
18237 let result_values =
18238 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
18239 .map_err(|e| PerlError::runtime(format!("pmap_on remote: {e}"), line))?;
18240 for _ in 0..result_values.len() {
18241 pmap_progress.tick();
18242 }
18243 pmap_progress.finish();
18244
18245 if flat_outputs {
18246 let flattened: Vec<PerlValue> = result_values
18247 .into_iter()
18248 .flat_map(|v| v.map_flatten_outputs(true))
18249 .collect();
18250 Ok(PerlValue::array(flattened))
18251 } else {
18252 Ok(PerlValue::array(result_values))
18253 }
18254 }
18255
18256 pub(crate) fn eval_par_lines_expr(
18258 &mut self,
18259 path: &Expr,
18260 callback: &Expr,
18261 progress: Option<&Expr>,
18262 line: usize,
18263 ) -> Result<PerlValue, FlowOrError> {
18264 let show_progress = progress
18265 .map(|p| self.eval_expr(p))
18266 .transpose()?
18267 .map(|v| v.is_true())
18268 .unwrap_or(false);
18269 let path_s = self.eval_expr(path)?.to_string();
18270 let cb_val = self.eval_expr(callback)?;
18271 let sub = if let Some(s) = cb_val.as_code_ref() {
18272 s
18273 } else {
18274 return Err(PerlError::runtime(
18275 "par_lines: second argument must be a code reference",
18276 line,
18277 )
18278 .into());
18279 };
18280 let subs = self.subs.clone();
18281 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18282 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
18283 FlowOrError::Error(PerlError::runtime(format!("par_lines: {}", e), line))
18284 })?;
18285 let mmap = unsafe {
18286 memmap2::Mmap::map(&file).map_err(|e| {
18287 FlowOrError::Error(PerlError::runtime(format!("par_lines: mmap: {}", e), line))
18288 })?
18289 };
18290 let data: &[u8] = &mmap;
18291 if data.is_empty() {
18292 return Ok(PerlValue::UNDEF);
18293 }
18294 let line_total = crate::par_lines::line_count_bytes(data);
18295 let pmap_progress = PmapProgress::new(show_progress, line_total);
18296 if self.num_threads == 0 {
18297 self.num_threads = rayon::current_num_threads();
18298 }
18299 let num_chunks = self.num_threads.saturating_mul(8).max(1);
18300 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
18301 chunks.into_par_iter().try_for_each(|(start, end)| {
18302 let slice = &data[start..end];
18303 let mut s = 0usize;
18304 while s < slice.len() {
18305 let e = slice[s..]
18306 .iter()
18307 .position(|&b| b == b'\n')
18308 .map(|p| s + p)
18309 .unwrap_or(slice.len());
18310 let line_bytes = &slice[s..e];
18311 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
18312 let mut local_interp = Interpreter::new();
18313 local_interp.subs = subs.clone();
18314 local_interp.scope.restore_capture(&scope_capture);
18315 local_interp
18316 .scope
18317 .restore_atomics(&atomic_arrays, &atomic_hashes);
18318 local_interp.enable_parallel_guard();
18319 local_interp.scope.set_topic(PerlValue::string(line_str));
18320 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
18321 Ok(_) => {}
18322 Err(e) => return Err(e),
18323 }
18324 pmap_progress.tick();
18325 if e >= slice.len() {
18326 break;
18327 }
18328 s = e + 1;
18329 }
18330 Ok(())
18331 })?;
18332 pmap_progress.finish();
18333 Ok(PerlValue::UNDEF)
18334 }
18335
18336 pub(crate) fn eval_par_walk_expr(
18338 &mut self,
18339 path: &Expr,
18340 callback: &Expr,
18341 progress: Option<&Expr>,
18342 line: usize,
18343 ) -> Result<PerlValue, FlowOrError> {
18344 let show_progress = progress
18345 .map(|p| self.eval_expr(p))
18346 .transpose()?
18347 .map(|v| v.is_true())
18348 .unwrap_or(false);
18349 let path_val = self.eval_expr(path)?;
18350 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
18351 arr.into_iter()
18352 .map(|v| PathBuf::from(v.to_string()))
18353 .collect()
18354 } else {
18355 vec![PathBuf::from(path_val.to_string())]
18356 };
18357 let cb_val = self.eval_expr(callback)?;
18358 let sub = if let Some(s) = cb_val.as_code_ref() {
18359 s
18360 } else {
18361 return Err(PerlError::runtime(
18362 "par_walk: second argument must be a code reference",
18363 line,
18364 )
18365 .into());
18366 };
18367 let subs = self.subs.clone();
18368 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18369
18370 if show_progress {
18371 let paths = crate::par_walk::collect_paths(&roots);
18372 let pmap_progress = PmapProgress::new(true, paths.len());
18373 paths.into_par_iter().try_for_each(|p| {
18374 let s = p.to_string_lossy().into_owned();
18375 let mut local_interp = Interpreter::new();
18376 local_interp.subs = subs.clone();
18377 local_interp.scope.restore_capture(&scope_capture);
18378 local_interp
18379 .scope
18380 .restore_atomics(&atomic_arrays, &atomic_hashes);
18381 local_interp.enable_parallel_guard();
18382 local_interp.scope.set_topic(PerlValue::string(s));
18383 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
18384 Ok(_) => {}
18385 Err(e) => return Err(e),
18386 }
18387 pmap_progress.tick();
18388 Ok(())
18389 })?;
18390 pmap_progress.finish();
18391 } else {
18392 for r in &roots {
18393 par_walk_recursive(
18394 r.as_path(),
18395 &sub,
18396 &subs,
18397 &scope_capture,
18398 &atomic_arrays,
18399 &atomic_hashes,
18400 line,
18401 )?;
18402 }
18403 }
18404 Ok(PerlValue::UNDEF)
18405 }
18406
18407 pub(crate) fn builtin_par_sed(
18409 &mut self,
18410 args: &[PerlValue],
18411 line: usize,
18412 has_progress: bool,
18413 ) -> PerlResult<PerlValue> {
18414 let show_progress = if has_progress {
18415 args.last().map(|v| v.is_true()).unwrap_or(false)
18416 } else {
18417 false
18418 };
18419 let slice = if has_progress {
18420 &args[..args.len().saturating_sub(1)]
18421 } else {
18422 args
18423 };
18424 if slice.len() < 3 {
18425 return Err(PerlError::runtime(
18426 "par_sed: need pattern, replacement, and at least one file path",
18427 line,
18428 ));
18429 }
18430 let pat_val = &slice[0];
18431 let repl = slice[1].to_string();
18432 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
18433
18434 let re = if let Some(rx) = pat_val.as_regex() {
18435 rx
18436 } else {
18437 let pattern = pat_val.to_string();
18438 match self.compile_regex(&pattern, "g", line) {
18439 Ok(r) => r,
18440 Err(FlowOrError::Error(e)) => return Err(e),
18441 Err(FlowOrError::Flow(f)) => {
18442 return Err(PerlError::runtime(format!("par_sed: {:?}", f), line))
18443 }
18444 }
18445 };
18446
18447 let pmap = PmapProgress::new(show_progress, files.len());
18448 let touched = AtomicUsize::new(0);
18449 files.par_iter().try_for_each(|path| {
18450 let content = read_file_text_perl_compat(path)
18451 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18452 let new_s = re.replace_all(&content, &repl);
18453 if new_s != content {
18454 std::fs::write(path, new_s.as_bytes())
18455 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18456 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
18457 }
18458 pmap.tick();
18459 Ok(())
18460 })?;
18461 pmap.finish();
18462 Ok(PerlValue::integer(
18463 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
18464 ))
18465 }
18466
18467 pub(crate) fn eval_pwatch_expr(
18469 &mut self,
18470 path: &Expr,
18471 callback: &Expr,
18472 line: usize,
18473 ) -> Result<PerlValue, FlowOrError> {
18474 let pattern_s = self.eval_expr(path)?.to_string();
18475 let cb_val = self.eval_expr(callback)?;
18476 let sub = if let Some(s) = cb_val.as_code_ref() {
18477 s
18478 } else {
18479 return Err(PerlError::runtime(
18480 "pwatch: second argument must be a code reference",
18481 line,
18482 )
18483 .into());
18484 };
18485 let subs = self.subs.clone();
18486 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18487 crate::pwatch::run_pwatch(
18488 &pattern_s,
18489 sub,
18490 subs,
18491 scope_capture,
18492 atomic_arrays,
18493 atomic_hashes,
18494 line,
18495 )
18496 .map_err(FlowOrError::Error)
18497 }
18498
18499 fn interpolate_replacement_string(&self, replacement: &str) -> String {
18501 let mut out = String::with_capacity(replacement.len());
18502 let chars: Vec<char> = replacement.chars().collect();
18503 let mut i = 0;
18504 while i < chars.len() {
18505 if chars[i] == '\\' && i + 1 < chars.len() {
18506 out.push(chars[i]);
18507 out.push(chars[i + 1]);
18508 i += 2;
18509 continue;
18510 }
18511 if chars[i] == '$' && i + 1 < chars.len() {
18512 let start = i;
18513 i += 1;
18514 if chars[i].is_ascii_digit() {
18515 out.push('$');
18516 while i < chars.len() && chars[i].is_ascii_digit() {
18517 out.push(chars[i]);
18518 i += 1;
18519 }
18520 continue;
18521 }
18522 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
18523 out.push('$');
18524 out.push(chars[i]);
18525 i += 1;
18526 continue;
18527 }
18528 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
18529 out.push('$');
18530 continue;
18531 }
18532 let mut name = String::new();
18533 if chars[i] == '{' {
18534 i += 1;
18535 while i < chars.len() && chars[i] != '}' {
18536 name.push(chars[i]);
18537 i += 1;
18538 }
18539 if i < chars.len() {
18540 i += 1;
18541 }
18542 } else {
18543 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18544 name.push(chars[i]);
18545 i += 1;
18546 }
18547 }
18548 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
18549 let val = self.scope.get_scalar(&name);
18550 out.push_str(&val.to_string());
18551 } else if !name.is_empty() {
18552 out.push_str(&replacement[start..i]);
18553 } else {
18554 out.push('$');
18555 }
18556 continue;
18557 }
18558 out.push(chars[i]);
18559 i += 1;
18560 }
18561 out
18562 }
18563
18564 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
18566 let mut out = String::with_capacity(pattern.len());
18567 let chars: Vec<char> = pattern.chars().collect();
18568 let mut i = 0;
18569 while i < chars.len() {
18570 if chars[i] == '\\' && i + 1 < chars.len() {
18571 out.push(chars[i]);
18573 out.push(chars[i + 1]);
18574 i += 2;
18575 continue;
18576 }
18577 if chars[i] == '$' && i + 1 < chars.len() {
18578 i += 1;
18579 if i >= chars.len()
18581 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
18582 {
18583 out.push('$');
18584 continue;
18585 }
18586 let mut name = String::new();
18587 if chars[i] == '{' {
18588 i += 1;
18589 while i < chars.len() && chars[i] != '}' {
18590 name.push(chars[i]);
18591 i += 1;
18592 }
18593 if i < chars.len() {
18594 i += 1;
18595 } } else {
18597 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18598 name.push(chars[i]);
18599 i += 1;
18600 }
18601 }
18602 if !name.is_empty() {
18603 let val = self.scope.get_scalar(&name);
18604 out.push_str(&val.to_string());
18605 } else {
18606 out.push('$');
18607 }
18608 continue;
18609 }
18610 out.push(chars[i]);
18611 i += 1;
18612 }
18613 out
18614 }
18615
18616 pub(crate) fn compile_regex(
18617 &mut self,
18618 pattern: &str,
18619 flags: &str,
18620 line: usize,
18621 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
18622 let pattern = if pattern.contains('$') || pattern.contains('@') {
18624 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
18625 } else {
18626 std::borrow::Cow::Borrowed(pattern)
18627 };
18628 let pattern = pattern.as_ref();
18629 let multiline = self.multiline_match;
18632 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
18633 if lp == pattern && lf == flags && *lm == multiline {
18634 return Ok(lr.clone());
18635 }
18636 }
18637 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
18639 if let Some(cached) = self.regex_cache.get(&key) {
18640 self.regex_last = Some((
18641 pattern.to_string(),
18642 flags.to_string(),
18643 multiline,
18644 cached.clone(),
18645 ));
18646 return Ok(cached.clone());
18647 }
18648 let expanded = expand_perl_regex_quotemeta(pattern);
18649 let expanded = expand_perl_regex_octal_escapes(&expanded);
18650 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
18651 let mut re_str = String::new();
18652 if flags.contains('i') {
18653 re_str.push_str("(?i)");
18654 }
18655 if flags.contains('s') {
18656 re_str.push_str("(?s)");
18657 }
18658 if flags.contains('m') {
18659 re_str.push_str("(?m)");
18660 }
18661 if flags.contains('x') {
18662 re_str.push_str("(?x)");
18663 }
18664 if multiline {
18666 re_str.push_str("(?s)");
18667 }
18668 re_str.push_str(&expanded);
18669 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
18670 FlowOrError::Error(PerlError::runtime(
18671 format!("Invalid regex /{}/: {}", pattern, e),
18672 line,
18673 ))
18674 })?;
18675 let arc = re;
18676 self.regex_last = Some((
18677 pattern.to_string(),
18678 flags.to_string(),
18679 multiline,
18680 arc.clone(),
18681 ));
18682 self.regex_cache.insert(key, arc.clone());
18683 Ok(arc)
18684 }
18685
18686 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
18688 if self.last_readline_handle.is_empty() {
18689 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
18690 }
18691 let n = *self
18692 .handle_line_numbers
18693 .get(&self.last_readline_handle)
18694 .unwrap_or(&0);
18695 if n <= 0 {
18696 return None;
18697 }
18698 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
18699 {
18700 return Some(("<>".to_string(), n));
18701 }
18702 if self.last_readline_handle == "STDIN" {
18703 return Some((self.last_stdin_die_bracket.clone(), n));
18704 }
18705 Some((format!("<{}>", self.last_readline_handle), n))
18706 }
18707
18708 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
18710 let mut s = format!(" at {} line {}", self.file, source_line);
18711 if let Some((bracket, n)) = self.die_warn_io_annotation() {
18712 s.push_str(&format!(", {} line {}.", bracket, n));
18713 } else {
18714 s.push('.');
18715 }
18716 s
18717 }
18718
18719 pub fn process_line(
18724 &mut self,
18725 line_str: &str,
18726 program: &Program,
18727 is_last_input_line: bool,
18728 ) -> PerlResult<Option<String>> {
18729 self.line_mode_eof_pending = is_last_input_line;
18730 let result: PerlResult<Option<String>> = (|| {
18731 self.line_number += 1;
18732 self.scope
18733 .set_topic(PerlValue::string(line_str.to_string()));
18734
18735 if self.auto_split {
18736 let sep = self.field_separator.as_deref().unwrap_or(" ");
18737 let re = regex::Regex::new(sep).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
18738 let fields: Vec<PerlValue> = re
18739 .split(line_str)
18740 .map(|s| PerlValue::string(s.to_string()))
18741 .collect();
18742 self.scope.set_array("F", fields)?;
18743 }
18744
18745 for stmt in &program.statements {
18746 match &stmt.kind {
18747 StmtKind::SubDecl { .. }
18748 | StmtKind::Begin(_)
18749 | StmtKind::UnitCheck(_)
18750 | StmtKind::Check(_)
18751 | StmtKind::Init(_)
18752 | StmtKind::End(_) => continue,
18753 _ => match self.exec_statement(stmt) {
18754 Ok(_) => {}
18755 Err(FlowOrError::Error(e)) => return Err(e),
18756 Err(FlowOrError::Flow(_)) => {}
18757 },
18758 }
18759 }
18760
18761 let mut out = self.scope.get_scalar("_").to_string();
18763 out.push_str(&self.ors);
18764 Ok(Some(out))
18765 })();
18766 self.line_mode_eof_pending = false;
18767 result
18768 }
18769}
18770
18771fn par_walk_invoke_entry(
18772 path: &Path,
18773 sub: &Arc<PerlSub>,
18774 subs: &HashMap<String, Arc<PerlSub>>,
18775 scope_capture: &[(String, PerlValue)],
18776 atomic_arrays: &[(String, crate::scope::AtomicArray)],
18777 atomic_hashes: &[(String, crate::scope::AtomicHash)],
18778 line: usize,
18779) -> Result<(), FlowOrError> {
18780 let s = path.to_string_lossy().into_owned();
18781 let mut local_interp = Interpreter::new();
18782 local_interp.subs = subs.clone();
18783 local_interp.scope.restore_capture(scope_capture);
18784 local_interp
18785 .scope
18786 .restore_atomics(atomic_arrays, atomic_hashes);
18787 local_interp.enable_parallel_guard();
18788 local_interp.scope.set_topic(PerlValue::string(s));
18789 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
18790 Ok(())
18791}
18792
18793fn par_walk_recursive(
18794 path: &Path,
18795 sub: &Arc<PerlSub>,
18796 subs: &HashMap<String, Arc<PerlSub>>,
18797 scope_capture: &[(String, PerlValue)],
18798 atomic_arrays: &[(String, crate::scope::AtomicArray)],
18799 atomic_hashes: &[(String, crate::scope::AtomicHash)],
18800 line: usize,
18801) -> Result<(), FlowOrError> {
18802 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
18803 return par_walk_invoke_entry(
18804 path,
18805 sub,
18806 subs,
18807 scope_capture,
18808 atomic_arrays,
18809 atomic_hashes,
18810 line,
18811 );
18812 }
18813 if !path.is_dir() {
18814 return Ok(());
18815 }
18816 par_walk_invoke_entry(
18817 path,
18818 sub,
18819 subs,
18820 scope_capture,
18821 atomic_arrays,
18822 atomic_hashes,
18823 line,
18824 )?;
18825 let read = match std::fs::read_dir(path) {
18826 Ok(r) => r,
18827 Err(_) => return Ok(()),
18828 };
18829 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
18830 entries.par_iter().try_for_each(|e| {
18831 par_walk_recursive(
18832 &e.path(),
18833 sub,
18834 subs,
18835 scope_capture,
18836 atomic_arrays,
18837 atomic_hashes,
18838 line,
18839 )
18840 })?;
18841 Ok(())
18842}
18843
18844pub(crate) fn perl_sprintf_format_with<F>(
18846 fmt: &str,
18847 args: &[PerlValue],
18848 mut string_for_s: F,
18849) -> Result<String, FlowOrError>
18850where
18851 F: FnMut(&PerlValue) -> Result<String, FlowOrError>,
18852{
18853 let mut result = String::new();
18854 let mut arg_idx = 0;
18855 let chars: Vec<char> = fmt.chars().collect();
18856 let mut i = 0;
18857
18858 while i < chars.len() {
18859 if chars[i] == '%' {
18860 i += 1;
18861 if i >= chars.len() {
18862 break;
18863 }
18864 if chars[i] == '%' {
18865 result.push('%');
18866 i += 1;
18867 continue;
18868 }
18869
18870 let mut flags = String::new();
18872 while i < chars.len() && "-+ #0".contains(chars[i]) {
18873 flags.push(chars[i]);
18874 i += 1;
18875 }
18876 let mut width = String::new();
18877 while i < chars.len() && chars[i].is_ascii_digit() {
18878 width.push(chars[i]);
18879 i += 1;
18880 }
18881 let mut precision = String::new();
18882 if i < chars.len() && chars[i] == '.' {
18883 i += 1;
18884 while i < chars.len() && chars[i].is_ascii_digit() {
18885 precision.push(chars[i]);
18886 i += 1;
18887 }
18888 }
18889 if i >= chars.len() {
18890 break;
18891 }
18892 let spec = chars[i];
18893 i += 1;
18894
18895 let arg = args.get(arg_idx).cloned().unwrap_or(PerlValue::UNDEF);
18896 arg_idx += 1;
18897
18898 let w: usize = width.parse().unwrap_or(0);
18899 let p: usize = precision.parse().unwrap_or(6);
18900
18901 let zero_pad = flags.contains('0') && !flags.contains('-');
18902 let left_align = flags.contains('-');
18903 let formatted = match spec {
18904 'd' | 'i' => {
18905 if zero_pad {
18906 format!("{:0width$}", arg.to_int(), width = w)
18907 } else if left_align {
18908 format!("{:<width$}", arg.to_int(), width = w)
18909 } else {
18910 format!("{:width$}", arg.to_int(), width = w)
18911 }
18912 }
18913 'u' => {
18914 if zero_pad {
18915 format!("{:0width$}", arg.to_int() as u64, width = w)
18916 } else {
18917 format!("{:width$}", arg.to_int() as u64, width = w)
18918 }
18919 }
18920 'f' => format!("{:width$.prec$}", arg.to_number(), width = w, prec = p),
18921 'e' => format!("{:width$.prec$e}", arg.to_number(), width = w, prec = p),
18922 'g' => {
18923 let n = arg.to_number();
18924 if n.abs() >= 1e-4 && n.abs() < 1e15 {
18925 format!("{:width$.prec$}", n, width = w, prec = p)
18926 } else {
18927 format!("{:width$.prec$e}", n, width = w, prec = p)
18928 }
18929 }
18930 's' => {
18931 let s = string_for_s(&arg)?;
18932 if !precision.is_empty() {
18933 let truncated: String = s.chars().take(p).collect();
18934 if flags.contains('-') {
18935 format!("{:<width$}", truncated, width = w)
18936 } else {
18937 format!("{:>width$}", truncated, width = w)
18938 }
18939 } else if flags.contains('-') {
18940 format!("{:<width$}", s, width = w)
18941 } else {
18942 format!("{:>width$}", s, width = w)
18943 }
18944 }
18945 'x' => {
18946 let v = arg.to_int();
18947 if zero_pad && w > 0 {
18948 format!("{:0width$x}", v, width = w)
18949 } else if left_align {
18950 format!("{:<width$x}", v, width = w)
18951 } else if w > 0 {
18952 format!("{:width$x}", v, width = w)
18953 } else {
18954 format!("{:x}", v)
18955 }
18956 }
18957 'X' => {
18958 let v = arg.to_int();
18959 if zero_pad && w > 0 {
18960 format!("{:0width$X}", v, width = w)
18961 } else if left_align {
18962 format!("{:<width$X}", v, width = w)
18963 } else if w > 0 {
18964 format!("{:width$X}", v, width = w)
18965 } else {
18966 format!("{:X}", v)
18967 }
18968 }
18969 'o' => {
18970 let v = arg.to_int();
18971 if zero_pad && w > 0 {
18972 format!("{:0width$o}", v, width = w)
18973 } else if left_align {
18974 format!("{:<width$o}", v, width = w)
18975 } else if w > 0 {
18976 format!("{:width$o}", v, width = w)
18977 } else {
18978 format!("{:o}", v)
18979 }
18980 }
18981 'b' => {
18982 let v = arg.to_int();
18983 if zero_pad && w > 0 {
18984 format!("{:0width$b}", v, width = w)
18985 } else if left_align {
18986 format!("{:<width$b}", v, width = w)
18987 } else if w > 0 {
18988 format!("{:width$b}", v, width = w)
18989 } else {
18990 format!("{:b}", v)
18991 }
18992 }
18993 'c' => char::from_u32(arg.to_int() as u32)
18994 .map(|c| c.to_string())
18995 .unwrap_or_default(),
18996 _ => arg.to_string(),
18997 };
18998
18999 result.push_str(&formatted);
19000 } else {
19001 result.push(chars[i]);
19002 i += 1;
19003 }
19004 }
19005 Ok(result)
19006}
19007
19008#[cfg(test)]
19009mod regex_expand_tests {
19010 use super::Interpreter;
19011
19012 #[test]
19013 fn compile_regex_quotemeta_qe_matches_literal() {
19014 let mut i = Interpreter::new();
19015 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
19016 assert!(re.is_match("a.c"));
19017 assert!(!re.is_match("abc"));
19018 }
19019
19020 #[test]
19023 fn compile_regex_char_class_leading_close_bracket_is_literal() {
19024 let mut i = Interpreter::new();
19025 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
19026 assert!(re.is_match("$"));
19027 assert!(re.is_match("]"));
19028 assert!(!re.is_match("x"));
19029 }
19030}
19031
19032#[cfg(test)]
19033mod special_scalar_name_tests {
19034 use super::Interpreter;
19035
19036 #[test]
19037 fn special_scalar_name_for_get_matches_magic_globals() {
19038 assert!(Interpreter::is_special_scalar_name_for_get("0"));
19039 assert!(Interpreter::is_special_scalar_name_for_get("!"));
19040 assert!(Interpreter::is_special_scalar_name_for_get("^W"));
19041 assert!(Interpreter::is_special_scalar_name_for_get("^O"));
19042 assert!(Interpreter::is_special_scalar_name_for_get("^MATCH"));
19043 assert!(Interpreter::is_special_scalar_name_for_get("<"));
19044 assert!(Interpreter::is_special_scalar_name_for_get("?"));
19045 assert!(Interpreter::is_special_scalar_name_for_get("|"));
19046 assert!(Interpreter::is_special_scalar_name_for_get("^UNICODE"));
19047 assert!(Interpreter::is_special_scalar_name_for_get("\""));
19048 assert!(!Interpreter::is_special_scalar_name_for_get("foo"));
19049 assert!(!Interpreter::is_special_scalar_name_for_get("plainvar"));
19050 }
19051
19052 #[test]
19053 fn special_scalar_name_for_set_matches_set_special_var_arms() {
19054 assert!(Interpreter::is_special_scalar_name_for_set("0"));
19055 assert!(Interpreter::is_special_scalar_name_for_set("^D"));
19056 assert!(Interpreter::is_special_scalar_name_for_set("^H"));
19057 assert!(Interpreter::is_special_scalar_name_for_set("^WARNING_BITS"));
19058 assert!(Interpreter::is_special_scalar_name_for_set("ARGV"));
19059 assert!(Interpreter::is_special_scalar_name_for_set("|"));
19060 assert!(Interpreter::is_special_scalar_name_for_set("?"));
19061 assert!(Interpreter::is_special_scalar_name_for_set("^UNICODE"));
19062 assert!(Interpreter::is_special_scalar_name_for_set("."));
19063 assert!(!Interpreter::is_special_scalar_name_for_set("foo"));
19064 assert!(!Interpreter::is_special_scalar_name_for_set("__PACKAGE__"));
19065 }
19066
19067 #[test]
19068 fn caret_and_id_specials_roundtrip_get() {
19069 let i = Interpreter::new();
19070 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
19071 assert_eq!(
19072 i.get_special_var("^V").to_string(),
19073 format!("v{}", env!("CARGO_PKG_VERSION"))
19074 );
19075 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
19076 assert!(i.get_special_var("^T").to_int() >= 0);
19077 #[cfg(unix)]
19078 {
19079 assert!(i.get_special_var("<").to_int() >= 0);
19080 }
19081 }
19082
19083 #[test]
19084 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
19085 let mut i = Interpreter::new();
19086 i.last_readline_handle.clear();
19087 i.line_number = 3;
19088 i.prepare_flip_flop_vm_slots(1);
19089 assert_eq!(
19090 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
19091 1
19092 );
19093 assert!(i.flip_flop_active[0]);
19094 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
19095 assert_eq!(
19097 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
19098 1
19099 );
19100 assert!(i.flip_flop_active[0]);
19101 }
19102
19103 #[test]
19104 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
19105 let mut i = Interpreter::new();
19106 i.last_readline_handle.clear();
19107 i.line_number = 2;
19108 i.prepare_flip_flop_vm_slots(1);
19109 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
19110 assert!(i.flip_flop_active[0]);
19111 i.line_number = 3;
19112 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
19113 assert!(!i.flip_flop_active[0]);
19114 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
19115 }
19116}