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 _ => WantarrayCtx::Scalar,
380 }
381}
382
383#[derive(Clone)]
388pub(crate) struct RegexMatchMemo {
389 pub pattern: String,
390 pub flags: String,
391 pub multiline: bool,
392 pub haystack: String,
393 pub result: PerlValue,
394}
395
396#[derive(Clone, Copy, Default)]
398struct FlipFlopTreeState {
399 active: bool,
400 exclusive_left_line: Option<i64>,
404}
405
406#[derive(Clone)]
408pub(crate) struct IoSharedFile(pub Arc<Mutex<File>>);
409
410impl Read for IoSharedFile {
411 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
412 self.0.lock().read(buf)
413 }
414}
415
416pub(crate) struct IoSharedFileWrite(pub Arc<Mutex<File>>);
417
418impl IoWrite for IoSharedFileWrite {
419 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
420 self.0.lock().write(buf)
421 }
422
423 fn flush(&mut self) -> io::Result<()> {
424 self.0.lock().flush()
425 }
426}
427
428pub struct Interpreter {
429 pub scope: Scope,
430 pub(crate) subs: HashMap<String, Arc<PerlSub>>,
431 pub(crate) file: String,
432 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
434 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
435 pub ofs: String,
437 pub ors: String,
439 pub irs: Option<String>,
442 pub errno: String,
444 pub errno_code: i32,
446 pub eval_error: String,
448 pub eval_error_code: i32,
450 pub eval_error_value: Option<PerlValue>,
452 pub argv: Vec<String>,
454 pub env: IndexMap<String, PerlValue>,
456 pub env_materialized: bool,
458 pub program_name: String,
460 pub line_number: i64,
462 pub last_readline_handle: String,
464 pub(crate) last_stdin_die_bracket: String,
466 pub handle_line_numbers: HashMap<String, i64>,
468 pub(crate) flip_flop_active: Vec<bool>,
472 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
475 pub(crate) flip_flop_sequence: Vec<i64>,
479 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
483 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
485 pub sigint_pending_caret: Cell<bool>,
487 pub auto_split: bool,
489 pub field_separator: Option<String>,
491 begin_blocks: Vec<Block>,
493 unit_check_blocks: Vec<Block>,
495 check_blocks: Vec<Block>,
497 init_blocks: Vec<Block>,
499 end_blocks: Vec<Block>,
501 pub warnings: bool,
503 pub output_autoflush: bool,
505 pub default_print_handle: String,
507 pub suppress_stdout: bool,
509 pub child_exit_status: i64,
511 pub last_match: String,
513 pub prematch: String,
515 pub postmatch: String,
517 pub last_paren_match: String,
519 pub list_separator: String,
521 pub script_start_time: i64,
523 pub compile_hints: i64,
525 pub warning_bits: i64,
527 pub global_phase: String,
529 pub subscript_sep: String,
531 pub inplace_edit: String,
534 pub debug_flags: i64,
536 pub perl_debug_flags: i64,
538 pub eval_nesting: u32,
540 pub argv_current_file: String,
542 pub(crate) diamond_next_idx: usize,
544 pub(crate) diamond_reader: Option<BufReader<File>>,
546 pub strict_refs: bool,
548 pub strict_subs: bool,
549 pub strict_vars: bool,
550 pub utf8_pragma: bool,
552 pub open_pragma_utf8: bool,
554 pub feature_bits: u64,
556 pub num_threads: usize,
558 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
560 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
563 regex_match_memo: Option<RegexMatchMemo>,
572 regex_capture_scope_fresh: bool,
576 pub(crate) regex_pos: HashMap<String, Option<usize>>,
578 pub(crate) state_vars: HashMap<String, PerlValue>,
580 state_bindings_stack: Vec<Vec<(String, String)>>,
582 pub(crate) rand_rng: StdRng,
584 pub(crate) dir_handles: HashMap<String, DirHandleState>,
586 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
588 pub(crate) pipe_children: HashMap<String, Child>,
590 pub(crate) socket_handles: HashMap<String, PerlSocket>,
592 pub(crate) wantarray_kind: WantarrayCtx,
594 pub struct_defs: HashMap<String, Arc<StructDef>>,
596 pub enum_defs: HashMap<String, Arc<EnumDef>>,
598 pub class_defs: HashMap<String, Arc<ClassDef>>,
600 pub trait_defs: HashMap<String, Arc<TraitDef>>,
602 pub profiler: Option<Profiler>,
605 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
607 pub(crate) tied_hashes: HashMap<String, PerlValue>,
609 pub(crate) tied_scalars: HashMap<String, PerlValue>,
611 pub(crate) tied_arrays: HashMap<String, PerlValue>,
613 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
615 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
617 pub(crate) special_caret_scalars: HashMap<String, PerlValue>,
619 pub format_page_number: i64,
621 pub format_lines_per_page: i64,
623 pub format_lines_left: i64,
625 pub format_line_break_chars: String,
627 pub format_top_name: String,
629 pub accumulator_format: String,
631 pub max_system_fd: i64,
633 pub emergency_memory: String,
635 pub last_subpattern_name: String,
637 pub inc_hook_index: i64,
639 pub multiline_match: bool,
641 pub executable_path: String,
643 pub formfeed_string: String,
645 pub(crate) glob_handle_alias: HashMap<String, String>,
647 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
649 pub(crate) special_var_restore_frames: Vec<Vec<(String, PerlValue)>>,
654 pub(crate) english_enabled: bool,
656 pub(crate) english_no_match_vars: bool,
658 pub(crate) english_match_vars_ever_enabled: bool,
662 english_lexical_scalars: Vec<HashSet<String>>,
664 our_lexical_scalars: Vec<HashSet<String>>,
666 pub vm_jit_enabled: bool,
669 pub disasm_bytecode: bool,
671 pub pec_precompiled_chunk: Option<crate::bytecode::Chunk>,
675 pub pec_cache_fingerprint: Option<[u8; 32]>,
679 pub(crate) in_generator: bool,
681 pub line_mode_skip_main: bool,
683 pub(crate) line_mode_eof_pending: bool,
687 pub line_mode_stdin_pending: VecDeque<String>,
690 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
692 pub(crate) log_level_override: Option<LogLevelFilter>,
694 pub(crate) current_sub_stack: Vec<Arc<PerlSub>>,
697 pub debugger: Option<crate::debugger::Debugger>,
699 pub(crate) debug_call_stack: Vec<(String, usize)>,
701}
702
703#[derive(Debug, Clone, Default)]
705pub struct ReplCompletionSnapshot {
706 pub subs: Vec<String>,
707 pub blessed_scalars: HashMap<String, String>,
708 pub isa_for_class: HashMap<String, Vec<String>>,
709}
710
711impl ReplCompletionSnapshot {
712 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
714 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
715 let mro = linearize_c3(class, &parents, 0);
716 let mut names = HashSet::new();
717 for pkg in &mro {
718 if pkg == "UNIVERSAL" {
719 continue;
720 }
721 let prefix = format!("{}::", pkg);
722 for k in &self.subs {
723 if k.starts_with(&prefix) {
724 let rest = &k[prefix.len()..];
725 if !rest.contains("::") {
726 names.insert(rest.to_string());
727 }
728 }
729 }
730 }
731 for k in &self.subs {
732 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
733 if !rest.contains("::") {
734 names.insert(rest.to_string());
735 }
736 }
737 }
738 let mut v: Vec<String> = names.into_iter().collect();
739 v.sort();
740 v
741 }
742}
743
744fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
745 let left = left.trim_end();
746 if left.is_empty() {
747 return None;
748 }
749 if let Some(i) = left.rfind('$') {
750 let name = left[i + 1..].trim();
751 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
752 return state.blessed_scalars.get(name).cloned();
753 }
754 }
755 let tok = left.split_whitespace().last()?;
756 if tok.contains("::") {
757 return Some(tok.to_string());
758 }
759 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
760 return Some(tok.to_string());
761 }
762 None
763}
764
765pub fn repl_arrow_method_completions(
767 state: &ReplCompletionSnapshot,
768 line: &str,
769 pos: usize,
770) -> Option<(usize, Vec<String>)> {
771 let pos = pos.min(line.len());
772 let before = &line[..pos];
773 let arrow_idx = before.rfind("->")?;
774 let after_arrow = &before[arrow_idx + 2..];
775 let rest = after_arrow.trim_start();
776 let ws_len = after_arrow.len() - rest.len();
777 let method_start = arrow_idx + 2 + ws_len;
778 let method_prefix = &line[method_start..pos];
779 if !method_prefix
780 .chars()
781 .all(|c| c.is_alphanumeric() || c == '_')
782 {
783 return None;
784 }
785 let left = line[..arrow_idx].trim_end();
786 let class = repl_resolve_class_for_arrow(state, left)?;
787 let mut methods = state.methods_for_class(&class);
788 methods.retain(|m| m.starts_with(method_prefix));
789 Some((method_start, methods))
790}
791
792#[derive(Debug, Clone, Default)]
794pub(crate) struct ModuleExportLists {
795 pub export: Vec<String>,
797 pub export_ok: Vec<String>,
799}
800
801fn piped_shell_command(cmd: &str) -> Command {
803 if cfg!(windows) {
804 let mut c = Command::new("cmd");
805 c.arg("/C").arg(cmd);
806 c
807 } else {
808 let mut c = Command::new("sh");
809 c.arg("-c").arg(cmd);
810 c
811 }
812}
813
814fn expand_perl_regex_octal_escapes(pat: &str) -> String {
821 let mut out = String::with_capacity(pat.len());
822 let mut it = pat.chars().peekable();
823 while let Some(c) = it.next() {
824 if c == '\\' {
825 if let Some(&'0') = it.peek() {
826 let mut oct = String::new();
828 while oct.len() < 3 {
829 if let Some(&d) = it.peek() {
830 if ('0'..='7').contains(&d) {
831 oct.push(d);
832 it.next();
833 } else {
834 break;
835 }
836 } else {
837 break;
838 }
839 }
840 if let Ok(val) = u8::from_str_radix(&oct, 8) {
841 out.push_str(&format!("\\x{:02x}", val));
842 } else {
843 out.push('\\');
844 out.push_str(&oct);
845 }
846 continue;
847 }
848 }
849 out.push(c);
850 }
851 out
852}
853
854fn expand_perl_regex_quotemeta(pat: &str) -> String {
855 let mut out = String::with_capacity(pat.len().saturating_mul(2));
856 let mut it = pat.chars().peekable();
857 let mut in_q = false;
858 while let Some(c) = it.next() {
859 if in_q {
860 if c == '\\' && it.peek() == Some(&'E') {
861 it.next();
862 in_q = false;
863 continue;
864 }
865 out.push_str(&perl_quotemeta(&c.to_string()));
866 continue;
867 }
868 if c == '\\' && it.peek() == Some(&'Q') {
869 it.next();
870 in_q = true;
871 continue;
872 }
873 out.push(c);
874 }
875 out
876}
877
878pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
884 let mut out = String::with_capacity(replacement.len() + 8);
885 let mut it = replacement.chars().peekable();
886 while let Some(c) = it.next() {
887 if c == '\\' {
888 match it.peek() {
889 Some(&d) if d.is_ascii_digit() => {
890 it.next();
891 out.push_str("${");
892 out.push(d);
893 while let Some(&d2) = it.peek() {
894 if !d2.is_ascii_digit() {
895 break;
896 }
897 it.next();
898 out.push(d2);
899 }
900 out.push('}');
901 }
902 Some(&'\\') => {
903 it.next();
904 out.push('\\');
905 }
906 _ => out.push('\\'),
907 }
908 } else if c == '$' {
909 match it.peek() {
910 Some(&d) if d.is_ascii_digit() => {
911 it.next();
912 out.push_str("${");
913 out.push(d);
914 while let Some(&d2) = it.peek() {
915 if !d2.is_ascii_digit() {
916 break;
917 }
918 it.next();
919 out.push(d2);
920 }
921 out.push('}');
922 }
923 Some(&'{') => {
924 out.push('$');
926 }
927 _ => out.push('$'),
928 }
929 } else {
930 out.push(c);
931 }
932 }
933 out
934}
935
936fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
939 debug_assert_eq!(chars.get(i), Some(&'['));
940 out.push('[');
941 i += 1;
942 if i < chars.len() && chars[i] == '^' {
943 out.push('^');
944 i += 1;
945 }
946 if i >= chars.len() {
947 return i;
948 }
949 if chars[i] == ']' {
953 if i + 1 < chars.len() && chars[i + 1] == ']' {
954 out.push(']');
956 i += 1;
957 } else {
958 let mut scan = i + 1;
959 let mut found_closing = false;
960 while scan < chars.len() {
961 if chars[scan] == '\\' && scan + 1 < chars.len() {
962 scan += 2;
963 continue;
964 }
965 if chars[scan] == ']' {
966 found_closing = true;
967 break;
968 }
969 scan += 1;
970 }
971 if found_closing {
972 out.push(']');
973 i += 1;
974 } else {
975 out.push(']');
976 return i + 1;
977 }
978 }
979 }
980 while i < chars.len() && chars[i] != ']' {
981 if chars[i] == '\\' && i + 1 < chars.len() {
982 out.push(chars[i]);
983 out.push(chars[i + 1]);
984 i += 2;
985 continue;
986 }
987 out.push(chars[i]);
988 i += 1;
989 }
990 if i < chars.len() {
991 out.push(']');
992 i += 1;
993 }
994 i
995}
996
997fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1002 if multiline_flag {
1003 return pat.to_string();
1004 }
1005 let chars: Vec<char> = pat.chars().collect();
1006 let mut out = String::with_capacity(pat.len().saturating_add(16));
1007 let mut i = 0usize;
1008 while i < chars.len() {
1009 let c = chars[i];
1010 if c == '\\' && i + 1 < chars.len() {
1011 out.push(c);
1012 out.push(chars[i + 1]);
1013 i += 2;
1014 continue;
1015 }
1016 if c == '[' {
1017 i = copy_regex_char_class(&chars, i, &mut out);
1018 continue;
1019 }
1020 if c == '$' {
1021 if let Some(&next) = chars.get(i + 1) {
1022 if next.is_ascii_digit() {
1023 out.push(c);
1024 i += 1;
1025 continue;
1026 }
1027 if next == '{' {
1028 out.push(c);
1029 i += 1;
1030 continue;
1031 }
1032 if next.is_ascii_alphanumeric() || next == '_' {
1033 out.push(c);
1034 i += 1;
1035 continue;
1036 }
1037 }
1038 out.push_str("(?=\\n?\\z)");
1039 i += 1;
1040 continue;
1041 }
1042 out.push(c);
1043 i += 1;
1044 }
1045 out
1046}
1047
1048#[derive(Debug, Clone)]
1050pub(crate) struct DirHandleState {
1051 pub entries: Vec<String>,
1052 pub pos: usize,
1053}
1054
1055pub(crate) fn perl_osname() -> String {
1057 match std::env::consts::OS {
1058 "linux" => "linux".to_string(),
1059 "macos" => "darwin".to_string(),
1060 "windows" => "MSWin32".to_string(),
1061 other => other.to_string(),
1062 }
1063}
1064
1065fn perl_version_v_string() -> String {
1066 format!("v{}", env!("CARGO_PKG_VERSION"))
1067}
1068
1069fn extended_os_error_string() -> String {
1070 std::io::Error::last_os_error().to_string()
1071}
1072
1073#[cfg(unix)]
1074fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1075 unsafe {
1076 (
1077 libc::getuid() as i64,
1078 libc::geteuid() as i64,
1079 libc::getgid() as i64,
1080 libc::getegid() as i64,
1081 )
1082 }
1083}
1084
1085#[cfg(not(unix))]
1086fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1087 (0, 0, 0, 0)
1088}
1089
1090fn unix_id_for_special(name: &str) -> i64 {
1091 let (r, e, _, _) = unix_real_effective_ids();
1092 match name {
1093 "<" => r,
1094 ">" => e,
1095 _ => 0,
1096 }
1097}
1098
1099#[cfg(unix)]
1100fn unix_group_list_string(primary: libc::gid_t) -> String {
1101 let mut buf = vec![0 as libc::gid_t; 256];
1102 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1103 if n <= 0 {
1104 return format!("{}", primary);
1105 }
1106 let mut parts = vec![format!("{}", primary)];
1107 for g in buf.iter().take(n as usize) {
1108 parts.push(format!("{}", g));
1109 }
1110 parts.join(" ")
1111}
1112
1113#[cfg(unix)]
1115fn unix_group_list_for_special(name: &str) -> String {
1116 let (_, _, gid, egid) = unix_real_effective_ids();
1117 match name {
1118 "(" => unix_group_list_string(gid as libc::gid_t),
1119 ")" => unix_group_list_string(egid as libc::gid_t),
1120 _ => String::new(),
1121 }
1122}
1123
1124#[cfg(not(unix))]
1125fn unix_group_list_for_special(_name: &str) -> String {
1126 String::new()
1127}
1128
1129#[cfg(unix)]
1132fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1133 use libc::{getpwuid_r, getuid};
1134 use std::ffi::CStr;
1135 use std::os::unix::ffi::OsStringExt;
1136 let uid = unsafe { getuid() };
1137 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1138 let mut result: *mut libc::passwd = std::ptr::null_mut();
1139 let mut buf = vec![0u8; 16_384];
1140 let rc = unsafe {
1141 getpwuid_r(
1142 uid,
1143 &mut pw,
1144 buf.as_mut_ptr().cast::<libc::c_char>(),
1145 buf.len(),
1146 &mut result,
1147 )
1148 };
1149 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1150 return None;
1151 }
1152 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1153 if bytes.is_empty() {
1154 return None;
1155 }
1156 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1157}
1158
1159#[cfg(unix)]
1161fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1162 use libc::getpwnam_r;
1163 use std::ffi::{CStr, CString};
1164 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1165 let bytes = login.as_bytes();
1166 if bytes.is_empty() || bytes.contains(&0) {
1167 return None;
1168 }
1169 let cname = CString::new(bytes).ok()?;
1170 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1171 let mut result: *mut libc::passwd = std::ptr::null_mut();
1172 let mut buf = vec![0u8; 16_384];
1173 let rc = unsafe {
1174 getpwnam_r(
1175 cname.as_ptr(),
1176 &mut pw,
1177 buf.as_mut_ptr().cast::<libc::c_char>(),
1178 buf.len(),
1179 &mut result,
1180 )
1181 };
1182 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1183 return None;
1184 }
1185 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1186 if dir_bytes.is_empty() {
1187 return None;
1188 }
1189 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1190}
1191
1192impl Default for Interpreter {
1193 fn default() -> Self {
1194 Self::new()
1195 }
1196}
1197
1198#[derive(Clone, Copy)]
1200pub(crate) enum CaptureAllMode {
1201 Empty,
1203 Append,
1205 Skip,
1207}
1208
1209impl Interpreter {
1210 pub fn new() -> Self {
1211 let mut scope = Scope::new();
1212 scope.declare_array("INC", vec![PerlValue::string(".".to_string())]);
1213 scope.declare_hash("INC", IndexMap::new());
1214 scope.declare_array("ARGV", vec![]);
1215 scope.declare_array("_", vec![]);
1216 scope.declare_hash("ENV", IndexMap::new());
1217 scope.declare_hash("SIG", IndexMap::new());
1218 let builtins_map = crate::builtins::builtins_hash_map();
1234 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1235 let extensions_map = crate::builtins::extensions_hash_map();
1236 let aliases_map = crate::builtins::aliases_hash_map();
1237 let descriptions_map = crate::builtins::descriptions_hash_map();
1238 let categories_map = crate::builtins::categories_hash_map();
1239 let primaries_map = crate::builtins::primaries_hash_map();
1240 let all_map = crate::builtins::all_hash_map();
1241 scope.declare_hash("stryke::builtins", builtins_map.clone());
1242 scope.declare_hash("stryke::perl_compats", perl_compats_map.clone());
1243 scope.declare_hash("stryke::extensions", extensions_map.clone());
1244 scope.declare_hash("stryke::aliases", aliases_map.clone());
1245 scope.declare_hash("stryke::descriptions", descriptions_map.clone());
1246 scope.declare_hash("stryke::categories", categories_map.clone());
1247 scope.declare_hash("stryke::primaries", primaries_map.clone());
1248 scope.declare_hash("stryke::all", all_map.clone());
1249 scope.declare_scalar(
1250 "stryke::VERSION",
1251 PerlValue::string(env!("CARGO_PKG_VERSION").to_string()),
1252 );
1253 scope.declare_hash("b", builtins_map);
1254 scope.declare_hash("pc", perl_compats_map);
1255 scope.declare_hash("e", extensions_map);
1256 scope.declare_hash("a", aliases_map);
1257 scope.declare_hash("d", descriptions_map);
1258 scope.declare_hash("c", categories_map);
1259 scope.declare_hash("p", primaries_map);
1260 scope.declare_hash("all", all_map);
1261 scope.declare_array("-", vec![]);
1262 scope.declare_array("+", vec![]);
1263 scope.declare_array("^CAPTURE", vec![]);
1264 scope.declare_array("^CAPTURE_ALL", vec![]);
1265 scope.declare_hash("^HOOK", IndexMap::new());
1266 scope.declare_scalar("~", PerlValue::string("STDOUT".to_string()));
1267
1268 let script_start_time = std::time::SystemTime::now()
1269 .duration_since(std::time::UNIX_EPOCH)
1270 .map(|d| d.as_secs() as i64)
1271 .unwrap_or(0);
1272
1273 let executable_path = cached_executable_path();
1274
1275 let mut special_caret_scalars: HashMap<String, PerlValue> = HashMap::new();
1276 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1277 special_caret_scalars.insert(format!("^{}", name), PerlValue::UNDEF);
1278 }
1279
1280 let mut s = Self {
1281 scope,
1282 subs: HashMap::new(),
1283 struct_defs: HashMap::new(),
1284 enum_defs: HashMap::new(),
1285 class_defs: HashMap::new(),
1286 trait_defs: HashMap::new(),
1287 file: "-e".to_string(),
1288 output_handles: HashMap::new(),
1289 input_handles: HashMap::new(),
1290 ofs: String::new(),
1291 ors: String::new(),
1292 irs: Some("\n".to_string()),
1293 errno: String::new(),
1294 errno_code: 0,
1295 eval_error: String::new(),
1296 eval_error_code: 0,
1297 eval_error_value: None,
1298 argv: Vec::new(),
1299 env: IndexMap::new(),
1300 env_materialized: false,
1301 program_name: "stryke".to_string(),
1302 line_number: 0,
1303 last_readline_handle: String::new(),
1304 last_stdin_die_bracket: "<STDIN>".to_string(),
1305 handle_line_numbers: HashMap::new(),
1306 flip_flop_active: Vec::new(),
1307 flip_flop_exclusive_left_line: Vec::new(),
1308 flip_flop_sequence: Vec::new(),
1309 flip_flop_last_dot: Vec::new(),
1310 flip_flop_tree: HashMap::new(),
1311 sigint_pending_caret: Cell::new(false),
1312 auto_split: false,
1313 field_separator: None,
1314 begin_blocks: Vec::new(),
1315 unit_check_blocks: Vec::new(),
1316 check_blocks: Vec::new(),
1317 init_blocks: Vec::new(),
1318 end_blocks: Vec::new(),
1319 warnings: false,
1320 output_autoflush: false,
1321 default_print_handle: "STDOUT".to_string(),
1322 suppress_stdout: false,
1323 child_exit_status: 0,
1324 last_match: String::new(),
1325 prematch: String::new(),
1326 postmatch: String::new(),
1327 last_paren_match: String::new(),
1328 list_separator: " ".to_string(),
1329 script_start_time,
1330 compile_hints: 0,
1331 warning_bits: 0,
1332 global_phase: "RUN".to_string(),
1333 subscript_sep: "\x1c".to_string(),
1334 inplace_edit: String::new(),
1335 debug_flags: 0,
1336 perl_debug_flags: 0,
1337 eval_nesting: 0,
1338 argv_current_file: String::new(),
1339 diamond_next_idx: 0,
1340 diamond_reader: None,
1341 strict_refs: false,
1342 strict_subs: false,
1343 strict_vars: false,
1344 utf8_pragma: false,
1345 open_pragma_utf8: false,
1346 feature_bits: FEAT_SAY,
1348 num_threads: 0, regex_cache: HashMap::new(),
1350 regex_last: None,
1351 regex_match_memo: None,
1352 regex_capture_scope_fresh: false,
1353 regex_pos: HashMap::new(),
1354 state_vars: HashMap::new(),
1355 state_bindings_stack: Vec::new(),
1356 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1357 dir_handles: HashMap::new(),
1358 io_file_slots: HashMap::new(),
1359 pipe_children: HashMap::new(),
1360 socket_handles: HashMap::new(),
1361 wantarray_kind: WantarrayCtx::Scalar,
1362 profiler: None,
1363 module_export_lists: HashMap::new(),
1364 tied_hashes: HashMap::new(),
1365 tied_scalars: HashMap::new(),
1366 tied_arrays: HashMap::new(),
1367 overload_table: HashMap::new(),
1368 format_templates: HashMap::new(),
1369 special_caret_scalars,
1370 format_page_number: 0,
1371 format_lines_per_page: 60,
1372 format_lines_left: 0,
1373 format_line_break_chars: "\n".to_string(),
1374 format_top_name: String::new(),
1375 accumulator_format: String::new(),
1376 max_system_fd: 2,
1377 emergency_memory: String::new(),
1378 last_subpattern_name: String::new(),
1379 inc_hook_index: 0,
1380 multiline_match: false,
1381 executable_path,
1382 formfeed_string: "\x0c".to_string(),
1383 glob_handle_alias: HashMap::new(),
1384 glob_restore_frames: vec![Vec::new()],
1385 special_var_restore_frames: vec![Vec::new()],
1386 english_enabled: false,
1387 english_no_match_vars: false,
1388 english_match_vars_ever_enabled: false,
1389 english_lexical_scalars: vec![HashSet::new()],
1390 our_lexical_scalars: vec![HashSet::new()],
1391 vm_jit_enabled: !matches!(
1392 std::env::var("STRYKE_NO_JIT"),
1393 Ok(v)
1394 if v == "1"
1395 || v.eq_ignore_ascii_case("true")
1396 || v.eq_ignore_ascii_case("yes")
1397 ),
1398 disasm_bytecode: false,
1399 pec_precompiled_chunk: None,
1400 pec_cache_fingerprint: None,
1401 in_generator: false,
1402 line_mode_skip_main: false,
1403 line_mode_eof_pending: false,
1404 line_mode_stdin_pending: VecDeque::new(),
1405 rate_limit_slots: Vec::new(),
1406 log_level_override: None,
1407 current_sub_stack: Vec::new(),
1408 debugger: None,
1409 debug_call_stack: Vec::new(),
1410 };
1411 s.install_overload_pragma_stubs();
1412 crate::list_util::install_scalar_util(&mut s);
1413 crate::list_util::install_sub_util(&mut s);
1414 s.install_utf8_unicode_to_native_stub();
1415 s
1416 }
1417
1418 fn install_utf8_unicode_to_native_stub(&mut self) {
1420 let empty: Block = vec![];
1421 let key = "utf8::unicode_to_native".to_string();
1422 self.subs.insert(
1423 key.clone(),
1424 Arc::new(PerlSub {
1425 name: key,
1426 params: vec![],
1427 body: empty,
1428 prototype: None,
1429 closure_env: None,
1430 fib_like: None,
1431 }),
1432 );
1433 }
1434
1435 fn install_overload_pragma_stubs(&mut self) {
1439 let empty: Block = vec![];
1440 for key in ["overload::import", "overload::unimport"] {
1441 let name = key.to_string();
1442 self.subs.insert(
1443 name.clone(),
1444 Arc::new(PerlSub {
1445 name,
1446 params: vec![],
1447 body: empty.clone(),
1448 prototype: None,
1449 closure_env: None,
1450 fib_like: None,
1451 }),
1452 );
1453 }
1454 }
1455
1456 pub fn line_mode_worker_clone(&self) -> Interpreter {
1459 Interpreter {
1460 scope: self.scope.clone(),
1461 subs: self.subs.clone(),
1462 struct_defs: self.struct_defs.clone(),
1463 enum_defs: self.enum_defs.clone(),
1464 class_defs: self.class_defs.clone(),
1465 trait_defs: self.trait_defs.clone(),
1466 file: self.file.clone(),
1467 output_handles: HashMap::new(),
1468 input_handles: HashMap::new(),
1469 ofs: self.ofs.clone(),
1470 ors: self.ors.clone(),
1471 irs: self.irs.clone(),
1472 errno: self.errno.clone(),
1473 errno_code: self.errno_code,
1474 eval_error: self.eval_error.clone(),
1475 eval_error_code: self.eval_error_code,
1476 eval_error_value: self.eval_error_value.clone(),
1477 argv: self.argv.clone(),
1478 env: self.env.clone(),
1479 env_materialized: self.env_materialized,
1480 program_name: self.program_name.clone(),
1481 line_number: 0,
1482 last_readline_handle: String::new(),
1483 last_stdin_die_bracket: "<STDIN>".to_string(),
1484 handle_line_numbers: HashMap::new(),
1485 flip_flop_active: Vec::new(),
1486 flip_flop_exclusive_left_line: Vec::new(),
1487 flip_flop_sequence: Vec::new(),
1488 flip_flop_last_dot: Vec::new(),
1489 flip_flop_tree: HashMap::new(),
1490 sigint_pending_caret: Cell::new(false),
1491 auto_split: self.auto_split,
1492 field_separator: self.field_separator.clone(),
1493 begin_blocks: self.begin_blocks.clone(),
1494 unit_check_blocks: self.unit_check_blocks.clone(),
1495 check_blocks: self.check_blocks.clone(),
1496 init_blocks: self.init_blocks.clone(),
1497 end_blocks: self.end_blocks.clone(),
1498 warnings: self.warnings,
1499 output_autoflush: self.output_autoflush,
1500 default_print_handle: self.default_print_handle.clone(),
1501 suppress_stdout: self.suppress_stdout,
1502 child_exit_status: self.child_exit_status,
1503 last_match: self.last_match.clone(),
1504 prematch: self.prematch.clone(),
1505 postmatch: self.postmatch.clone(),
1506 last_paren_match: self.last_paren_match.clone(),
1507 list_separator: self.list_separator.clone(),
1508 script_start_time: self.script_start_time,
1509 compile_hints: self.compile_hints,
1510 warning_bits: self.warning_bits,
1511 global_phase: self.global_phase.clone(),
1512 subscript_sep: self.subscript_sep.clone(),
1513 inplace_edit: self.inplace_edit.clone(),
1514 debug_flags: self.debug_flags,
1515 perl_debug_flags: self.perl_debug_flags,
1516 eval_nesting: self.eval_nesting,
1517 argv_current_file: String::new(),
1518 diamond_next_idx: 0,
1519 diamond_reader: None,
1520 strict_refs: self.strict_refs,
1521 strict_subs: self.strict_subs,
1522 strict_vars: self.strict_vars,
1523 utf8_pragma: self.utf8_pragma,
1524 open_pragma_utf8: self.open_pragma_utf8,
1525 feature_bits: self.feature_bits,
1526 num_threads: 0,
1527 regex_cache: self.regex_cache.clone(),
1528 regex_last: self.regex_last.clone(),
1529 regex_match_memo: self.regex_match_memo.clone(),
1530 regex_capture_scope_fresh: false,
1531 regex_pos: self.regex_pos.clone(),
1532 state_vars: self.state_vars.clone(),
1533 state_bindings_stack: Vec::new(),
1534 rand_rng: self.rand_rng.clone(),
1535 dir_handles: HashMap::new(),
1536 io_file_slots: HashMap::new(),
1537 pipe_children: HashMap::new(),
1538 socket_handles: HashMap::new(),
1539 wantarray_kind: self.wantarray_kind,
1540 profiler: None,
1541 module_export_lists: self.module_export_lists.clone(),
1542 tied_hashes: self.tied_hashes.clone(),
1543 tied_scalars: self.tied_scalars.clone(),
1544 tied_arrays: self.tied_arrays.clone(),
1545 overload_table: self.overload_table.clone(),
1546 format_templates: self.format_templates.clone(),
1547 special_caret_scalars: self.special_caret_scalars.clone(),
1548 format_page_number: self.format_page_number,
1549 format_lines_per_page: self.format_lines_per_page,
1550 format_lines_left: self.format_lines_left,
1551 format_line_break_chars: self.format_line_break_chars.clone(),
1552 format_top_name: self.format_top_name.clone(),
1553 accumulator_format: self.accumulator_format.clone(),
1554 max_system_fd: self.max_system_fd,
1555 emergency_memory: self.emergency_memory.clone(),
1556 last_subpattern_name: self.last_subpattern_name.clone(),
1557 inc_hook_index: self.inc_hook_index,
1558 multiline_match: self.multiline_match,
1559 executable_path: self.executable_path.clone(),
1560 formfeed_string: self.formfeed_string.clone(),
1561 glob_handle_alias: self.glob_handle_alias.clone(),
1562 glob_restore_frames: self.glob_restore_frames.clone(),
1563 special_var_restore_frames: self.special_var_restore_frames.clone(),
1564 english_enabled: self.english_enabled,
1565 english_no_match_vars: self.english_no_match_vars,
1566 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1567 english_lexical_scalars: self.english_lexical_scalars.clone(),
1568 our_lexical_scalars: self.our_lexical_scalars.clone(),
1569 vm_jit_enabled: self.vm_jit_enabled,
1570 disasm_bytecode: self.disasm_bytecode,
1571 pec_precompiled_chunk: None,
1573 pec_cache_fingerprint: None,
1574 in_generator: false,
1575 line_mode_skip_main: false,
1576 line_mode_eof_pending: false,
1577 line_mode_stdin_pending: VecDeque::new(),
1578 rate_limit_slots: Vec::new(),
1579 log_level_override: self.log_level_override,
1580 current_sub_stack: Vec::new(),
1581 debugger: None,
1582 debug_call_stack: Vec::new(),
1583 }
1584 }
1585
1586 pub(crate) fn parallel_thread_count(&mut self) -> usize {
1588 if self.num_threads == 0 {
1589 self.num_threads = rayon::current_num_threads();
1590 }
1591 self.num_threads
1592 }
1593
1594 pub(crate) fn eval_par_list_call(
1596 &mut self,
1597 name: &str,
1598 args: &[PerlValue],
1599 ctx: WantarrayCtx,
1600 line: usize,
1601 ) -> PerlResult<PerlValue> {
1602 match name {
1603 "puniq" => {
1604 let (list_src, show_prog) = match args.len() {
1605 0 => return Err(PerlError::runtime("puniq: expected LIST", line)),
1606 1 => (&args[0], false),
1607 2 => (&args[0], args[1].is_true()),
1608 _ => {
1609 return Err(PerlError::runtime(
1610 "puniq: expected LIST [, progress => EXPR]",
1611 line,
1612 ));
1613 }
1614 };
1615 let list = list_src.to_list();
1616 let n_threads = self.parallel_thread_count();
1617 let pmap_progress = PmapProgress::new(show_prog, list.len());
1618 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
1619 pmap_progress.finish();
1620 if ctx == WantarrayCtx::List {
1621 Ok(PerlValue::array(out))
1622 } else {
1623 Ok(PerlValue::integer(out.len() as i64))
1624 }
1625 }
1626 "pfirst" => {
1627 let (code_val, list_src, show_prog) = match args.len() {
1628 2 => (&args[0], &args[1], false),
1629 3 => (&args[0], &args[1], args[2].is_true()),
1630 _ => {
1631 return Err(PerlError::runtime(
1632 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
1633 line,
1634 ));
1635 }
1636 };
1637 let Some(sub) = code_val.as_code_ref() else {
1638 return Err(PerlError::runtime(
1639 "pfirst: first argument must be a code reference",
1640 line,
1641 ));
1642 };
1643 let sub = sub.clone();
1644 let list = list_src.to_list();
1645 if list.is_empty() {
1646 return Ok(PerlValue::UNDEF);
1647 }
1648 let pmap_progress = PmapProgress::new(show_prog, list.len());
1649 let subs = self.subs.clone();
1650 let (scope_capture, atomic_arrays, atomic_hashes) =
1651 self.scope.capture_with_atomics();
1652 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
1653 let mut local_interp = Interpreter::new();
1654 local_interp.subs = subs.clone();
1655 local_interp.scope.restore_capture(&scope_capture);
1656 local_interp
1657 .scope
1658 .restore_atomics(&atomic_arrays, &atomic_hashes);
1659 local_interp.enable_parallel_guard();
1660 local_interp.scope.set_topic(item);
1661 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1662 Ok(v) => v.is_true(),
1663 Err(_) => false,
1664 }
1665 });
1666 pmap_progress.finish();
1667 Ok(out.unwrap_or(PerlValue::UNDEF))
1668 }
1669 "pany" => {
1670 let (code_val, list_src, show_prog) = match args.len() {
1671 2 => (&args[0], &args[1], false),
1672 3 => (&args[0], &args[1], args[2].is_true()),
1673 _ => {
1674 return Err(PerlError::runtime(
1675 "pany: expected BLOCK, LIST [, progress => EXPR]",
1676 line,
1677 ));
1678 }
1679 };
1680 let Some(sub) = code_val.as_code_ref() else {
1681 return Err(PerlError::runtime(
1682 "pany: first argument must be a code reference",
1683 line,
1684 ));
1685 };
1686 let sub = sub.clone();
1687 let list = list_src.to_list();
1688 let pmap_progress = PmapProgress::new(show_prog, list.len());
1689 let subs = self.subs.clone();
1690 let (scope_capture, atomic_arrays, atomic_hashes) =
1691 self.scope.capture_with_atomics();
1692 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
1693 let mut local_interp = Interpreter::new();
1694 local_interp.subs = subs.clone();
1695 local_interp.scope.restore_capture(&scope_capture);
1696 local_interp
1697 .scope
1698 .restore_atomics(&atomic_arrays, &atomic_hashes);
1699 local_interp.enable_parallel_guard();
1700 local_interp.scope.set_topic(item);
1701 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
1702 Ok(v) => v.is_true(),
1703 Err(_) => false,
1704 }
1705 });
1706 pmap_progress.finish();
1707 Ok(PerlValue::integer(if b { 1 } else { 0 }))
1708 }
1709 _ => Err(PerlError::runtime(
1710 format!("internal: unknown par_list builtin {name}"),
1711 line,
1712 )),
1713 }
1714 }
1715
1716 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
1717 #[cfg(unix)]
1718 if let Some(sig) = s.signal() {
1719 return sig as i64 & 0x7f;
1720 }
1721 let code = s.code().unwrap_or(0) as i64;
1722 code << 8
1723 }
1724
1725 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
1726 self.child_exit_status = self.encode_exit_status(s);
1727 }
1728
1729 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
1731 self.errno = e.to_string();
1732 self.errno_code = e.raw_os_error().unwrap_or(0);
1733 }
1734
1735 pub(crate) fn ssh_builtin_execute(&mut self, args: &[PerlValue]) -> PerlResult<PerlValue> {
1746 use std::process::Command;
1747 let mut cmd = Command::new("ssh");
1748 #[cfg(unix)]
1749 {
1750 use libc::geteuid;
1751 let home_for_ssh = if unsafe { geteuid() } == 0 {
1752 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
1753 } else {
1754 None
1755 };
1756 if let Some(h) = home_for_ssh {
1757 cmd.env("HOME", h);
1758 } else if std::env::var_os("HOME").is_none() {
1759 if let Some(h) = pw_home_dir_for_current_uid() {
1760 cmd.env("HOME", h);
1761 }
1762 }
1763 }
1764 for a in args {
1765 cmd.arg(a.to_string());
1766 }
1767 match cmd.status() {
1768 Ok(s) => {
1769 self.record_child_exit_status(s);
1770 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
1771 }
1772 Err(e) => {
1773 self.apply_io_error_to_errno(&e);
1774 Ok(PerlValue::integer(-1))
1775 }
1776 }
1777 }
1778
1779 pub(crate) fn set_eval_error(&mut self, msg: String) {
1781 self.eval_error = msg;
1782 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
1783 self.eval_error_value = None;
1784 }
1785
1786 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &PerlError) {
1787 self.eval_error = e.to_string();
1788 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
1789 self.eval_error_value = e.die_value.clone();
1790 }
1791
1792 pub(crate) fn clear_eval_error(&mut self) {
1793 self.eval_error = String::new();
1794 self.eval_error_code = 0;
1795 self.eval_error_value = None;
1796 }
1797
1798 fn bump_line_for_handle(&mut self, handle_key: &str) {
1800 self.last_readline_handle = handle_key.to_string();
1801 *self
1802 .handle_line_numbers
1803 .entry(handle_key.to_string())
1804 .or_insert(0) += 1;
1805 }
1806
1807 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
1809 if name.starts_with('^') {
1810 return name.to_string();
1811 }
1812 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
1813 let pkg = self.current_package();
1814 if !pkg.is_empty() && pkg != "main" {
1815 return format!("{}::{}", pkg, name);
1816 }
1817 }
1818 name.to_string()
1819 }
1820
1821 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
1823 if name.contains("::") {
1824 return name.to_string();
1825 }
1826 let pkg = self.current_package();
1827 if pkg.is_empty() || pkg == "main" {
1828 format!("main::{}", name)
1829 } else {
1830 format!("{}::{}", pkg, name)
1831 }
1832 }
1833
1834 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
1836 if name.contains("::") {
1837 return name.to_string();
1838 }
1839 for (lex, our) in self
1840 .english_lexical_scalars
1841 .iter()
1842 .zip(self.our_lexical_scalars.iter())
1843 .rev()
1844 {
1845 if lex.contains(name) {
1846 if our.contains(name) {
1847 return self.stash_scalar_name_for_package(name);
1848 }
1849 return name.to_string();
1850 }
1851 }
1852 name.to_string()
1853 }
1854
1855 pub(crate) fn tie_execute(
1857 &mut self,
1858 target_kind: u8,
1859 target_name: &str,
1860 class_and_args: Vec<PerlValue>,
1861 line: usize,
1862 ) -> PerlResult<PerlValue> {
1863 let mut it = class_and_args.into_iter();
1864 let class = it.next().unwrap_or(PerlValue::UNDEF);
1865 let pkg = class.to_string();
1866 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
1867 let tie_ctor = match target_kind {
1868 0 => "TIESCALAR",
1869 1 => "TIEARRAY",
1870 2 => "TIEHASH",
1871 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
1872 };
1873 let tie_fn = format!("{}::{}", pkg, tie_ctor);
1874 let sub = self
1875 .subs
1876 .get(&tie_fn)
1877 .cloned()
1878 .ok_or_else(|| PerlError::runtime(format!("tie: cannot find &{}", tie_fn), line))?;
1879 let mut call_args = vec![PerlValue::string(pkg.clone())];
1880 call_args.extend(it);
1881 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
1882 Ok(v) => v,
1883 Err(FlowOrError::Flow(_)) => PerlValue::UNDEF,
1884 Err(FlowOrError::Error(e)) => return Err(e),
1885 };
1886 match target_kind {
1887 0 => {
1888 self.tied_scalars.insert(target_name.to_string(), obj);
1889 }
1890 1 => {
1891 let key = self.stash_array_name_for_package(target_name);
1892 self.tied_arrays.insert(key, obj);
1893 }
1894 2 => {
1895 self.tied_hashes.insert(target_name.to_string(), obj);
1896 }
1897 _ => return Err(PerlError::runtime("tie: invalid target kind", line)),
1898 }
1899 Ok(PerlValue::UNDEF)
1900 }
1901
1902 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
1904 let key = format!("{}::ISA", class);
1905 self.scope
1906 .get_array(&key)
1907 .into_iter()
1908 .map(|v| v.to_string())
1909 .collect()
1910 }
1911
1912 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
1913 let p = |c: &str| self.parents_of_class(c);
1914 linearize_c3(class, &p, 0)
1915 }
1916
1917 pub(crate) fn resolve_method_full_name(
1919 &self,
1920 invocant_class: &str,
1921 method: &str,
1922 super_mode: bool,
1923 ) -> Option<String> {
1924 let mro = self.mro_linearize(invocant_class);
1925 let start = if super_mode {
1929 mro.iter()
1930 .position(|p| p == invocant_class)
1931 .map(|i| i + 1)
1932 .unwrap_or(1)
1935 } else {
1936 0
1937 };
1938 for pkg in mro.iter().skip(start) {
1939 if pkg == "UNIVERSAL" {
1940 continue;
1941 }
1942 let fq = format!("{}::{}", pkg, method);
1943 if self.subs.contains_key(&fq) {
1944 return Some(fq);
1945 }
1946 }
1947 mro.iter()
1948 .skip(start)
1949 .find(|p| *p != "UNIVERSAL")
1950 .map(|pkg| format!("{}::{}", pkg, method))
1951 }
1952
1953 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
1954 if let Some(alias) = self.glob_handle_alias.get(name) {
1955 return alias.clone();
1956 }
1957 if let Some(var_name) = name.strip_prefix('$') {
1960 let val = self.scope.get_scalar(var_name);
1961 let s = val.to_string();
1962 if !s.is_empty() {
1963 return self.resolve_io_handle_name(&s);
1964 }
1965 }
1966 name.to_string()
1967 }
1968
1969 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
1971 if name.contains("::") {
1972 name.to_string()
1973 } else {
1974 self.qualify_sub_key(name)
1975 }
1976 }
1977
1978 pub(crate) fn copy_typeglob_slots(
1980 &mut self,
1981 lhs: &str,
1982 rhs: &str,
1983 line: usize,
1984 ) -> PerlResult<()> {
1985 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
1986 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
1987 match self.subs.get(&rhs_sub).cloned() {
1988 Some(s) => {
1989 self.subs.insert(lhs_sub, s);
1990 }
1991 None => {
1992 self.subs.remove(&lhs_sub);
1993 }
1994 }
1995 let sv = self.scope.get_scalar(rhs);
1996 self.scope
1997 .set_scalar(lhs, sv.clone())
1998 .map_err(|e| e.at_line(line))?;
1999 let lhs_an = self.stash_array_name_for_package(lhs);
2000 let rhs_an = self.stash_array_name_for_package(rhs);
2001 let av = self.scope.get_array(&rhs_an);
2002 self.scope
2003 .set_array(&lhs_an, av.clone())
2004 .map_err(|e| e.at_line(line))?;
2005 let hv = self.scope.get_hash(rhs);
2006 self.scope
2007 .set_hash(lhs, hv.clone())
2008 .map_err(|e| e.at_line(line))?;
2009 match self.glob_handle_alias.get(rhs).cloned() {
2010 Some(t) => {
2011 self.glob_handle_alias.insert(lhs.to_string(), t);
2012 }
2013 None => {
2014 self.glob_handle_alias.remove(lhs);
2015 }
2016 }
2017 Ok(())
2018 }
2019
2020 pub(crate) fn install_format_decl(
2022 &mut self,
2023 basename: &str,
2024 lines: &[String],
2025 line: usize,
2026 ) -> PerlResult<()> {
2027 let pkg = self.current_package();
2028 let key = format!("{}::{}", pkg, basename);
2029 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2030 self.format_templates.insert(key, Arc::new(tmpl));
2031 Ok(())
2032 }
2033
2034 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2036 let pkg = self.current_package();
2037 let ent = self.overload_table.entry(pkg).or_default();
2038 for (k, v) in pairs {
2039 ent.insert(k.clone(), v.clone());
2040 }
2041 }
2042
2043 pub(crate) fn local_declare_typeglob(
2046 &mut self,
2047 lhs: &str,
2048 rhs: Option<&str>,
2049 line: usize,
2050 ) -> PerlResult<()> {
2051 let old = self.glob_handle_alias.remove(lhs);
2052 let Some(frame) = self.glob_restore_frames.last_mut() else {
2053 return Err(PerlError::runtime(
2054 "internal: no glob restore frame for local *GLOB",
2055 line,
2056 ));
2057 };
2058 frame.push((lhs.to_string(), old));
2059 if let Some(r) = rhs {
2060 self.glob_handle_alias
2061 .insert(lhs.to_string(), r.to_string());
2062 }
2063 Ok(())
2064 }
2065
2066 pub(crate) fn scope_push_hook(&mut self) {
2067 self.scope.push_frame();
2068 self.glob_restore_frames.push(Vec::new());
2069 self.special_var_restore_frames.push(Vec::new());
2070 self.english_lexical_scalars.push(HashSet::new());
2071 self.our_lexical_scalars.push(HashSet::new());
2072 self.state_bindings_stack.push(Vec::new());
2073 }
2074
2075 #[inline]
2076 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2077 if let Some(s) = self.english_lexical_scalars.last_mut() {
2078 s.insert(name.to_string());
2079 }
2080 }
2081
2082 #[inline]
2083 fn note_our_scalar(&mut self, bare_name: &str) {
2084 if let Some(s) = self.our_lexical_scalars.last_mut() {
2085 s.insert(bare_name.to_string());
2086 }
2087 }
2088
2089 pub(crate) fn scope_pop_hook(&mut self) {
2090 if !self.scope.can_pop_frame() {
2091 return;
2092 }
2093 let defers = self.scope.take_defers();
2097 for coderef in defers {
2098 if let Some(sub) = coderef.as_code_ref() {
2099 let saved_wa = self.wantarray_kind;
2103 self.wantarray_kind = WantarrayCtx::Void;
2104 let _ = self.exec_block_no_scope(&sub.body);
2105 self.wantarray_kind = saved_wa;
2106 }
2107 }
2108 if let Some(bindings) = self.state_bindings_stack.pop() {
2110 for (var_name, state_key) in &bindings {
2111 let val = self.scope.get_scalar(var_name).clone();
2112 self.state_vars.insert(state_key.clone(), val);
2113 }
2114 }
2115 if let Some(entries) = self.special_var_restore_frames.pop() {
2118 for (name, old) in entries.into_iter().rev() {
2119 let _ = self.set_special_var(&name, &old);
2120 }
2121 }
2122 if let Some(entries) = self.glob_restore_frames.pop() {
2123 for (name, old) in entries.into_iter().rev() {
2124 match old {
2125 Some(s) => {
2126 self.glob_handle_alias.insert(name, s);
2127 }
2128 None => {
2129 self.glob_handle_alias.remove(&name);
2130 }
2131 }
2132 }
2133 }
2134 self.scope.pop_frame();
2135 let _ = self.english_lexical_scalars.pop();
2136 let _ = self.our_lexical_scalars.pop();
2137 }
2138
2139 #[inline]
2142 pub(crate) fn enable_parallel_guard(&mut self) {
2143 self.scope.set_parallel_guard(true);
2144 }
2145
2146 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2149 self.begin_blocks.clear();
2150 self.unit_check_blocks.clear();
2151 self.check_blocks.clear();
2152 self.init_blocks.clear();
2153 self.end_blocks.clear();
2154 }
2155
2156 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2162 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2163 self.scope_pop_hook();
2164 }
2165 }
2166
2167 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> PerlResult<()> {
2174 self.touch_env_hash("SIG");
2175 let v = self.scope.get_hash_element("SIG", sig);
2176 if v.is_undef() {
2177 return Self::default_sig_action(sig);
2178 }
2179 if let Some(s) = v.as_str() {
2180 if s == "IGNORE" {
2181 return Ok(());
2182 }
2183 if s == "DEFAULT" {
2184 return Self::default_sig_action(sig);
2185 }
2186 }
2187 if let Some(sub) = v.as_code_ref() {
2188 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2189 Ok(_) => Ok(()),
2190 Err(FlowOrError::Flow(_)) => Ok(()),
2191 Err(FlowOrError::Error(e)) => Err(e),
2192 }
2193 } else {
2194 Self::default_sig_action(sig)
2195 }
2196 }
2197
2198 #[inline]
2200 fn default_sig_action(sig: &str) -> PerlResult<()> {
2201 match sig {
2202 "INT" => std::process::exit(130),
2204 "TERM" => std::process::exit(143),
2205 "ALRM" => std::process::exit(142),
2206 "CHLD" => Ok(()),
2208 _ => Ok(()),
2209 }
2210 }
2211
2212 pub fn materialize_env_if_needed(&mut self) {
2215 if self.env_materialized {
2216 return;
2217 }
2218 self.env = std::env::vars()
2219 .map(|(k, v)| (k, PerlValue::string(v)))
2220 .collect();
2221 self.scope
2222 .set_hash("ENV", self.env.clone())
2223 .expect("set %ENV");
2224 self.env_materialized = true;
2225 }
2226
2227 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2229 self.materialize_env_if_needed();
2230 if let Some(x) = self.log_level_override {
2231 return x;
2232 }
2233 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2234 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2235 }
2236
2237 pub(crate) fn no_color_effective(&mut self) -> bool {
2239 self.materialize_env_if_needed();
2240 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2241 if v.is_undef() {
2242 return false;
2243 }
2244 !v.to_string().is_empty()
2245 }
2246
2247 #[inline]
2248 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2249 if hash_name == "ENV" {
2250 self.materialize_env_if_needed();
2251 }
2252 }
2253
2254 pub(crate) fn exists_arrow_hash_element(
2256 &self,
2257 container: PerlValue,
2258 key: &str,
2259 line: usize,
2260 ) -> PerlResult<bool> {
2261 if let Some(r) = container.as_hash_ref() {
2262 return Ok(r.read().contains_key(key));
2263 }
2264 if let Some(b) = container.as_blessed_ref() {
2265 let data = b.data.read();
2266 if let Some(r) = data.as_hash_ref() {
2267 return Ok(r.read().contains_key(key));
2268 }
2269 if let Some(hm) = data.as_hash_map() {
2270 return Ok(hm.contains_key(key));
2271 }
2272 return Err(PerlError::runtime(
2273 "exists argument is not a HASH reference",
2274 line,
2275 ));
2276 }
2277 Err(PerlError::runtime(
2278 "exists argument is not a HASH reference",
2279 line,
2280 ))
2281 }
2282
2283 pub(crate) fn delete_arrow_hash_element(
2285 &self,
2286 container: PerlValue,
2287 key: &str,
2288 line: usize,
2289 ) -> PerlResult<PerlValue> {
2290 if let Some(r) = container.as_hash_ref() {
2291 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2292 }
2293 if let Some(b) = container.as_blessed_ref() {
2294 let mut data = b.data.write();
2295 if let Some(r) = data.as_hash_ref() {
2296 return Ok(r.write().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2297 }
2298 if let Some(mut map) = data.as_hash_map() {
2299 let v = map.shift_remove(key).unwrap_or(PerlValue::UNDEF);
2300 *data = PerlValue::hash(map);
2301 return Ok(v);
2302 }
2303 return Err(PerlError::runtime(
2304 "delete argument is not a HASH reference",
2305 line,
2306 ));
2307 }
2308 Err(PerlError::runtime(
2309 "delete argument is not a HASH reference",
2310 line,
2311 ))
2312 }
2313
2314 pub(crate) fn exists_arrow_array_element(
2316 &self,
2317 container: PerlValue,
2318 idx: i64,
2319 line: usize,
2320 ) -> PerlResult<bool> {
2321 if let Some(a) = container.as_array_ref() {
2322 let arr = a.read();
2323 let i = if idx < 0 {
2324 (arr.len() as i64 + idx) as usize
2325 } else {
2326 idx as usize
2327 };
2328 return Ok(i < arr.len());
2329 }
2330 Err(PerlError::runtime(
2331 "exists argument is not an ARRAY reference",
2332 line,
2333 ))
2334 }
2335
2336 pub(crate) fn delete_arrow_array_element(
2338 &self,
2339 container: PerlValue,
2340 idx: i64,
2341 line: usize,
2342 ) -> PerlResult<PerlValue> {
2343 if let Some(a) = container.as_array_ref() {
2344 let mut arr = a.write();
2345 let i = if idx < 0 {
2346 (arr.len() as i64 + idx) as usize
2347 } else {
2348 idx as usize
2349 };
2350 if i >= arr.len() {
2351 return Ok(PerlValue::UNDEF);
2352 }
2353 let old = arr.get(i).cloned().unwrap_or(PerlValue::UNDEF);
2354 arr[i] = PerlValue::UNDEF;
2355 return Ok(old);
2356 }
2357 Err(PerlError::runtime(
2358 "delete argument is not an ARRAY reference",
2359 line,
2360 ))
2361 }
2362
2363 pub(crate) fn inc_directories(&self) -> Vec<String> {
2365 let mut v: Vec<String> = self
2366 .scope
2367 .get_array("INC")
2368 .into_iter()
2369 .map(|x| x.to_string())
2370 .filter(|s| !s.is_empty())
2371 .collect();
2372 if v.is_empty() {
2373 v.push(".".to_string());
2374 }
2375 v
2376 }
2377
2378 #[inline]
2379 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
2380 matches!(
2381 name,
2382 "_" | "0"
2383 | "!"
2384 | "@"
2385 | "/"
2386 | "\\"
2387 | ","
2388 | "."
2389 | "__PACKAGE__"
2390 | "$$"
2391 | "|"
2392 | "?"
2393 | "\""
2394 | "&"
2395 | "`"
2396 | "'"
2397 | "+"
2398 | "<"
2399 | ">"
2400 | "("
2401 | ")"
2402 | "]"
2403 | ";"
2404 | "ARGV"
2405 | "%"
2406 | "="
2407 | "-"
2408 | ":"
2409 | "*"
2410 | "INC"
2411 ) || name.chars().all(|c| c.is_ascii_digit())
2412 || name.starts_with('^')
2413 || (name.starts_with('#') && name.len() > 1)
2414 }
2415
2416 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2417 if !self.strict_vars
2418 || Self::strict_scalar_exempt(name)
2419 || name.contains("::")
2420 || self.scope.scalar_binding_exists(name)
2421 {
2422 return Ok(());
2423 }
2424 Err(PerlError::runtime(
2425 format!(
2426 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
2427 name, name
2428 ),
2429 line,
2430 )
2431 .into())
2432 }
2433
2434 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2435 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
2436 return Ok(());
2437 }
2438 Err(PerlError::runtime(
2439 format!(
2440 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
2441 name, name
2442 ),
2443 line,
2444 )
2445 .into())
2446 }
2447
2448 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
2449 if !self.strict_vars
2451 || name.contains("::")
2452 || self.scope.hash_binding_exists(name)
2453 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
2454 {
2455 return Ok(());
2456 }
2457 Err(PerlError::runtime(
2458 format!(
2459 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
2460 name, name
2461 ),
2462 line,
2463 )
2464 .into())
2465 }
2466
2467 fn looks_like_version_only(spec: &str) -> bool {
2468 let t = spec.trim();
2469 !t.is_empty()
2470 && !t.contains('/')
2471 && !t.contains('\\')
2472 && !t.contains("::")
2473 && t.chars()
2474 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
2475 && t.chars().any(|c| c.is_ascii_digit())
2476 }
2477
2478 fn module_spec_to_relpath(spec: &str) -> String {
2479 let t = spec.trim();
2480 if t.contains("::") {
2481 format!("{}.pm", t.replace("::", "/"))
2482 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
2483 t.replace('\\', "/")
2484 } else {
2485 format!("{}.pm", t)
2486 }
2487 }
2488
2489 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
2492 if name.contains("::") {
2493 return name.to_string();
2494 }
2495 let pkg = self.current_package();
2496 if pkg.is_empty() || pkg == "main" {
2497 name.to_string()
2498 } else {
2499 format!("{}::{}", pkg, name)
2500 }
2501 }
2502
2503 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
2505 let mut msg = format!("Undefined subroutine &{}", name);
2506 if self.strict_subs {
2507 msg.push_str(
2508 " (strict subs: declare the sub or use a fully qualified name before calling)",
2509 );
2510 }
2511 msg
2512 }
2513
2514 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
2516 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
2517 if self.strict_subs {
2518 msg.push_str(
2519 " (strict subs: declare the sub or use a fully qualified name before calling)",
2520 );
2521 }
2522 msg
2523 }
2524
2525 fn import_alias_key(&self, short: &str) -> String {
2527 self.qualify_sub_key(short)
2528 }
2529
2530 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
2532 if imports.len() == 1 {
2533 match &imports[0].kind {
2534 ExprKind::QW(ws) => return ws.is_empty(),
2535 ExprKind::List(xs) => return xs.is_empty(),
2537 _ => {}
2538 }
2539 }
2540 false
2541 }
2542
2543 fn apply_module_import(
2545 &mut self,
2546 module: &str,
2547 imports: &[Expr],
2548 line: usize,
2549 ) -> PerlResult<()> {
2550 if imports.is_empty() {
2551 return self.import_all_from_module(module, line);
2552 }
2553 if Self::is_explicit_empty_import_list(imports) {
2554 return Ok(());
2555 }
2556 let names = Self::pragma_import_strings(imports, line)?;
2557 if names.is_empty() {
2558 return Ok(());
2559 }
2560 for name in names {
2561 self.import_one_symbol(module, &name, line)?;
2562 }
2563 Ok(())
2564 }
2565
2566 fn import_all_from_module(&mut self, module: &str, line: usize) -> PerlResult<()> {
2567 if module == "List::Util" {
2568 crate::list_util::ensure_list_util(self);
2569 }
2570 if let Some(lists) = self.module_export_lists.get(module) {
2571 let export: Vec<String> = lists.export.clone();
2572 for short in export {
2573 self.import_named_sub(module, &short, line)?;
2574 }
2575 return Ok(());
2576 }
2577 let prefix = format!("{}::", module);
2579 let keys: Vec<String> = self
2580 .subs
2581 .keys()
2582 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
2583 .cloned()
2584 .collect();
2585 for k in keys {
2586 let short = k[prefix.len()..].to_string();
2587 if let Some(sub) = self.subs.get(&k).cloned() {
2588 let alias = self.import_alias_key(&short);
2589 self.subs.insert(alias, sub);
2590 }
2591 }
2592 Ok(())
2593 }
2594
2595 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> PerlResult<()> {
2597 if module == "List::Util" {
2598 crate::list_util::ensure_list_util(self);
2599 }
2600 let qual = format!("{}::{}", module, short);
2601 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
2602 PerlError::runtime(
2603 format!(
2604 "`{}` is not defined in module `{}` (expected `{}`)",
2605 short, module, qual
2606 ),
2607 line,
2608 )
2609 })?;
2610 let alias = self.import_alias_key(short);
2611 self.subs.insert(alias, sub);
2612 Ok(())
2613 }
2614
2615 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> PerlResult<()> {
2616 if let Some(lists) = self.module_export_lists.get(module) {
2617 let allowed: HashSet<&str> = lists
2618 .export
2619 .iter()
2620 .map(|s| s.as_str())
2621 .chain(lists.export_ok.iter().map(|s| s.as_str()))
2622 .collect();
2623 if !allowed.contains(export) {
2624 return Err(PerlError::runtime(
2625 format!(
2626 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
2627 export, module
2628 ),
2629 line,
2630 ));
2631 }
2632 }
2633 self.import_named_sub(module, export, line)
2634 }
2635
2636 fn record_exporter_our_array_name(&mut self, name: &str, items: &[PerlValue]) {
2638 if name != "EXPORT" && name != "EXPORT_OK" {
2639 return;
2640 }
2641 let pkg = self.current_package();
2642 if pkg.is_empty() || pkg == "main" {
2643 return;
2644 }
2645 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
2646 let ent = self.module_export_lists.entry(pkg).or_default();
2647 if name == "EXPORT" {
2648 ent.export = names;
2649 } else {
2650 ent.export_ok = names;
2651 }
2652 }
2653
2654 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
2658 let key = self.qualify_sub_key(name);
2659 let Some(sub) = self.subs.get(&key).cloned() else {
2660 return;
2661 };
2662 let captured = self.scope.capture();
2663 let closure_env = if captured.is_empty() {
2664 None
2665 } else {
2666 Some(captured)
2667 };
2668 let mut new_sub = (*sub).clone();
2669 new_sub.closure_env = closure_env;
2670 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
2671 self.subs.insert(key, Arc::new(new_sub));
2672 }
2673
2674 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<PerlSub>> {
2675 if let Some(s) = self.subs.get(name) {
2676 return Some(s.clone());
2677 }
2678 if !name.contains("::") {
2679 let pkg = self.current_package();
2680 if !pkg.is_empty() && pkg != "main" {
2681 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
2682 q.push_str(&pkg);
2683 q.push_str("::");
2684 q.push_str(name);
2685 return self.subs.get(&q).cloned();
2686 }
2687 }
2688 None
2689 }
2690
2691 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
2694 if let Some(first) = imports.first() {
2695 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
2696 return &imports[1..];
2697 }
2698 }
2699 imports
2700 }
2701
2702 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> PerlResult<Vec<String>> {
2704 let mut out = Vec::new();
2705 for e in imports {
2706 match &e.kind {
2707 ExprKind::String(s) => out.push(s.clone()),
2708 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
2709 ExprKind::Integer(n) => out.push(n.to_string()),
2710 ExprKind::InterpolatedString(parts) => {
2713 let mut s = String::new();
2714 for p in parts {
2715 match p {
2716 StringPart::Literal(l) => s.push_str(l),
2717 StringPart::ScalarVar(v) => {
2718 s.push('$');
2719 s.push_str(v);
2720 }
2721 StringPart::ArrayVar(v) => {
2722 s.push('@');
2723 s.push_str(v);
2724 }
2725 _ => {
2726 return Err(PerlError::runtime(
2727 "pragma import must be a compile-time string, qw(), or integer",
2728 e.line.max(default_line),
2729 ));
2730 }
2731 }
2732 }
2733 out.push(s);
2734 }
2735 _ => {
2736 return Err(PerlError::runtime(
2737 "pragma import must be a compile-time string, qw(), or integer",
2738 e.line.max(default_line),
2739 ));
2740 }
2741 }
2742 }
2743 Ok(out)
2744 }
2745
2746 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2747 if imports.is_empty() {
2748 self.strict_refs = true;
2749 self.strict_subs = true;
2750 self.strict_vars = true;
2751 return Ok(());
2752 }
2753 let names = Self::pragma_import_strings(imports, line)?;
2754 for name in names {
2755 match name.as_str() {
2756 "refs" => self.strict_refs = true,
2757 "subs" => self.strict_subs = true,
2758 "vars" => self.strict_vars = true,
2759 _ => {
2760 return Err(PerlError::runtime(
2761 format!("Unknown strict mode `{}`", name),
2762 line,
2763 ));
2764 }
2765 }
2766 }
2767 Ok(())
2768 }
2769
2770 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2771 if imports.is_empty() {
2772 self.strict_refs = false;
2773 self.strict_subs = false;
2774 self.strict_vars = false;
2775 return Ok(());
2776 }
2777 let names = Self::pragma_import_strings(imports, line)?;
2778 for name in names {
2779 match name.as_str() {
2780 "refs" => self.strict_refs = false,
2781 "subs" => self.strict_subs = false,
2782 "vars" => self.strict_vars = false,
2783 _ => {
2784 return Err(PerlError::runtime(
2785 format!("Unknown strict mode `{}`", name),
2786 line,
2787 ));
2788 }
2789 }
2790 }
2791 Ok(())
2792 }
2793
2794 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2795 let items = Self::pragma_import_strings(imports, line)?;
2796 if items.is_empty() {
2797 return Err(PerlError::runtime(
2798 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
2799 line,
2800 ));
2801 }
2802 for item in items {
2803 let s = item.trim();
2804 if let Some(rest) = s.strip_prefix(':') {
2805 self.apply_feature_bundle(rest, line)?;
2806 } else {
2807 self.apply_feature_name(s, true, line)?;
2808 }
2809 }
2810 Ok(())
2811 }
2812
2813 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
2814 if imports.is_empty() {
2815 self.feature_bits = 0;
2816 return Ok(());
2817 }
2818 let items = Self::pragma_import_strings(imports, line)?;
2819 for item in items {
2820 let s = item.trim();
2821 if let Some(rest) = s.strip_prefix(':') {
2822 self.clear_feature_bundle(rest);
2823 } else {
2824 self.apply_feature_name(s, false, line)?;
2825 }
2826 }
2827 Ok(())
2828 }
2829
2830 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> PerlResult<()> {
2831 let key = v.trim();
2832 match key {
2833 "5.10" | "5.010" | "5.10.0" => {
2834 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
2835 }
2836 "5.12" | "5.012" | "5.12.0" => {
2837 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
2838 }
2839 _ => {
2840 return Err(PerlError::runtime(
2841 format!("unsupported feature bundle :{}", key),
2842 line,
2843 ));
2844 }
2845 }
2846 Ok(())
2847 }
2848
2849 fn clear_feature_bundle(&mut self, v: &str) {
2850 let key = v.trim();
2851 if matches!(
2852 key,
2853 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
2854 ) {
2855 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
2856 }
2857 }
2858
2859 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> PerlResult<()> {
2860 let bit = match name {
2861 "say" => FEAT_SAY,
2862 "state" => FEAT_STATE,
2863 "switch" => FEAT_SWITCH,
2864 "unicode_strings" => FEAT_UNICODE_STRINGS,
2865 "postderef"
2869 | "postderef_qq"
2870 | "evalbytes"
2871 | "current_sub"
2872 | "fc"
2873 | "lexical_subs"
2874 | "signatures"
2875 | "refaliasing"
2876 | "bitwise"
2877 | "isa"
2878 | "indirect"
2879 | "multidimensional"
2880 | "bareword_filehandles"
2881 | "try"
2882 | "defer"
2883 | "extra_paired_delimiters"
2884 | "module_true"
2885 | "class"
2886 | "array_base" => return Ok(()),
2887 _ => {
2888 return Err(PerlError::runtime(
2889 format!("unknown feature `{}`", name),
2890 line,
2891 ));
2892 }
2893 };
2894 if enable {
2895 self.feature_bits |= bit;
2896 } else {
2897 self.feature_bits &= !bit;
2898 }
2899 Ok(())
2900 }
2901
2902 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> PerlResult<PerlValue> {
2904 let t = spec.trim();
2905 if t.is_empty() {
2906 return Err(PerlError::runtime("require: empty argument", line));
2907 }
2908 match t {
2909 "strict" => {
2910 self.apply_use_strict(&[], line)?;
2911 return Ok(PerlValue::integer(1));
2912 }
2913 "utf8" => {
2914 self.utf8_pragma = true;
2915 return Ok(PerlValue::integer(1));
2916 }
2917 "feature" | "v5" => {
2918 return Ok(PerlValue::integer(1));
2919 }
2920 "warnings" => {
2921 self.warnings = true;
2922 return Ok(PerlValue::integer(1));
2923 }
2924 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
2925 return Ok(PerlValue::integer(1));
2926 }
2927 _ => {}
2928 }
2929 let p = Path::new(t);
2930 if p.is_absolute() {
2931 return self.require_absolute_path(p, line);
2932 }
2933 if Self::looks_like_version_only(t) {
2934 return Ok(PerlValue::integer(1));
2935 }
2936 let relpath = Self::module_spec_to_relpath(t);
2937 self.require_from_inc(&relpath, line)
2938 }
2939
2940 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> PerlResult<()> {
2942 let v = self.scope.get_hash_element("^HOOK", key);
2943 if v.is_undef() {
2944 return Ok(());
2945 }
2946 let Some(sub) = v.as_code_ref() else {
2947 return Ok(());
2948 };
2949 let r = self.call_sub(
2950 sub.as_ref(),
2951 vec![PerlValue::string(path.to_string())],
2952 WantarrayCtx::Scalar,
2953 line,
2954 );
2955 match r {
2956 Ok(_) => Ok(()),
2957 Err(FlowOrError::Error(e)) => Err(e),
2958 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
2959 Err(FlowOrError::Flow(other)) => Err(PerlError::runtime(
2960 format!(
2961 "require hook {:?} returned unexpected control flow: {:?}",
2962 key, other
2963 ),
2964 line,
2965 )),
2966 }
2967 }
2968
2969 fn require_absolute_path(&mut self, path: &Path, line: usize) -> PerlResult<PerlValue> {
2970 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
2971 let key = canon.to_string_lossy().into_owned();
2972 if self.scope.exists_hash_element("INC", &key) {
2973 return Ok(PerlValue::integer(1));
2974 }
2975 self.invoke_require_hook("require__before", &key, line)?;
2976 let code = read_file_text_perl_compat(&canon).map_err(|e| {
2977 PerlError::runtime(
2978 format!("Can't open {} for reading: {}", canon.display(), e),
2979 line,
2980 )
2981 })?;
2982 let code = crate::data_section::strip_perl_end_marker(&code);
2983 self.scope
2984 .set_hash_element("INC", &key, PerlValue::string(key.clone()))?;
2985 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
2986 let r = crate::parse_and_run_string_in_file(code, self, &key);
2987 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
2988 r?;
2989 self.invoke_require_hook("require__after", &key, line)?;
2990 Ok(PerlValue::integer(1))
2991 }
2992
2993 fn require_from_inc(&mut self, relpath: &str, line: usize) -> PerlResult<PerlValue> {
2994 if self.scope.exists_hash_element("INC", relpath) {
2995 return Ok(PerlValue::integer(1));
2996 }
2997 self.invoke_require_hook("require__before", relpath, line)?;
2998 for dir in self.inc_directories() {
2999 let full = Path::new(&dir).join(relpath);
3000 if full.is_file() {
3001 let code = read_file_text_perl_compat(&full).map_err(|e| {
3002 PerlError::runtime(
3003 format!("Can't open {} for reading: {}", full.display(), e),
3004 line,
3005 )
3006 })?;
3007 let code = crate::data_section::strip_perl_end_marker(&code);
3008 let abs = full.canonicalize().unwrap_or(full);
3009 let abs_s = abs.to_string_lossy().into_owned();
3010 self.scope
3011 .set_hash_element("INC", relpath, PerlValue::string(abs_s.clone()))?;
3012 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3013 let r = crate::parse_and_run_string_in_file(code, self, &abs_s);
3014 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3015 r?;
3016 self.invoke_require_hook("require__after", relpath, line)?;
3017 return Ok(PerlValue::integer(1));
3018 }
3019 }
3020 Err(PerlError::runtime(
3021 format!(
3022 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3023 relpath
3024 ),
3025 line,
3026 ))
3027 }
3028
3029 pub(crate) fn exec_use_stmt(
3031 &mut self,
3032 module: &str,
3033 imports: &[Expr],
3034 line: usize,
3035 ) -> PerlResult<()> {
3036 match module {
3037 "strict" => self.apply_use_strict(imports, line),
3038 "utf8" => {
3039 if !imports.is_empty() {
3040 return Err(PerlError::runtime("use utf8 takes no arguments", line));
3041 }
3042 self.utf8_pragma = true;
3043 Ok(())
3044 }
3045 "feature" => self.apply_use_feature(imports, line),
3046 "v5" => Ok(()),
3047 "warnings" => {
3048 self.warnings = true;
3049 Ok(())
3050 }
3051 "English" => {
3052 self.english_enabled = true;
3053 let args = Self::pragma_import_strings(imports, line)?;
3054 let no_match = args.iter().any(|a| a == "-no_match_vars");
3055 if !no_match {
3059 self.english_match_vars_ever_enabled = true;
3060 }
3061 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3062 Ok(())
3063 }
3064 "Env" => self.apply_use_env(imports, line),
3065 "open" => self.apply_use_open(imports, line),
3066 "constant" => self.apply_use_constant(imports, line),
3067 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3068 _ => {
3069 self.require_execute(module, line)?;
3070 let imports = Self::imports_after_leading_use_version(imports);
3071 self.apply_module_import(module, imports, line)?;
3072 Ok(())
3073 }
3074 }
3075 }
3076
3077 pub(crate) fn exec_no_stmt(
3079 &mut self,
3080 module: &str,
3081 imports: &[Expr],
3082 line: usize,
3083 ) -> PerlResult<()> {
3084 match module {
3085 "strict" => self.apply_no_strict(imports, line),
3086 "utf8" => {
3087 if !imports.is_empty() {
3088 return Err(PerlError::runtime("no utf8 takes no arguments", line));
3089 }
3090 self.utf8_pragma = false;
3091 Ok(())
3092 }
3093 "feature" => self.apply_no_feature(imports, line),
3094 "v5" => Ok(()),
3095 "warnings" => {
3096 self.warnings = false;
3097 Ok(())
3098 }
3099 "English" => {
3100 self.english_enabled = false;
3101 if !self.english_match_vars_ever_enabled {
3104 self.english_no_match_vars = false;
3105 }
3106 Ok(())
3107 }
3108 "open" => {
3109 self.open_pragma_utf8 = false;
3110 Ok(())
3111 }
3112 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3113 _ => Ok(()),
3114 }
3115 }
3116
3117 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3119 let names = Self::pragma_import_strings(imports, line)?;
3120 for n in names {
3121 let key = n.trim_start_matches('@');
3122 if key.eq_ignore_ascii_case("PATH") {
3123 let path_env = std::env::var("PATH").unwrap_or_default();
3124 let path_vec: Vec<PerlValue> = std::env::split_paths(&path_env)
3125 .map(|p| PerlValue::string(p.to_string_lossy().into_owned()))
3126 .collect();
3127 let aname = self.stash_array_name_for_package("PATH");
3128 self.scope.declare_array(&aname, path_vec);
3129 }
3130 }
3131 Ok(())
3132 }
3133
3134 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3136 let items = Self::pragma_import_strings(imports, line)?;
3137 for item in items {
3138 let s = item.trim();
3139 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3140 self.open_pragma_utf8 = true;
3141 continue;
3142 }
3143 if let Some(rest) = s.strip_prefix(":encoding(") {
3144 if let Some(inner) = rest.strip_suffix(')') {
3145 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3146 self.open_pragma_utf8 = true;
3147 }
3148 }
3149 }
3150 }
3151 Ok(())
3152 }
3153
3154 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> PerlResult<()> {
3156 if imports.is_empty() {
3157 return Ok(());
3158 }
3159 if imports.len() == 1 {
3161 match &imports[0].kind {
3162 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
3163 _ => {}
3164 }
3165 }
3166 for imp in imports {
3167 match &imp.kind {
3168 ExprKind::List(items) => {
3169 if items.len() % 2 != 0 {
3170 return Err(PerlError::runtime(
3171 format!(
3172 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
3173 items.len()
3174 ),
3175 line,
3176 ));
3177 }
3178 let mut i = 0;
3179 while i < items.len() {
3180 let name = match &items[i].kind {
3181 ExprKind::String(s) => s.clone(),
3182 _ => {
3183 return Err(PerlError::runtime(
3184 "use constant: constant name must be a string literal",
3185 line,
3186 ));
3187 }
3188 };
3189 let val = match self.eval_expr(&items[i + 1]) {
3190 Ok(v) => v,
3191 Err(FlowOrError::Error(e)) => return Err(e),
3192 Err(FlowOrError::Flow(_)) => {
3193 return Err(PerlError::runtime(
3194 "use constant: unexpected control flow in initializer",
3195 line,
3196 ));
3197 }
3198 };
3199 self.install_constant_sub(&name, &val, line)?;
3200 i += 2;
3201 }
3202 }
3203 _ => {
3204 return Err(PerlError::runtime(
3205 "use constant: expected list of NAME => VALUE pairs",
3206 line,
3207 ));
3208 }
3209 }
3210 }
3211 Ok(())
3212 }
3213
3214 fn install_constant_sub(&mut self, name: &str, val: &PerlValue, line: usize) -> PerlResult<()> {
3215 let key = self.qualify_sub_key(name);
3216 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
3217 let body = vec![Statement {
3218 label: None,
3219 kind: StmtKind::Return(Some(ret_expr)),
3220 line,
3221 }];
3222 self.subs.insert(
3223 key.clone(),
3224 Arc::new(PerlSub {
3225 name: key,
3226 params: vec![],
3227 body,
3228 prototype: None,
3229 closure_env: None,
3230 fib_like: None,
3231 }),
3232 );
3233 Ok(())
3234 }
3235
3236 fn perl_value_to_const_literal_expr(&self, v: &PerlValue, line: usize) -> PerlResult<Expr> {
3238 if v.is_undef() {
3239 return Ok(Expr {
3240 kind: ExprKind::Undef,
3241 line,
3242 });
3243 }
3244 if let Some(n) = v.as_integer() {
3245 return Ok(Expr {
3246 kind: ExprKind::Integer(n),
3247 line,
3248 });
3249 }
3250 if let Some(f) = v.as_float() {
3251 return Ok(Expr {
3252 kind: ExprKind::Float(f),
3253 line,
3254 });
3255 }
3256 if let Some(s) = v.as_str() {
3257 return Ok(Expr {
3258 kind: ExprKind::String(s),
3259 line,
3260 });
3261 }
3262 if let Some(arr) = v.as_array_vec() {
3263 let mut elems = Vec::with_capacity(arr.len());
3264 for e in &arr {
3265 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3266 }
3267 return Ok(Expr {
3268 kind: ExprKind::ArrayRef(elems),
3269 line,
3270 });
3271 }
3272 if let Some(h) = v.as_hash_map() {
3273 let mut pairs = Vec::with_capacity(h.len());
3274 for (k, vv) in h.iter() {
3275 pairs.push((
3276 Expr {
3277 kind: ExprKind::String(k.clone()),
3278 line,
3279 },
3280 self.perl_value_to_const_literal_expr(vv, line)?,
3281 ));
3282 }
3283 return Ok(Expr {
3284 kind: ExprKind::HashRef(pairs),
3285 line,
3286 });
3287 }
3288 if let Some(aref) = v.as_array_ref() {
3289 let arr = aref.read();
3290 let mut elems = Vec::with_capacity(arr.len());
3291 for e in arr.iter() {
3292 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3293 }
3294 return Ok(Expr {
3295 kind: ExprKind::ArrayRef(elems),
3296 line,
3297 });
3298 }
3299 if let Some(href) = v.as_hash_ref() {
3300 let h = href.read();
3301 let mut pairs = Vec::with_capacity(h.len());
3302 for (k, vv) in h.iter() {
3303 pairs.push((
3304 Expr {
3305 kind: ExprKind::String(k.clone()),
3306 line,
3307 },
3308 self.perl_value_to_const_literal_expr(vv, line)?,
3309 ));
3310 }
3311 return Ok(Expr {
3312 kind: ExprKind::HashRef(pairs),
3313 line,
3314 });
3315 }
3316 Err(PerlError::runtime(
3317 format!("use constant: unsupported value type ({v:?})"),
3318 line,
3319 ))
3320 }
3321
3322 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> PerlResult<()> {
3324 if crate::list_util::program_needs_list_util(program) {
3325 crate::list_util::ensure_list_util(self);
3326 }
3327 for stmt in &program.statements {
3328 match &stmt.kind {
3329 StmtKind::Package { name } => {
3330 let _ = self
3331 .scope
3332 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
3333 }
3334 StmtKind::SubDecl {
3335 name,
3336 params,
3337 body,
3338 prototype,
3339 } => {
3340 let key = self.qualify_sub_key(name);
3341 let mut sub = PerlSub {
3342 name: name.clone(),
3343 params: params.clone(),
3344 body: body.clone(),
3345 closure_env: None,
3346 prototype: prototype.clone(),
3347 fib_like: None,
3348 };
3349 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
3350 self.subs.insert(key, Arc::new(sub));
3351 }
3352 StmtKind::UsePerlVersion { .. } => {}
3353 StmtKind::Use { module, imports } => {
3354 self.exec_use_stmt(module, imports, stmt.line)?;
3355 }
3356 StmtKind::UseOverload { pairs } => {
3357 self.install_use_overload_pairs(pairs);
3358 }
3359 StmtKind::FormatDecl { name, lines } => {
3360 self.install_format_decl(name, lines, stmt.line)?;
3361 }
3362 StmtKind::No { module, imports } => {
3363 self.exec_no_stmt(module, imports, stmt.line)?;
3364 }
3365 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
3366 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
3367 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
3368 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
3369 StmtKind::End(block) => self.end_blocks.push(block.clone()),
3370 _ => {}
3371 }
3372 }
3373 Ok(())
3374 }
3375
3376 pub fn install_data_handle(&mut self, data: Vec<u8>) {
3378 self.input_handles.insert(
3379 "DATA".to_string(),
3380 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
3381 );
3382 }
3383
3384 pub(crate) fn open_builtin_execute(
3390 &mut self,
3391 handle_name: String,
3392 mode_s: String,
3393 file_opt: Option<String>,
3394 line: usize,
3395 ) -> PerlResult<PerlValue> {
3396 let (actual_mode, path) = if let Some(f) = file_opt {
3401 (mode_s, f)
3402 } else {
3403 let trimmed = mode_s.trim();
3404 if let Some(rest) = trimmed.strip_prefix('|') {
3405 ("|-".to_string(), rest.trim_start().to_string())
3406 } else if trimmed.ends_with('|') {
3407 let mut cmd = trimmed.to_string();
3408 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
3410 } else if let Some(rest) = trimmed.strip_prefix(">>") {
3411 (">>".to_string(), rest.trim().to_string())
3412 } else if let Some(rest) = trimmed.strip_prefix('>') {
3413 (">".to_string(), rest.trim().to_string())
3414 } else if let Some(rest) = trimmed.strip_prefix('<') {
3415 ("<".to_string(), rest.trim().to_string())
3416 } else {
3417 ("<".to_string(), trimmed.to_string())
3418 }
3419 };
3420 let handle_return = handle_name.clone();
3421 match actual_mode.as_str() {
3422 "-|" => {
3423 let mut cmd = piped_shell_command(&path);
3424 cmd.stdout(Stdio::piped());
3425 let mut child = cmd.spawn().map_err(|e| {
3426 self.apply_io_error_to_errno(&e);
3427 PerlError::runtime(format!("Can't open pipe from command: {}", e), line)
3428 })?;
3429 let stdout = child
3430 .stdout
3431 .take()
3432 .ok_or_else(|| PerlError::runtime("pipe: child has no stdout", line))?;
3433 self.input_handles
3434 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
3435 self.pipe_children.insert(handle_name, child);
3436 }
3437 "|-" => {
3438 let mut cmd = piped_shell_command(&path);
3439 cmd.stdin(Stdio::piped());
3440 let mut child = cmd.spawn().map_err(|e| {
3441 self.apply_io_error_to_errno(&e);
3442 PerlError::runtime(format!("Can't open pipe to command: {}", e), line)
3443 })?;
3444 let stdin = child
3445 .stdin
3446 .take()
3447 .ok_or_else(|| PerlError::runtime("pipe: child has no stdin", line))?;
3448 self.output_handles
3449 .insert(handle_name.clone(), Box::new(stdin));
3450 self.pipe_children.insert(handle_name, child);
3451 }
3452 "<" => {
3453 let file = std::fs::File::open(&path).map_err(|e| {
3454 self.apply_io_error_to_errno(&e);
3455 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3456 })?;
3457 let shared = Arc::new(Mutex::new(file));
3458 self.io_file_slots
3459 .insert(handle_name.clone(), Arc::clone(&shared));
3460 self.input_handles.insert(
3461 handle_name.clone(),
3462 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
3463 );
3464 }
3465 ">" => {
3466 let file = std::fs::File::create(&path).map_err(|e| {
3467 self.apply_io_error_to_errno(&e);
3468 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3469 })?;
3470 let shared = Arc::new(Mutex::new(file));
3471 self.io_file_slots
3472 .insert(handle_name.clone(), Arc::clone(&shared));
3473 self.output_handles.insert(
3474 handle_name.clone(),
3475 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3476 );
3477 }
3478 ">>" => {
3479 let file = std::fs::OpenOptions::new()
3480 .append(true)
3481 .create(true)
3482 .open(&path)
3483 .map_err(|e| {
3484 self.apply_io_error_to_errno(&e);
3485 PerlError::runtime(format!("Can't open '{}': {}", path, e), line)
3486 })?;
3487 let shared = Arc::new(Mutex::new(file));
3488 self.io_file_slots
3489 .insert(handle_name.clone(), Arc::clone(&shared));
3490 self.output_handles.insert(
3491 handle_name.clone(),
3492 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
3493 );
3494 }
3495 _ => {
3496 return Err(PerlError::runtime(
3497 format!("Unknown open mode '{}'", actual_mode),
3498 line,
3499 ));
3500 }
3501 }
3502 Ok(PerlValue::io_handle(handle_return))
3503 }
3504
3505 pub(crate) fn eval_chunk_by_builtin(
3509 &mut self,
3510 key_spec: &Expr,
3511 list_expr: &Expr,
3512 ctx: WantarrayCtx,
3513 line: usize,
3514 ) -> ExecResult {
3515 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
3516 let chunks = match &key_spec.kind {
3517 ExprKind::CodeRef { .. } => {
3518 let cr = self.eval_expr(key_spec)?;
3519 let Some(sub) = cr.as_code_ref() else {
3520 return Err(PerlError::runtime(
3521 "group_by/chunk_by: first argument must be { BLOCK }",
3522 line,
3523 )
3524 .into());
3525 };
3526 let sub = sub.clone();
3527 let mut chunks: Vec<PerlValue> = Vec::new();
3528 let mut run: Vec<PerlValue> = Vec::new();
3529 let mut prev_key: Option<PerlValue> = None;
3530 for item in list {
3531 self.scope.set_topic(item.clone());
3532 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
3533 Ok(k) => k,
3534 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
3535 Err(FlowOrError::Flow(Flow::Return(v))) => v,
3536 Err(_) => PerlValue::UNDEF,
3537 };
3538 match &prev_key {
3539 None => {
3540 run.push(item);
3541 prev_key = Some(key);
3542 }
3543 Some(pk) => {
3544 if key.str_eq(pk) {
3545 run.push(item);
3546 } else {
3547 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3548 std::mem::take(&mut run),
3549 ))));
3550 run.push(item);
3551 prev_key = Some(key);
3552 }
3553 }
3554 }
3555 }
3556 if !run.is_empty() {
3557 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3558 }
3559 chunks
3560 }
3561 _ => {
3562 let mut chunks: Vec<PerlValue> = Vec::new();
3563 let mut run: Vec<PerlValue> = Vec::new();
3564 let mut prev_key: Option<PerlValue> = None;
3565 for item in list {
3566 self.scope.set_topic(item.clone());
3567 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
3568 match &prev_key {
3569 None => {
3570 run.push(item);
3571 prev_key = Some(key);
3572 }
3573 Some(pk) => {
3574 if key.str_eq(pk) {
3575 run.push(item);
3576 } else {
3577 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(
3578 std::mem::take(&mut run),
3579 ))));
3580 run.push(item);
3581 prev_key = Some(key);
3582 }
3583 }
3584 }
3585 }
3586 if !run.is_empty() {
3587 chunks.push(PerlValue::array_ref(Arc::new(RwLock::new(run))));
3588 }
3589 chunks
3590 }
3591 };
3592 Ok(match ctx {
3593 WantarrayCtx::List => PerlValue::array(chunks),
3594 WantarrayCtx::Scalar => PerlValue::integer(chunks.len() as i64),
3595 WantarrayCtx::Void => PerlValue::UNDEF,
3596 })
3597 }
3598
3599 pub(crate) fn list_higher_order_block_builtin(
3601 &mut self,
3602 name: &str,
3603 args: &[PerlValue],
3604 line: usize,
3605 ) -> PerlResult<PerlValue> {
3606 match self.list_higher_order_block_builtin_exec(name, args, line) {
3607 Ok(v) => Ok(v),
3608 Err(FlowOrError::Error(e)) => Err(e),
3609 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
3610 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
3611 format!("{name}: unsupported control flow in block"),
3612 line,
3613 )),
3614 }
3615 }
3616
3617 fn list_higher_order_block_builtin_exec(
3618 &mut self,
3619 name: &str,
3620 args: &[PerlValue],
3621 line: usize,
3622 ) -> ExecResult {
3623 if args.is_empty() {
3624 return Err(
3625 PerlError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
3626 );
3627 }
3628 let Some(sub) = args[0].as_code_ref() else {
3629 return Err(PerlError::runtime(
3630 format!("{name}: first argument must be {{ BLOCK }}"),
3631 line,
3632 )
3633 .into());
3634 };
3635 let sub = sub.clone();
3636 let items: Vec<PerlValue> = args[1..].to_vec();
3637 if matches!(name, "tap" | "peek") && items.len() == 1 {
3638 if let Some(p) = items[0].as_pipeline() {
3639 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
3640 return Ok(PerlValue::pipeline(Arc::clone(&p)));
3641 }
3642 let v = &items[0];
3643 if v.is_iterator() || v.as_array_vec().is_some() {
3644 let source = crate::map_stream::into_pull_iter(v.clone());
3645 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
3646 return Ok(PerlValue::iterator(Arc::new(
3647 crate::map_stream::TapIterator::new(
3648 source,
3649 sub,
3650 self.subs.clone(),
3651 capture,
3652 atomic_arrays,
3653 atomic_hashes,
3654 ),
3655 )));
3656 }
3657 }
3658 let wa = self.wantarray_kind;
3663 match name {
3664 "take_while" => {
3665 let mut out = Vec::new();
3666 for item in items {
3667 self.scope_push_hook();
3668 self.scope.set_topic(item.clone());
3669 let pred = self.exec_block(&sub.body)?;
3670 self.scope_pop_hook();
3671 if !pred.is_true() {
3672 break;
3673 }
3674 out.push(item);
3675 }
3676 Ok(match wa {
3677 WantarrayCtx::List => PerlValue::array(out),
3678 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3679 WantarrayCtx::Void => PerlValue::UNDEF,
3680 })
3681 }
3682 "drop_while" | "skip_while" => {
3683 let mut i = 0usize;
3684 while i < items.len() {
3685 self.scope_push_hook();
3686 self.scope.set_topic(items[i].clone());
3687 let pred = self.exec_block(&sub.body)?;
3688 self.scope_pop_hook();
3689 if !pred.is_true() {
3690 break;
3691 }
3692 i += 1;
3693 }
3694 let rest = items[i..].to_vec();
3695 Ok(match wa {
3696 WantarrayCtx::List => PerlValue::array(rest),
3697 WantarrayCtx::Scalar => PerlValue::integer(rest.len() as i64),
3698 WantarrayCtx::Void => PerlValue::UNDEF,
3699 })
3700 }
3701 "reject" => {
3702 let mut out = Vec::new();
3703 for item in items {
3704 self.scope_push_hook();
3705 self.scope.set_topic(item.clone());
3706 let pred = self.exec_block(&sub.body)?;
3707 self.scope_pop_hook();
3708 if !pred.is_true() {
3709 out.push(item);
3710 }
3711 }
3712 Ok(match wa {
3713 WantarrayCtx::List => PerlValue::array(out),
3714 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3715 WantarrayCtx::Void => PerlValue::UNDEF,
3716 })
3717 }
3718 "tap" | "peek" => {
3719 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
3720 Ok(match wa {
3721 WantarrayCtx::List => PerlValue::array(items),
3722 WantarrayCtx::Scalar => PerlValue::integer(items.len() as i64),
3723 WantarrayCtx::Void => PerlValue::UNDEF,
3724 })
3725 }
3726 "partition" => {
3727 let mut yes = Vec::new();
3728 let mut no = Vec::new();
3729 for item in items {
3730 self.scope.set_topic(item.clone());
3731 let pred = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3732 if pred.is_true() {
3733 yes.push(item);
3734 } else {
3735 no.push(item);
3736 }
3737 }
3738 let yes_ref = PerlValue::array_ref(Arc::new(RwLock::new(yes)));
3739 let no_ref = PerlValue::array_ref(Arc::new(RwLock::new(no)));
3740 Ok(match wa {
3741 WantarrayCtx::List => PerlValue::array(vec![yes_ref, no_ref]),
3742 WantarrayCtx::Scalar => PerlValue::integer(2),
3743 WantarrayCtx::Void => PerlValue::UNDEF,
3744 })
3745 }
3746 "min_by" => {
3747 let mut best: Option<(PerlValue, PerlValue)> = None;
3748 for item in items {
3749 self.scope.set_topic(item.clone());
3750 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3751 best = Some(match best {
3752 None => (item, key),
3753 Some((bv, bk)) => {
3754 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
3755 (item, key)
3756 } else {
3757 (bv, bk)
3758 }
3759 }
3760 });
3761 }
3762 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
3763 }
3764 "max_by" => {
3765 let mut best: Option<(PerlValue, PerlValue)> = None;
3766 for item in items {
3767 self.scope.set_topic(item.clone());
3768 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3769 best = Some(match best {
3770 None => (item, key),
3771 Some((bv, bk)) => {
3772 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
3773 (item, key)
3774 } else {
3775 (bv, bk)
3776 }
3777 }
3778 });
3779 }
3780 Ok(best.map(|(v, _)| v).unwrap_or(PerlValue::UNDEF))
3781 }
3782 "zip_with" => {
3783 let flat: Vec<PerlValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
3786 let refs: Vec<Vec<PerlValue>> = flat
3787 .iter()
3788 .map(|el| {
3789 if let Some(ar) = el.as_array_ref() {
3790 ar.read().clone()
3791 } else if let Some(name) = el.as_array_binding_name() {
3792 self.scope.get_array(&name)
3793 } else {
3794 vec![el.clone()]
3795 }
3796 })
3797 .collect();
3798 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
3799 let mut out = Vec::with_capacity(max_len);
3800 for i in 0..max_len {
3801 let pair: Vec<PerlValue> = refs
3802 .iter()
3803 .map(|l| l.get(i).cloned().unwrap_or(PerlValue::UNDEF))
3804 .collect();
3805 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
3806 out.push(result);
3807 }
3808 Ok(match wa {
3809 WantarrayCtx::List => PerlValue::array(out),
3810 WantarrayCtx::Scalar => PerlValue::integer(out.len() as i64),
3811 WantarrayCtx::Void => PerlValue::UNDEF,
3812 })
3813 }
3814 "count_by" => {
3815 let mut counts = indexmap::IndexMap::new();
3816 for item in items {
3817 self.scope.set_topic(item.clone());
3818 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
3819 let k = key.to_string();
3820 let entry = counts.entry(k).or_insert(PerlValue::integer(0));
3821 *entry = PerlValue::integer(entry.to_int() + 1);
3822 }
3823 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(counts))))
3824 }
3825 _ => Err(PerlError::runtime(
3826 format!("internal: unknown list block builtin `{name}`"),
3827 line,
3828 )
3829 .into()),
3830 }
3831 }
3832
3833 pub(crate) fn builtin_rmdir_execute(
3835 &mut self,
3836 args: &[PerlValue],
3837 _line: usize,
3838 ) -> PerlResult<PerlValue> {
3839 let mut count = 0i64;
3840 for a in args {
3841 let p = a.to_string();
3842 if p.is_empty() {
3843 continue;
3844 }
3845 if std::fs::remove_dir(&p).is_ok() {
3846 count += 1;
3847 }
3848 }
3849 Ok(PerlValue::integer(count))
3850 }
3851
3852 pub(crate) fn builtin_touch_execute(
3854 &mut self,
3855 args: &[PerlValue],
3856 _line: usize,
3857 ) -> PerlResult<PerlValue> {
3858 let paths: Vec<String> = args.iter().map(|v| v.to_string()).collect();
3859 Ok(PerlValue::integer(crate::perl_fs::touch_paths(&paths)))
3860 }
3861
3862 pub(crate) fn builtin_utime_execute(
3864 &mut self,
3865 args: &[PerlValue],
3866 line: usize,
3867 ) -> PerlResult<PerlValue> {
3868 if args.len() < 3 {
3869 return Err(PerlError::runtime(
3870 "utime requires at least three arguments (atime, mtime, files...)",
3871 line,
3872 ));
3873 }
3874 let at = args[0].to_int();
3875 let mt = args[1].to_int();
3876 let paths: Vec<String> = args.iter().skip(2).map(|v| v.to_string()).collect();
3877 let n = crate::perl_fs::utime_paths(at, mt, &paths);
3878 #[cfg(not(unix))]
3879 if !paths.is_empty() && n == 0 {
3880 return Err(PerlError::runtime(
3881 "utime is not supported on this platform",
3882 line,
3883 ));
3884 }
3885 Ok(PerlValue::integer(n))
3886 }
3887
3888 pub(crate) fn builtin_umask_execute(
3890 &mut self,
3891 args: &[PerlValue],
3892 line: usize,
3893 ) -> PerlResult<PerlValue> {
3894 #[cfg(unix)]
3895 {
3896 let _ = line;
3897 if args.is_empty() {
3898 let cur = unsafe { libc::umask(0) };
3899 unsafe { libc::umask(cur) };
3900 return Ok(PerlValue::integer(cur as i64));
3901 }
3902 let new_m = args[0].to_int() as libc::mode_t;
3903 let old = unsafe { libc::umask(new_m) };
3904 Ok(PerlValue::integer(old as i64))
3905 }
3906 #[cfg(not(unix))]
3907 {
3908 let _ = args;
3909 Err(PerlError::runtime(
3910 "umask is not supported on this platform",
3911 line,
3912 ))
3913 }
3914 }
3915
3916 pub(crate) fn builtin_getcwd_execute(
3918 &mut self,
3919 args: &[PerlValue],
3920 line: usize,
3921 ) -> PerlResult<PerlValue> {
3922 if !args.is_empty() {
3923 return Err(PerlError::runtime("getcwd takes no arguments", line));
3924 }
3925 match std::env::current_dir() {
3926 Ok(p) => Ok(PerlValue::string(p.to_string_lossy().into_owned())),
3927 Err(e) => {
3928 self.apply_io_error_to_errno(&e);
3929 Ok(PerlValue::UNDEF)
3930 }
3931 }
3932 }
3933
3934 pub(crate) fn builtin_realpath_execute(
3936 &mut self,
3937 args: &[PerlValue],
3938 line: usize,
3939 ) -> PerlResult<PerlValue> {
3940 let path = args
3941 .first()
3942 .ok_or_else(|| PerlError::runtime("realpath: need path", line))?
3943 .to_string();
3944 if path.is_empty() {
3945 return Err(PerlError::runtime("realpath: need path", line));
3946 }
3947 match crate::perl_fs::realpath_resolved(&path) {
3948 Ok(s) => Ok(PerlValue::string(s)),
3949 Err(e) => {
3950 self.apply_io_error_to_errno(&e);
3951 Ok(PerlValue::UNDEF)
3952 }
3953 }
3954 }
3955
3956 pub(crate) fn builtin_pipe_execute(
3958 &mut self,
3959 args: &[PerlValue],
3960 line: usize,
3961 ) -> PerlResult<PerlValue> {
3962 if args.len() != 2 {
3963 return Err(PerlError::runtime(
3964 "pipe requires exactly two arguments",
3965 line,
3966 ));
3967 }
3968 #[cfg(unix)]
3969 {
3970 use std::fs::File;
3971 use std::os::unix::io::FromRawFd;
3972
3973 let read_name = args[0].to_string();
3974 let write_name = args[1].to_string();
3975 if read_name.is_empty() || write_name.is_empty() {
3976 return Err(PerlError::runtime("pipe: invalid handle name", line));
3977 }
3978 let mut fds = [0i32; 2];
3979 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
3980 let e = std::io::Error::last_os_error();
3981 self.apply_io_error_to_errno(&e);
3982 return Ok(PerlValue::integer(0));
3983 }
3984 let read_file = unsafe { File::from_raw_fd(fds[0]) };
3985 let write_file = unsafe { File::from_raw_fd(fds[1]) };
3986
3987 let read_shared = Arc::new(Mutex::new(read_file));
3988 let write_shared = Arc::new(Mutex::new(write_file));
3989
3990 self.close_builtin_execute(read_name.clone()).ok();
3991 self.close_builtin_execute(write_name.clone()).ok();
3992
3993 self.io_file_slots
3994 .insert(read_name.clone(), Arc::clone(&read_shared));
3995 self.input_handles.insert(
3996 read_name,
3997 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
3998 );
3999
4000 self.io_file_slots
4001 .insert(write_name.clone(), Arc::clone(&write_shared));
4002 self.output_handles
4003 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
4004
4005 Ok(PerlValue::integer(1))
4006 }
4007 #[cfg(not(unix))]
4008 {
4009 let _ = args;
4010 Err(PerlError::runtime(
4011 "pipe is not supported on this platform",
4012 line,
4013 ))
4014 }
4015 }
4016
4017 pub(crate) fn close_builtin_execute(&mut self, name: String) -> PerlResult<PerlValue> {
4018 self.output_handles.remove(&name);
4019 self.input_handles.remove(&name);
4020 self.io_file_slots.remove(&name);
4021 if let Some(mut child) = self.pipe_children.remove(&name) {
4022 if let Ok(st) = child.wait() {
4023 self.record_child_exit_status(st);
4024 }
4025 }
4026 Ok(PerlValue::integer(1))
4027 }
4028
4029 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
4030 self.input_handles.contains_key(name)
4031 }
4032
4033 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
4037 self.line_mode_eof_pending
4038 }
4039
4040 pub(crate) fn eof_builtin_execute(
4044 &self,
4045 args: &[PerlValue],
4046 line: usize,
4047 ) -> PerlResult<PerlValue> {
4048 match args.len() {
4049 0 => Ok(PerlValue::integer(if self.eof_without_arg_is_true() {
4050 1
4051 } else {
4052 0
4053 })),
4054 1 => {
4055 let name = args[0].to_string();
4056 let at_eof = !self.has_input_handle(&name);
4057 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
4058 }
4059 _ => Err(PerlError::runtime("eof: too many arguments", line)),
4060 }
4061 }
4062
4063 pub(crate) fn study_return_value(s: &str) -> PerlValue {
4066 if s.is_empty() {
4067 PerlValue::string(String::new())
4068 } else {
4069 PerlValue::integer(1)
4070 }
4071 }
4072
4073 pub(crate) fn readline_builtin_execute(
4074 &mut self,
4075 handle: Option<&str>,
4076 ) -> PerlResult<PerlValue> {
4077 if handle.is_none() {
4079 let argv = self.scope.get_array("ARGV");
4080 if !argv.is_empty() {
4081 loop {
4082 if self.diamond_reader.is_none() {
4083 while self.diamond_next_idx < argv.len() {
4084 let path = argv[self.diamond_next_idx].to_string();
4085 self.diamond_next_idx += 1;
4086 match File::open(&path) {
4087 Ok(f) => {
4088 self.argv_current_file = path;
4089 self.diamond_reader = Some(BufReader::new(f));
4090 break;
4091 }
4092 Err(e) => {
4093 self.apply_io_error_to_errno(&e);
4094 }
4095 }
4096 }
4097 if self.diamond_reader.is_none() {
4098 return Ok(PerlValue::UNDEF);
4099 }
4100 }
4101 let mut line_str = String::new();
4102 let read_result: Result<usize, io::Error> =
4103 if let Some(reader) = self.diamond_reader.as_mut() {
4104 if self.open_pragma_utf8 {
4105 let mut buf = Vec::new();
4106 reader.read_until(b'\n', &mut buf).inspect(|n| {
4107 if *n > 0 {
4108 line_str = String::from_utf8_lossy(&buf).into_owned();
4109 }
4110 })
4111 } else {
4112 let mut buf = Vec::new();
4113 match reader.read_until(b'\n', &mut buf) {
4114 Ok(n) => {
4115 if n > 0 {
4116 line_str =
4117 crate::perl_decode::decode_utf8_or_latin1_read_until(
4118 &buf,
4119 );
4120 }
4121 Ok(n)
4122 }
4123 Err(e) => Err(e),
4124 }
4125 }
4126 } else {
4127 unreachable!()
4128 };
4129 match read_result {
4130 Ok(0) => {
4131 self.diamond_reader = None;
4132 continue;
4133 }
4134 Ok(_) => {
4135 self.bump_line_for_handle(&self.argv_current_file.clone());
4136 return Ok(PerlValue::string(line_str));
4137 }
4138 Err(e) => {
4139 self.apply_io_error_to_errno(&e);
4140 self.diamond_reader = None;
4141 continue;
4142 }
4143 }
4144 }
4145 } else {
4146 self.argv_current_file.clear();
4147 }
4148 }
4149
4150 let handle_name = handle.unwrap_or("STDIN");
4151 let mut line_str = String::new();
4152 if handle_name == "STDIN" {
4153 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
4154 self.last_stdin_die_bracket = if handle.is_none() {
4155 "<>".to_string()
4156 } else {
4157 "<STDIN>".to_string()
4158 };
4159 self.bump_line_for_handle("STDIN");
4160 return Ok(PerlValue::string(queued));
4161 }
4162 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4163 let mut buf = Vec::new();
4164 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
4165 if *n > 0 {
4166 line_str = String::from_utf8_lossy(&buf).into_owned();
4167 }
4168 })
4169 } else {
4170 let mut buf = Vec::new();
4171 let mut lock = io::stdin().lock();
4172 match lock.read_until(b'\n', &mut buf) {
4173 Ok(n) => {
4174 if n > 0 {
4175 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4176 }
4177 Ok(n)
4178 }
4179 Err(e) => Err(e),
4180 }
4181 };
4182 match r {
4183 Ok(0) => Ok(PerlValue::UNDEF),
4184 Ok(_) => {
4185 self.last_stdin_die_bracket = if handle.is_none() {
4186 "<>".to_string()
4187 } else {
4188 "<STDIN>".to_string()
4189 };
4190 self.bump_line_for_handle("STDIN");
4191 Ok(PerlValue::string(line_str))
4192 }
4193 Err(e) => {
4194 self.apply_io_error_to_errno(&e);
4195 Ok(PerlValue::UNDEF)
4196 }
4197 }
4198 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
4199 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4200 let mut buf = Vec::new();
4201 reader.read_until(b'\n', &mut buf).inspect(|n| {
4202 if *n > 0 {
4203 line_str = String::from_utf8_lossy(&buf).into_owned();
4204 }
4205 })
4206 } else {
4207 let mut buf = Vec::new();
4208 match reader.read_until(b'\n', &mut buf) {
4209 Ok(n) => {
4210 if n > 0 {
4211 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4212 }
4213 Ok(n)
4214 }
4215 Err(e) => Err(e),
4216 }
4217 };
4218 match r {
4219 Ok(0) => Ok(PerlValue::UNDEF),
4220 Ok(_) => {
4221 self.bump_line_for_handle(handle_name);
4222 Ok(PerlValue::string(line_str))
4223 }
4224 Err(e) => {
4225 self.apply_io_error_to_errno(&e);
4226 Ok(PerlValue::UNDEF)
4227 }
4228 }
4229 } else {
4230 Ok(PerlValue::UNDEF)
4231 }
4232 }
4233
4234 pub(crate) fn readline_builtin_execute_list(
4236 &mut self,
4237 handle: Option<&str>,
4238 ) -> PerlResult<PerlValue> {
4239 let mut lines = Vec::new();
4240 loop {
4241 let v = self.readline_builtin_execute(handle)?;
4242 if v.is_undef() {
4243 break;
4244 }
4245 lines.push(v);
4246 }
4247 Ok(PerlValue::array(lines))
4248 }
4249
4250 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> PerlValue {
4251 match std::fs::read_dir(path) {
4252 Ok(rd) => {
4253 let entries: Vec<String> = rd
4254 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
4255 .collect();
4256 self.dir_handles
4257 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
4258 PerlValue::integer(1)
4259 }
4260 Err(e) => {
4261 self.apply_io_error_to_errno(&e);
4262 PerlValue::integer(0)
4263 }
4264 }
4265 }
4266
4267 pub(crate) fn readdir_handle(&mut self, handle: &str) -> PerlValue {
4268 if let Some(dh) = self.dir_handles.get_mut(handle) {
4269 if dh.pos < dh.entries.len() {
4270 let s = dh.entries[dh.pos].clone();
4271 dh.pos += 1;
4272 PerlValue::string(s)
4273 } else {
4274 PerlValue::UNDEF
4275 }
4276 } else {
4277 PerlValue::UNDEF
4278 }
4279 }
4280
4281 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> PerlValue {
4283 if let Some(dh) = self.dir_handles.get_mut(handle) {
4284 let rest: Vec<PerlValue> = dh.entries[dh.pos..]
4285 .iter()
4286 .cloned()
4287 .map(PerlValue::string)
4288 .collect();
4289 dh.pos = dh.entries.len();
4290 PerlValue::array(rest)
4291 } else {
4292 PerlValue::array(Vec::new())
4293 }
4294 }
4295
4296 pub(crate) fn closedir_handle(&mut self, handle: &str) -> PerlValue {
4297 PerlValue::integer(if self.dir_handles.remove(handle).is_some() {
4298 1
4299 } else {
4300 0
4301 })
4302 }
4303
4304 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> PerlValue {
4305 if let Some(dh) = self.dir_handles.get_mut(handle) {
4306 dh.pos = 0;
4307 PerlValue::integer(1)
4308 } else {
4309 PerlValue::integer(0)
4310 }
4311 }
4312
4313 pub(crate) fn telldir_handle(&mut self, handle: &str) -> PerlValue {
4314 self.dir_handles
4315 .get(handle)
4316 .map(|dh| PerlValue::integer(dh.pos as i64))
4317 .unwrap_or(PerlValue::UNDEF)
4318 }
4319
4320 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> PerlValue {
4321 if let Some(dh) = self.dir_handles.get_mut(handle) {
4322 dh.pos = pos.min(dh.entries.len());
4323 PerlValue::integer(1)
4324 } else {
4325 PerlValue::integer(0)
4326 }
4327 }
4328
4329 #[inline]
4334 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
4335 crate::special_vars::is_regex_match_scalar_name(name)
4336 }
4337
4338 #[inline]
4342 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
4343 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
4344 self.regex_capture_scope_fresh = false;
4345 }
4346 }
4347
4348 pub(crate) fn apply_regex_captures(
4349 &mut self,
4350 haystack: &str,
4351 offset: usize,
4352 re: &PerlCompiledRegex,
4353 caps: &PerlCaptures<'_>,
4354 capture_all: CaptureAllMode,
4355 ) -> Result<(), FlowOrError> {
4356 let m0 = caps.get(0).expect("regex capture 0");
4357 let s0 = offset + m0.start;
4358 let e0 = offset + m0.end;
4359 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
4360 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
4361 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
4362 let mut last_paren = String::new();
4363 for i in 1..caps.len() {
4364 if let Some(m) = caps.get(i) {
4365 last_paren = m.text.to_string();
4366 }
4367 }
4368 self.last_paren_match = last_paren;
4369 self.last_subpattern_name = String::new();
4370 for n in re.capture_names().flatten() {
4371 if caps.name(n).is_some() {
4372 self.last_subpattern_name = n.to_string();
4373 }
4374 }
4375 self.scope
4376 .set_scalar("&", PerlValue::string(self.last_match.clone()))?;
4377 self.scope
4378 .set_scalar("`", PerlValue::string(self.prematch.clone()))?;
4379 self.scope
4380 .set_scalar("'", PerlValue::string(self.postmatch.clone()))?;
4381 self.scope
4382 .set_scalar("+", PerlValue::string(self.last_paren_match.clone()))?;
4383 for i in 1..caps.len() {
4384 if let Some(m) = caps.get(i) {
4385 self.scope
4386 .set_scalar(&i.to_string(), PerlValue::string(m.text.to_string()))?;
4387 }
4388 }
4389 let mut start_arr = vec![PerlValue::integer(s0 as i64)];
4390 let mut end_arr = vec![PerlValue::integer(e0 as i64)];
4391 for i in 1..caps.len() {
4392 if let Some(m) = caps.get(i) {
4393 start_arr.push(PerlValue::integer((offset + m.start) as i64));
4394 end_arr.push(PerlValue::integer((offset + m.end) as i64));
4395 } else {
4396 start_arr.push(PerlValue::integer(-1));
4397 end_arr.push(PerlValue::integer(-1));
4398 }
4399 }
4400 self.scope.set_array("-", start_arr)?;
4401 self.scope.set_array("+", end_arr)?;
4402 let mut named = IndexMap::new();
4403 for name in re.capture_names().flatten() {
4404 if let Some(m) = caps.name(name) {
4405 named.insert(name.to_string(), PerlValue::string(m.text.to_string()));
4406 }
4407 }
4408 self.scope.set_hash("+", named.clone())?;
4409 let mut named_minus = IndexMap::new();
4411 for (name, val) in &named {
4412 named_minus.insert(
4413 name.clone(),
4414 PerlValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
4415 );
4416 }
4417 self.scope.set_hash("-", named_minus)?;
4418 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
4419 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
4420 match capture_all {
4421 CaptureAllMode::Empty => {
4422 self.scope.set_array("^CAPTURE_ALL", vec![])?;
4423 }
4424 CaptureAllMode::Append => {
4425 let mut rows = self.scope.get_array("^CAPTURE_ALL");
4426 rows.push(PerlValue::array(cap_flat));
4427 self.scope.set_array("^CAPTURE_ALL", rows)?;
4428 }
4429 CaptureAllMode::Skip => {}
4430 }
4431 Ok(())
4432 }
4433
4434 pub(crate) fn clear_flip_flop_state(&mut self) {
4435 self.flip_flop_active.clear();
4436 self.flip_flop_exclusive_left_line.clear();
4437 self.flip_flop_sequence.clear();
4438 self.flip_flop_last_dot.clear();
4439 self.flip_flop_tree.clear();
4440 }
4441
4442 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
4443 self.flip_flop_active.resize(slots as usize, false);
4444 self.flip_flop_active.fill(false);
4445 self.flip_flop_exclusive_left_line
4446 .resize(slots as usize, None);
4447 self.flip_flop_exclusive_left_line.fill(None);
4448 self.flip_flop_sequence.resize(slots as usize, 0);
4449 self.flip_flop_sequence.fill(0);
4450 self.flip_flop_last_dot.resize(slots as usize, None);
4451 self.flip_flop_last_dot.fill(None);
4452 }
4453
4454 #[inline]
4458 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
4459 if self.last_readline_handle.is_empty() {
4460 self.line_number
4461 } else {
4462 *self
4463 .handle_line_numbers
4464 .get(&self.last_readline_handle)
4465 .unwrap_or(&0)
4466 }
4467 }
4468
4469 pub(crate) fn scalar_flip_flop_eval(
4478 &mut self,
4479 left: i64,
4480 right: i64,
4481 slot: usize,
4482 exclusive: bool,
4483 ) -> PerlResult<PerlValue> {
4484 if self.flip_flop_active.len() <= slot {
4485 self.flip_flop_active.resize(slot + 1, false);
4486 }
4487 if self.flip_flop_exclusive_left_line.len() <= slot {
4488 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4489 }
4490 if self.flip_flop_sequence.len() <= slot {
4491 self.flip_flop_sequence.resize(slot + 1, 0);
4492 }
4493 if self.flip_flop_last_dot.len() <= slot {
4494 self.flip_flop_last_dot.resize(slot + 1, None);
4495 }
4496 let dot = self.scalar_flipflop_dot_line();
4497 let active = &mut self.flip_flop_active[slot];
4498 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4499 let seq = &mut self.flip_flop_sequence[slot];
4500 let last_dot = &mut self.flip_flop_last_dot[slot];
4501 if !*active {
4502 if dot == left {
4503 *active = true;
4504 *seq = 1;
4505 *last_dot = Some(dot);
4506 if exclusive {
4507 *excl_left = Some(dot);
4508 } else {
4509 *excl_left = None;
4510 if dot == right {
4511 *active = false;
4512 return Ok(PerlValue::string(format!("{}E0", *seq)));
4513 }
4514 }
4515 return Ok(PerlValue::string(seq.to_string()));
4516 }
4517 *last_dot = Some(dot);
4518 return Ok(PerlValue::string(String::new()));
4519 }
4520 if *last_dot != Some(dot) {
4523 *seq += 1;
4524 *last_dot = Some(dot);
4525 }
4526 let cur_seq = *seq;
4527 if let Some(ll) = *excl_left {
4528 if dot == right && dot > ll {
4529 *active = false;
4530 *excl_left = None;
4531 *seq = 0;
4532 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4533 }
4534 } else if dot == right {
4535 *active = false;
4536 *seq = 0;
4537 return Ok(PerlValue::string(format!("{}E0", cur_seq)));
4538 }
4539 Ok(PerlValue::string(cur_seq.to_string()))
4540 }
4541
4542 fn regex_flip_flop_transition(
4543 active: &mut bool,
4544 excl_left: &mut Option<i64>,
4545 exclusive: bool,
4546 dot: i64,
4547 left_m: bool,
4548 right_m: bool,
4549 ) -> i64 {
4550 if !*active {
4551 if left_m {
4552 *active = true;
4553 if exclusive {
4554 *excl_left = Some(dot);
4555 } else {
4556 *excl_left = None;
4557 if right_m {
4558 *active = false;
4559 }
4560 }
4561 return 1;
4562 }
4563 return 0;
4564 }
4565 if let Some(ll) = *excl_left {
4566 if right_m && dot > ll {
4567 *active = false;
4568 *excl_left = None;
4569 }
4570 } else if right_m {
4571 *active = false;
4572 }
4573 1
4574 }
4575
4576 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
4581 &mut self,
4582 left_pat: &str,
4583 left_flags: &str,
4584 right_pat: &str,
4585 right_flags: &str,
4586 slot: usize,
4587 exclusive: bool,
4588 line: usize,
4589 ) -> PerlResult<PerlValue> {
4590 let dot = self.scalar_flipflop_dot_line();
4591 let subject = self.scope.get_scalar("_").to_string();
4592 let left_re = self
4593 .compile_regex(left_pat, left_flags, line)
4594 .map_err(|e| match e {
4595 FlowOrError::Error(err) => err,
4596 FlowOrError::Flow(_) => {
4597 PerlError::runtime("unexpected flow in regex flip-flop", line)
4598 }
4599 })?;
4600 let right_re = self
4601 .compile_regex(right_pat, right_flags, line)
4602 .map_err(|e| match e {
4603 FlowOrError::Error(err) => err,
4604 FlowOrError::Flow(_) => {
4605 PerlError::runtime("unexpected flow in regex flip-flop", line)
4606 }
4607 })?;
4608 let left_m = left_re.is_match(&subject);
4609 let right_m = right_re.is_match(&subject);
4610 if self.flip_flop_active.len() <= slot {
4611 self.flip_flop_active.resize(slot + 1, false);
4612 }
4613 if self.flip_flop_exclusive_left_line.len() <= slot {
4614 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4615 }
4616 let active = &mut self.flip_flop_active[slot];
4617 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4618 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4619 active, excl_left, exclusive, dot, left_m, right_m,
4620 )))
4621 }
4622
4623 pub(crate) fn regex_flip_flop_eval_dynamic_right(
4625 &mut self,
4626 left_pat: &str,
4627 left_flags: &str,
4628 slot: usize,
4629 exclusive: bool,
4630 line: usize,
4631 right_m: bool,
4632 ) -> PerlResult<PerlValue> {
4633 let dot = self.scalar_flipflop_dot_line();
4634 let subject = self.scope.get_scalar("_").to_string();
4635 let left_re = self
4636 .compile_regex(left_pat, left_flags, line)
4637 .map_err(|e| match e {
4638 FlowOrError::Error(err) => err,
4639 FlowOrError::Flow(_) => {
4640 PerlError::runtime("unexpected flow in regex flip-flop", line)
4641 }
4642 })?;
4643 let left_m = left_re.is_match(&subject);
4644 if self.flip_flop_active.len() <= slot {
4645 self.flip_flop_active.resize(slot + 1, false);
4646 }
4647 if self.flip_flop_exclusive_left_line.len() <= slot {
4648 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4649 }
4650 let active = &mut self.flip_flop_active[slot];
4651 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4652 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4653 active, excl_left, exclusive, dot, left_m, right_m,
4654 )))
4655 }
4656
4657 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
4659 &mut self,
4660 left_pat: &str,
4661 left_flags: &str,
4662 slot: usize,
4663 exclusive: bool,
4664 line: usize,
4665 rhs_line: i64,
4666 ) -> PerlResult<PerlValue> {
4667 let dot = self.scalar_flipflop_dot_line();
4668 let subject = self.scope.get_scalar("_").to_string();
4669 let left_re = self
4670 .compile_regex(left_pat, left_flags, line)
4671 .map_err(|e| match e {
4672 FlowOrError::Error(err) => err,
4673 FlowOrError::Flow(_) => {
4674 PerlError::runtime("unexpected flow in regex flip-flop", line)
4675 }
4676 })?;
4677 let left_m = left_re.is_match(&subject);
4678 let right_m = dot == rhs_line;
4679 if self.flip_flop_active.len() <= slot {
4680 self.flip_flop_active.resize(slot + 1, false);
4681 }
4682 if self.flip_flop_exclusive_left_line.len() <= slot {
4683 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4684 }
4685 let active = &mut self.flip_flop_active[slot];
4686 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4687 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4688 active, excl_left, exclusive, dot, left_m, right_m,
4689 )))
4690 }
4691
4692 pub(crate) fn regex_eof_flip_flop_eval(
4697 &mut self,
4698 left_pat: &str,
4699 left_flags: &str,
4700 slot: usize,
4701 exclusive: bool,
4702 line: usize,
4703 ) -> PerlResult<PerlValue> {
4704 let dot = self.scalar_flipflop_dot_line();
4705 let subject = self.scope.get_scalar("_").to_string();
4706 let left_re = self
4707 .compile_regex(left_pat, left_flags, line)
4708 .map_err(|e| match e {
4709 FlowOrError::Error(err) => err,
4710 FlowOrError::Flow(_) => {
4711 PerlError::runtime("unexpected flow in regex/eof flip-flop", line)
4712 }
4713 })?;
4714 let left_m = left_re.is_match(&subject);
4715 let right_m = self.eof_without_arg_is_true();
4716 if self.flip_flop_active.len() <= slot {
4717 self.flip_flop_active.resize(slot + 1, false);
4718 }
4719 if self.flip_flop_exclusive_left_line.len() <= slot {
4720 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
4721 }
4722 let active = &mut self.flip_flop_active[slot];
4723 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
4724 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
4725 active, excl_left, exclusive, dot, left_m, right_m,
4726 )))
4727 }
4728
4729 pub(crate) fn chomp_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
4731 let mut s = val.to_string();
4732 let removed = if s.ends_with('\n') {
4733 s.pop();
4734 1i64
4735 } else {
4736 0i64
4737 };
4738 self.assign_value(target, PerlValue::string(s))?;
4739 Ok(PerlValue::integer(removed))
4740 }
4741
4742 pub(crate) fn chop_inplace_execute(&mut self, val: PerlValue, target: &Expr) -> ExecResult {
4744 let mut s = val.to_string();
4745 let chopped = s
4746 .pop()
4747 .map(|c| PerlValue::string(c.to_string()))
4748 .unwrap_or(PerlValue::UNDEF);
4749 self.assign_value(target, PerlValue::string(s))?;
4750 Ok(chopped)
4751 }
4752
4753 pub(crate) fn regex_match_execute(
4755 &mut self,
4756 s: String,
4757 pattern: &str,
4758 flags: &str,
4759 scalar_g: bool,
4760 pos_key: &str,
4761 line: usize,
4762 ) -> ExecResult {
4763 if !flags.contains('g') && !scalar_g {
4771 let memo_hit = {
4772 if let Some(ref mem) = self.regex_match_memo {
4773 mem.pattern == pattern
4774 && mem.flags == flags
4775 && mem.multiline == self.multiline_match
4776 && mem.haystack == s
4777 } else {
4778 false
4779 }
4780 };
4781 if memo_hit {
4782 if self.regex_capture_scope_fresh {
4783 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
4784 }
4785 let (memo_s, memo_result) = {
4788 let mem = self.regex_match_memo.as_ref().expect("memo");
4789 (mem.haystack.clone(), mem.result.clone())
4790 };
4791 let re = self.compile_regex(pattern, flags, line)?;
4792 if let Some(caps) = re.captures(&memo_s) {
4793 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
4794 }
4795 self.regex_capture_scope_fresh = true;
4796 return Ok(memo_result);
4797 }
4798 }
4799 let re = self.compile_regex(pattern, flags, line)?;
4800 if flags.contains('g') && scalar_g {
4801 let key = pos_key.to_string();
4802 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
4803 if start == 0 {
4804 self.scope.set_array("^CAPTURE_ALL", vec![])?;
4805 }
4806 if start > s.len() {
4807 self.regex_pos.insert(key, None);
4808 return Ok(PerlValue::integer(0));
4809 }
4810 let sub = s.get(start..).unwrap_or("");
4811 if let Some(caps) = re.captures(sub) {
4812 let overall = caps.get(0).expect("capture 0");
4813 let abs_end = start + overall.end;
4814 self.regex_pos.insert(key, Some(abs_end));
4815 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
4816 Ok(PerlValue::integer(1))
4817 } else {
4818 self.regex_pos.insert(key, None);
4819 Ok(PerlValue::integer(0))
4820 }
4821 } else if flags.contains('g') {
4822 let mut rows = Vec::new();
4823 let mut last_caps: Option<PerlCaptures<'_>> = None;
4824 for caps in re.captures_iter(&s) {
4825 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
4826 &caps,
4827 )));
4828 last_caps = Some(caps);
4829 }
4830 self.scope.set_array("^CAPTURE_ALL", rows)?;
4831 let matches: Vec<PerlValue> = match &*re {
4832 PerlCompiledRegex::Rust(r) => r
4833 .find_iter(&s)
4834 .map(|m| PerlValue::string(m.as_str().to_string()))
4835 .collect(),
4836 PerlCompiledRegex::Fancy(r) => r
4837 .find_iter(&s)
4838 .filter_map(|m| m.ok())
4839 .map(|m| PerlValue::string(m.as_str().to_string()))
4840 .collect(),
4841 PerlCompiledRegex::Pcre2(r) => r
4842 .find_iter(s.as_bytes())
4843 .filter_map(|m| m.ok())
4844 .map(|m| {
4845 let t = s.get(m.start()..m.end()).unwrap_or("");
4846 PerlValue::string(t.to_string())
4847 })
4848 .collect(),
4849 };
4850 if matches.is_empty() {
4851 Ok(PerlValue::integer(0))
4852 } else {
4853 if let Some(caps) = last_caps {
4854 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
4855 }
4856 Ok(PerlValue::array(matches))
4857 }
4858 } else if let Some(caps) = re.captures(&s) {
4859 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
4860 let result = PerlValue::integer(1);
4861 self.regex_match_memo = Some(RegexMatchMemo {
4862 pattern: pattern.to_string(),
4863 flags: flags.to_string(),
4864 multiline: self.multiline_match,
4865 haystack: s,
4866 result: result.clone(),
4867 });
4868 self.regex_capture_scope_fresh = true;
4869 Ok(result)
4870 } else {
4871 let result = PerlValue::integer(0);
4872 self.regex_match_memo = Some(RegexMatchMemo {
4874 pattern: pattern.to_string(),
4875 flags: flags.to_string(),
4876 multiline: self.multiline_match,
4877 haystack: s,
4878 result: result.clone(),
4879 });
4880 Ok(result)
4883 }
4884 }
4885
4886 pub(crate) fn expand_env_braces_in_subst(
4890 &mut self,
4891 raw: &str,
4892 line: usize,
4893 ) -> PerlResult<String> {
4894 self.materialize_env_if_needed();
4895 let mut out = String::new();
4896 let mut rest = raw;
4897 while let Some(idx) = rest.find("$ENV{") {
4898 out.push_str(&rest[..idx]);
4899 let after = &rest[idx + 5..];
4900 let end = after
4901 .find('}')
4902 .ok_or_else(|| PerlError::runtime("Unclosed $ENV{...} in s///", line))?;
4903 let key = &after[..end];
4904 let val = self.scope.get_hash_element("ENV", key);
4905 out.push_str(&val.to_string());
4906 rest = &after[end + 1..];
4907 }
4908 out.push_str(rest);
4909 Ok(out)
4910 }
4911
4912 pub(crate) fn regex_subst_execute(
4918 &mut self,
4919 s: String,
4920 pattern: &str,
4921 replacement: &str,
4922 flags: &str,
4923 target: &Expr,
4924 line: usize,
4925 ) -> ExecResult {
4926 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
4927 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
4928 let re = self.compile_regex(&pattern, &re_flags, line)?;
4929 if flags.contains('e') {
4930 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
4931 }
4932 let replacement =
4933 normalize_replacement_backrefs(&self.expand_env_braces_in_subst(replacement, line)?);
4934 let last_caps = if flags.contains('g') {
4935 let mut rows = Vec::new();
4936 let mut last = None;
4937 for caps in re.captures_iter(&s) {
4938 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
4939 &caps,
4940 )));
4941 last = Some(caps);
4942 }
4943 self.scope.set_array("^CAPTURE_ALL", rows)?;
4944 last
4945 } else {
4946 re.captures(&s)
4947 };
4948 if let Some(caps) = last_caps {
4949 let mode = if flags.contains('g') {
4950 CaptureAllMode::Skip
4951 } else {
4952 CaptureAllMode::Empty
4953 };
4954 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
4955 }
4956 let (new_s, count) = if flags.contains('g') {
4957 let count = re.find_iter_count(&s);
4958 (re.replace_all(&s, replacement.as_str()), count)
4959 } else {
4960 let count = if re.is_match(&s) { 1 } else { 0 };
4961 (re.replace(&s, replacement.as_str()), count)
4962 };
4963 if flags.contains('r') {
4964 Ok(PerlValue::string(new_s))
4966 } else {
4967 self.assign_value(target, PerlValue::string(new_s))?;
4968 Ok(PerlValue::integer(count as i64))
4969 }
4970 }
4971
4972 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
4975 let prep_source = |raw: &str| -> String {
4976 let mut code = raw.trim().to_string();
4977 if !code.ends_with(';') {
4978 code.push(';');
4979 }
4980 code
4981 };
4982 let mut cur = prep_source(replacement);
4983 let mut last = PerlValue::UNDEF;
4984 for round in 0..e_count {
4985 last = crate::parse_and_run_string(&cur, self)?;
4986 if round + 1 < e_count {
4987 cur = prep_source(&last.to_string());
4988 }
4989 }
4990 Ok(last)
4991 }
4992
4993 fn regex_subst_execute_eval(
4994 &mut self,
4995 s: String,
4996 re: &PerlCompiledRegex,
4997 replacement: &str,
4998 flags: &str,
4999 target: &Expr,
5000 line: usize,
5001 ) -> ExecResult {
5002 let e_count = flags.chars().filter(|c| *c == 'e').count();
5003 if e_count == 0 {
5004 return Err(PerlError::runtime("s///e: internal error (no e flag)", line).into());
5005 }
5006
5007 if flags.contains('g') {
5008 let mut rows = Vec::new();
5009 let mut out = String::new();
5010 let mut last = 0usize;
5011 let mut count = 0usize;
5012 for caps in re.captures_iter(&s) {
5013 let m0 = caps.get(0).expect("regex capture 0");
5014 out.push_str(&s[last..m0.start]);
5015 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5016 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5017 out.push_str(&repl_val.to_string());
5018 last = m0.end;
5019 count += 1;
5020 rows.push(PerlValue::array(crate::perl_regex::numbered_capture_flat(
5021 &caps,
5022 )));
5023 }
5024 self.scope.set_array("^CAPTURE_ALL", rows)?;
5025 out.push_str(&s[last..]);
5026 if flags.contains('r') {
5027 return Ok(PerlValue::string(out));
5028 }
5029 self.assign_value(target, PerlValue::string(out))?;
5030 return Ok(PerlValue::integer(count as i64));
5031 }
5032 if let Some(caps) = re.captures(&s) {
5033 let m0 = caps.get(0).expect("regex capture 0");
5034 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5035 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5036 let mut out = String::new();
5037 out.push_str(&s[..m0.start]);
5038 out.push_str(&repl_val.to_string());
5039 out.push_str(&s[m0.end..]);
5040 if flags.contains('r') {
5041 return Ok(PerlValue::string(out));
5042 }
5043 self.assign_value(target, PerlValue::string(out))?;
5044 return Ok(PerlValue::integer(1));
5045 }
5046 if flags.contains('r') {
5047 return Ok(PerlValue::string(s));
5048 }
5049 self.assign_value(target, PerlValue::string(s))?;
5050 Ok(PerlValue::integer(0))
5051 }
5052
5053 pub(crate) fn regex_transliterate_execute(
5055 &mut self,
5056 s: String,
5057 from: &str,
5058 to: &str,
5059 flags: &str,
5060 target: &Expr,
5061 line: usize,
5062 ) -> ExecResult {
5063 let _ = line;
5064 let from_chars = Self::tr_expand_ranges(from);
5065 let to_chars = Self::tr_expand_ranges(to);
5066 let mut count = 0i64;
5067 let new_s: String = s
5068 .chars()
5069 .map(|c| {
5070 if let Some(pos) = from_chars.iter().position(|&fc| fc == c) {
5071 count += 1;
5072 to_chars.get(pos).or(to_chars.last()).copied().unwrap_or(c)
5073 } else {
5074 c
5075 }
5076 })
5077 .collect();
5078 if flags.contains('r') {
5079 Ok(PerlValue::string(new_s))
5081 } else {
5082 if !flags.contains('d') {
5083 self.assign_value(target, PerlValue::string(new_s))?;
5084 }
5085 Ok(PerlValue::integer(count))
5086 }
5087 }
5088
5089 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
5092 let raw: Vec<char> = spec.chars().collect();
5093 let mut out = Vec::with_capacity(raw.len());
5094 let mut i = 0;
5095 while i < raw.len() {
5096 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
5097 let start = raw[i] as u32;
5098 let end = raw[i + 2] as u32;
5099 for code in start..=end {
5100 if let Some(c) = char::from_u32(code) {
5101 out.push(c);
5102 }
5103 }
5104 i += 3;
5105 } else {
5106 out.push(raw[i]);
5107 i += 1;
5108 }
5109 }
5110 out
5111 }
5112
5113 pub(crate) fn splice_builtin_execute(
5115 &mut self,
5116 args: &[PerlValue],
5117 line: usize,
5118 ) -> PerlResult<PerlValue> {
5119 if args.is_empty() {
5120 return Err(PerlError::runtime("splice: missing array", line));
5121 }
5122 let arr_name = args[0].to_string();
5123 let arr_len = self.scope.array_len(&arr_name);
5124 let offset_val = args
5125 .get(1)
5126 .cloned()
5127 .unwrap_or_else(|| PerlValue::integer(0));
5128 let length_val = match args.get(2) {
5129 None => PerlValue::UNDEF,
5130 Some(v) => v.clone(),
5131 };
5132 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
5133 let rep_vals: Vec<PerlValue> = args.iter().skip(3).cloned().collect();
5134 let arr = self.scope.get_array_mut(&arr_name)?;
5135 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
5136 for (i, v) in rep_vals.into_iter().enumerate() {
5137 arr.insert(off + i, v);
5138 }
5139 Ok(match self.wantarray_kind {
5140 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
5141 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
5142 })
5143 }
5144
5145 pub(crate) fn unshift_builtin_execute(
5147 &mut self,
5148 args: &[PerlValue],
5149 line: usize,
5150 ) -> PerlResult<PerlValue> {
5151 if args.is_empty() {
5152 return Err(PerlError::runtime("unshift: missing array", line));
5153 }
5154 let arr_name = args[0].to_string();
5155 let mut flat_vals: Vec<PerlValue> = Vec::new();
5156 for a in args.iter().skip(1) {
5157 if let Some(items) = a.as_array_vec() {
5158 flat_vals.extend(items);
5159 } else {
5160 flat_vals.push(a.clone());
5161 }
5162 }
5163 let arr = self.scope.get_array_mut(&arr_name)?;
5164 for (i, v) in flat_vals.into_iter().enumerate() {
5165 arr.insert(i, v);
5166 }
5167 Ok(PerlValue::integer(arr.len() as i64))
5168 }
5169
5170 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
5173 if upper == 0.0 {
5174 self.rand_rng.gen_range(0.0..1.0)
5175 } else if upper > 0.0 {
5176 self.rand_rng.gen_range(0.0..upper)
5177 } else {
5178 self.rand_rng.gen_range(upper..0.0)
5179 }
5180 }
5181
5182 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
5184 let n = if let Some(s) = seed {
5185 s as i64
5186 } else {
5187 std::time::SystemTime::now()
5188 .duration_since(std::time::UNIX_EPOCH)
5189 .map(|d| d.as_secs() as i64)
5190 .unwrap_or(1)
5191 };
5192 let mag = n.unsigned_abs();
5193 self.rand_rng = StdRng::seed_from_u64(mag);
5194 n.abs()
5195 }
5196
5197 pub fn set_file(&mut self, file: &str) {
5198 self.file = file.to_string();
5199 }
5200
5201 pub fn repl_completion_names(&self) -> Vec<String> {
5203 let mut v = self.scope.repl_binding_names();
5204 v.extend(self.subs.keys().cloned());
5205 v.sort();
5206 v.dedup();
5207 v
5208 }
5209
5210 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
5212 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
5213 subs.sort();
5214 let mut classes: HashSet<String> = HashSet::new();
5215 for k in &subs {
5216 if let Some((pkg, rest)) = k.split_once("::") {
5217 if !rest.contains("::") {
5218 classes.insert(pkg.to_string());
5219 }
5220 }
5221 }
5222 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
5223 for bn in self.scope.repl_binding_names() {
5224 if let Some(r) = bn.strip_prefix('$') {
5225 let v = self.scope.get_scalar(r);
5226 if let Some(b) = v.as_blessed_ref() {
5227 blessed_scalars.insert(r.to_string(), b.class.clone());
5228 classes.insert(b.class.clone());
5229 }
5230 }
5231 }
5232 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
5233 for c in classes {
5234 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
5235 }
5236 ReplCompletionSnapshot {
5237 subs,
5238 blessed_scalars,
5239 isa_for_class,
5240 }
5241 }
5242
5243 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
5244 if n == 0 {
5245 return Err(FlowOrError::Error(PerlError::runtime(
5246 "bench: iteration count must be positive",
5247 line,
5248 )));
5249 }
5250 let warmup = (n / 10).clamp(1, 10);
5251 for _ in 0..warmup {
5252 self.exec_block(body)?;
5253 }
5254 let mut samples = Vec::with_capacity(n);
5255 for _ in 0..n {
5256 let start = std::time::Instant::now();
5257 self.exec_block(body)?;
5258 samples.push(start.elapsed().as_secs_f64() * 1000.0);
5259 }
5260 let mut sorted = samples.clone();
5261 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
5262 let min_ms = sorted[0];
5263 let mean = samples.iter().sum::<f64>() / n as f64;
5264 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
5265 .saturating_sub(1)
5266 .min(n - 1);
5267 let p99_ms = sorted[p99_idx];
5268 Ok(PerlValue::string(format!(
5269 "bench: n={} warmup={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
5270 n, warmup, min_ms, mean, p99_ms
5271 )))
5272 }
5273
5274 pub fn execute(&mut self, program: &Program) -> PerlResult<PerlValue> {
5275 if self.line_mode_skip_main {
5278 return self.execute_tree(program);
5279 }
5280 if let Some(result) = crate::try_vm_execute(program, self) {
5283 return result;
5284 }
5285
5286 self.execute_tree(program)
5288 }
5289
5290 pub fn run_end_blocks(&mut self) -> PerlResult<()> {
5292 self.global_phase = "END".to_string();
5293 let ends = std::mem::take(&mut self.end_blocks);
5294 for block in &ends {
5295 self.exec_block(block).map_err(|e| match e {
5296 FlowOrError::Error(e) => e,
5297 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in END", 0),
5298 })?;
5299 }
5300 Ok(())
5301 }
5302
5303 pub fn run_global_teardown(&mut self) -> PerlResult<()> {
5306 self.global_phase = "DESTRUCT".to_string();
5307 self.drain_pending_destroys(0)
5308 }
5309
5310 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> PerlResult<()> {
5312 loop {
5313 let batch = crate::pending_destroy::take_queue();
5314 if batch.is_empty() {
5315 break;
5316 }
5317 for (class, payload) in batch {
5318 let fq = format!("{}::DESTROY", class);
5319 let Some(sub) = self.subs.get(&fq).cloned() else {
5320 continue;
5321 };
5322 let inv = PerlValue::blessed(Arc::new(
5323 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
5324 ));
5325 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
5326 Ok(_) => {}
5327 Err(FlowOrError::Error(e)) => return Err(e),
5328 Err(FlowOrError::Flow(Flow::Return(_))) => {}
5329 Err(FlowOrError::Flow(other)) => {
5330 return Err(PerlError::runtime(
5331 format!("DESTROY: unexpected control flow ({other:?})"),
5332 line,
5333 ));
5334 }
5335 }
5336 }
5337 }
5338 Ok(())
5339 }
5340
5341 pub fn execute_tree(&mut self, program: &Program) -> PerlResult<PerlValue> {
5343 self.global_phase = "RUN".to_string();
5345 self.clear_flip_flop_state();
5346 self.prepare_program_top_level(program)?;
5348
5349 let begins = std::mem::take(&mut self.begin_blocks);
5351 if !begins.is_empty() {
5352 self.global_phase = "START".to_string();
5353 }
5354 for block in &begins {
5355 self.exec_block(block).map_err(|e| match e {
5356 FlowOrError::Error(e) => e,
5357 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in BEGIN", 0),
5358 })?;
5359 }
5360
5361 let ucs = std::mem::take(&mut self.unit_check_blocks);
5364 for block in ucs.iter().rev() {
5365 self.exec_block(block).map_err(|e| match e {
5366 FlowOrError::Error(e) => e,
5367 FlowOrError::Flow(_) => {
5368 PerlError::runtime("Unexpected flow control in UNITCHECK", 0)
5369 }
5370 })?;
5371 }
5372
5373 let checks = std::mem::take(&mut self.check_blocks);
5375 if !checks.is_empty() {
5376 self.global_phase = "CHECK".to_string();
5377 }
5378 for block in checks.iter().rev() {
5379 self.exec_block(block).map_err(|e| match e {
5380 FlowOrError::Error(e) => e,
5381 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in CHECK", 0),
5382 })?;
5383 }
5384
5385 let inits = std::mem::take(&mut self.init_blocks);
5387 if !inits.is_empty() {
5388 self.global_phase = "INIT".to_string();
5389 }
5390 for block in &inits {
5391 self.exec_block(block).map_err(|e| match e {
5392 FlowOrError::Error(e) => e,
5393 FlowOrError::Flow(_) => PerlError::runtime("Unexpected flow control in INIT", 0),
5394 })?;
5395 }
5396
5397 self.global_phase = "RUN".to_string();
5398
5399 if self.line_mode_skip_main {
5400 return Ok(PerlValue::UNDEF);
5403 }
5404
5405 let mut last = PerlValue::UNDEF;
5407 for stmt in &program.statements {
5408 match &stmt.kind {
5409 StmtKind::Begin(_)
5410 | StmtKind::UnitCheck(_)
5411 | StmtKind::Check(_)
5412 | StmtKind::Init(_)
5413 | StmtKind::End(_)
5414 | StmtKind::UsePerlVersion { .. }
5415 | StmtKind::Use { .. }
5416 | StmtKind::No { .. }
5417 | StmtKind::FormatDecl { .. } => continue,
5418 _ => {
5419 match self.exec_statement(stmt) {
5420 Ok(val) => last = val,
5421 Err(FlowOrError::Error(e)) => {
5422 self.global_phase = "END".to_string();
5424 let ends = std::mem::take(&mut self.end_blocks);
5425 for block in &ends {
5426 let _ = self.exec_block(block);
5427 }
5428 return Err(e);
5429 }
5430 Err(FlowOrError::Flow(Flow::Return(v))) => {
5431 last = v;
5432 break;
5433 }
5434 Err(FlowOrError::Flow(_)) => {}
5435 }
5436 }
5437 }
5438 }
5439
5440 self.global_phase = "END".to_string();
5442 let ends = std::mem::take(&mut self.end_blocks);
5443 for block in &ends {
5444 let _ = self.exec_block(block);
5445 }
5446
5447 self.drain_pending_destroys(0)?;
5448 Ok(last)
5449 }
5450
5451 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
5452 self.exec_block_with_tail(block, WantarrayCtx::Void)
5453 }
5454
5455 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5458 let uses_goto = block
5459 .iter()
5460 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
5461 if uses_goto {
5462 self.scope_push_hook();
5463 let r = self.exec_block_with_goto_tail(block, tail);
5464 self.scope_pop_hook();
5465 r
5466 } else {
5467 self.scope_push_hook();
5468 let result = self.exec_block_no_scope_with_tail(block, tail);
5469 self.scope_pop_hook();
5470 result
5471 }
5472 }
5473
5474 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
5475 let mut map: HashMap<String, usize> = HashMap::new();
5476 for (i, s) in block.iter().enumerate() {
5477 if let Some(l) = &s.label {
5478 map.insert(l.clone(), i);
5479 }
5480 }
5481 let mut pc = 0usize;
5482 let mut last = PerlValue::UNDEF;
5483 let last_idx = block.len().saturating_sub(1);
5484 while pc < block.len() {
5485 if let StmtKind::Goto { target } = &block[pc].kind {
5486 let line = block[pc].line;
5487 let name = self.eval_expr(target)?.to_string();
5488 pc = *map.get(&name).ok_or_else(|| {
5489 FlowOrError::Error(PerlError::runtime(
5490 format!("goto: unknown label {}", name),
5491 line,
5492 ))
5493 })?;
5494 continue;
5495 }
5496 let v = if pc == last_idx {
5497 match &block[pc].kind {
5498 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
5499 _ => self.exec_statement(&block[pc])?,
5500 }
5501 } else {
5502 self.exec_statement(&block[pc])?
5503 };
5504 last = v;
5505 pc += 1;
5506 }
5507 Ok(last)
5508 }
5509
5510 #[inline]
5513 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
5514 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
5515 }
5516
5517 pub(crate) fn exec_block_no_scope_with_tail(
5518 &mut self,
5519 block: &Block,
5520 tail: WantarrayCtx,
5521 ) -> ExecResult {
5522 if block.is_empty() {
5523 return Ok(PerlValue::UNDEF);
5524 }
5525 let last_i = block.len() - 1;
5526 for (i, stmt) in block.iter().enumerate() {
5527 if i < last_i {
5528 self.exec_statement(stmt)?;
5529 } else {
5530 return match &stmt.kind {
5531 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
5532 _ => self.exec_statement(stmt),
5533 };
5534 }
5535 }
5536 Ok(PerlValue::UNDEF)
5537 }
5538
5539 pub(crate) fn spawn_async_block(&self, block: &Block) -> PerlValue {
5541 use parking_lot::Mutex as ParkMutex;
5542
5543 let block = block.clone();
5544 let subs = self.subs.clone();
5545 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5546 let result = Arc::new(ParkMutex::new(None));
5547 let join = Arc::new(ParkMutex::new(None));
5548 let result2 = result.clone();
5549 let h = std::thread::spawn(move || {
5550 let mut interp = Interpreter::new();
5551 interp.subs = subs;
5552 interp.scope.restore_capture(&scalars);
5553 interp.scope.restore_atomics(&aar, &ahash);
5554 interp.enable_parallel_guard();
5555 let r = match interp.exec_block(&block) {
5556 Ok(v) => Ok(v),
5557 Err(FlowOrError::Error(e)) => Err(e),
5558 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5559 Err(PerlError::runtime("yield inside async/spawn block", 0))
5560 }
5561 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5562 };
5563 *result2.lock() = Some(r);
5564 });
5565 *join.lock() = Some(h);
5566 PerlValue::async_task(Arc::new(PerlAsyncTask { result, join }))
5567 }
5568
5569 pub(crate) fn eval_timeout_block(
5571 &mut self,
5572 body: &Block,
5573 secs: f64,
5574 line: usize,
5575 ) -> ExecResult {
5576 use std::sync::mpsc::channel;
5577 use std::time::Duration;
5578
5579 let block = body.clone();
5580 let subs = self.subs.clone();
5581 let struct_defs = self.struct_defs.clone();
5582 let enum_defs = self.enum_defs.clone();
5583 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
5584 self.materialize_env_if_needed();
5585 let env = self.env.clone();
5586 let argv = self.argv.clone();
5587 let inc = self.scope.get_array("INC");
5588 let (tx, rx) = channel::<PerlResult<PerlValue>>();
5589 let _handle = std::thread::spawn(move || {
5590 let mut interp = Interpreter::new();
5591 interp.subs = subs;
5592 interp.struct_defs = struct_defs;
5593 interp.enum_defs = enum_defs;
5594 interp.env = env.clone();
5595 interp.argv = argv.clone();
5596 interp.scope.declare_array(
5597 "ARGV",
5598 argv.iter().map(|s| PerlValue::string(s.clone())).collect(),
5599 );
5600 for (k, v) in env {
5601 interp
5602 .scope
5603 .set_hash_element("ENV", &k, v)
5604 .expect("set ENV in timeout thread");
5605 }
5606 interp.scope.declare_array("INC", inc);
5607 interp.scope.restore_capture(&scalars);
5608 interp.scope.restore_atomics(&aar, &ahash);
5609 interp.enable_parallel_guard();
5610 let out: PerlResult<PerlValue> = match interp.exec_block(&block) {
5611 Ok(v) => Ok(v),
5612 Err(FlowOrError::Error(e)) => Err(e),
5613 Err(FlowOrError::Flow(Flow::Yield(_))) => {
5614 Err(PerlError::runtime("yield inside eval_timeout block", 0))
5615 }
5616 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
5617 };
5618 let _ = tx.send(out);
5619 });
5620 let dur = Duration::from_secs_f64(secs.max(0.0));
5621 match rx.recv_timeout(dur) {
5622 Ok(Ok(v)) => Ok(v),
5623 Ok(Err(e)) => Err(FlowOrError::Error(e)),
5624 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(PerlError::runtime(
5625 format!(
5626 "eval_timeout: exceeded {} second(s) (worker continues in background)",
5627 secs
5628 ),
5629 line,
5630 )
5631 .into()),
5632 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(PerlError::runtime(
5633 "eval_timeout: worker thread panicked or disconnected",
5634 line,
5635 )
5636 .into()),
5637 }
5638 }
5639
5640 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
5641 let mut last = PerlValue::UNDEF;
5642 for stmt in body {
5643 match &stmt.kind {
5644 StmtKind::When { cond, body: wb } => {
5645 if self.when_matches(cond)? {
5646 return self.exec_block_smart(wb);
5647 }
5648 }
5649 StmtKind::DefaultCase { body: db } => {
5650 return self.exec_block_smart(db);
5651 }
5652 _ => {
5653 last = self.exec_statement(stmt)?;
5654 }
5655 }
5656 }
5657 Ok(last)
5658 }
5659
5660 pub(crate) fn exec_given_with_topic_value(
5662 &mut self,
5663 topic: PerlValue,
5664 body: &Block,
5665 ) -> ExecResult {
5666 self.scope_push_hook();
5667 self.scope.declare_scalar("_", topic);
5668 self.english_note_lexical_scalar("_");
5669 let r = self.exec_given_body(body);
5670 self.scope_pop_hook();
5671 r
5672 }
5673
5674 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
5675 let t = self.eval_expr(topic)?;
5676 self.exec_given_with_topic_value(t, body)
5677 }
5678
5679 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5681 let topic = self.scope.get_scalar("_");
5682 let line = cond.line;
5683 match &cond.kind {
5684 ExprKind::Regex(pattern, flags) => {
5685 let re = self.compile_regex(pattern, flags, line)?;
5686 let s = topic.to_string();
5687 Ok(re.is_match(&s))
5688 }
5689 ExprKind::String(s) => Ok(topic.to_string() == *s),
5690 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
5691 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
5692 _ => {
5693 let c = self.eval_expr(cond)?;
5694 Ok(self.smartmatch_when(&topic, &c))
5695 }
5696 }
5697 }
5698
5699 fn smartmatch_when(&self, topic: &PerlValue, c: &PerlValue) -> bool {
5700 if let Some(re) = c.as_regex() {
5701 return re.is_match(&topic.to_string());
5702 }
5703 topic.to_string() == c.to_string()
5704 }
5705
5706 pub(crate) fn eval_boolean_rvalue_condition(
5708 &mut self,
5709 cond: &Expr,
5710 ) -> Result<bool, FlowOrError> {
5711 match &cond.kind {
5712 ExprKind::Regex(pattern, flags) => {
5713 let topic = self.scope.get_scalar("_");
5714 let line = cond.line;
5715 let s = topic.to_string();
5716 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
5717 Ok(v.is_true())
5718 }
5719 ExprKind::ReadLine(_) => {
5721 let v = self.eval_expr(cond)?;
5722 self.scope.set_topic(v.clone());
5723 Ok(!v.is_undef())
5724 }
5725 _ => {
5726 let v = self.eval_expr(cond)?;
5727 Ok(v.is_true())
5728 }
5729 }
5730 }
5731
5732 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
5734 self.eval_boolean_rvalue_condition(cond)
5735 }
5736
5737 pub(crate) fn eval_algebraic_match(
5738 &mut self,
5739 subject: &Expr,
5740 arms: &[MatchArm],
5741 line: usize,
5742 ) -> ExecResult {
5743 let val = self.eval_algebraic_match_subject(subject, line)?;
5744 self.eval_algebraic_match_with_subject_value(val, arms, line)
5745 }
5746
5747 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
5749 match &subject.kind {
5750 ExprKind::ArrayVar(name) => {
5751 self.check_strict_array_var(name, line)?;
5752 let aname = self.stash_array_name_for_package(name);
5753 Ok(PerlValue::array_binding_ref(aname))
5754 }
5755 ExprKind::HashVar(name) => {
5756 self.check_strict_hash_var(name, line)?;
5757 self.touch_env_hash(name);
5758 Ok(PerlValue::hash_binding_ref(name.clone()))
5759 }
5760 _ => self.eval_expr(subject),
5761 }
5762 }
5763
5764 pub(crate) fn eval_algebraic_match_with_subject_value(
5766 &mut self,
5767 val: PerlValue,
5768 arms: &[MatchArm],
5769 line: usize,
5770 ) -> ExecResult {
5771 if let Some(e) = val.as_enum_inst() {
5773 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
5774 if !has_catchall {
5775 let covered: Vec<String> = arms
5776 .iter()
5777 .filter_map(|a| {
5778 if let MatchPattern::Value(expr) = &a.pattern {
5779 if let ExprKind::FuncCall { name, .. } = &expr.kind {
5780 return name.rsplit_once("::").map(|(_, v)| v.to_string());
5781 }
5782 }
5783 None
5784 })
5785 .collect();
5786 let missing: Vec<&str> = e
5787 .def
5788 .variants
5789 .iter()
5790 .filter(|v| !covered.contains(&v.name))
5791 .map(|v| v.name.as_str())
5792 .collect();
5793 if !missing.is_empty() {
5794 return Err(PerlError::runtime(
5795 format!(
5796 "non-exhaustive match on enum `{}`: missing variant(s) {}",
5797 e.def.name,
5798 missing.join(", ")
5799 ),
5800 line,
5801 )
5802 .into());
5803 }
5804 }
5805 }
5806 for arm in arms {
5807 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
5808 let re = self.compile_regex(pattern, flags, line)?;
5809 let s = val.to_string();
5810 if let Some(caps) = re.captures(&s) {
5811 self.scope_push_hook();
5812 self.scope.declare_scalar("_", val.clone());
5813 self.english_note_lexical_scalar("_");
5814 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
5815 let guard_ok = if let Some(g) = &arm.guard {
5816 self.eval_expr(g)?.is_true()
5817 } else {
5818 true
5819 };
5820 if !guard_ok {
5821 self.scope_pop_hook();
5822 continue;
5823 }
5824 let out = self.eval_expr(&arm.body);
5825 self.scope_pop_hook();
5826 return out;
5827 }
5828 continue;
5829 }
5830 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
5831 self.scope_push_hook();
5832 self.scope.declare_scalar("_", val.clone());
5833 self.english_note_lexical_scalar("_");
5834 for b in bindings {
5835 match b {
5836 PatternBinding::Scalar(name, v) => {
5837 self.scope.declare_scalar(&name, v);
5838 self.english_note_lexical_scalar(&name);
5839 }
5840 PatternBinding::Array(name, elems) => {
5841 self.scope.declare_array(&name, elems);
5842 }
5843 }
5844 }
5845 let guard_ok = if let Some(g) = &arm.guard {
5846 self.eval_expr(g)?.is_true()
5847 } else {
5848 true
5849 };
5850 if !guard_ok {
5851 self.scope_pop_hook();
5852 continue;
5853 }
5854 let out = self.eval_expr(&arm.body);
5855 self.scope_pop_hook();
5856 return out;
5857 }
5858 }
5859 Err(PerlError::runtime(
5860 "match: no arm matched the value (add a `_` catch-all)",
5861 line,
5862 )
5863 .into())
5864 }
5865
5866 fn parse_duration_seconds(pv: &PerlValue) -> Option<f64> {
5867 let s = pv.to_string();
5868 let s = s.trim();
5869 if let Some(rest) = s.strip_suffix("ms") {
5870 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
5871 }
5872 if let Some(rest) = s.strip_suffix('s') {
5873 return rest.trim().parse::<f64>().ok();
5874 }
5875 if let Some(rest) = s.strip_suffix('m') {
5876 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
5877 }
5878 s.parse::<f64>().ok()
5879 }
5880
5881 fn eval_retry_block(
5882 &mut self,
5883 body: &Block,
5884 times: &Expr,
5885 backoff: RetryBackoff,
5886 _line: usize,
5887 ) -> ExecResult {
5888 let max = self.eval_expr(times)?.to_int().max(1) as usize;
5889 let base_ms: u64 = 10;
5890 let mut attempt = 0usize;
5891 loop {
5892 attempt += 1;
5893 match self.exec_block(body) {
5894 Ok(v) => return Ok(v),
5895 Err(FlowOrError::Error(e)) => {
5896 if attempt >= max {
5897 return Err(FlowOrError::Error(e));
5898 }
5899 let delay_ms = match backoff {
5900 RetryBackoff::None => 0,
5901 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
5902 RetryBackoff::Exponential => {
5903 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
5904 }
5905 };
5906 if delay_ms > 0 {
5907 std::thread::sleep(Duration::from_millis(delay_ms));
5908 }
5909 }
5910 Err(e) => return Err(e),
5911 }
5912 }
5913 }
5914
5915 fn eval_rate_limit_block(
5916 &mut self,
5917 slot: u32,
5918 max: &Expr,
5919 window: &Expr,
5920 body: &Block,
5921 _line: usize,
5922 ) -> ExecResult {
5923 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
5924 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
5925 .filter(|s| *s > 0.0)
5926 .unwrap_or(1.0);
5927 let window_d = Duration::from_secs_f64(window_sec);
5928 let slot = slot as usize;
5929 while self.rate_limit_slots.len() <= slot {
5930 self.rate_limit_slots.push(VecDeque::new());
5931 }
5932 {
5933 let dq = &mut self.rate_limit_slots[slot];
5934 loop {
5935 let now = Instant::now();
5936 while let Some(t0) = dq.front().copied() {
5937 if now.duration_since(t0) >= window_d {
5938 dq.pop_front();
5939 } else {
5940 break;
5941 }
5942 }
5943 if dq.len() < max_n || max_n == 0 {
5944 break;
5945 }
5946 let t0 = dq.front().copied().unwrap();
5947 let wait = window_d.saturating_sub(now.duration_since(t0));
5948 if wait.is_zero() {
5949 dq.pop_front();
5950 continue;
5951 }
5952 std::thread::sleep(wait);
5953 }
5954 dq.push_back(Instant::now());
5955 }
5956 self.exec_block(body)
5957 }
5958
5959 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
5960 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
5961 .filter(|s| *s > 0.0)
5962 .unwrap_or(1.0);
5963 loop {
5964 match self.exec_block(body) {
5965 Ok(_) => {}
5966 Err(e) => return Err(e),
5967 }
5968 std::thread::sleep(Duration::from_secs_f64(sec));
5969 }
5970 }
5971
5972 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> PerlResult<PerlValue> {
5974 let pair = |value: PerlValue, more: i64| {
5975 PerlValue::array_ref(Arc::new(RwLock::new(vec![value, PerlValue::integer(more)])))
5976 };
5977 let mut exhausted = gen.exhausted.lock();
5978 if *exhausted {
5979 return Ok(pair(PerlValue::UNDEF, 0));
5980 }
5981 let mut pc = gen.pc.lock();
5982 let mut scope_started = gen.scope_started.lock();
5983 if *pc >= gen.block.len() {
5984 if *scope_started {
5985 self.scope_pop_hook();
5986 *scope_started = false;
5987 }
5988 *exhausted = true;
5989 return Ok(pair(PerlValue::UNDEF, 0));
5990 }
5991 if !*scope_started {
5992 self.scope_push_hook();
5993 *scope_started = true;
5994 }
5995 self.in_generator = true;
5996 while *pc < gen.block.len() {
5997 let stmt = &gen.block[*pc];
5998 match self.exec_statement(stmt) {
5999 Ok(_) => {
6000 *pc += 1;
6001 }
6002 Err(FlowOrError::Flow(Flow::Yield(v))) => {
6003 *pc += 1;
6004 self.in_generator = false;
6005 if *scope_started {
6008 self.scope_pop_hook();
6009 *scope_started = false;
6010 }
6011 return Ok(pair(v, 1));
6012 }
6013 Err(e) => {
6014 self.in_generator = false;
6015 if *scope_started {
6016 self.scope_pop_hook();
6017 *scope_started = false;
6018 }
6019 return Err(match e {
6020 FlowOrError::Error(ee) => ee,
6021 FlowOrError::Flow(Flow::Yield(_)) => {
6022 unreachable!("yield handled above")
6023 }
6024 FlowOrError::Flow(flow) => PerlError::runtime(
6025 format!("unexpected control flow in generator: {:?}", flow),
6026 0,
6027 ),
6028 });
6029 }
6030 }
6031 }
6032 self.in_generator = false;
6033 if *scope_started {
6034 self.scope_pop_hook();
6035 *scope_started = false;
6036 }
6037 *exhausted = true;
6038 Ok(pair(PerlValue::UNDEF, 0))
6039 }
6040
6041 fn match_pattern_try(
6042 &mut self,
6043 subject: &PerlValue,
6044 pattern: &MatchPattern,
6045 line: usize,
6046 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6047 match pattern {
6048 MatchPattern::Any => Ok(Some(vec![])),
6049 MatchPattern::Regex { .. } => {
6050 unreachable!("regex arms are handled in eval_algebraic_match")
6051 }
6052 MatchPattern::Value(expr) => {
6053 let pv = self.eval_expr(expr)?;
6054 if self.smartmatch_when(subject, &pv) {
6055 Ok(Some(vec![]))
6056 } else {
6057 Ok(None)
6058 }
6059 }
6060 MatchPattern::Array(elems) => {
6061 let Some(arr) = self.match_subject_as_array(subject) else {
6062 return Ok(None);
6063 };
6064 self.match_array_pattern_elems(&arr, elems, line)
6065 }
6066 MatchPattern::Hash(pairs) => {
6067 let Some(h) = self.match_subject_as_hash(subject) else {
6068 return Ok(None);
6069 };
6070 self.match_hash_pattern_pairs(&h, pairs, line)
6071 }
6072 MatchPattern::OptionSome(name) => {
6073 let Some(arr) = self.match_subject_as_array(subject) else {
6074 return Ok(None);
6075 };
6076 if arr.len() < 2 {
6077 return Ok(None);
6078 }
6079 if !arr[1].is_true() {
6080 return Ok(None);
6081 }
6082 Ok(Some(vec![PatternBinding::Scalar(
6083 name.clone(),
6084 arr[0].clone(),
6085 )]))
6086 }
6087 }
6088 }
6089
6090 fn match_subject_as_array(&self, v: &PerlValue) -> Option<Vec<PerlValue>> {
6092 if let Some(a) = v.as_array_vec() {
6093 return Some(a);
6094 }
6095 if let Some(r) = v.as_array_ref() {
6096 return Some(r.read().clone());
6097 }
6098 if let Some(name) = v.as_array_binding_name() {
6099 return Some(self.scope.get_array(&name));
6100 }
6101 None
6102 }
6103
6104 fn match_subject_as_hash(&mut self, v: &PerlValue) -> Option<IndexMap<String, PerlValue>> {
6105 if let Some(h) = v.as_hash_map() {
6106 return Some(h);
6107 }
6108 if let Some(r) = v.as_hash_ref() {
6109 return Some(r.read().clone());
6110 }
6111 if let Some(name) = v.as_hash_binding_name() {
6112 self.touch_env_hash(&name);
6113 return Some(self.scope.get_hash(&name));
6114 }
6115 None
6116 }
6117
6118 pub(crate) fn hash_slice_deref_values(
6121 &mut self,
6122 container: &PerlValue,
6123 key_values: &[PerlValue],
6124 line: usize,
6125 ) -> Result<PerlValue, FlowOrError> {
6126 let h = if let Some(m) = self.match_subject_as_hash(container) {
6127 m
6128 } else {
6129 return Err(PerlError::runtime(
6130 "Hash slice dereference needs a hash or hash reference value",
6131 line,
6132 )
6133 .into());
6134 };
6135 let mut result = Vec::new();
6136 for kv in key_values {
6137 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6138 vv.iter().map(|x| x.to_string()).collect()
6139 } else {
6140 vec![kv.to_string()]
6141 };
6142 for k in key_strings {
6143 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6144 }
6145 }
6146 Ok(PerlValue::array(result))
6147 }
6148
6149 pub(crate) fn assign_hash_slice_one_key(
6152 &mut self,
6153 container: PerlValue,
6154 key: &str,
6155 val: PerlValue,
6156 line: usize,
6157 ) -> Result<PerlValue, FlowOrError> {
6158 if let Some(r) = container.as_hash_ref() {
6159 r.write().insert(key.to_string(), val);
6160 return Ok(PerlValue::UNDEF);
6161 }
6162 if let Some(name) = container.as_hash_binding_name() {
6163 self.touch_env_hash(&name);
6164 self.scope
6165 .set_hash_element(&name, key, val)
6166 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6167 return Ok(PerlValue::UNDEF);
6168 }
6169 if let Some(s) = container.as_str() {
6170 self.touch_env_hash(&s);
6171 if self.strict_refs {
6172 return Err(PerlError::runtime(
6173 format!(
6174 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6175 s
6176 ),
6177 line,
6178 )
6179 .into());
6180 }
6181 self.scope
6182 .set_hash_element(&s, key, val)
6183 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6184 return Ok(PerlValue::UNDEF);
6185 }
6186 Err(PerlError::runtime(
6187 "Hash slice assignment needs a hash or hash reference value",
6188 line,
6189 )
6190 .into())
6191 }
6192
6193 pub(crate) fn assign_named_hash_slice(
6196 &mut self,
6197 hash: &str,
6198 key_values: Vec<PerlValue>,
6199 val: PerlValue,
6200 line: usize,
6201 ) -> Result<PerlValue, FlowOrError> {
6202 self.touch_env_hash(hash);
6203 let mut ks: Vec<String> = Vec::new();
6204 for kv in key_values {
6205 if let Some(vv) = kv.as_array_vec() {
6206 ks.extend(vv.iter().map(|x| x.to_string()));
6207 } else {
6208 ks.push(kv.to_string());
6209 }
6210 }
6211 if ks.is_empty() {
6212 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6213 }
6214 let items = val.to_list();
6215 for (i, k) in ks.iter().enumerate() {
6216 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6217 self.scope
6218 .set_hash_element(hash, k, v)
6219 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6220 }
6221 Ok(PerlValue::UNDEF)
6222 }
6223
6224 pub(crate) fn assign_hash_slice_deref(
6226 &mut self,
6227 container: PerlValue,
6228 key_values: Vec<PerlValue>,
6229 val: PerlValue,
6230 line: usize,
6231 ) -> Result<PerlValue, FlowOrError> {
6232 let mut ks: Vec<String> = Vec::new();
6233 for kv in key_values {
6234 if let Some(vv) = kv.as_array_vec() {
6235 ks.extend(vv.iter().map(|x| x.to_string()));
6236 } else {
6237 ks.push(kv.to_string());
6238 }
6239 }
6240 if ks.is_empty() {
6241 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6242 }
6243 let items = val.to_list();
6244 if let Some(r) = container.as_hash_ref() {
6245 let mut h = r.write();
6246 for (i, k) in ks.iter().enumerate() {
6247 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6248 h.insert(k.clone(), v);
6249 }
6250 return Ok(PerlValue::UNDEF);
6251 }
6252 if let Some(name) = container.as_hash_binding_name() {
6253 self.touch_env_hash(&name);
6254 for (i, k) in ks.iter().enumerate() {
6255 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6256 self.scope
6257 .set_hash_element(&name, k, v)
6258 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6259 }
6260 return Ok(PerlValue::UNDEF);
6261 }
6262 if let Some(s) = container.as_str() {
6263 if self.strict_refs {
6264 return Err(PerlError::runtime(
6265 format!(
6266 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
6267 s
6268 ),
6269 line,
6270 )
6271 .into());
6272 }
6273 self.touch_env_hash(&s);
6274 for (i, k) in ks.iter().enumerate() {
6275 let v = items.get(i).cloned().unwrap_or(PerlValue::UNDEF);
6276 self.scope
6277 .set_hash_element(&s, k, v)
6278 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
6279 }
6280 return Ok(PerlValue::UNDEF);
6281 }
6282 Err(PerlError::runtime(
6283 "Hash slice assignment needs a hash or hash reference value",
6284 line,
6285 )
6286 .into())
6287 }
6288
6289 pub(crate) fn compound_assign_hash_slice_deref(
6292 &mut self,
6293 container: PerlValue,
6294 key_values: Vec<PerlValue>,
6295 op: BinOp,
6296 rhs: PerlValue,
6297 line: usize,
6298 ) -> Result<PerlValue, FlowOrError> {
6299 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6300 let last_old = old_list
6301 .to_list()
6302 .last()
6303 .cloned()
6304 .unwrap_or(PerlValue::UNDEF);
6305 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6306 let mut ks: Vec<String> = Vec::new();
6307 for kv in &key_values {
6308 if let Some(vv) = kv.as_array_vec() {
6309 ks.extend(vv.iter().map(|x| x.to_string()));
6310 } else {
6311 ks.push(kv.to_string());
6312 }
6313 }
6314 if ks.is_empty() {
6315 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6316 }
6317 let last_key = ks.last().expect("non-empty ks");
6318 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6319 Ok(new_val)
6320 }
6321
6322 pub(crate) fn hash_slice_deref_inc_dec(
6328 &mut self,
6329 container: PerlValue,
6330 key_values: Vec<PerlValue>,
6331 kind: u8,
6332 line: usize,
6333 ) -> Result<PerlValue, FlowOrError> {
6334 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
6335 let last_old = old_list
6336 .to_list()
6337 .last()
6338 .cloned()
6339 .unwrap_or(PerlValue::UNDEF);
6340 let new_val = if kind & 1 == 0 {
6341 PerlValue::integer(last_old.to_int() + 1)
6342 } else {
6343 PerlValue::integer(last_old.to_int() - 1)
6344 };
6345 let mut ks: Vec<String> = Vec::new();
6346 for kv in &key_values {
6347 if let Some(vv) = kv.as_array_vec() {
6348 ks.extend(vv.iter().map(|x| x.to_string()));
6349 } else {
6350 ks.push(kv.to_string());
6351 }
6352 }
6353 let last_key = ks.last().ok_or_else(|| {
6354 PerlError::runtime("Hash slice increment needs at least one key", line)
6355 })?;
6356 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6357 Ok(if kind < 2 { new_val } else { last_old })
6358 }
6359
6360 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[PerlValue]) -> PerlValue {
6361 self.touch_env_hash(hash);
6362 let h = self.scope.get_hash(hash);
6363 let mut result = Vec::new();
6364 for kv in key_values {
6365 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
6366 vv.iter().map(|x| x.to_string()).collect()
6367 } else {
6368 vec![kv.to_string()]
6369 };
6370 for k in key_strings {
6371 result.push(h.get(&k).cloned().unwrap_or(PerlValue::UNDEF));
6372 }
6373 }
6374 PerlValue::array(result)
6375 }
6376
6377 pub(crate) fn compound_assign_named_hash_slice(
6379 &mut self,
6380 hash: &str,
6381 key_values: Vec<PerlValue>,
6382 op: BinOp,
6383 rhs: PerlValue,
6384 line: usize,
6385 ) -> Result<PerlValue, FlowOrError> {
6386 let old_list = self.hash_slice_named_values(hash, &key_values);
6387 let last_old = old_list
6388 .to_list()
6389 .last()
6390 .cloned()
6391 .unwrap_or(PerlValue::UNDEF);
6392 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
6393 let mut ks: Vec<String> = Vec::new();
6394 for kv in &key_values {
6395 if let Some(vv) = kv.as_array_vec() {
6396 ks.extend(vv.iter().map(|x| x.to_string()));
6397 } else {
6398 ks.push(kv.to_string());
6399 }
6400 }
6401 if ks.is_empty() {
6402 return Err(PerlError::runtime("assign to empty hash slice", line).into());
6403 }
6404 let last_key = ks.last().expect("non-empty ks");
6405 let container = PerlValue::string(hash.to_string());
6406 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6407 Ok(new_val)
6408 }
6409
6410 pub(crate) fn named_hash_slice_inc_dec(
6412 &mut self,
6413 hash: &str,
6414 key_values: Vec<PerlValue>,
6415 kind: u8,
6416 line: usize,
6417 ) -> Result<PerlValue, FlowOrError> {
6418 let old_list = self.hash_slice_named_values(hash, &key_values);
6419 let last_old = old_list
6420 .to_list()
6421 .last()
6422 .cloned()
6423 .unwrap_or(PerlValue::UNDEF);
6424 let new_val = if kind & 1 == 0 {
6425 PerlValue::integer(last_old.to_int() + 1)
6426 } else {
6427 PerlValue::integer(last_old.to_int() - 1)
6428 };
6429 let mut ks: Vec<String> = Vec::new();
6430 for kv in &key_values {
6431 if let Some(vv) = kv.as_array_vec() {
6432 ks.extend(vv.iter().map(|x| x.to_string()));
6433 } else {
6434 ks.push(kv.to_string());
6435 }
6436 }
6437 let last_key = ks.last().ok_or_else(|| {
6438 PerlError::runtime("Hash slice increment needs at least one key", line)
6439 })?;
6440 let container = PerlValue::string(hash.to_string());
6441 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
6442 Ok(if kind < 2 { new_val } else { last_old })
6443 }
6444
6445 fn match_array_pattern_elems(
6446 &mut self,
6447 arr: &[PerlValue],
6448 elems: &[MatchArrayElem],
6449 line: usize,
6450 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6451 let has_rest = elems
6452 .iter()
6453 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
6454 let mut binds: Vec<PatternBinding> = Vec::new();
6455 let mut idx = 0usize;
6456 for (i, elem) in elems.iter().enumerate() {
6457 match elem {
6458 MatchArrayElem::Rest => {
6459 if i != elems.len() - 1 {
6460 return Err(PerlError::runtime(
6461 "internal: `*` must be last in array match pattern",
6462 line,
6463 )
6464 .into());
6465 }
6466 return Ok(Some(binds));
6467 }
6468 MatchArrayElem::RestBind(name) => {
6469 if i != elems.len() - 1 {
6470 return Err(PerlError::runtime(
6471 "internal: `@name` rest bind must be last in array match pattern",
6472 line,
6473 )
6474 .into());
6475 }
6476 let tail = arr[idx..].to_vec();
6477 binds.push(PatternBinding::Array(name.clone(), tail));
6478 return Ok(Some(binds));
6479 }
6480 MatchArrayElem::CaptureScalar(name) => {
6481 if idx >= arr.len() {
6482 return Ok(None);
6483 }
6484 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
6485 idx += 1;
6486 }
6487 MatchArrayElem::Expr(e) => {
6488 if idx >= arr.len() {
6489 return Ok(None);
6490 }
6491 let expected = self.eval_expr(e)?;
6492 if !self.smartmatch_when(&arr[idx], &expected) {
6493 return Ok(None);
6494 }
6495 idx += 1;
6496 }
6497 }
6498 }
6499 if !has_rest && idx != arr.len() {
6500 return Ok(None);
6501 }
6502 Ok(Some(binds))
6503 }
6504
6505 fn match_hash_pattern_pairs(
6506 &mut self,
6507 h: &IndexMap<String, PerlValue>,
6508 pairs: &[MatchHashPair],
6509 _line: usize,
6510 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6511 let mut binds = Vec::new();
6512 for pair in pairs {
6513 match pair {
6514 MatchHashPair::KeyOnly { key } => {
6515 let ks = self.eval_expr(key)?.to_string();
6516 if !h.contains_key(&ks) {
6517 return Ok(None);
6518 }
6519 }
6520 MatchHashPair::Capture { key, name } => {
6521 let ks = self.eval_expr(key)?.to_string();
6522 let Some(v) = h.get(&ks) else {
6523 return Ok(None);
6524 };
6525 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
6526 }
6527 }
6528 }
6529 Ok(Some(binds))
6530 }
6531
6532 #[inline]
6534 fn block_needs_scope(block: &Block) -> bool {
6535 block.iter().any(|s| match &s.kind {
6536 StmtKind::My(_)
6537 | StmtKind::Our(_)
6538 | StmtKind::Local(_)
6539 | StmtKind::State(_)
6540 | StmtKind::LocalExpr { .. } => true,
6541 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
6542 _ => false,
6543 })
6544 }
6545
6546 #[inline]
6548 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
6549 if Self::block_needs_scope(block) {
6550 self.exec_block(block)
6551 } else {
6552 self.exec_block_no_scope(block)
6553 }
6554 }
6555
6556 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
6557 let t0 = self.profiler.is_some().then(std::time::Instant::now);
6558 let r = self.exec_statement_inner(stmt);
6559 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
6560 prof.on_line(&self.file, stmt.line, t0.elapsed());
6561 }
6562 r
6563 }
6564
6565 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
6566 if let Err(e) = crate::perl_signal::poll(self) {
6567 return Err(FlowOrError::Error(e));
6568 }
6569 if let Err(e) = self.drain_pending_destroys(stmt.line) {
6570 return Err(FlowOrError::Error(e));
6571 }
6572 match &stmt.kind {
6573 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
6574 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
6575 StmtKind::If {
6576 condition,
6577 body,
6578 elsifs,
6579 else_block,
6580 } => {
6581 if self.eval_boolean_rvalue_condition(condition)? {
6582 return self.exec_block(body);
6583 }
6584 for (c, b) in elsifs {
6585 if self.eval_boolean_rvalue_condition(c)? {
6586 return self.exec_block(b);
6587 }
6588 }
6589 if let Some(eb) = else_block {
6590 return self.exec_block(eb);
6591 }
6592 Ok(PerlValue::UNDEF)
6593 }
6594 StmtKind::Unless {
6595 condition,
6596 body,
6597 else_block,
6598 } => {
6599 if !self.eval_boolean_rvalue_condition(condition)? {
6600 return self.exec_block(body);
6601 }
6602 if let Some(eb) = else_block {
6603 return self.exec_block(eb);
6604 }
6605 Ok(PerlValue::UNDEF)
6606 }
6607 StmtKind::While {
6608 condition,
6609 body,
6610 label,
6611 continue_block,
6612 } => {
6613 'outer: loop {
6614 if !self.eval_boolean_rvalue_condition(condition)? {
6615 break;
6616 }
6617 'inner: loop {
6618 match self.exec_block_smart(body) {
6619 Ok(_) => break 'inner,
6620 Err(FlowOrError::Flow(Flow::Last(ref l)))
6621 if l == label || l.is_none() =>
6622 {
6623 break 'outer;
6624 }
6625 Err(FlowOrError::Flow(Flow::Next(ref l)))
6626 if l == label || l.is_none() =>
6627 {
6628 if let Some(cb) = continue_block {
6629 let _ = self.exec_block_smart(cb);
6630 }
6631 continue 'outer;
6632 }
6633 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6634 if l == label || l.is_none() =>
6635 {
6636 continue 'inner;
6637 }
6638 Err(e) => return Err(e),
6639 }
6640 }
6641 if let Some(cb) = continue_block {
6642 let _ = self.exec_block_smart(cb);
6643 }
6644 }
6645 Ok(PerlValue::UNDEF)
6646 }
6647 StmtKind::Until {
6648 condition,
6649 body,
6650 label,
6651 continue_block,
6652 } => {
6653 'outer: loop {
6654 if self.eval_boolean_rvalue_condition(condition)? {
6655 break;
6656 }
6657 'inner: loop {
6658 match self.exec_block(body) {
6659 Ok(_) => break 'inner,
6660 Err(FlowOrError::Flow(Flow::Last(ref l)))
6661 if l == label || l.is_none() =>
6662 {
6663 break 'outer;
6664 }
6665 Err(FlowOrError::Flow(Flow::Next(ref l)))
6666 if l == label || l.is_none() =>
6667 {
6668 if let Some(cb) = continue_block {
6669 let _ = self.exec_block_smart(cb);
6670 }
6671 continue 'outer;
6672 }
6673 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6674 if l == label || l.is_none() =>
6675 {
6676 continue 'inner;
6677 }
6678 Err(e) => return Err(e),
6679 }
6680 }
6681 if let Some(cb) = continue_block {
6682 let _ = self.exec_block_smart(cb);
6683 }
6684 }
6685 Ok(PerlValue::UNDEF)
6686 }
6687 StmtKind::DoWhile { body, condition } => {
6688 loop {
6689 self.exec_block(body)?;
6690 if !self.eval_boolean_rvalue_condition(condition)? {
6691 break;
6692 }
6693 }
6694 Ok(PerlValue::UNDEF)
6695 }
6696 StmtKind::For {
6697 init,
6698 condition,
6699 step,
6700 body,
6701 label,
6702 continue_block,
6703 } => {
6704 self.scope_push_hook();
6705 if let Some(init) = init {
6706 self.exec_statement(init)?;
6707 }
6708 'outer: loop {
6709 if let Some(cond) = condition {
6710 if !self.eval_boolean_rvalue_condition(cond)? {
6711 break;
6712 }
6713 }
6714 'inner: loop {
6715 match self.exec_block_smart(body) {
6716 Ok(_) => break 'inner,
6717 Err(FlowOrError::Flow(Flow::Last(ref l)))
6718 if l == label || l.is_none() =>
6719 {
6720 break 'outer;
6721 }
6722 Err(FlowOrError::Flow(Flow::Next(ref l)))
6723 if l == label || l.is_none() =>
6724 {
6725 if let Some(cb) = continue_block {
6726 let _ = self.exec_block_smart(cb);
6727 }
6728 if let Some(step) = step {
6729 self.eval_expr(step)?;
6730 }
6731 continue 'outer;
6732 }
6733 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6734 if l == label || l.is_none() =>
6735 {
6736 continue 'inner;
6737 }
6738 Err(e) => {
6739 self.scope_pop_hook();
6740 return Err(e);
6741 }
6742 }
6743 }
6744 if let Some(cb) = continue_block {
6745 let _ = self.exec_block_smart(cb);
6746 }
6747 if let Some(step) = step {
6748 self.eval_expr(step)?;
6749 }
6750 }
6751 self.scope_pop_hook();
6752 Ok(PerlValue::UNDEF)
6753 }
6754 StmtKind::Foreach {
6755 var,
6756 list,
6757 body,
6758 label,
6759 continue_block,
6760 } => {
6761 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
6762 let items = list_val.to_list();
6763 self.scope_push_hook();
6764 self.scope.declare_scalar(var, PerlValue::UNDEF);
6765 self.english_note_lexical_scalar(var);
6766 let mut i = 0usize;
6767 'outer: while i < items.len() {
6768 self.scope
6769 .set_scalar(var, items[i].clone())
6770 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
6771 'inner: loop {
6772 match self.exec_block_smart(body) {
6773 Ok(_) => break 'inner,
6774 Err(FlowOrError::Flow(Flow::Last(ref l)))
6775 if l == label || l.is_none() =>
6776 {
6777 break 'outer;
6778 }
6779 Err(FlowOrError::Flow(Flow::Next(ref l)))
6780 if l == label || l.is_none() =>
6781 {
6782 if let Some(cb) = continue_block {
6783 let _ = self.exec_block_smart(cb);
6784 }
6785 i += 1;
6786 continue 'outer;
6787 }
6788 Err(FlowOrError::Flow(Flow::Redo(ref l)))
6789 if l == label || l.is_none() =>
6790 {
6791 continue 'inner;
6792 }
6793 Err(e) => {
6794 self.scope_pop_hook();
6795 return Err(e);
6796 }
6797 }
6798 }
6799 if let Some(cb) = continue_block {
6800 let _ = self.exec_block_smart(cb);
6801 }
6802 i += 1;
6803 }
6804 self.scope_pop_hook();
6805 Ok(PerlValue::UNDEF)
6806 }
6807 StmtKind::SubDecl {
6808 name,
6809 params,
6810 body,
6811 prototype,
6812 } => {
6813 let key = self.qualify_sub_key(name);
6814 let captured = self.scope.capture();
6815 let closure_env = if captured.is_empty() {
6816 None
6817 } else {
6818 Some(captured)
6819 };
6820 let mut sub = PerlSub {
6821 name: name.clone(),
6822 params: params.clone(),
6823 body: body.clone(),
6824 closure_env,
6825 prototype: prototype.clone(),
6826 fib_like: None,
6827 };
6828 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
6829 self.subs.insert(key, Arc::new(sub));
6830 Ok(PerlValue::UNDEF)
6831 }
6832 StmtKind::StructDecl { def } => {
6833 if self.struct_defs.contains_key(&def.name) {
6834 return Err(PerlError::runtime(
6835 format!("duplicate struct `{}`", def.name),
6836 stmt.line,
6837 )
6838 .into());
6839 }
6840 self.struct_defs
6841 .insert(def.name.clone(), Arc::new(def.clone()));
6842 Ok(PerlValue::UNDEF)
6843 }
6844 StmtKind::EnumDecl { def } => {
6845 if self.enum_defs.contains_key(&def.name) {
6846 return Err(PerlError::runtime(
6847 format!("duplicate enum `{}`", def.name),
6848 stmt.line,
6849 )
6850 .into());
6851 }
6852 self.enum_defs
6853 .insert(def.name.clone(), Arc::new(def.clone()));
6854 Ok(PerlValue::UNDEF)
6855 }
6856 StmtKind::ClassDecl { def } => {
6857 if self.class_defs.contains_key(&def.name) {
6858 return Err(PerlError::runtime(
6859 format!("duplicate class `{}`", def.name),
6860 stmt.line,
6861 )
6862 .into());
6863 }
6864 for parent_name in &def.extends {
6866 if let Some(parent_def) = self.class_defs.get(parent_name) {
6867 if parent_def.is_final {
6868 return Err(PerlError::runtime(
6869 format!("cannot extend final class `{}`", parent_name),
6870 stmt.line,
6871 )
6872 .into());
6873 }
6874 for m in &def.methods {
6876 if let Some(parent_method) = parent_def.method(&m.name) {
6877 if parent_method.is_final {
6878 return Err(PerlError::runtime(
6879 format!(
6880 "cannot override final method `{}` from class `{}`",
6881 m.name, parent_name
6882 ),
6883 stmt.line,
6884 )
6885 .into());
6886 }
6887 }
6888 }
6889 }
6890 }
6891 let mut def = def.clone();
6893 for trait_name in &def.implements.clone() {
6894 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
6895 for required in trait_def.required_methods() {
6896 let has_method = def.methods.iter().any(|m| m.name == required.name);
6897 if !has_method {
6898 return Err(PerlError::runtime(
6899 format!(
6900 "class `{}` implements trait `{}` but does not define required method `{}`",
6901 def.name, trait_name, required.name
6902 ),
6903 stmt.line,
6904 )
6905 .into());
6906 }
6907 }
6908 for tm in &trait_def.methods {
6910 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
6911 def.methods.push(tm.clone());
6912 }
6913 }
6914 }
6915 }
6916 if !def.is_abstract {
6919 for parent_name in &def.extends.clone() {
6920 if let Some(parent_def) = self.class_defs.get(parent_name) {
6921 if parent_def.is_abstract {
6922 for m in &parent_def.methods {
6923 if m.body.is_none()
6924 && !def.methods.iter().any(|dm| dm.name == m.name)
6925 {
6926 return Err(PerlError::runtime(
6927 format!(
6928 "class `{}` must implement abstract method `{}` from `{}`",
6929 def.name, m.name, parent_name
6930 ),
6931 stmt.line,
6932 )
6933 .into());
6934 }
6935 }
6936 }
6937 }
6938 }
6939 }
6940 for sf in &def.static_fields {
6942 let val = if let Some(ref expr) = sf.default {
6943 self.eval_expr(expr)?
6944 } else {
6945 PerlValue::UNDEF
6946 };
6947 let key = format!("{}::{}", def.name, sf.name);
6948 self.scope.declare_scalar(&key, val);
6949 }
6950 self.class_defs.insert(def.name.clone(), Arc::new(def));
6951 Ok(PerlValue::UNDEF)
6952 }
6953 StmtKind::TraitDecl { def } => {
6954 if self.trait_defs.contains_key(&def.name) {
6955 return Err(PerlError::runtime(
6956 format!("duplicate trait `{}`", def.name),
6957 stmt.line,
6958 )
6959 .into());
6960 }
6961 self.trait_defs
6962 .insert(def.name.clone(), Arc::new(def.clone()));
6963 Ok(PerlValue::UNDEF)
6964 }
6965 StmtKind::My(decls) | StmtKind::Our(decls) => {
6966 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
6967 if decls.len() > 1 && decls[0].initializer.is_some() {
6970 let val = self.eval_expr_ctx(
6971 decls[0].initializer.as_ref().unwrap(),
6972 WantarrayCtx::List,
6973 )?;
6974 let items = val.to_list();
6975 let mut idx = 0;
6976 for decl in decls {
6977 match decl.sigil {
6978 Sigil::Scalar => {
6979 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
6980 let skey = if is_our {
6981 self.stash_scalar_name_for_package(&decl.name)
6982 } else {
6983 decl.name.clone()
6984 };
6985 self.scope.declare_scalar_frozen(
6986 &skey,
6987 v,
6988 decl.frozen,
6989 decl.type_annotation.clone(),
6990 )?;
6991 self.english_note_lexical_scalar(&decl.name);
6992 if is_our {
6993 self.note_our_scalar(&decl.name);
6994 }
6995 idx += 1;
6996 }
6997 Sigil::Array => {
6998 let rest: Vec<PerlValue> = items[idx..].to_vec();
7000 idx = items.len();
7001 if is_our {
7002 self.record_exporter_our_array_name(&decl.name, &rest);
7003 }
7004 let aname = self.stash_array_name_for_package(&decl.name);
7005 self.scope.declare_array(&aname, rest);
7006 }
7007 Sigil::Hash => {
7008 let rest: Vec<PerlValue> = items[idx..].to_vec();
7009 idx = items.len();
7010 let mut map = IndexMap::new();
7011 let mut i = 0;
7012 while i + 1 < rest.len() {
7013 map.insert(rest[i].to_string(), rest[i + 1].clone());
7014 i += 2;
7015 }
7016 self.scope.declare_hash(&decl.name, map);
7017 }
7018 Sigil::Typeglob => {
7019 return Err(PerlError::runtime(
7020 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
7021 stmt.line,
7022 )
7023 .into());
7024 }
7025 }
7026 }
7027 } else {
7028 for decl in decls {
7030 let compound_init = decl
7034 .initializer
7035 .as_ref()
7036 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
7037
7038 if compound_init {
7039 match decl.sigil {
7040 Sigil::Typeglob => {
7041 return Err(PerlError::runtime(
7042 "compound assignment on typeglob declaration is not supported",
7043 stmt.line,
7044 )
7045 .into());
7046 }
7047 Sigil::Scalar => {
7048 let skey = if is_our {
7049 self.stash_scalar_name_for_package(&decl.name)
7050 } else {
7051 decl.name.clone()
7052 };
7053 self.scope.declare_scalar_frozen(
7054 &skey,
7055 PerlValue::UNDEF,
7056 decl.frozen,
7057 decl.type_annotation.clone(),
7058 )?;
7059 self.english_note_lexical_scalar(&decl.name);
7060 if is_our {
7061 self.note_our_scalar(&decl.name);
7062 }
7063 let init = decl.initializer.as_ref().unwrap();
7064 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7065 }
7066 Sigil::Array => {
7067 let aname = self.stash_array_name_for_package(&decl.name);
7068 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
7069 let init = decl.initializer.as_ref().unwrap();
7070 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7071 if is_our {
7072 let items = self.scope.get_array(&aname);
7073 self.record_exporter_our_array_name(&decl.name, &items);
7074 }
7075 }
7076 Sigil::Hash => {
7077 self.scope.declare_hash_frozen(
7078 &decl.name,
7079 IndexMap::new(),
7080 decl.frozen,
7081 );
7082 let init = decl.initializer.as_ref().unwrap();
7083 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
7084 }
7085 }
7086 continue;
7087 }
7088
7089 let val = if let Some(init) = &decl.initializer {
7090 let ctx = match decl.sigil {
7091 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7092 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7093 };
7094 self.eval_expr_ctx(init, ctx)?
7095 } else {
7096 PerlValue::UNDEF
7097 };
7098 match decl.sigil {
7099 Sigil::Typeglob => {
7100 return Err(PerlError::runtime(
7101 "`my *FH` / typeglob declaration is not supported",
7102 stmt.line,
7103 )
7104 .into());
7105 }
7106 Sigil::Scalar => {
7107 let skey = if is_our {
7108 self.stash_scalar_name_for_package(&decl.name)
7109 } else {
7110 decl.name.clone()
7111 };
7112 self.scope.declare_scalar_frozen(
7113 &skey,
7114 val,
7115 decl.frozen,
7116 decl.type_annotation.clone(),
7117 )?;
7118 self.english_note_lexical_scalar(&decl.name);
7119 if is_our {
7120 self.note_our_scalar(&decl.name);
7121 }
7122 }
7123 Sigil::Array => {
7124 let items = val.to_list();
7125 if is_our {
7126 self.record_exporter_our_array_name(&decl.name, &items);
7127 }
7128 let aname = self.stash_array_name_for_package(&decl.name);
7129 self.scope.declare_array_frozen(&aname, items, decl.frozen);
7130 }
7131 Sigil::Hash => {
7132 let items = val.to_list();
7133 let mut map = IndexMap::new();
7134 let mut i = 0;
7135 while i + 1 < items.len() {
7136 let k = items[i].to_string();
7137 let v = items[i + 1].clone();
7138 map.insert(k, v);
7139 i += 2;
7140 }
7141 self.scope.declare_hash_frozen(&decl.name, map, decl.frozen);
7142 }
7143 }
7144 }
7145 }
7146 Ok(PerlValue::UNDEF)
7147 }
7148 StmtKind::State(decls) => {
7149 for decl in decls {
7152 let state_key = format!("{}:{}", stmt.line, decl.name);
7153 match decl.sigil {
7154 Sigil::Scalar => {
7155 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
7156 self.scope.declare_scalar(&decl.name, prev);
7158 } else {
7159 let val = if let Some(init) = &decl.initializer {
7161 self.eval_expr(init)?
7162 } else {
7163 PerlValue::UNDEF
7164 };
7165 self.state_vars.insert(state_key.clone(), val.clone());
7166 self.scope.declare_scalar(&decl.name, val);
7167 }
7168 if let Some(frame) = self.state_bindings_stack.last_mut() {
7170 frame.push((decl.name.clone(), state_key));
7171 }
7172 }
7173 _ => {
7174 let val = if let Some(init) = &decl.initializer {
7176 self.eval_expr(init)?
7177 } else {
7178 PerlValue::UNDEF
7179 };
7180 match decl.sigil {
7181 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
7182 Sigil::Hash => {
7183 let items = val.to_list();
7184 let mut map = IndexMap::new();
7185 let mut i = 0;
7186 while i + 1 < items.len() {
7187 map.insert(items[i].to_string(), items[i + 1].clone());
7188 i += 2;
7189 }
7190 self.scope.declare_hash(&decl.name, map);
7191 }
7192 _ => {}
7193 }
7194 }
7195 }
7196 }
7197 Ok(PerlValue::UNDEF)
7198 }
7199 StmtKind::Local(decls) => {
7200 if decls.len() > 1 && decls[0].initializer.is_some() {
7201 let val = self.eval_expr_ctx(
7202 decls[0].initializer.as_ref().unwrap(),
7203 WantarrayCtx::List,
7204 )?;
7205 let items = val.to_list();
7206 let mut idx = 0;
7207 for decl in decls {
7208 match decl.sigil {
7209 Sigil::Scalar => {
7210 let v = items.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
7211 idx += 1;
7212 self.scope.local_set_scalar(&decl.name, v)?;
7213 }
7214 Sigil::Array => {
7215 let rest: Vec<PerlValue> = items[idx..].to_vec();
7216 idx = items.len();
7217 self.scope.local_set_array(&decl.name, rest)?;
7218 }
7219 Sigil::Hash => {
7220 let rest: Vec<PerlValue> = items[idx..].to_vec();
7221 idx = items.len();
7222 if decl.name == "ENV" {
7223 self.materialize_env_if_needed();
7224 }
7225 let mut map = IndexMap::new();
7226 let mut i = 0;
7227 while i + 1 < rest.len() {
7228 map.insert(rest[i].to_string(), rest[i + 1].clone());
7229 i += 2;
7230 }
7231 self.scope.local_set_hash(&decl.name, map)?;
7232 }
7233 Sigil::Typeglob => {
7234 return Err(PerlError::runtime(
7235 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
7236 stmt.line,
7237 )
7238 .into());
7239 }
7240 }
7241 }
7242 Ok(val)
7243 } else {
7244 let mut last_val = PerlValue::UNDEF;
7245 for decl in decls {
7246 let val = if let Some(init) = &decl.initializer {
7247 let ctx = match decl.sigil {
7248 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
7249 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
7250 };
7251 self.eval_expr_ctx(init, ctx)?
7252 } else {
7253 PerlValue::UNDEF
7254 };
7255 last_val = val.clone();
7256 match decl.sigil {
7257 Sigil::Typeglob => {
7258 let old = self.glob_handle_alias.remove(&decl.name);
7259 if let Some(frame) = self.glob_restore_frames.last_mut() {
7260 frame.push((decl.name.clone(), old));
7261 }
7262 if let Some(init) = &decl.initializer {
7263 if let ExprKind::Typeglob(rhs) = &init.kind {
7264 self.glob_handle_alias
7265 .insert(decl.name.clone(), rhs.clone());
7266 } else {
7267 return Err(PerlError::runtime(
7268 "local *GLOB = *OTHER — right side must be a typeglob",
7269 stmt.line,
7270 )
7271 .into());
7272 }
7273 }
7274 }
7275 Sigil::Scalar => {
7276 if Self::is_special_scalar_name_for_set(&decl.name) {
7282 let old = self.get_special_var(&decl.name);
7283 if let Some(frame) = self.special_var_restore_frames.last_mut()
7284 {
7285 frame.push((decl.name.clone(), old));
7286 }
7287 self.set_special_var(&decl.name, &val)
7288 .map_err(|e| e.at_line(stmt.line))?;
7289 }
7290 self.scope.local_set_scalar(&decl.name, val)?;
7291 }
7292 Sigil::Array => {
7293 self.scope.local_set_array(&decl.name, val.to_list())?;
7294 }
7295 Sigil::Hash => {
7296 if decl.name == "ENV" {
7297 self.materialize_env_if_needed();
7298 }
7299 let items = val.to_list();
7300 let mut map = IndexMap::new();
7301 let mut i = 0;
7302 while i + 1 < items.len() {
7303 let k = items[i].to_string();
7304 let v = items[i + 1].clone();
7305 map.insert(k, v);
7306 i += 2;
7307 }
7308 self.scope.local_set_hash(&decl.name, map)?;
7309 }
7310 }
7311 }
7312 Ok(last_val)
7313 }
7314 }
7315 StmtKind::LocalExpr {
7316 target,
7317 initializer,
7318 } => {
7319 let rhs_name = |init: &Expr| -> PerlResult<Option<String>> {
7320 match &init.kind {
7321 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
7322 _ => Err(PerlError::runtime(
7323 "local *GLOB = *OTHER — right side must be a typeglob",
7324 stmt.line,
7325 )),
7326 }
7327 };
7328 match &target.kind {
7329 ExprKind::Typeglob(name) => {
7330 let rhs = if let Some(init) = initializer {
7331 rhs_name(init)?
7332 } else {
7333 None
7334 };
7335 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
7336 return Ok(PerlValue::UNDEF);
7337 }
7338 ExprKind::Deref {
7339 expr,
7340 kind: Sigil::Typeglob,
7341 } => {
7342 let lhs = self.eval_expr(expr)?.to_string();
7343 let rhs = if let Some(init) = initializer {
7344 rhs_name(init)?
7345 } else {
7346 None
7347 };
7348 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7349 return Ok(PerlValue::UNDEF);
7350 }
7351 ExprKind::TypeglobExpr(e) => {
7352 let lhs = self.eval_expr(e)?.to_string();
7353 let rhs = if let Some(init) = initializer {
7354 rhs_name(init)?
7355 } else {
7356 None
7357 };
7358 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
7359 return Ok(PerlValue::UNDEF);
7360 }
7361 _ => {}
7362 }
7363 let val = if let Some(init) = initializer {
7364 let ctx = match &target.kind {
7365 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
7366 _ => WantarrayCtx::Scalar,
7367 };
7368 self.eval_expr_ctx(init, ctx)?
7369 } else {
7370 PerlValue::UNDEF
7371 };
7372 match &target.kind {
7373 ExprKind::ScalarVar(name) => {
7374 if Self::is_special_scalar_name_for_set(name) {
7377 let old = self.get_special_var(name);
7378 if let Some(frame) = self.special_var_restore_frames.last_mut() {
7379 frame.push((name.clone(), old));
7380 }
7381 self.set_special_var(name, &val)
7382 .map_err(|e| e.at_line(stmt.line))?;
7383 }
7384 self.scope.local_set_scalar(name, val.clone())?;
7385 }
7386 ExprKind::ArrayVar(name) => {
7387 self.scope.local_set_array(name, val.to_list())?;
7388 }
7389 ExprKind::HashVar(name) => {
7390 if name == "ENV" {
7391 self.materialize_env_if_needed();
7392 }
7393 let items = val.to_list();
7394 let mut map = IndexMap::new();
7395 let mut i = 0;
7396 while i + 1 < items.len() {
7397 map.insert(items[i].to_string(), items[i + 1].clone());
7398 i += 2;
7399 }
7400 self.scope.local_set_hash(name, map)?;
7401 }
7402 ExprKind::HashElement { hash, key } => {
7403 let ks = self.eval_expr(key)?.to_string();
7404 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
7405 }
7406 ExprKind::ArrayElement { array, index } => {
7407 self.check_strict_array_var(array, stmt.line)?;
7408 let aname = self.stash_array_name_for_package(array);
7409 let idx = self.eval_expr(index)?.to_int();
7410 self.scope
7411 .local_set_array_element(&aname, idx, val.clone())?;
7412 }
7413 _ => {
7414 return Err(PerlError::runtime(
7415 format!(
7416 "local on this lvalue is not supported yet ({:?})",
7417 target.kind
7418 ),
7419 stmt.line,
7420 )
7421 .into());
7422 }
7423 }
7424 Ok(val)
7425 }
7426 StmtKind::MySync(decls) => {
7427 for decl in decls {
7428 let val = if let Some(init) = &decl.initializer {
7429 self.eval_expr(init)?
7430 } else {
7431 PerlValue::UNDEF
7432 };
7433 match decl.sigil {
7434 Sigil::Typeglob => {
7435 return Err(PerlError::runtime(
7436 "`mysync` does not support typeglob variables",
7437 stmt.line,
7438 )
7439 .into());
7440 }
7441 Sigil::Scalar => {
7442 let stored = if val.is_mysync_deque_or_heap() {
7445 val
7446 } else {
7447 PerlValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(val)))
7448 };
7449 self.scope.declare_scalar(&decl.name, stored);
7450 }
7451 Sigil::Array => {
7452 self.scope.declare_atomic_array(&decl.name, val.to_list());
7453 }
7454 Sigil::Hash => {
7455 let items = val.to_list();
7456 let mut map = IndexMap::new();
7457 let mut i = 0;
7458 while i + 1 < items.len() {
7459 map.insert(items[i].to_string(), items[i + 1].clone());
7460 i += 2;
7461 }
7462 self.scope.declare_atomic_hash(&decl.name, map);
7463 }
7464 }
7465 }
7466 Ok(PerlValue::UNDEF)
7467 }
7468 StmtKind::Package { name } => {
7469 let _ = self
7471 .scope
7472 .set_scalar("__PACKAGE__", PerlValue::string(name.clone()));
7473 Ok(PerlValue::UNDEF)
7474 }
7475 StmtKind::UsePerlVersion { .. } => Ok(PerlValue::UNDEF),
7476 StmtKind::Use { .. } => {
7477 Ok(PerlValue::UNDEF)
7479 }
7480 StmtKind::UseOverload { pairs } => {
7481 self.install_use_overload_pairs(pairs);
7482 Ok(PerlValue::UNDEF)
7483 }
7484 StmtKind::No { .. } => {
7485 Ok(PerlValue::UNDEF)
7487 }
7488 StmtKind::Return(val) => {
7489 let v = if let Some(e) = val {
7490 self.eval_expr_ctx(e, self.wantarray_kind)?
7494 } else {
7495 PerlValue::UNDEF
7496 };
7497 Err(Flow::Return(v).into())
7498 }
7499 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
7500 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
7501 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
7502 StmtKind::Block(block) => self.exec_block(block),
7503 StmtKind::Begin(_)
7504 | StmtKind::UnitCheck(_)
7505 | StmtKind::Check(_)
7506 | StmtKind::Init(_)
7507 | StmtKind::End(_) => Ok(PerlValue::UNDEF),
7508 StmtKind::Empty => Ok(PerlValue::UNDEF),
7509 StmtKind::Goto { target } => {
7510 if let ExprKind::SubroutineRef(name) = &target.kind {
7512 return Err(Flow::GotoSub(name.clone()).into());
7513 }
7514 Err(PerlError::runtime("goto reached outside goto-aware block", stmt.line).into())
7515 }
7516 StmtKind::EvalTimeout { timeout, body } => {
7517 let secs = self.eval_expr(timeout)?.to_number();
7518 self.eval_timeout_block(body, secs, stmt.line)
7519 }
7520 StmtKind::Tie {
7521 target,
7522 class,
7523 args,
7524 } => {
7525 let kind = match &target {
7526 TieTarget::Scalar(_) => 0u8,
7527 TieTarget::Array(_) => 1u8,
7528 TieTarget::Hash(_) => 2u8,
7529 };
7530 let name = match &target {
7531 TieTarget::Scalar(s) => s.as_str(),
7532 TieTarget::Array(a) => a.as_str(),
7533 TieTarget::Hash(h) => h.as_str(),
7534 };
7535 let mut vals = vec![self.eval_expr(class)?];
7536 for a in args {
7537 vals.push(self.eval_expr(a)?);
7538 }
7539 self.tie_execute(kind, name, vals, stmt.line)
7540 .map_err(Into::into)
7541 }
7542 StmtKind::TryCatch {
7543 try_block,
7544 catch_var,
7545 catch_block,
7546 finally_block,
7547 } => match self.exec_block(try_block) {
7548 Ok(v) => {
7549 if let Some(fb) = finally_block {
7550 self.exec_block(fb)?;
7551 }
7552 Ok(v)
7553 }
7554 Err(FlowOrError::Error(e)) => {
7555 if matches!(e.kind, ErrorKind::Exit(_)) {
7556 return Err(FlowOrError::Error(e));
7557 }
7558 self.scope_push_hook();
7559 self.scope
7560 .declare_scalar(catch_var, PerlValue::string(e.to_string()));
7561 self.english_note_lexical_scalar(catch_var);
7562 let r = self.exec_block(catch_block);
7563 self.scope_pop_hook();
7564 if let Some(fb) = finally_block {
7565 self.exec_block(fb)?;
7566 }
7567 r
7568 }
7569 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
7570 },
7571 StmtKind::Given { topic, body } => self.exec_given(topic, body),
7572 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(PerlError::runtime(
7573 "when/default may only appear inside a given block",
7574 stmt.line,
7575 )
7576 .into()),
7577 StmtKind::FormatDecl { .. } => {
7578 Ok(PerlValue::UNDEF)
7580 }
7581 StmtKind::Continue(block) => self.exec_block_smart(block),
7582 }
7583 }
7584
7585 #[inline]
7586 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
7587 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
7588 }
7589
7590 pub(crate) fn scalar_compound_assign_scalar_target(
7594 &mut self,
7595 name: &str,
7596 op: BinOp,
7597 rhs: PerlValue,
7598 ) -> Result<PerlValue, PerlError> {
7599 if op == BinOp::Concat {
7600 return self.scope.scalar_concat_inplace(name, &rhs);
7601 }
7602 Ok(self
7603 .scope
7604 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs)))
7605 }
7606
7607 fn compound_scalar_binop(old: &PerlValue, op: BinOp, rhs: &PerlValue) -> PerlValue {
7608 match op {
7609 BinOp::Add => {
7610 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7611 PerlValue::integer(a.wrapping_add(b))
7612 } else {
7613 PerlValue::float(old.to_number() + rhs.to_number())
7614 }
7615 }
7616 BinOp::Sub => {
7617 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7618 PerlValue::integer(a.wrapping_sub(b))
7619 } else {
7620 PerlValue::float(old.to_number() - rhs.to_number())
7621 }
7622 }
7623 BinOp::Mul => {
7624 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
7625 PerlValue::integer(a.wrapping_mul(b))
7626 } else {
7627 PerlValue::float(old.to_number() * rhs.to_number())
7628 }
7629 }
7630 BinOp::BitAnd => {
7631 if let Some(s) = crate::value::set_intersection(old, rhs) {
7632 s
7633 } else {
7634 PerlValue::integer(old.to_int() & rhs.to_int())
7635 }
7636 }
7637 BinOp::BitOr => {
7638 if let Some(s) = crate::value::set_union(old, rhs) {
7639 s
7640 } else {
7641 PerlValue::integer(old.to_int() | rhs.to_int())
7642 }
7643 }
7644 BinOp::BitXor => PerlValue::integer(old.to_int() ^ rhs.to_int()),
7645 BinOp::ShiftLeft => PerlValue::integer(old.to_int() << rhs.to_int()),
7646 BinOp::ShiftRight => PerlValue::integer(old.to_int() >> rhs.to_int()),
7647 BinOp::Div => PerlValue::float(old.to_number() / rhs.to_number()),
7648 BinOp::Mod => PerlValue::float(old.to_number() % rhs.to_number()),
7649 BinOp::Pow => PerlValue::float(old.to_number().powf(rhs.to_number())),
7650 BinOp::LogOr => {
7651 if old.is_true() {
7652 old.clone()
7653 } else {
7654 rhs.clone()
7655 }
7656 }
7657 BinOp::DefinedOr => {
7658 if !old.is_undef() {
7659 old.clone()
7660 } else {
7661 rhs.clone()
7662 }
7663 }
7664 BinOp::LogAnd => {
7665 if old.is_true() {
7666 rhs.clone()
7667 } else {
7668 old.clone()
7669 }
7670 }
7671 _ => PerlValue::float(old.to_number() + rhs.to_number()),
7672 }
7673 }
7674
7675 fn eval_hash_slice_key_components(
7679 &mut self,
7680 key_expr: &Expr,
7681 ) -> Result<Vec<String>, FlowOrError> {
7682 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
7683 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
7684 } else {
7685 self.eval_expr(key_expr)?
7686 };
7687 if let Some(vv) = v.as_array_vec() {
7688 Ok(vv.iter().map(|x| x.to_string()).collect())
7689 } else {
7690 Ok(vec![v.to_string()])
7691 }
7692 }
7693
7694 pub(crate) fn symbolic_deref(
7696 &mut self,
7697 val: PerlValue,
7698 kind: Sigil,
7699 line: usize,
7700 ) -> ExecResult {
7701 match kind {
7702 Sigil::Scalar => {
7703 if let Some(name) = val.as_scalar_binding_name() {
7704 return Ok(self.get_special_var(&name));
7705 }
7706 if let Some(r) = val.as_scalar_ref() {
7707 return Ok(r.read().clone());
7708 }
7709 if let Some(r) = val.as_array_ref() {
7711 return Ok(PerlValue::array(r.read().clone()));
7712 }
7713 if let Some(name) = val.as_array_binding_name() {
7714 return Ok(PerlValue::array(self.scope.get_array(&name)));
7715 }
7716 if let Some(r) = val.as_hash_ref() {
7717 return Ok(PerlValue::hash(r.read().clone()));
7718 }
7719 if let Some(name) = val.as_hash_binding_name() {
7720 self.touch_env_hash(&name);
7721 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
7722 }
7723 if let Some(s) = val.as_str() {
7724 if self.strict_refs {
7725 return Err(PerlError::runtime(
7726 format!(
7727 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
7728 s
7729 ),
7730 line,
7731 )
7732 .into());
7733 }
7734 return Ok(self.get_special_var(&s));
7735 }
7736 Err(PerlError::runtime("Can't dereference non-reference as scalar", line).into())
7737 }
7738 Sigil::Array => {
7739 if let Some(r) = val.as_array_ref() {
7740 return Ok(PerlValue::array(r.read().clone()));
7741 }
7742 if let Some(name) = val.as_array_binding_name() {
7743 return Ok(PerlValue::array(self.scope.get_array(&name)));
7744 }
7745 if let Some(s) = val.as_str() {
7746 if self.strict_refs {
7747 return Err(PerlError::runtime(
7748 format!(
7749 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
7750 s
7751 ),
7752 line,
7753 )
7754 .into());
7755 }
7756 return Ok(PerlValue::array(self.scope.get_array(&s)));
7757 }
7758 Err(PerlError::runtime("Can't dereference non-reference as array", line).into())
7759 }
7760 Sigil::Hash => {
7761 if let Some(r) = val.as_hash_ref() {
7762 return Ok(PerlValue::hash(r.read().clone()));
7763 }
7764 if let Some(name) = val.as_hash_binding_name() {
7765 self.touch_env_hash(&name);
7766 return Ok(PerlValue::hash(self.scope.get_hash(&name)));
7767 }
7768 if let Some(s) = val.as_str() {
7769 if self.strict_refs {
7770 return Err(PerlError::runtime(
7771 format!(
7772 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7773 s
7774 ),
7775 line,
7776 )
7777 .into());
7778 }
7779 self.touch_env_hash(&s);
7780 return Ok(PerlValue::hash(self.scope.get_hash(&s)));
7781 }
7782 Err(PerlError::runtime("Can't dereference non-reference as hash", line).into())
7783 }
7784 Sigil::Typeglob => {
7785 if let Some(s) = val.as_str() {
7786 return Ok(PerlValue::string(self.resolve_io_handle_name(&s)));
7787 }
7788 Err(PerlError::runtime("Can't dereference non-reference as typeglob", line).into())
7789 }
7790 }
7791 }
7792
7793 #[inline]
7796 pub(crate) fn peel_array_ref_for_list_join(&self, v: PerlValue) -> PerlValue {
7797 if let Some(r) = v.as_array_ref() {
7798 return PerlValue::array(r.read().clone());
7799 }
7800 v
7801 }
7802
7803 pub(crate) fn make_array_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
7805 if let Some(a) = val.as_array_ref() {
7806 return Ok(PerlValue::array_ref(Arc::clone(&a)));
7807 }
7808 if let Some(name) = val.as_array_binding_name() {
7809 return Ok(PerlValue::array_binding_ref(name));
7810 }
7811 if let Some(s) = val.as_str() {
7812 if self.strict_refs {
7813 return Err(PerlError::runtime(
7814 format!(
7815 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
7816 s
7817 ),
7818 line,
7819 )
7820 .into());
7821 }
7822 return Ok(PerlValue::array_binding_ref(s.to_string()));
7823 }
7824 if let Some(r) = val.as_scalar_ref() {
7825 let inner = r.read().clone();
7826 return self.make_array_ref_alias(inner, line);
7827 }
7828 Err(PerlError::runtime("Can't make array reference from value", line).into())
7829 }
7830
7831 pub(crate) fn make_hash_ref_alias(&self, val: PerlValue, line: usize) -> ExecResult {
7833 if let Some(h) = val.as_hash_ref() {
7834 return Ok(PerlValue::hash_ref(Arc::clone(&h)));
7835 }
7836 if let Some(name) = val.as_hash_binding_name() {
7837 return Ok(PerlValue::hash_binding_ref(name));
7838 }
7839 if let Some(s) = val.as_str() {
7840 if self.strict_refs {
7841 return Err(PerlError::runtime(
7842 format!(
7843 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7844 s
7845 ),
7846 line,
7847 )
7848 .into());
7849 }
7850 return Ok(PerlValue::hash_binding_ref(s.to_string()));
7851 }
7852 if let Some(r) = val.as_scalar_ref() {
7853 let inner = r.read().clone();
7854 return self.make_hash_ref_alias(inner, line);
7855 }
7856 Err(PerlError::runtime("Can't make hash reference from value", line).into())
7857 }
7858
7859 pub(crate) fn process_case_escapes(s: &str) -> String {
7862 if !s.contains('\\') {
7864 return s.to_string();
7865 }
7866 let mut result = String::with_capacity(s.len());
7867 let mut chars = s.chars().peekable();
7868 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
7872 if c == '\\' {
7873 match chars.peek() {
7874 Some(&'U') => {
7875 chars.next();
7876 mode = Some('U');
7877 continue;
7878 }
7879 Some(&'L') => {
7880 chars.next();
7881 mode = Some('L');
7882 continue;
7883 }
7884 Some(&'Q') => {
7885 chars.next();
7886 mode = Some('Q');
7887 continue;
7888 }
7889 Some(&'E') => {
7890 chars.next();
7891 mode = None;
7892 next_char_mod = None;
7893 continue;
7894 }
7895 Some(&'u') => {
7896 chars.next();
7897 next_char_mod = Some('u');
7898 continue;
7899 }
7900 Some(&'l') => {
7901 chars.next();
7902 next_char_mod = Some('l');
7903 continue;
7904 }
7905 _ => {}
7906 }
7907 }
7908
7909 let ch = c;
7910
7911 if let Some(m) = next_char_mod.take() {
7913 let transformed = match m {
7914 'u' => ch.to_uppercase().next().unwrap_or(ch),
7915 'l' => ch.to_lowercase().next().unwrap_or(ch),
7916 _ => ch,
7917 };
7918 result.push(transformed);
7919 } else {
7920 match mode {
7922 Some('U') => {
7923 for uc in ch.to_uppercase() {
7924 result.push(uc);
7925 }
7926 }
7927 Some('L') => {
7928 for lc in ch.to_lowercase() {
7929 result.push(lc);
7930 }
7931 }
7932 Some('Q') => {
7933 if !ch.is_ascii_alphanumeric() && ch != '_' {
7934 result.push('\\');
7935 }
7936 result.push(ch);
7937 }
7938 None | Some(_) => {
7939 result.push(ch);
7940 }
7941 }
7942 }
7943 }
7944 result
7945 }
7946
7947 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
7948 let line = expr.line;
7949 match &expr.kind {
7950 ExprKind::Integer(n) => Ok(PerlValue::integer(*n)),
7951 ExprKind::Float(f) => Ok(PerlValue::float(*f)),
7952 ExprKind::String(s) => {
7953 let processed = Self::process_case_escapes(s);
7954 Ok(PerlValue::string(processed))
7955 }
7956 ExprKind::Bareword(s) => {
7957 if s == "__PACKAGE__" {
7958 return Ok(PerlValue::string(self.current_package()));
7959 }
7960 if let Some(sub) = self.resolve_sub_by_name(s) {
7961 return self.call_sub(&sub, vec![], ctx, line);
7962 }
7963 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
7965 return r.map_err(Into::into);
7966 }
7967 Ok(PerlValue::string(s.clone()))
7968 }
7969 ExprKind::Undef => Ok(PerlValue::UNDEF),
7970 ExprKind::MagicConst(MagicConstKind::File) => Ok(PerlValue::string(self.file.clone())),
7971 ExprKind::MagicConst(MagicConstKind::Line) => Ok(PerlValue::integer(expr.line as i64)),
7972 ExprKind::MagicConst(MagicConstKind::Sub) => {
7973 if let Some(sub) = self.current_sub_stack.last().cloned() {
7974 Ok(PerlValue::code_ref(sub))
7975 } else {
7976 Ok(PerlValue::UNDEF)
7977 }
7978 }
7979 ExprKind::Regex(pattern, flags) => {
7980 if ctx == WantarrayCtx::Void {
7981 let topic = self.scope.get_scalar("_");
7983 let s = topic.to_string();
7984 self.regex_match_execute(s, pattern, flags, false, "_", line)
7985 } else {
7986 let re = self.compile_regex(pattern, flags, line)?;
7987 Ok(PerlValue::regex(re, pattern.clone(), flags.clone()))
7988 }
7989 }
7990 ExprKind::QW(words) => Ok(PerlValue::array(
7991 words.iter().map(|w| PerlValue::string(w.clone())).collect(),
7992 )),
7993
7994 ExprKind::InterpolatedString(parts) => {
7996 let mut raw_result = String::new();
7997 for part in parts {
7998 match part {
7999 StringPart::Literal(s) => raw_result.push_str(s),
8000 StringPart::ScalarVar(name) => {
8001 self.check_strict_scalar_var(name, line)?;
8002 let val = self.get_special_var(name);
8003 let s = self.stringify_value(val, line)?;
8004 raw_result.push_str(&s);
8005 }
8006 StringPart::ArrayVar(name) => {
8007 self.check_strict_array_var(name, line)?;
8008 let aname = self.stash_array_name_for_package(name);
8009 let arr = self.scope.get_array(&aname);
8010 let mut parts = Vec::with_capacity(arr.len());
8011 for v in &arr {
8012 parts.push(self.stringify_value(v.clone(), line)?);
8013 }
8014 let sep = self.list_separator.clone();
8015 raw_result.push_str(&parts.join(&sep));
8016 }
8017 StringPart::Expr(e) => {
8018 if let ExprKind::ArraySlice { array, .. } = &e.kind {
8019 self.check_strict_array_var(array, line)?;
8020 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8021 let val = self.peel_array_ref_for_list_join(val);
8022 let list = val.to_list();
8023 let sep = self.list_separator.clone();
8024 let mut parts = Vec::with_capacity(list.len());
8025 for v in list {
8026 parts.push(self.stringify_value(v, line)?);
8027 }
8028 raw_result.push_str(&parts.join(&sep));
8029 } else if let ExprKind::Deref {
8030 kind: Sigil::Array, ..
8031 } = &e.kind
8032 {
8033 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8034 let val = self.peel_array_ref_for_list_join(val);
8035 let list = val.to_list();
8036 let sep = self.list_separator.clone();
8037 let mut parts = Vec::with_capacity(list.len());
8038 for v in list {
8039 parts.push(self.stringify_value(v, line)?);
8040 }
8041 raw_result.push_str(&parts.join(&sep));
8042 } else {
8043 let val = self.eval_expr(e)?;
8044 let s = self.stringify_value(val, line)?;
8045 raw_result.push_str(&s);
8046 }
8047 }
8048 }
8049 }
8050 let result = Self::process_case_escapes(&raw_result);
8051 Ok(PerlValue::string(result))
8052 }
8053
8054 ExprKind::ScalarVar(name) => {
8056 self.check_strict_scalar_var(name, line)?;
8057 let stor = self.tree_scalar_storage_name(name);
8058 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
8059 let class = obj
8060 .as_blessed_ref()
8061 .map(|b| b.class.clone())
8062 .unwrap_or_default();
8063 let full = format!("{}::FETCH", class);
8064 if let Some(sub) = self.subs.get(&full).cloned() {
8065 return self.call_sub(&sub, vec![obj], ctx, line);
8066 }
8067 }
8068 Ok(self.get_special_var(&stor))
8069 }
8070 ExprKind::ArrayVar(name) => {
8071 self.check_strict_array_var(name, line)?;
8072 let aname = self.stash_array_name_for_package(name);
8073 let arr = self.scope.get_array(&aname);
8074 if ctx == WantarrayCtx::List {
8075 Ok(PerlValue::array(arr))
8076 } else {
8077 Ok(PerlValue::integer(arr.len() as i64))
8078 }
8079 }
8080 ExprKind::HashVar(name) => {
8081 self.check_strict_hash_var(name, line)?;
8082 self.touch_env_hash(name);
8083 let h = self.scope.get_hash(name);
8084 let pv = PerlValue::hash(h);
8085 if ctx == WantarrayCtx::List {
8086 Ok(pv)
8087 } else {
8088 Ok(pv.scalar_context())
8089 }
8090 }
8091 ExprKind::Typeglob(name) => {
8092 let n = self.resolve_io_handle_name(name);
8093 Ok(PerlValue::string(n))
8094 }
8095 ExprKind::TypeglobExpr(e) => {
8096 let name = self.eval_expr(e)?.to_string();
8097 let n = self.resolve_io_handle_name(&name);
8098 Ok(PerlValue::string(n))
8099 }
8100 ExprKind::ArrayElement { array, index } => {
8101 self.check_strict_array_var(array, line)?;
8102 let idx = self.eval_expr(index)?.to_int();
8103 let aname = self.stash_array_name_for_package(array);
8104 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
8105 let class = obj
8106 .as_blessed_ref()
8107 .map(|b| b.class.clone())
8108 .unwrap_or_default();
8109 let full = format!("{}::FETCH", class);
8110 if let Some(sub) = self.subs.get(&full).cloned() {
8111 let arg_vals = vec![obj, PerlValue::integer(idx)];
8112 return self.call_sub(&sub, arg_vals, ctx, line);
8113 }
8114 }
8115 Ok(self.scope.get_array_element(&aname, idx))
8116 }
8117 ExprKind::HashElement { hash, key } => {
8118 self.check_strict_hash_var(hash, line)?;
8119 let k = self.eval_expr(key)?.to_string();
8120 self.touch_env_hash(hash);
8121 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
8122 let class = obj
8123 .as_blessed_ref()
8124 .map(|b| b.class.clone())
8125 .unwrap_or_default();
8126 let full = format!("{}::FETCH", class);
8127 if let Some(sub) = self.subs.get(&full).cloned() {
8128 let arg_vals = vec![obj, PerlValue::string(k)];
8129 return self.call_sub(&sub, arg_vals, ctx, line);
8130 }
8131 }
8132 Ok(self.scope.get_hash_element(hash, &k))
8133 }
8134 ExprKind::ArraySlice { array, indices } => {
8135 self.check_strict_array_var(array, line)?;
8136 let aname = self.stash_array_name_for_package(array);
8137 let flat = self.flatten_array_slice_index_specs(indices)?;
8138 let mut result = Vec::with_capacity(flat.len());
8139 for idx in flat {
8140 result.push(self.scope.get_array_element(&aname, idx));
8141 }
8142 Ok(PerlValue::array(result))
8143 }
8144 ExprKind::HashSlice { hash, keys } => {
8145 self.check_strict_hash_var(hash, line)?;
8146 self.touch_env_hash(hash);
8147 let mut result = Vec::new();
8148 for key_expr in keys {
8149 for k in self.eval_hash_slice_key_components(key_expr)? {
8150 result.push(self.scope.get_hash_element(hash, &k));
8151 }
8152 }
8153 Ok(PerlValue::array(result))
8154 }
8155 ExprKind::HashSliceDeref { container, keys } => {
8156 let hv = self.eval_expr(container)?;
8157 let mut key_vals = Vec::with_capacity(keys.len());
8158 for key_expr in keys {
8159 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
8160 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8161 } else {
8162 self.eval_expr(key_expr)?
8163 };
8164 key_vals.push(v);
8165 }
8166 self.hash_slice_deref_values(&hv, &key_vals, line)
8167 }
8168 ExprKind::AnonymousListSlice { source, indices } => {
8169 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
8170 let items = list_val.to_list();
8171 let flat = self.flatten_array_slice_index_specs(indices)?;
8172 let mut out = Vec::with_capacity(flat.len());
8173 for idx in flat {
8174 let i = if idx < 0 {
8175 (items.len() as i64 + idx) as usize
8176 } else {
8177 idx as usize
8178 };
8179 out.push(items.get(i).cloned().unwrap_or(PerlValue::UNDEF));
8180 }
8181 let arr = PerlValue::array(out);
8182 if ctx != WantarrayCtx::List {
8183 let v = arr.to_list();
8184 Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF))
8185 } else {
8186 Ok(arr)
8187 }
8188 }
8189
8190 ExprKind::ScalarRef(inner) => match &inner.kind {
8192 ExprKind::ScalarVar(name) => Ok(PerlValue::scalar_binding_ref(name.clone())),
8193 ExprKind::ArrayVar(name) => {
8194 self.check_strict_array_var(name, line)?;
8195 let aname = self.stash_array_name_for_package(name);
8196 Ok(PerlValue::array_binding_ref(aname))
8197 }
8198 ExprKind::HashVar(name) => {
8199 self.check_strict_hash_var(name, line)?;
8200 Ok(PerlValue::hash_binding_ref(name.clone()))
8201 }
8202 ExprKind::Deref {
8203 expr: e,
8204 kind: Sigil::Array,
8205 } => {
8206 let v = self.eval_expr(e)?;
8207 self.make_array_ref_alias(v, line)
8208 }
8209 ExprKind::Deref {
8210 expr: e,
8211 kind: Sigil::Hash,
8212 } => {
8213 let v = self.eval_expr(e)?;
8214 self.make_hash_ref_alias(v, line)
8215 }
8216 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
8217 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8218 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8219 }
8220 ExprKind::HashSliceDeref { .. } => {
8221 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
8222 Ok(PerlValue::array_ref(Arc::new(RwLock::new(list.to_list()))))
8223 }
8224 _ => {
8225 let val = self.eval_expr(inner)?;
8226 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8227 }
8228 },
8229 ExprKind::ArrayRef(elems) => {
8230 let mut arr = Vec::with_capacity(elems.len());
8234 for e in elems {
8235 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
8236 if let Some(vec) = v.as_array_vec() {
8237 arr.extend(vec);
8238 } else {
8239 arr.push(v);
8240 }
8241 }
8242 Ok(PerlValue::array_ref(Arc::new(RwLock::new(arr))))
8243 }
8244 ExprKind::HashRef(pairs) => {
8245 let mut map = IndexMap::new();
8248 for (k, v) in pairs {
8249 let key_str = self.eval_expr(k)?.to_string();
8250 if key_str == "__HASH_SPREAD__" {
8251 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8253 let items = spread.to_list();
8254 let mut i = 0;
8255 while i + 1 < items.len() {
8256 map.insert(items[i].to_string(), items[i + 1].clone());
8257 i += 2;
8258 }
8259 } else {
8260 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
8261 map.insert(key_str, val);
8262 }
8263 }
8264 Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map))))
8265 }
8266 ExprKind::CodeRef { params, body } => {
8267 let captured = self.scope.capture();
8268 Ok(PerlValue::code_ref(Arc::new(PerlSub {
8269 name: "__ANON__".to_string(),
8270 params: params.clone(),
8271 body: body.clone(),
8272 closure_env: Some(captured),
8273 prototype: None,
8274 fib_like: None,
8275 })))
8276 }
8277 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
8278 ExprKind::SubroutineCodeRef(name) => {
8279 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
8280 PerlError::runtime(self.undefined_subroutine_resolve_message(name), line)
8281 })?;
8282 Ok(PerlValue::code_ref(sub))
8283 }
8284 ExprKind::DynamicSubCodeRef(expr) => {
8285 let name = self.eval_expr(expr)?.to_string();
8286 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
8287 PerlError::runtime(self.undefined_subroutine_resolve_message(&name), line)
8288 })?;
8289 Ok(PerlValue::code_ref(sub))
8290 }
8291 ExprKind::Deref { expr, kind } => {
8292 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
8293 let val = self.eval_expr(expr)?;
8294 let n = self.array_deref_len(val, line)?;
8295 return Ok(PerlValue::integer(n));
8296 }
8297 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
8298 let val = self.eval_expr(expr)?;
8299 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
8300 return Ok(h.scalar_context());
8301 }
8302 let val = self.eval_expr(expr)?;
8303 self.symbolic_deref(val, *kind, line)
8304 }
8305 ExprKind::ArrowDeref { expr, index, kind } => {
8306 match kind {
8307 DerefKind::Array => {
8308 let container = self.eval_arrow_array_base(expr, line)?;
8309 if let ExprKind::List(indices) = &index.kind {
8310 let mut out = Vec::with_capacity(indices.len());
8311 for ix in indices {
8312 let idx = self.eval_expr(ix)?.to_int();
8313 out.push(self.read_arrow_array_element(
8314 container.clone(),
8315 idx,
8316 line,
8317 )?);
8318 }
8319 let arr = PerlValue::array(out);
8320 if ctx != WantarrayCtx::List {
8321 let v = arr.to_list();
8322 return Ok(v.last().cloned().unwrap_or(PerlValue::UNDEF));
8323 }
8324 return Ok(arr);
8325 }
8326 let idx = self.eval_expr(index)?.to_int();
8327 self.read_arrow_array_element(container, idx, line)
8328 }
8329 DerefKind::Hash => {
8330 let val = self.eval_arrow_hash_base(expr, line)?;
8331 let key = self.eval_expr(index)?.to_string();
8332 self.read_arrow_hash_element(val, key.as_str(), line)
8333 }
8334 DerefKind::Call => {
8335 let val = self.eval_expr(expr)?;
8337 if let ExprKind::List(ref arg_exprs) = index.kind {
8338 let mut args = Vec::new();
8339 for a in arg_exprs {
8340 args.push(self.eval_expr(a)?);
8341 }
8342 if let Some(sub) = val.as_code_ref() {
8343 return self.call_sub(&sub, args, ctx, line);
8344 }
8345 Err(PerlError::runtime("Not a code reference", line).into())
8346 } else {
8347 Err(PerlError::runtime("Invalid call deref", line).into())
8348 }
8349 }
8350 }
8351 }
8352
8353 ExprKind::BinOp { left, op, right } => {
8355 match op {
8357 BinOp::BindMatch => {
8358 let lv = self.eval_expr(left)?;
8359 let rv = self.eval_expr(right)?;
8360 let s = lv.to_string();
8361 let pat = rv.to_string();
8362 return self.regex_match_execute(s, &pat, "", false, "_", line);
8363 }
8364 BinOp::BindNotMatch => {
8365 let lv = self.eval_expr(left)?;
8366 let rv = self.eval_expr(right)?;
8367 let s = lv.to_string();
8368 let pat = rv.to_string();
8369 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
8370 return Ok(PerlValue::integer(if m.is_true() { 0 } else { 1 }));
8371 }
8372 BinOp::LogAnd | BinOp::LogAndWord => {
8373 match &left.kind {
8374 ExprKind::Regex(_, _) => {
8375 if !self.eval_boolean_rvalue_condition(left)? {
8376 return Ok(PerlValue::string(String::new()));
8377 }
8378 }
8379 _ => {
8380 let lv = self.eval_expr(left)?;
8381 if !lv.is_true() {
8382 return Ok(lv);
8383 }
8384 }
8385 }
8386 return match &right.kind {
8387 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8388 if self.eval_boolean_rvalue_condition(right)? {
8389 1
8390 } else {
8391 0
8392 },
8393 )),
8394 _ => self.eval_expr(right),
8395 };
8396 }
8397 BinOp::LogOr | BinOp::LogOrWord => {
8398 match &left.kind {
8399 ExprKind::Regex(_, _) => {
8400 if self.eval_boolean_rvalue_condition(left)? {
8401 return Ok(PerlValue::integer(1));
8402 }
8403 }
8404 _ => {
8405 let lv = self.eval_expr(left)?;
8406 if lv.is_true() {
8407 return Ok(lv);
8408 }
8409 }
8410 }
8411 return match &right.kind {
8412 ExprKind::Regex(_, _) => Ok(PerlValue::integer(
8413 if self.eval_boolean_rvalue_condition(right)? {
8414 1
8415 } else {
8416 0
8417 },
8418 )),
8419 _ => self.eval_expr(right),
8420 };
8421 }
8422 BinOp::DefinedOr => {
8423 let lv = self.eval_expr(left)?;
8424 if !lv.is_undef() {
8425 return Ok(lv);
8426 }
8427 return self.eval_expr(right);
8428 }
8429 _ => {}
8430 }
8431 let lv = self.eval_expr(left)?;
8432 let rv = self.eval_expr(right)?;
8433 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
8434 return r;
8435 }
8436 self.eval_binop(*op, &lv, &rv, line)
8437 }
8438
8439 ExprKind::UnaryOp { op, expr } => match op {
8441 UnaryOp::PreIncrement => {
8442 if let ExprKind::ScalarVar(name) = &expr.kind {
8443 self.check_strict_scalar_var(name, line)?;
8444 let n = self.english_scalar_name(name);
8445 return Ok(self
8446 .scope
8447 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() + 1)));
8448 }
8449 if let ExprKind::Deref { kind, .. } = &expr.kind {
8450 if matches!(kind, Sigil::Array | Sigil::Hash) {
8451 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8452 *kind, true, true, line,
8453 ));
8454 }
8455 }
8456 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8457 let href = self.eval_expr(container)?;
8458 let mut key_vals = Vec::with_capacity(keys.len());
8459 for key_expr in keys {
8460 key_vals.push(self.eval_expr(key_expr)?);
8461 }
8462 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
8463 }
8464 if let ExprKind::ArrowDeref {
8465 expr: arr_expr,
8466 index,
8467 kind: DerefKind::Array,
8468 } = &expr.kind
8469 {
8470 if let ExprKind::List(indices) = &index.kind {
8471 let container = self.eval_arrow_array_base(arr_expr, line)?;
8472 let mut idxs = Vec::with_capacity(indices.len());
8473 for ix in indices {
8474 idxs.push(self.eval_expr(ix)?.to_int());
8475 }
8476 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
8477 }
8478 }
8479 let val = self.eval_expr(expr)?;
8480 let new_val = PerlValue::integer(val.to_int() + 1);
8481 self.assign_value(expr, new_val.clone())?;
8482 Ok(new_val)
8483 }
8484 UnaryOp::PreDecrement => {
8485 if let ExprKind::ScalarVar(name) = &expr.kind {
8486 self.check_strict_scalar_var(name, line)?;
8487 let n = self.english_scalar_name(name);
8488 return Ok(self
8489 .scope
8490 .atomic_mutate(n, |v| PerlValue::integer(v.to_int() - 1)));
8491 }
8492 if let ExprKind::Deref { kind, .. } = &expr.kind {
8493 if matches!(kind, Sigil::Array | Sigil::Hash) {
8494 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8495 *kind, true, false, line,
8496 ));
8497 }
8498 }
8499 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8500 let href = self.eval_expr(container)?;
8501 let mut key_vals = Vec::with_capacity(keys.len());
8502 for key_expr in keys {
8503 key_vals.push(self.eval_expr(key_expr)?);
8504 }
8505 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
8506 }
8507 if let ExprKind::ArrowDeref {
8508 expr: arr_expr,
8509 index,
8510 kind: DerefKind::Array,
8511 } = &expr.kind
8512 {
8513 if let ExprKind::List(indices) = &index.kind {
8514 let container = self.eval_arrow_array_base(arr_expr, line)?;
8515 let mut idxs = Vec::with_capacity(indices.len());
8516 for ix in indices {
8517 idxs.push(self.eval_expr(ix)?.to_int());
8518 }
8519 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
8520 }
8521 }
8522 let val = self.eval_expr(expr)?;
8523 let new_val = PerlValue::integer(val.to_int() - 1);
8524 self.assign_value(expr, new_val.clone())?;
8525 Ok(new_val)
8526 }
8527 _ => {
8528 match op {
8529 UnaryOp::LogNot | UnaryOp::LogNotWord => {
8530 if let ExprKind::Regex(pattern, flags) = &expr.kind {
8531 let topic = self.scope.get_scalar("_");
8532 let rl = expr.line;
8533 let s = topic.to_string();
8534 let v =
8535 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
8536 return Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }));
8537 }
8538 }
8539 _ => {}
8540 }
8541 let val = self.eval_expr(expr)?;
8542 match op {
8543 UnaryOp::Negate => {
8544 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
8545 return r;
8546 }
8547 if let Some(n) = val.as_integer() {
8548 Ok(PerlValue::integer(-n))
8549 } else {
8550 Ok(PerlValue::float(-val.to_number()))
8551 }
8552 }
8553 UnaryOp::LogNot => {
8554 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8555 let pv = r?;
8556 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8557 }
8558 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8559 }
8560 UnaryOp::BitNot => Ok(PerlValue::integer(!val.to_int())),
8561 UnaryOp::LogNotWord => {
8562 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
8563 let pv = r?;
8564 return Ok(PerlValue::integer(if pv.is_true() { 0 } else { 1 }));
8565 }
8566 Ok(PerlValue::integer(if val.is_true() { 0 } else { 1 }))
8567 }
8568 UnaryOp::Ref => {
8569 if let ExprKind::ScalarVar(name) = &expr.kind {
8570 return Ok(PerlValue::scalar_binding_ref(name.clone()));
8571 }
8572 Ok(PerlValue::scalar_ref(Arc::new(RwLock::new(val))))
8573 }
8574 _ => unreachable!(),
8575 }
8576 }
8577 },
8578
8579 ExprKind::PostfixOp { expr, op } => {
8580 if let ExprKind::ScalarVar(name) = &expr.kind {
8583 self.check_strict_scalar_var(name, line)?;
8584 let n = self.english_scalar_name(name);
8585 let f: fn(&PerlValue) -> PerlValue = match op {
8586 PostfixOp::Increment => |v| PerlValue::integer(v.to_int() + 1),
8587 PostfixOp::Decrement => |v| PerlValue::integer(v.to_int() - 1),
8588 };
8589 return Ok(self.scope.atomic_mutate_post(n, f));
8590 }
8591 if let ExprKind::Deref { kind, .. } = &expr.kind {
8592 if matches!(kind, Sigil::Array | Sigil::Hash) {
8593 let is_inc = matches!(op, PostfixOp::Increment);
8594 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
8595 *kind, false, is_inc, line,
8596 ));
8597 }
8598 }
8599 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
8600 let href = self.eval_expr(container)?;
8601 let mut key_vals = Vec::with_capacity(keys.len());
8602 for key_expr in keys {
8603 key_vals.push(self.eval_expr(key_expr)?);
8604 }
8605 let kind_byte = match op {
8606 PostfixOp::Increment => 2u8,
8607 PostfixOp::Decrement => 3u8,
8608 };
8609 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
8610 }
8611 if let ExprKind::ArrowDeref {
8612 expr: arr_expr,
8613 index,
8614 kind: DerefKind::Array,
8615 } = &expr.kind
8616 {
8617 if let ExprKind::List(indices) = &index.kind {
8618 let container = self.eval_arrow_array_base(arr_expr, line)?;
8619 let mut idxs = Vec::with_capacity(indices.len());
8620 for ix in indices {
8621 idxs.push(self.eval_expr(ix)?.to_int());
8622 }
8623 let kind_byte = match op {
8624 PostfixOp::Increment => 2u8,
8625 PostfixOp::Decrement => 3u8,
8626 };
8627 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
8628 }
8629 }
8630 let val = self.eval_expr(expr)?;
8631 let old = val.clone();
8632 let new_val = match op {
8633 PostfixOp::Increment => PerlValue::integer(val.to_int() + 1),
8634 PostfixOp::Decrement => PerlValue::integer(val.to_int() - 1),
8635 };
8636 self.assign_value(expr, new_val)?;
8637 Ok(old)
8638 }
8639
8640 ExprKind::Assign { target, value } => {
8642 if let ExprKind::Typeglob(lhs) = &target.kind {
8643 if let ExprKind::Typeglob(rhs) = &value.kind {
8644 self.copy_typeglob_slots(lhs, rhs, line)?;
8645 return self.eval_expr(value);
8646 }
8647 }
8648 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
8649 self.assign_value(target, val.clone())?;
8650 Ok(val)
8651 }
8652 ExprKind::CompoundAssign { target, op, value } => {
8653 if let ExprKind::ScalarVar(name) = &target.kind {
8656 self.check_strict_scalar_var(name, line)?;
8657 let n = self.english_scalar_name(name);
8658 let op = *op;
8659 let rhs = match op {
8660 BinOp::LogOr => {
8661 let old = self.scope.get_scalar(n);
8662 if old.is_true() {
8663 return Ok(old);
8664 }
8665 self.eval_expr(value)?
8666 }
8667 BinOp::DefinedOr => {
8668 let old = self.scope.get_scalar(n);
8669 if !old.is_undef() {
8670 return Ok(old);
8671 }
8672 self.eval_expr(value)?
8673 }
8674 BinOp::LogAnd => {
8675 let old = self.scope.get_scalar(n);
8676 if !old.is_true() {
8677 return Ok(old);
8678 }
8679 self.eval_expr(value)?
8680 }
8681 _ => self.eval_expr(value)?,
8682 };
8683 return Ok(self.scalar_compound_assign_scalar_target(n, op, rhs)?);
8684 }
8685 let rhs = self.eval_expr(value)?;
8686 if let ExprKind::HashElement { hash, key } = &target.kind {
8688 self.check_strict_hash_var(hash, line)?;
8689 let k = self.eval_expr(key)?.to_string();
8690 let op = *op;
8691 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
8692 BinOp::Add => {
8693 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8694 PerlValue::integer(a.wrapping_add(b))
8695 } else {
8696 PerlValue::float(old.to_number() + rhs.to_number())
8697 }
8698 }
8699 BinOp::Sub => {
8700 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8701 PerlValue::integer(a.wrapping_sub(b))
8702 } else {
8703 PerlValue::float(old.to_number() - rhs.to_number())
8704 }
8705 }
8706 BinOp::Concat => {
8707 let mut s = old.to_string();
8708 rhs.append_to(&mut s);
8709 PerlValue::string(s)
8710 }
8711 _ => PerlValue::float(old.to_number() + rhs.to_number()),
8712 })?);
8713 }
8714 if let ExprKind::ArrayElement { array, index } = &target.kind {
8716 self.check_strict_array_var(array, line)?;
8717 let idx = self.eval_expr(index)?.to_int();
8718 let op = *op;
8719 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
8720 BinOp::Add => {
8721 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8722 PerlValue::integer(a.wrapping_add(b))
8723 } else {
8724 PerlValue::float(old.to_number() + rhs.to_number())
8725 }
8726 }
8727 _ => PerlValue::float(old.to_number() + rhs.to_number()),
8728 })?);
8729 }
8730 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
8731 let href = self.eval_expr(container)?;
8732 let mut key_vals = Vec::with_capacity(keys.len());
8733 for key_expr in keys {
8734 key_vals.push(self.eval_expr(key_expr)?);
8735 }
8736 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
8737 }
8738 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
8739 if let ExprKind::Deref {
8740 expr: inner,
8741 kind: Sigil::Array,
8742 } = &source.kind
8743 {
8744 let container = self.eval_arrow_array_base(inner, line)?;
8745 let idxs = self.flatten_array_slice_index_specs(indices)?;
8746 return self
8747 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
8748 }
8749 }
8750 if let ExprKind::ArrowDeref {
8751 expr: arr_expr,
8752 index,
8753 kind: DerefKind::Array,
8754 } = &target.kind
8755 {
8756 if let ExprKind::List(indices) = &index.kind {
8757 let container = self.eval_arrow_array_base(arr_expr, line)?;
8758 let mut idxs = Vec::with_capacity(indices.len());
8759 for ix in indices {
8760 idxs.push(self.eval_expr(ix)?.to_int());
8761 }
8762 return self
8763 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
8764 }
8765 }
8766 let old = self.eval_expr(target)?;
8767 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
8768 self.assign_value(target, new_val.clone())?;
8769 Ok(new_val)
8770 }
8771
8772 ExprKind::Ternary {
8774 condition,
8775 then_expr,
8776 else_expr,
8777 } => {
8778 if self.eval_boolean_rvalue_condition(condition)? {
8779 self.eval_expr(then_expr)
8780 } else {
8781 self.eval_expr(else_expr)
8782 }
8783 }
8784
8785 ExprKind::Range {
8787 from,
8788 to,
8789 exclusive,
8790 } => {
8791 if ctx == WantarrayCtx::List {
8792 let f = self.eval_expr(from)?;
8793 let t = self.eval_expr(to)?;
8794 let list = perl_list_range_expand(f, t);
8795 Ok(PerlValue::array(list))
8796 } else {
8797 let key = std::ptr::from_ref(expr) as usize;
8798 match (&from.kind, &to.kind) {
8799 (
8800 ExprKind::Regex(left_pat, left_flags),
8801 ExprKind::Regex(right_pat, right_flags),
8802 ) => {
8803 let dot = self.scalar_flipflop_dot_line();
8804 let subject = self.scope.get_scalar("_").to_string();
8805 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
8806 |e| match e {
8807 FlowOrError::Error(err) => err,
8808 FlowOrError::Flow(_) => PerlError::runtime(
8809 "unexpected flow in regex flip-flop",
8810 line,
8811 ),
8812 },
8813 )?;
8814 let right_re = self
8815 .compile_regex(right_pat, right_flags, line)
8816 .map_err(|e| match e {
8817 FlowOrError::Error(err) => err,
8818 FlowOrError::Flow(_) => PerlError::runtime(
8819 "unexpected flow in regex flip-flop",
8820 line,
8821 ),
8822 })?;
8823 let left_m = left_re.is_match(&subject);
8824 let right_m = right_re.is_match(&subject);
8825 let st = self.flip_flop_tree.entry(key).or_default();
8826 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
8827 &mut st.active,
8828 &mut st.exclusive_left_line,
8829 *exclusive,
8830 dot,
8831 left_m,
8832 right_m,
8833 )))
8834 }
8835 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
8836 let dot = self.scalar_flipflop_dot_line();
8837 let subject = self.scope.get_scalar("_").to_string();
8838 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
8839 |e| match e {
8840 FlowOrError::Error(err) => err,
8841 FlowOrError::Flow(_) => PerlError::runtime(
8842 "unexpected flow in regex/eof flip-flop",
8843 line,
8844 ),
8845 },
8846 )?;
8847 let left_m = left_re.is_match(&subject);
8848 let right_m = self.eof_without_arg_is_true();
8849 let st = self.flip_flop_tree.entry(key).or_default();
8850 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
8851 &mut st.active,
8852 &mut st.exclusive_left_line,
8853 *exclusive,
8854 dot,
8855 left_m,
8856 right_m,
8857 )))
8858 }
8859 (
8860 ExprKind::Regex(left_pat, left_flags),
8861 ExprKind::Integer(_) | ExprKind::Float(_),
8862 ) => {
8863 let dot = self.scalar_flipflop_dot_line();
8864 let right = self.eval_expr(to)?.to_int();
8865 let subject = self.scope.get_scalar("_").to_string();
8866 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
8867 |e| match e {
8868 FlowOrError::Error(err) => err,
8869 FlowOrError::Flow(_) => PerlError::runtime(
8870 "unexpected flow in regex flip-flop",
8871 line,
8872 ),
8873 },
8874 )?;
8875 let left_m = left_re.is_match(&subject);
8876 let right_m = dot == right;
8877 let st = self.flip_flop_tree.entry(key).or_default();
8878 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
8879 &mut st.active,
8880 &mut st.exclusive_left_line,
8881 *exclusive,
8882 dot,
8883 left_m,
8884 right_m,
8885 )))
8886 }
8887 (ExprKind::Regex(left_pat, left_flags), _) => {
8888 if let ExprKind::Eof(Some(_)) = &to.kind {
8889 return Err(FlowOrError::Error(PerlError::runtime(
8890 "regex flip-flop with eof(HANDLE) is not supported",
8891 line,
8892 )));
8893 }
8894 let dot = self.scalar_flipflop_dot_line();
8895 let subject = self.scope.get_scalar("_").to_string();
8896 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
8897 |e| match e {
8898 FlowOrError::Error(err) => err,
8899 FlowOrError::Flow(_) => PerlError::runtime(
8900 "unexpected flow in regex flip-flop",
8901 line,
8902 ),
8903 },
8904 )?;
8905 let left_m = left_re.is_match(&subject);
8906 let right_m = self.eval_boolean_rvalue_condition(to)?;
8907 let st = self.flip_flop_tree.entry(key).or_default();
8908 Ok(PerlValue::integer(Self::regex_flip_flop_transition(
8909 &mut st.active,
8910 &mut st.exclusive_left_line,
8911 *exclusive,
8912 dot,
8913 left_m,
8914 right_m,
8915 )))
8916 }
8917 _ => {
8918 let left = self.eval_expr(from)?.to_int();
8919 let right = self.eval_expr(to)?.to_int();
8920 let dot = self.scalar_flipflop_dot_line();
8921 let st = self.flip_flop_tree.entry(key).or_default();
8922 if !st.active {
8923 if dot == left {
8924 st.active = true;
8925 if *exclusive {
8926 st.exclusive_left_line = Some(dot);
8927 } else {
8928 st.exclusive_left_line = None;
8929 if dot == right {
8930 st.active = false;
8931 }
8932 }
8933 return Ok(PerlValue::integer(1));
8934 }
8935 return Ok(PerlValue::integer(0));
8936 }
8937 if let Some(ll) = st.exclusive_left_line {
8938 if dot == right && dot > ll {
8939 st.active = false;
8940 st.exclusive_left_line = None;
8941 }
8942 } else if dot == right {
8943 st.active = false;
8944 }
8945 Ok(PerlValue::integer(1))
8946 }
8947 }
8948 }
8949 }
8950
8951 ExprKind::Repeat { expr, count } => {
8953 let val = self.eval_expr(expr)?;
8954 let n = self.eval_expr(count)?.to_int().max(0) as usize;
8955 if let Some(s) = val.as_str() {
8956 Ok(PerlValue::string(s.repeat(n)))
8957 } else if let Some(a) = val.as_array_vec() {
8958 let mut result = Vec::with_capacity(a.len() * n);
8959 for _ in 0..n {
8960 result.extend(a.iter().cloned());
8961 }
8962 Ok(PerlValue::array(result))
8963 } else {
8964 Ok(PerlValue::string(val.to_string().repeat(n)))
8965 }
8966 }
8967
8968 ExprKind::MyExpr { keyword, decls } => {
8973 let stmt_kind = match keyword.as_str() {
8976 "my" => StmtKind::My(decls.clone()),
8977 "our" => StmtKind::Our(decls.clone()),
8978 "state" => StmtKind::State(decls.clone()),
8979 "local" => StmtKind::Local(decls.clone()),
8980 _ => StmtKind::My(decls.clone()),
8981 };
8982 let stmt = Statement {
8983 label: None,
8984 kind: stmt_kind,
8985 line,
8986 };
8987 self.exec_statement(&stmt)?;
8988 let first = decls.first().ok_or_else(|| {
8992 FlowOrError::Error(PerlError::runtime("MyExpr: empty decl list", line))
8993 })?;
8994 Ok(match first.sigil {
8995 Sigil::Scalar => self.scope.get_scalar(&first.name),
8996 Sigil::Array => PerlValue::array(self.scope.get_array(&first.name)),
8997 Sigil::Hash => {
8998 let h = self.scope.get_hash(&first.name);
8999 let mut flat: Vec<PerlValue> = Vec::with_capacity(h.len() * 2);
9000 for (k, v) in h {
9001 flat.push(PerlValue::string(k));
9002 flat.push(v);
9003 }
9004 PerlValue::array(flat)
9005 }
9006 Sigil::Typeglob => PerlValue::UNDEF,
9007 })
9008 }
9009
9010 ExprKind::FuncCall { name, args } => {
9012 if matches!(name.as_str(), "read" | "CORE::read") && args.len() >= 3 {
9014 let fh_val = self.eval_expr(&args[0])?;
9015 let fh = fh_val
9016 .as_io_handle_name()
9017 .unwrap_or_else(|| fh_val.to_string());
9018 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
9019 let offset = if args.len() > 3 {
9020 self.eval_expr(&args[3])?.to_int().max(0) as usize
9021 } else {
9022 0
9023 };
9024 let var_name = match &args[1].kind {
9026 ExprKind::ScalarVar(n) => n.clone(),
9027 _ => self.eval_expr(&args[1])?.to_string(),
9028 };
9029 let mut buf = vec![0u8; len];
9030 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
9031 slot.lock().read(&mut buf).unwrap_or(0)
9032 } else if fh == "STDIN" {
9033 std::io::stdin().read(&mut buf).unwrap_or(0)
9034 } else {
9035 return Err(PerlError::runtime(
9036 format!("read: unopened handle {}", fh),
9037 line,
9038 )
9039 .into());
9040 };
9041 buf.truncate(n);
9042 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
9043 if offset > 0 {
9044 let mut existing = self.scope.get_scalar(&var_name).to_string();
9045 while existing.len() < offset {
9046 existing.push('\0');
9047 }
9048 existing.push_str(&read_str);
9049 let _ = self
9050 .scope
9051 .set_scalar(&var_name, PerlValue::string(existing));
9052 } else {
9053 let _ = self
9054 .scope
9055 .set_scalar(&var_name, PerlValue::string(read_str));
9056 }
9057 return Ok(PerlValue::integer(n as i64));
9058 }
9059 if matches!(name.as_str(), "group_by" | "chunk_by") {
9060 if args.len() != 2 {
9061 return Err(PerlError::runtime(
9062 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
9063 line,
9064 )
9065 .into());
9066 }
9067 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
9068 }
9069 if matches!(name.as_str(), "puniq" | "pfirst" | "pany") {
9070 let mut arg_vals = Vec::with_capacity(args.len());
9071 for a in args {
9072 arg_vals.push(self.eval_expr(a)?);
9073 }
9074 let saved_wa = self.wantarray_kind;
9075 self.wantarray_kind = ctx;
9076 let r = self.eval_par_list_call(name.as_str(), &arg_vals, ctx, line);
9077 self.wantarray_kind = saved_wa;
9078 return r.map_err(Into::into);
9079 }
9080 let arg_vals = if matches!(name.as_str(), "any" | "all" | "none" | "first")
9081 || matches!(
9082 name.as_str(),
9083 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9084 )
9085 || matches!(
9086 name.as_str(),
9087 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
9088 ) {
9089 if args.len() != 2 {
9090 return Err(PerlError::runtime(
9091 format!("{}: expected BLOCK, LIST", name),
9092 line,
9093 )
9094 .into());
9095 }
9096 let cr = self.eval_expr(&args[0])?;
9097 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
9098 let mut v = vec![cr];
9099 v.extend(list_src.to_list());
9100 v
9101 } else if matches!(
9102 name.as_str(),
9103 "zip" | "List::Util::zip" | "List::Util::zip_longest"
9104 ) {
9105 let mut v = Vec::with_capacity(args.len());
9106 for a in args {
9107 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9108 }
9109 v
9110 } else if matches!(
9111 name.as_str(),
9112 "uniq"
9113 | "distinct"
9114 | "uniqstr"
9115 | "uniqint"
9116 | "uniqnum"
9117 | "flatten"
9118 | "set"
9119 | "list_count"
9120 | "list_size"
9121 | "count"
9122 | "size"
9123 | "cnt"
9124 | "with_index"
9125 | "List::Util::uniq"
9126 | "List::Util::uniqstr"
9127 | "List::Util::uniqint"
9128 | "List::Util::uniqnum"
9129 | "shuffle"
9130 | "List::Util::shuffle"
9131 | "sum"
9132 | "sum0"
9133 | "product"
9134 | "min"
9135 | "max"
9136 | "minstr"
9137 | "maxstr"
9138 | "mean"
9139 | "median"
9140 | "mode"
9141 | "stddev"
9142 | "variance"
9143 | "List::Util::sum"
9144 | "List::Util::sum0"
9145 | "List::Util::product"
9146 | "List::Util::min"
9147 | "List::Util::max"
9148 | "List::Util::minstr"
9149 | "List::Util::maxstr"
9150 | "List::Util::mean"
9151 | "List::Util::median"
9152 | "List::Util::mode"
9153 | "List::Util::stddev"
9154 | "List::Util::variance"
9155 | "pairs"
9156 | "unpairs"
9157 | "pairkeys"
9158 | "pairvalues"
9159 | "List::Util::pairs"
9160 | "List::Util::unpairs"
9161 | "List::Util::pairkeys"
9162 | "List::Util::pairvalues"
9163 ) {
9164 let mut list_out = Vec::new();
9168 if args.len() == 1 {
9169 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
9170 } else {
9171 for a in args {
9172 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
9173 }
9174 }
9175 list_out
9176 } else if matches!(
9177 name.as_str(),
9178 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail"
9179 ) {
9180 if args.is_empty() {
9181 return Err(PerlError::runtime(
9182 "take/head/tail/drop/List::Util::head|tail: need LIST..., N or unary N",
9183 line,
9184 )
9185 .into());
9186 }
9187 let mut arg_vals = Vec::with_capacity(args.len());
9188 if args.len() == 1 {
9189 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9191 } else {
9192 for a in &args[..args.len() - 1] {
9193 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
9194 }
9195 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
9196 }
9197 arg_vals
9198 } else if matches!(
9199 name.as_str(),
9200 "chunked" | "List::Util::chunked" | "windowed" | "List::Util::windowed"
9201 ) {
9202 let mut list_out = Vec::new();
9203 match args.len() {
9204 0 => {
9205 return Err(PerlError::runtime(
9206 format!("{name}: expected (LIST, N) or unary N after |>"),
9207 line,
9208 )
9209 .into());
9210 }
9211 1 => {
9212 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
9214 }
9215 2 => {
9216 list_out.extend(
9217 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
9218 );
9219 list_out.push(self.eval_expr(&args[1])?);
9220 }
9221 _ => {
9222 return Err(PerlError::runtime(
9223 format!(
9224 "{name}: expected exactly (LIST, N); use one list expression then size"
9225 ),
9226 line,
9227 )
9228 .into());
9229 }
9230 }
9231 list_out
9232 } else {
9233 let mut arg_vals = Vec::with_capacity(args.len());
9236 for a in args {
9237 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
9238 if let Some(items) = v.as_array_vec() {
9239 arg_vals.extend(items);
9240 } else {
9241 arg_vals.push(v);
9242 }
9243 }
9244 arg_vals
9245 };
9246 let saved_wa = self.wantarray_kind;
9248 self.wantarray_kind = ctx;
9249 if let Some(sub) = self.resolve_sub_by_name(name) {
9251 self.wantarray_kind = saved_wa;
9252 let args = self.with_topic_default_args(arg_vals);
9253 return self.call_sub(&sub, args, ctx, line);
9254 }
9255 if matches!(
9256 name.as_str(),
9257 "take_while" | "drop_while" | "skip_while" | "reject" | "tap" | "peek"
9258 ) {
9259 let r = self.list_higher_order_block_builtin(name.as_str(), &arg_vals, line);
9260 self.wantarray_kind = saved_wa;
9261 return r.map_err(Into::into);
9262 }
9263 if let Some(r) = crate::builtins::try_builtin(self, name.as_str(), &arg_vals, line)
9264 {
9265 self.wantarray_kind = saved_wa;
9266 return r.map_err(Into::into);
9267 }
9268 self.wantarray_kind = saved_wa;
9269 self.call_named_sub(name, arg_vals, line, ctx)
9270 }
9271 ExprKind::IndirectCall {
9272 target,
9273 args,
9274 ampersand: _,
9275 pass_caller_arglist,
9276 } => {
9277 let tval = self.eval_expr(target)?;
9278 let arg_vals = if *pass_caller_arglist {
9279 self.scope.get_array("_")
9280 } else {
9281 let mut v = Vec::with_capacity(args.len());
9282 for a in args {
9283 v.push(self.eval_expr(a)?);
9284 }
9285 v
9286 };
9287 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
9288 }
9289 ExprKind::MethodCall {
9290 object,
9291 method,
9292 args,
9293 super_call,
9294 } => {
9295 let obj = self.eval_expr(object)?;
9296 let mut arg_vals = vec![obj.clone()];
9297 for a in args {
9298 arg_vals.push(self.eval_expr(a)?);
9299 }
9300 if let Some(r) =
9301 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
9302 {
9303 return r.map_err(Into::into);
9304 }
9305 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
9306 return r.map_err(Into::into);
9307 }
9308 let class = if let Some(b) = obj.as_blessed_ref() {
9310 b.class.clone()
9311 } else if let Some(s) = obj.as_str() {
9312 s } else {
9314 return Err(PerlError::runtime("Can't call method on non-object", line).into());
9315 };
9316 if method == "VERSION" && !*super_call {
9317 if let Some(ver) = self.package_version_scalar(class.as_str())? {
9318 return Ok(ver);
9319 }
9320 }
9321 if !*super_call {
9323 match method.as_str() {
9324 "isa" => {
9325 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9326 let mro = self.mro_linearize(&class);
9327 let result = mro.iter().any(|c| c == &target);
9328 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9329 }
9330 "can" => {
9331 let target_method =
9332 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9333 let found = self
9334 .resolve_method_full_name(&class, &target_method, false)
9335 .and_then(|fq| self.subs.get(&fq))
9336 .is_some();
9337 if found {
9338 return Ok(PerlValue::code_ref(Arc::new(PerlSub {
9339 name: target_method,
9340 params: vec![],
9341 body: vec![],
9342 closure_env: None,
9343 prototype: None,
9344 fib_like: None,
9345 })));
9346 } else {
9347 return Ok(PerlValue::UNDEF);
9348 }
9349 }
9350 "DOES" => {
9351 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
9352 let mro = self.mro_linearize(&class);
9353 let result = mro.iter().any(|c| c == &target);
9354 return Ok(PerlValue::integer(if result { 1 } else { 0 }));
9355 }
9356 _ => {}
9357 }
9358 }
9359 let full_name = self
9360 .resolve_method_full_name(&class, method, *super_call)
9361 .ok_or_else(|| {
9362 PerlError::runtime(
9363 format!(
9364 "Can't locate method \"{}\" for invocant \"{}\"",
9365 method, class
9366 ),
9367 line,
9368 )
9369 })?;
9370 if let Some(sub) = self.subs.get(&full_name).cloned() {
9371 self.call_sub(&sub, arg_vals, ctx, line)
9372 } else if method == "new" && !*super_call {
9373 self.builtin_new(&class, arg_vals, line)
9375 } else if let Some(r) =
9376 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
9377 {
9378 r
9379 } else {
9380 Err(PerlError::runtime(
9381 format!(
9382 "Can't locate method \"{}\" in package \"{}\"",
9383 method, class
9384 ),
9385 line,
9386 )
9387 .into())
9388 }
9389 }
9390
9391 ExprKind::Print { handle, args } => {
9393 self.exec_print(handle.as_deref(), args, false, line)
9394 }
9395 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
9396 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
9397 ExprKind::Die(args) => {
9398 if args.is_empty() {
9399 let current = self.scope.get_scalar("@");
9401 let msg = if current.is_undef() || current.to_string().is_empty() {
9402 let mut m = "Died".to_string();
9403 m.push_str(&self.die_warn_at_suffix(line));
9404 m.push('\n');
9405 m
9406 } else {
9407 current.to_string()
9408 };
9409 return Err(PerlError::die(msg, line).into());
9410 }
9411 if args.len() == 1 {
9413 let v = self.eval_expr(&args[0])?;
9414 if v.as_hash_ref().is_some()
9415 || v.as_blessed_ref().is_some()
9416 || v.as_array_ref().is_some()
9417 || v.as_code_ref().is_some()
9418 {
9419 let msg = v.to_string();
9420 return Err(PerlError::die_with_value(v, msg, line).into());
9421 }
9422 }
9423 let mut msg = String::new();
9424 for a in args {
9425 let v = self.eval_expr(a)?;
9426 msg.push_str(&v.to_string());
9427 }
9428 if msg.is_empty() {
9429 msg = "Died".to_string();
9430 }
9431 if !msg.ends_with('\n') {
9432 msg.push_str(&self.die_warn_at_suffix(line));
9433 msg.push('\n');
9434 }
9435 Err(PerlError::die(msg, line).into())
9436 }
9437 ExprKind::Warn(args) => {
9438 let mut msg = String::new();
9439 for a in args {
9440 let v = self.eval_expr(a)?;
9441 msg.push_str(&v.to_string());
9442 }
9443 if msg.is_empty() {
9444 msg = "Warning: something's wrong".to_string();
9445 }
9446 if !msg.ends_with('\n') {
9447 msg.push_str(&self.die_warn_at_suffix(line));
9448 msg.push('\n');
9449 }
9450 eprint!("{}", msg);
9451 Ok(PerlValue::integer(1))
9452 }
9453
9454 ExprKind::Match {
9456 expr,
9457 pattern,
9458 flags,
9459 scalar_g,
9460 delim: _,
9461 } => {
9462 let val = self.eval_expr(expr)?;
9463 if val.is_iterator() {
9464 let source = crate::map_stream::into_pull_iter(val);
9465 let re = self.compile_regex(pattern, flags, line)?;
9466 let global = flags.contains('g');
9467 if global {
9468 return Ok(PerlValue::iterator(std::sync::Arc::new(
9469 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
9470 )));
9471 } else {
9472 return Ok(PerlValue::iterator(std::sync::Arc::new(
9473 crate::map_stream::MatchStreamIterator::new(source, re),
9474 )));
9475 }
9476 }
9477 let s = val.to_string();
9478 let pos_key = match &expr.kind {
9479 ExprKind::ScalarVar(n) => n.as_str(),
9480 _ => "_",
9481 };
9482 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
9483 }
9484 ExprKind::Substitution {
9485 expr,
9486 pattern,
9487 replacement,
9488 flags,
9489 delim: _,
9490 } => {
9491 let val = self.eval_expr(expr)?;
9492 if val.is_iterator() {
9493 let source = crate::map_stream::into_pull_iter(val);
9494 let re = self.compile_regex(pattern, flags, line)?;
9495 let global = flags.contains('g');
9496 return Ok(PerlValue::iterator(std::sync::Arc::new(
9497 crate::map_stream::SubstStreamIterator::new(
9498 source,
9499 re,
9500 normalize_replacement_backrefs(replacement),
9501 global,
9502 ),
9503 )));
9504 }
9505 let s = val.to_string();
9506 self.regex_subst_execute(
9507 s,
9508 pattern,
9509 replacement.as_str(),
9510 flags.as_str(),
9511 expr,
9512 line,
9513 )
9514 }
9515 ExprKind::Transliterate {
9516 expr,
9517 from,
9518 to,
9519 flags,
9520 delim: _,
9521 } => {
9522 let val = self.eval_expr(expr)?;
9523 if val.is_iterator() {
9524 let source = crate::map_stream::into_pull_iter(val);
9525 return Ok(PerlValue::iterator(std::sync::Arc::new(
9526 crate::map_stream::TransliterateStreamIterator::new(
9527 source, from, to, flags,
9528 ),
9529 )));
9530 }
9531 let s = val.to_string();
9532 self.regex_transliterate_execute(
9533 s,
9534 from.as_str(),
9535 to.as_str(),
9536 flags.as_str(),
9537 expr,
9538 line,
9539 )
9540 }
9541
9542 ExprKind::MapExpr {
9544 block,
9545 list,
9546 flatten_array_refs,
9547 stream,
9548 } => {
9549 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9550 if *stream {
9551 let out =
9552 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
9553 if ctx == WantarrayCtx::List {
9554 return Ok(out);
9555 }
9556 return Ok(PerlValue::integer(out.to_list().len() as i64));
9557 }
9558 let items = list_val.to_list();
9559 if items.len() == 1 {
9560 if let Some(p) = items[0].as_pipeline() {
9561 if *flatten_array_refs {
9562 return Err(PerlError::runtime(
9563 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
9564 line,
9565 )
9566 .into());
9567 }
9568 let sub = self.anon_coderef_from_block(block);
9569 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
9570 return Ok(PerlValue::pipeline(Arc::clone(&p)));
9571 }
9572 }
9573 let mut result = Vec::new();
9578 for item in items {
9579 self.scope.set_topic(item);
9580 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
9581 result.extend(val.map_flatten_outputs(*flatten_array_refs));
9582 }
9583 if ctx == WantarrayCtx::List {
9584 Ok(PerlValue::array(result))
9585 } else {
9586 Ok(PerlValue::integer(result.len() as i64))
9587 }
9588 }
9589 ExprKind::ForEachExpr { block, list } => {
9590 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9591 if list_val.is_iterator() {
9593 let iter = list_val.into_iterator();
9594 let mut count = 0i64;
9595 while let Some(item) = iter.next_item() {
9596 count += 1;
9597 self.scope.set_topic(item);
9598 self.exec_block(block)?;
9599 }
9600 return Ok(PerlValue::integer(count));
9601 }
9602 let items = list_val.to_list();
9603 let count = items.len();
9604 for item in items {
9605 self.scope.set_topic(item);
9606 self.exec_block(block)?;
9607 }
9608 Ok(PerlValue::integer(count as i64))
9609 }
9610 ExprKind::MapExprComma {
9611 expr,
9612 list,
9613 flatten_array_refs,
9614 stream,
9615 } => {
9616 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9617 if *stream {
9618 let out =
9619 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
9620 if ctx == WantarrayCtx::List {
9621 return Ok(out);
9622 }
9623 return Ok(PerlValue::integer(out.to_list().len() as i64));
9624 }
9625 let items = list_val.to_list();
9626 let mut result = Vec::new();
9627 for item in items {
9628 self.scope.set_topic(item.clone());
9629 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
9630 result.extend(val.map_flatten_outputs(*flatten_array_refs));
9631 }
9632 if ctx == WantarrayCtx::List {
9633 Ok(PerlValue::array(result))
9634 } else {
9635 Ok(PerlValue::integer(result.len() as i64))
9636 }
9637 }
9638 ExprKind::GrepExpr {
9639 block,
9640 list,
9641 keyword,
9642 } => {
9643 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9644 if keyword.is_stream() {
9645 let out = self.filter_stream_block_output(list_val, block, line)?;
9646 if ctx == WantarrayCtx::List {
9647 return Ok(out);
9648 }
9649 return Ok(PerlValue::integer(out.to_list().len() as i64));
9650 }
9651 let items = list_val.to_list();
9652 if items.len() == 1 {
9653 if let Some(p) = items[0].as_pipeline() {
9654 let sub = self.anon_coderef_from_block(block);
9655 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
9656 return Ok(PerlValue::pipeline(Arc::clone(&p)));
9657 }
9658 }
9659 let mut result = Vec::new();
9660 for item in items {
9661 self.scope.set_topic(item.clone());
9662 let val = self.exec_block(block)?;
9663 let keep = if let Some(re) = val.as_regex() {
9666 re.is_match(&item.to_string())
9667 } else {
9668 val.is_true()
9669 };
9670 if keep {
9671 result.push(item);
9672 }
9673 }
9674 if ctx == WantarrayCtx::List {
9675 Ok(PerlValue::array(result))
9676 } else {
9677 Ok(PerlValue::integer(result.len() as i64))
9678 }
9679 }
9680 ExprKind::GrepExprComma {
9681 expr,
9682 list,
9683 keyword,
9684 } => {
9685 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9686 if keyword.is_stream() {
9687 let out = self.filter_stream_expr_output(list_val, expr, line)?;
9688 if ctx == WantarrayCtx::List {
9689 return Ok(out);
9690 }
9691 return Ok(PerlValue::integer(out.to_list().len() as i64));
9692 }
9693 let items = list_val.to_list();
9694 let mut result = Vec::new();
9695 for item in items {
9696 self.scope.set_topic(item.clone());
9697 let val = self.eval_expr(expr)?;
9698 let keep = if let Some(re) = val.as_regex() {
9699 re.is_match(&item.to_string())
9700 } else {
9701 val.is_true()
9702 };
9703 if keep {
9704 result.push(item);
9705 }
9706 }
9707 if ctx == WantarrayCtx::List {
9708 Ok(PerlValue::array(result))
9709 } else {
9710 Ok(PerlValue::integer(result.len() as i64))
9711 }
9712 }
9713 ExprKind::SortExpr { cmp, list } => {
9714 let list_val = self.eval_expr(list)?;
9715 let mut items = list_val.to_list();
9716 match cmp {
9717 Some(SortComparator::Code(code_expr)) => {
9718 let sub = self.eval_expr(code_expr)?;
9719 let Some(sub) = sub.as_code_ref() else {
9720 return Err(PerlError::runtime(
9721 "sort: comparator must be a code reference",
9722 line,
9723 )
9724 .into());
9725 };
9726 let sub = sub.clone();
9727 items.sort_by(|a, b| {
9728 let _ = self.scope.set_scalar("a", a.clone());
9729 let _ = self.scope.set_scalar("b", b.clone());
9730 let _ = self.scope.set_scalar("_0", a.clone());
9731 let _ = self.scope.set_scalar("_1", b.clone());
9732 match self.call_sub(&sub, vec![], ctx, line) {
9733 Ok(v) => {
9734 let n = v.to_int();
9735 if n < 0 {
9736 Ordering::Less
9737 } else if n > 0 {
9738 Ordering::Greater
9739 } else {
9740 Ordering::Equal
9741 }
9742 }
9743 Err(_) => Ordering::Equal,
9744 }
9745 });
9746 }
9747 Some(SortComparator::Block(cmp_block)) => {
9748 if let Some(mode) = detect_sort_block_fast(cmp_block) {
9749 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
9750 } else {
9751 let cmp_block = cmp_block.clone();
9752 items.sort_by(|a, b| {
9753 let _ = self.scope.set_scalar("a", a.clone());
9754 let _ = self.scope.set_scalar("b", b.clone());
9755 let _ = self.scope.set_scalar("_0", a.clone());
9756 let _ = self.scope.set_scalar("_1", b.clone());
9757 match self.exec_block(&cmp_block) {
9758 Ok(v) => {
9759 let n = v.to_int();
9760 if n < 0 {
9761 Ordering::Less
9762 } else if n > 0 {
9763 Ordering::Greater
9764 } else {
9765 Ordering::Equal
9766 }
9767 }
9768 Err(_) => Ordering::Equal,
9769 }
9770 });
9771 }
9772 }
9773 None => {
9774 items.sort_by_key(|a| a.to_string());
9775 }
9776 }
9777 Ok(PerlValue::array(items))
9778 }
9779 ExprKind::ScalarReverse(expr) => {
9780 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
9781 if val.is_iterator() {
9783 return Ok(PerlValue::iterator(Arc::new(
9784 crate::value::ScalarReverseIterator::new(val.into_iterator()),
9785 )));
9786 }
9787 let items = val.to_list();
9788 if items.len() <= 1 {
9789 let s = if items.is_empty() {
9790 String::new()
9791 } else {
9792 items[0].to_string()
9793 };
9794 Ok(PerlValue::string(s.chars().rev().collect()))
9795 } else {
9796 let mut items = items;
9797 items.reverse();
9798 Ok(PerlValue::array(items))
9799 }
9800 }
9801 ExprKind::ReverseExpr(list) => {
9802 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
9803 match ctx {
9804 WantarrayCtx::List => {
9805 let mut items = val.to_list();
9806 items.reverse();
9807 Ok(PerlValue::array(items))
9808 }
9809 _ => {
9810 let items = val.to_list();
9811 let s: String = items.iter().map(|v| v.to_string()).collect();
9812 Ok(PerlValue::string(s.chars().rev().collect()))
9813 }
9814 }
9815 }
9816
9817 ExprKind::ParLinesExpr {
9819 path,
9820 callback,
9821 progress,
9822 } => self.eval_par_lines_expr(
9823 path.as_ref(),
9824 callback.as_ref(),
9825 progress.as_deref(),
9826 line,
9827 ),
9828 ExprKind::ParWalkExpr {
9829 path,
9830 callback,
9831 progress,
9832 } => {
9833 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
9834 }
9835 ExprKind::PwatchExpr { path, callback } => {
9836 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
9837 }
9838 ExprKind::PMapExpr {
9839 block,
9840 list,
9841 progress,
9842 flat_outputs,
9843 on_cluster,
9844 } => {
9845 let show_progress = progress
9846 .as_ref()
9847 .map(|p| self.eval_expr(p))
9848 .transpose()?
9849 .map(|v| v.is_true())
9850 .unwrap_or(false);
9851 let list_val = self.eval_expr(list)?;
9852 if let Some(cluster_e) = on_cluster {
9853 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
9854 return self.eval_pmap_remote(
9855 cluster_val,
9856 list_val,
9857 show_progress,
9858 block,
9859 *flat_outputs,
9860 line,
9861 );
9862 }
9863 let items = list_val.to_list();
9864 let block = block.clone();
9865 let subs = self.subs.clone();
9866 let (scope_capture, atomic_arrays, atomic_hashes) =
9867 self.scope.capture_with_atomics();
9868 let pmap_progress = PmapProgress::new(show_progress, items.len());
9869
9870 if *flat_outputs {
9871 let mut indexed: Vec<(usize, Vec<PerlValue>)> = items
9872 .into_par_iter()
9873 .enumerate()
9874 .map(|(i, item)| {
9875 let mut local_interp = Interpreter::new();
9876 local_interp.subs = subs.clone();
9877 local_interp.scope.restore_capture(&scope_capture);
9878 local_interp
9879 .scope
9880 .restore_atomics(&atomic_arrays, &atomic_hashes);
9881 local_interp.enable_parallel_guard();
9882 local_interp.scope.set_topic(item);
9883 let val = match local_interp.exec_block(&block) {
9884 Ok(val) => val,
9885 Err(_) => PerlValue::UNDEF,
9886 };
9887 let chunk = val.map_flatten_outputs(true);
9888 pmap_progress.tick();
9889 (i, chunk)
9890 })
9891 .collect();
9892 pmap_progress.finish();
9893 indexed.sort_by_key(|(i, _)| *i);
9894 let results: Vec<PerlValue> =
9895 indexed.into_iter().flat_map(|(_, v)| v).collect();
9896 Ok(PerlValue::array(results))
9897 } else {
9898 let results: Vec<PerlValue> = items
9899 .into_par_iter()
9900 .map(|item| {
9901 let mut local_interp = Interpreter::new();
9902 local_interp.subs = subs.clone();
9903 local_interp.scope.restore_capture(&scope_capture);
9904 local_interp
9905 .scope
9906 .restore_atomics(&atomic_arrays, &atomic_hashes);
9907 local_interp.enable_parallel_guard();
9908 local_interp.scope.set_topic(item);
9909 let val = match local_interp.exec_block(&block) {
9910 Ok(val) => val,
9911 Err(_) => PerlValue::UNDEF,
9912 };
9913 pmap_progress.tick();
9914 val
9915 })
9916 .collect();
9917 pmap_progress.finish();
9918 Ok(PerlValue::array(results))
9919 }
9920 }
9921 ExprKind::PMapChunkedExpr {
9922 chunk_size,
9923 block,
9924 list,
9925 progress,
9926 } => {
9927 let show_progress = progress
9928 .as_ref()
9929 .map(|p| self.eval_expr(p))
9930 .transpose()?
9931 .map(|v| v.is_true())
9932 .unwrap_or(false);
9933 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
9934 let list_val = self.eval_expr(list)?;
9935 let items = list_val.to_list();
9936 let block = block.clone();
9937 let subs = self.subs.clone();
9938 let (scope_capture, atomic_arrays, atomic_hashes) =
9939 self.scope.capture_with_atomics();
9940
9941 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = items
9942 .chunks(chunk_n)
9943 .enumerate()
9944 .map(|(i, c)| (i, c.to_vec()))
9945 .collect();
9946
9947 let n_chunks = indexed_chunks.len();
9948 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
9949
9950 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
9951 .into_par_iter()
9952 .map(|(chunk_idx, chunk)| {
9953 let mut local_interp = Interpreter::new();
9954 local_interp.subs = subs.clone();
9955 local_interp.scope.restore_capture(&scope_capture);
9956 local_interp
9957 .scope
9958 .restore_atomics(&atomic_arrays, &atomic_hashes);
9959 local_interp.enable_parallel_guard();
9960 let mut out = Vec::with_capacity(chunk.len());
9961 for item in chunk {
9962 local_interp.scope.set_topic(item);
9963 match local_interp.exec_block(&block) {
9964 Ok(val) => out.push(val),
9965 Err(_) => out.push(PerlValue::UNDEF),
9966 }
9967 }
9968 pmap_progress.tick();
9969 (chunk_idx, out)
9970 })
9971 .collect();
9972
9973 pmap_progress.finish();
9974 chunk_results.sort_by_key(|(i, _)| *i);
9975 let results: Vec<PerlValue> =
9976 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
9977 Ok(PerlValue::array(results))
9978 }
9979 ExprKind::PGrepExpr {
9980 block,
9981 list,
9982 progress,
9983 } => {
9984 let show_progress = progress
9985 .as_ref()
9986 .map(|p| self.eval_expr(p))
9987 .transpose()?
9988 .map(|v| v.is_true())
9989 .unwrap_or(false);
9990 let list_val = self.eval_expr(list)?;
9991 let items = list_val.to_list();
9992 let block = block.clone();
9993 let subs = self.subs.clone();
9994 let (scope_capture, atomic_arrays, atomic_hashes) =
9995 self.scope.capture_with_atomics();
9996 let pmap_progress = PmapProgress::new(show_progress, items.len());
9997
9998 let results: Vec<PerlValue> = items
9999 .into_par_iter()
10000 .filter_map(|item| {
10001 let mut local_interp = Interpreter::new();
10002 local_interp.subs = subs.clone();
10003 local_interp.scope.restore_capture(&scope_capture);
10004 local_interp
10005 .scope
10006 .restore_atomics(&atomic_arrays, &atomic_hashes);
10007 local_interp.enable_parallel_guard();
10008 local_interp.scope.set_topic(item.clone());
10009 let keep = match local_interp.exec_block(&block) {
10010 Ok(val) => val.is_true(),
10011 Err(_) => false,
10012 };
10013 pmap_progress.tick();
10014 if keep {
10015 Some(item)
10016 } else {
10017 None
10018 }
10019 })
10020 .collect();
10021 pmap_progress.finish();
10022 Ok(PerlValue::array(results))
10023 }
10024 ExprKind::PForExpr {
10025 block,
10026 list,
10027 progress,
10028 } => {
10029 let show_progress = progress
10030 .as_ref()
10031 .map(|p| self.eval_expr(p))
10032 .transpose()?
10033 .map(|v| v.is_true())
10034 .unwrap_or(false);
10035 let list_val = self.eval_expr(list)?;
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
10042 let pmap_progress = PmapProgress::new(show_progress, items.len());
10043 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10044 items.into_par_iter().for_each(|item| {
10045 if first_err.lock().is_some() {
10046 return;
10047 }
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 match local_interp.exec_block(&block) {
10057 Ok(_) => {}
10058 Err(e) => {
10059 let stryke = match e {
10060 FlowOrError::Error(stryke) => stryke,
10061 FlowOrError::Flow(_) => PerlError::runtime(
10062 "return/last/next/redo not supported inside pfor block",
10063 line,
10064 ),
10065 };
10066 let mut g = first_err.lock();
10067 if g.is_none() {
10068 *g = Some(stryke);
10069 }
10070 }
10071 }
10072 pmap_progress.tick();
10073 });
10074 pmap_progress.finish();
10075 if let Some(e) = first_err.lock().take() {
10076 return Err(FlowOrError::Error(e));
10077 }
10078 Ok(PerlValue::UNDEF)
10079 }
10080 ExprKind::FanExpr {
10081 count,
10082 block,
10083 progress,
10084 capture,
10085 } => {
10086 let show_progress = progress
10087 .as_ref()
10088 .map(|p| self.eval_expr(p))
10089 .transpose()?
10090 .map(|v| v.is_true())
10091 .unwrap_or(false);
10092 let n = match count {
10093 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
10094 None => self.parallel_thread_count(),
10095 };
10096 let block = block.clone();
10097 let subs = self.subs.clone();
10098 let (scope_capture, atomic_arrays, atomic_hashes) =
10099 self.scope.capture_with_atomics();
10100
10101 let fan_progress = FanProgress::new(show_progress, n);
10102 if *capture {
10103 if n == 0 {
10104 return Ok(PerlValue::array(Vec::new()));
10105 }
10106 let pairs: Vec<(usize, ExecResult)> = (0..n)
10107 .into_par_iter()
10108 .map(|i| {
10109 fan_progress.start_worker(i);
10110 let mut local_interp = Interpreter::new();
10111 local_interp.subs = subs.clone();
10112 local_interp.suppress_stdout = show_progress;
10113 local_interp.scope.restore_capture(&scope_capture);
10114 local_interp
10115 .scope
10116 .restore_atomics(&atomic_arrays, &atomic_hashes);
10117 local_interp.enable_parallel_guard();
10118 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10119 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10120 let res = local_interp.exec_block(&block);
10121 crate::parallel_trace::fan_worker_set_index(None);
10122 fan_progress.finish_worker(i);
10123 (i, res)
10124 })
10125 .collect();
10126 fan_progress.finish();
10127 let mut pairs = pairs;
10128 pairs.sort_by_key(|(i, _)| *i);
10129 let mut out = Vec::with_capacity(n);
10130 for (_, r) in pairs {
10131 match r {
10132 Ok(v) => out.push(v),
10133 Err(e) => return Err(e),
10134 }
10135 }
10136 return Ok(PerlValue::array(out));
10137 }
10138 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
10139 (0..n).into_par_iter().for_each(|i| {
10140 if first_err.lock().is_some() {
10141 return;
10142 }
10143 fan_progress.start_worker(i);
10144 let mut local_interp = Interpreter::new();
10145 local_interp.subs = subs.clone();
10146 local_interp.suppress_stdout = show_progress;
10147 local_interp.scope.restore_capture(&scope_capture);
10148 local_interp
10149 .scope
10150 .restore_atomics(&atomic_arrays, &atomic_hashes);
10151 local_interp.enable_parallel_guard();
10152 local_interp.scope.set_topic(PerlValue::integer(i as i64));
10153 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
10154 match local_interp.exec_block(&block) {
10155 Ok(_) => {}
10156 Err(e) => {
10157 let stryke = match e {
10158 FlowOrError::Error(stryke) => stryke,
10159 FlowOrError::Flow(_) => PerlError::runtime(
10160 "return/last/next/redo not supported inside fan block",
10161 line,
10162 ),
10163 };
10164 let mut g = first_err.lock();
10165 if g.is_none() {
10166 *g = Some(stryke);
10167 }
10168 }
10169 }
10170 crate::parallel_trace::fan_worker_set_index(None);
10171 fan_progress.finish_worker(i);
10172 });
10173 fan_progress.finish();
10174 if let Some(e) = first_err.lock().take() {
10175 return Err(FlowOrError::Error(e));
10176 }
10177 Ok(PerlValue::UNDEF)
10178 }
10179 ExprKind::RetryBlock {
10180 body,
10181 times,
10182 backoff,
10183 } => self.eval_retry_block(body, times, *backoff, line),
10184 ExprKind::RateLimitBlock {
10185 slot,
10186 max,
10187 window,
10188 body,
10189 } => self.eval_rate_limit_block(*slot, max, window, body, line),
10190 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
10191 ExprKind::GenBlock { body } => {
10192 let g = Arc::new(PerlGenerator {
10193 block: body.clone(),
10194 pc: Mutex::new(0),
10195 scope_started: Mutex::new(false),
10196 exhausted: Mutex::new(false),
10197 });
10198 Ok(PerlValue::generator(g))
10199 }
10200 ExprKind::Yield(e) => {
10201 if !self.in_generator {
10202 return Err(PerlError::runtime("yield outside gen block", line).into());
10203 }
10204 let v = self.eval_expr(e)?;
10205 Err(FlowOrError::Flow(Flow::Yield(v)))
10206 }
10207 ExprKind::AlgebraicMatch { subject, arms } => {
10208 self.eval_algebraic_match(subject, arms, line)
10209 }
10210 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
10211 Ok(self.spawn_async_block(body))
10212 }
10213 ExprKind::Trace { body } => {
10214 crate::parallel_trace::trace_enter();
10215 let out = self.exec_block(body);
10216 crate::parallel_trace::trace_leave();
10217 out
10218 }
10219 ExprKind::Spinner { message, body } => {
10220 use std::io::Write as _;
10221 let msg = self.eval_expr(message)?.to_string();
10222 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
10223 let done2 = done.clone();
10224 let handle = std::thread::spawn(move || {
10225 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
10226 let mut i = 0;
10227 let stderr = std::io::stderr();
10228 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
10229 {
10230 let stdout = std::io::stdout();
10231 let _stdout_lock = stdout.lock();
10232 let mut err = stderr.lock();
10233 let _ = write!(
10234 err,
10235 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
10236 frames[i % frames.len()],
10237 msg
10238 );
10239 let _ = err.flush();
10240 }
10241 std::thread::sleep(std::time::Duration::from_millis(80));
10242 i += 1;
10243 }
10244 let mut err = stderr.lock();
10245 let _ = write!(err, "\r\x1b[2K");
10246 let _ = err.flush();
10247 });
10248 let result = self.exec_block(body);
10249 done.store(true, std::sync::atomic::Ordering::Relaxed);
10250 let _ = handle.join();
10251 result
10252 }
10253 ExprKind::Timer { body } => {
10254 let start = std::time::Instant::now();
10255 self.exec_block(body)?;
10256 let ms = start.elapsed().as_secs_f64() * 1000.0;
10257 Ok(PerlValue::float(ms))
10258 }
10259 ExprKind::Bench { body, times } => {
10260 let n = self.eval_expr(times)?.to_int();
10261 if n < 0 {
10262 return Err(PerlError::runtime(
10263 "bench: iteration count must be non-negative",
10264 line,
10265 )
10266 .into());
10267 }
10268 self.run_bench_block(body, n as usize, line)
10269 }
10270 ExprKind::Await(expr) => {
10271 let v = self.eval_expr(expr)?;
10272 if let Some(t) = v.as_async_task() {
10273 t.await_result().map_err(FlowOrError::from)
10274 } else {
10275 Ok(v)
10276 }
10277 }
10278 ExprKind::Slurp(e) => {
10279 let path = self.eval_expr(e)?.to_string();
10280 read_file_text_perl_compat(&path)
10281 .map(PerlValue::string)
10282 .map_err(|e| {
10283 FlowOrError::Error(PerlError::runtime(format!("slurp: {}", e), line))
10284 })
10285 }
10286 ExprKind::Capture(e) => {
10287 let cmd = self.eval_expr(e)?.to_string();
10288 let output = Command::new("sh")
10289 .arg("-c")
10290 .arg(&cmd)
10291 .output()
10292 .map_err(|e| {
10293 FlowOrError::Error(PerlError::runtime(format!("capture: {}", e), line))
10294 })?;
10295 self.record_child_exit_status(output.status);
10296 let exitcode = output.status.code().unwrap_or(-1) as i64;
10297 let stdout = decode_utf8_or_latin1(&output.stdout);
10298 let stderr = decode_utf8_or_latin1(&output.stderr);
10299 Ok(PerlValue::capture(Arc::new(CaptureResult {
10300 stdout,
10301 stderr,
10302 exitcode,
10303 })))
10304 }
10305 ExprKind::Qx(e) => {
10306 let cmd = self.eval_expr(e)?.to_string();
10307 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)
10308 }
10309 ExprKind::FetchUrl(e) => {
10310 let url = self.eval_expr(e)?.to_string();
10311 ureq::get(&url)
10312 .call()
10313 .map_err(|e| {
10314 FlowOrError::Error(PerlError::runtime(format!("fetch_url: {}", e), line))
10315 })
10316 .and_then(|r| {
10317 r.into_string().map(PerlValue::string).map_err(|e| {
10318 FlowOrError::Error(PerlError::runtime(
10319 format!("fetch_url: {}", e),
10320 line,
10321 ))
10322 })
10323 })
10324 }
10325 ExprKind::Pchannel { capacity } => {
10326 if let Some(c) = capacity {
10327 let n = self.eval_expr(c)?.to_int().max(1) as usize;
10328 Ok(crate::pchannel::create_bounded_pair(n))
10329 } else {
10330 Ok(crate::pchannel::create_pair())
10331 }
10332 }
10333 ExprKind::PSortExpr {
10334 cmp,
10335 list,
10336 progress,
10337 } => {
10338 let show_progress = progress
10339 .as_ref()
10340 .map(|p| self.eval_expr(p))
10341 .transpose()?
10342 .map(|v| v.is_true())
10343 .unwrap_or(false);
10344 let list_val = self.eval_expr(list)?;
10345 let mut items = list_val.to_list();
10346 let pmap_progress = PmapProgress::new(show_progress, 2);
10347 pmap_progress.tick();
10348 if let Some(cmp_block) = cmp {
10349 if let Some(mode) = detect_sort_block_fast(cmp_block) {
10350 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
10351 } else {
10352 let cmp_block = cmp_block.clone();
10353 let subs = self.subs.clone();
10354 let scope_capture = self.scope.capture();
10355 items.par_sort_by(|a, b| {
10356 let mut local_interp = Interpreter::new();
10357 local_interp.subs = subs.clone();
10358 local_interp.scope.restore_capture(&scope_capture);
10359 let _ = local_interp.scope.set_scalar("a", a.clone());
10360 let _ = local_interp.scope.set_scalar("b", b.clone());
10361 let _ = local_interp.scope.set_scalar("_0", a.clone());
10362 let _ = local_interp.scope.set_scalar("_1", b.clone());
10363 match local_interp.exec_block(&cmp_block) {
10364 Ok(v) => {
10365 let n = v.to_int();
10366 if n < 0 {
10367 std::cmp::Ordering::Less
10368 } else if n > 0 {
10369 std::cmp::Ordering::Greater
10370 } else {
10371 std::cmp::Ordering::Equal
10372 }
10373 }
10374 Err(_) => std::cmp::Ordering::Equal,
10375 }
10376 });
10377 }
10378 } else {
10379 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
10380 }
10381 pmap_progress.tick();
10382 pmap_progress.finish();
10383 Ok(PerlValue::array(items))
10384 }
10385
10386 ExprKind::ReduceExpr { block, list } => {
10387 let list_val = self.eval_expr(list)?;
10388 let items = list_val.to_list();
10389 if items.is_empty() {
10390 return Ok(PerlValue::UNDEF);
10391 }
10392 if items.len() == 1 {
10393 return Ok(items.into_iter().next().unwrap());
10394 }
10395 let block = block.clone();
10396 let subs = self.subs.clone();
10397 let scope_capture = self.scope.capture();
10398 let mut acc = items[0].clone();
10399 for b in items.into_iter().skip(1) {
10400 let mut local_interp = Interpreter::new();
10401 local_interp.subs = subs.clone();
10402 local_interp.scope.restore_capture(&scope_capture);
10403 let _ = local_interp.scope.set_scalar("a", acc.clone());
10404 let _ = local_interp.scope.set_scalar("b", b.clone());
10405 let _ = local_interp.scope.set_scalar("_0", acc);
10406 let _ = local_interp.scope.set_scalar("_1", b);
10407 acc = match local_interp.exec_block(&block) {
10408 Ok(val) => val,
10409 Err(_) => PerlValue::UNDEF,
10410 };
10411 }
10412 Ok(acc)
10413 }
10414
10415 ExprKind::PReduceExpr {
10416 block,
10417 list,
10418 progress,
10419 } => {
10420 let show_progress = progress
10421 .as_ref()
10422 .map(|p| self.eval_expr(p))
10423 .transpose()?
10424 .map(|v| v.is_true())
10425 .unwrap_or(false);
10426 let list_val = self.eval_expr(list)?;
10427 let items = list_val.to_list();
10428 if items.is_empty() {
10429 return Ok(PerlValue::UNDEF);
10430 }
10431 if items.len() == 1 {
10432 return Ok(items.into_iter().next().unwrap());
10433 }
10434 let block = block.clone();
10435 let subs = self.subs.clone();
10436 let scope_capture = self.scope.capture();
10437 let pmap_progress = PmapProgress::new(show_progress, items.len());
10438
10439 let result = items
10440 .into_par_iter()
10441 .map(|x| {
10442 pmap_progress.tick();
10443 x
10444 })
10445 .reduce_with(|a, b| {
10446 let mut local_interp = Interpreter::new();
10447 local_interp.subs = subs.clone();
10448 local_interp.scope.restore_capture(&scope_capture);
10449 let _ = local_interp.scope.set_scalar("a", a.clone());
10450 let _ = local_interp.scope.set_scalar("b", b.clone());
10451 let _ = local_interp.scope.set_scalar("_0", a);
10452 let _ = local_interp.scope.set_scalar("_1", b);
10453 match local_interp.exec_block(&block) {
10454 Ok(val) => val,
10455 Err(_) => PerlValue::UNDEF,
10456 }
10457 });
10458 pmap_progress.finish();
10459 Ok(result.unwrap_or(PerlValue::UNDEF))
10460 }
10461
10462 ExprKind::PReduceInitExpr {
10463 init,
10464 block,
10465 list,
10466 progress,
10467 } => {
10468 let show_progress = progress
10469 .as_ref()
10470 .map(|p| self.eval_expr(p))
10471 .transpose()?
10472 .map(|v| v.is_true())
10473 .unwrap_or(false);
10474 let init_val = self.eval_expr(init)?;
10475 let list_val = self.eval_expr(list)?;
10476 let items = list_val.to_list();
10477 if items.is_empty() {
10478 return Ok(init_val);
10479 }
10480 let block = block.clone();
10481 let subs = self.subs.clone();
10482 let scope_capture = self.scope.capture();
10483 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
10484 if items.len() == 1 {
10485 return Ok(fold_preduce_init_step(
10486 &subs,
10487 cap,
10488 &block,
10489 preduce_init_fold_identity(&init_val),
10490 items.into_iter().next().unwrap(),
10491 ));
10492 }
10493 let pmap_progress = PmapProgress::new(show_progress, items.len());
10494 let result = items
10495 .into_par_iter()
10496 .fold(
10497 || preduce_init_fold_identity(&init_val),
10498 |acc, item| {
10499 pmap_progress.tick();
10500 fold_preduce_init_step(&subs, cap, &block, acc, item)
10501 },
10502 )
10503 .reduce(
10504 || preduce_init_fold_identity(&init_val),
10505 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
10506 );
10507 pmap_progress.finish();
10508 Ok(result)
10509 }
10510
10511 ExprKind::PMapReduceExpr {
10512 map_block,
10513 reduce_block,
10514 list,
10515 progress,
10516 } => {
10517 let show_progress = progress
10518 .as_ref()
10519 .map(|p| self.eval_expr(p))
10520 .transpose()?
10521 .map(|v| v.is_true())
10522 .unwrap_or(false);
10523 let list_val = self.eval_expr(list)?;
10524 let items = list_val.to_list();
10525 if items.is_empty() {
10526 return Ok(PerlValue::UNDEF);
10527 }
10528 let map_block = map_block.clone();
10529 let reduce_block = reduce_block.clone();
10530 let subs = self.subs.clone();
10531 let scope_capture = self.scope.capture();
10532 if items.len() == 1 {
10533 let mut local_interp = Interpreter::new();
10534 local_interp.subs = subs.clone();
10535 local_interp.scope.restore_capture(&scope_capture);
10536 local_interp.scope.set_topic(items[0].clone());
10537 return match local_interp.exec_block_no_scope(&map_block) {
10538 Ok(v) => Ok(v),
10539 Err(_) => Ok(PerlValue::UNDEF),
10540 };
10541 }
10542 let pmap_progress = PmapProgress::new(show_progress, items.len());
10543 let result = items
10544 .into_par_iter()
10545 .map(|item| {
10546 let mut local_interp = Interpreter::new();
10547 local_interp.subs = subs.clone();
10548 local_interp.scope.restore_capture(&scope_capture);
10549 local_interp.scope.set_topic(item);
10550 let val = match local_interp.exec_block_no_scope(&map_block) {
10551 Ok(val) => val,
10552 Err(_) => PerlValue::UNDEF,
10553 };
10554 pmap_progress.tick();
10555 val
10556 })
10557 .reduce_with(|a, b| {
10558 let mut local_interp = Interpreter::new();
10559 local_interp.subs = subs.clone();
10560 local_interp.scope.restore_capture(&scope_capture);
10561 let _ = local_interp.scope.set_scalar("a", a.clone());
10562 let _ = local_interp.scope.set_scalar("b", b.clone());
10563 let _ = local_interp.scope.set_scalar("_0", a);
10564 let _ = local_interp.scope.set_scalar("_1", b);
10565 match local_interp.exec_block_no_scope(&reduce_block) {
10566 Ok(val) => val,
10567 Err(_) => PerlValue::UNDEF,
10568 }
10569 });
10570 pmap_progress.finish();
10571 Ok(result.unwrap_or(PerlValue::UNDEF))
10572 }
10573
10574 ExprKind::PcacheExpr {
10575 block,
10576 list,
10577 progress,
10578 } => {
10579 let show_progress = progress
10580 .as_ref()
10581 .map(|p| self.eval_expr(p))
10582 .transpose()?
10583 .map(|v| v.is_true())
10584 .unwrap_or(false);
10585 let list_val = self.eval_expr(list)?;
10586 let items = list_val.to_list();
10587 let block = block.clone();
10588 let subs = self.subs.clone();
10589 let scope_capture = self.scope.capture();
10590 let cache = &*crate::pcache::GLOBAL_PCACHE;
10591 let pmap_progress = PmapProgress::new(show_progress, items.len());
10592 let results: Vec<PerlValue> = items
10593 .into_par_iter()
10594 .map(|item| {
10595 let k = crate::pcache::cache_key(&item);
10596 if let Some(v) = cache.get(&k) {
10597 pmap_progress.tick();
10598 return v.clone();
10599 }
10600 let mut local_interp = Interpreter::new();
10601 local_interp.subs = subs.clone();
10602 local_interp.scope.restore_capture(&scope_capture);
10603 local_interp.scope.set_topic(item.clone());
10604 let val = match local_interp.exec_block_no_scope(&block) {
10605 Ok(v) => v,
10606 Err(_) => PerlValue::UNDEF,
10607 };
10608 cache.insert(k, val.clone());
10609 pmap_progress.tick();
10610 val
10611 })
10612 .collect();
10613 pmap_progress.finish();
10614 Ok(PerlValue::array(results))
10615 }
10616
10617 ExprKind::PselectExpr { receivers, timeout } => {
10618 let mut rx_vals = Vec::with_capacity(receivers.len());
10619 for r in receivers {
10620 rx_vals.push(self.eval_expr(r)?);
10621 }
10622 let dur = if let Some(t) = timeout.as_ref() {
10623 Some(std::time::Duration::from_secs_f64(
10624 self.eval_expr(t)?.to_number().max(0.0),
10625 ))
10626 } else {
10627 None
10628 };
10629 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
10630 &rx_vals, dur, line,
10631 )?)
10632 }
10633
10634 ExprKind::Push { array, values } => {
10636 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
10637 }
10638 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
10639 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
10640 ExprKind::Unshift { array, values } => {
10641 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
10642 }
10643 ExprKind::Splice {
10644 array,
10645 offset,
10646 length,
10647 replacement,
10648 } => self.eval_splice_expr(
10649 array.as_ref(),
10650 offset.as_deref(),
10651 length.as_deref(),
10652 replacement.as_slice(),
10653 ctx,
10654 line,
10655 ),
10656 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
10657 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
10658 ExprKind::Keys(expr) => {
10659 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10660 let keys = Self::keys_from_value(val, line)?;
10661 if ctx == WantarrayCtx::List {
10662 Ok(keys)
10663 } else {
10664 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
10665 Ok(PerlValue::integer(n as i64))
10666 }
10667 }
10668 ExprKind::Values(expr) => {
10669 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10670 let vals = Self::values_from_value(val, line)?;
10671 if ctx == WantarrayCtx::List {
10672 Ok(vals)
10673 } else {
10674 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
10675 Ok(PerlValue::integer(n as i64))
10676 }
10677 }
10678 ExprKind::Each(_) => {
10679 Ok(PerlValue::array(vec![]))
10681 }
10682
10683 ExprKind::Chomp(expr) => {
10685 let val = self.eval_expr(expr)?;
10686 self.chomp_inplace_execute(val, expr)
10687 }
10688 ExprKind::Chop(expr) => {
10689 let val = self.eval_expr(expr)?;
10690 self.chop_inplace_execute(val, expr)
10691 }
10692 ExprKind::Length(expr) => {
10693 let val = self.eval_expr(expr)?;
10694 Ok(if let Some(a) = val.as_array_vec() {
10695 PerlValue::integer(a.len() as i64)
10696 } else if let Some(h) = val.as_hash_map() {
10697 PerlValue::integer(h.len() as i64)
10698 } else if let Some(b) = val.as_bytes_arc() {
10699 PerlValue::integer(b.len() as i64)
10700 } else {
10701 PerlValue::integer(val.to_string().len() as i64)
10702 })
10703 }
10704 ExprKind::Substr {
10705 string,
10706 offset,
10707 length,
10708 replacement,
10709 } => self.eval_substr_expr(
10710 string.as_ref(),
10711 offset.as_ref(),
10712 length.as_deref(),
10713 replacement.as_deref(),
10714 line,
10715 ),
10716 ExprKind::Index {
10717 string,
10718 substr,
10719 position,
10720 } => {
10721 let s = self.eval_expr(string)?.to_string();
10722 let sub = self.eval_expr(substr)?.to_string();
10723 let pos = if let Some(p) = position {
10724 self.eval_expr(p)?.to_int() as usize
10725 } else {
10726 0
10727 };
10728 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
10729 Ok(PerlValue::integer(result))
10730 }
10731 ExprKind::Rindex {
10732 string,
10733 substr,
10734 position,
10735 } => {
10736 let s = self.eval_expr(string)?.to_string();
10737 let sub = self.eval_expr(substr)?.to_string();
10738 let end = if let Some(p) = position {
10739 self.eval_expr(p)?.to_int() as usize + sub.len()
10740 } else {
10741 s.len()
10742 };
10743 let search = &s[..end.min(s.len())];
10744 let result = search.rfind(&sub).map(|i| i as i64).unwrap_or(-1);
10745 Ok(PerlValue::integer(result))
10746 }
10747 ExprKind::Sprintf { format, args } => {
10748 let fmt = self.eval_expr(format)?.to_string();
10749 let mut arg_vals = Vec::new();
10752 for a in args {
10753 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
10754 if let Some(items) = v.as_array_vec() {
10755 arg_vals.extend(items);
10756 } else {
10757 arg_vals.push(v);
10758 }
10759 }
10760 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
10761 Ok(PerlValue::string(s))
10762 }
10763 ExprKind::JoinExpr { separator, list } => {
10764 let sep = self.eval_expr(separator)?.to_string();
10765 let items = if let ExprKind::List(exprs) = &list.kind {
10769 let saved = self.wantarray_kind;
10770 self.wantarray_kind = WantarrayCtx::List;
10771 let mut vals = Vec::new();
10772 for e in exprs {
10773 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
10774 if let Some(items) = v.as_array_vec() {
10775 vals.extend(items);
10776 } else {
10777 vals.push(v);
10778 }
10779 }
10780 self.wantarray_kind = saved;
10781 vals
10782 } else {
10783 let saved = self.wantarray_kind;
10784 self.wantarray_kind = WantarrayCtx::List;
10785 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
10786 self.wantarray_kind = saved;
10787 if let Some(items) = v.as_array_vec() {
10788 items
10789 } else {
10790 vec![v]
10791 }
10792 };
10793 let mut strs = Vec::with_capacity(items.len());
10794 for v in &items {
10795 strs.push(self.stringify_value(v.clone(), line)?);
10796 }
10797 Ok(PerlValue::string(strs.join(&sep)))
10798 }
10799 ExprKind::SplitExpr {
10800 pattern,
10801 string,
10802 limit,
10803 } => {
10804 let pat = self.eval_expr(pattern)?.to_string();
10805 let s = self.eval_expr(string)?.to_string();
10806 let lim = if let Some(l) = limit {
10807 self.eval_expr(l)?.to_int() as usize
10808 } else {
10809 0
10810 };
10811 let re = self.compile_regex(&pat, "", line)?;
10812 let parts: Vec<PerlValue> = if lim > 0 {
10813 re.splitn_strings(&s, lim)
10814 .into_iter()
10815 .map(PerlValue::string)
10816 .collect()
10817 } else {
10818 re.split_strings(&s)
10819 .into_iter()
10820 .map(PerlValue::string)
10821 .collect()
10822 };
10823 Ok(PerlValue::array(parts))
10824 }
10825
10826 ExprKind::Abs(expr) => {
10828 let val = self.eval_expr(expr)?;
10829 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
10830 return r;
10831 }
10832 Ok(PerlValue::float(val.to_number().abs()))
10833 }
10834 ExprKind::Int(expr) => {
10835 let val = self.eval_expr(expr)?;
10836 Ok(PerlValue::integer(val.to_number() as i64))
10837 }
10838 ExprKind::Sqrt(expr) => {
10839 let val = self.eval_expr(expr)?;
10840 Ok(PerlValue::float(val.to_number().sqrt()))
10841 }
10842 ExprKind::Sin(expr) => {
10843 let val = self.eval_expr(expr)?;
10844 Ok(PerlValue::float(val.to_number().sin()))
10845 }
10846 ExprKind::Cos(expr) => {
10847 let val = self.eval_expr(expr)?;
10848 Ok(PerlValue::float(val.to_number().cos()))
10849 }
10850 ExprKind::Atan2 { y, x } => {
10851 let yv = self.eval_expr(y)?.to_number();
10852 let xv = self.eval_expr(x)?.to_number();
10853 Ok(PerlValue::float(yv.atan2(xv)))
10854 }
10855 ExprKind::Exp(expr) => {
10856 let val = self.eval_expr(expr)?;
10857 Ok(PerlValue::float(val.to_number().exp()))
10858 }
10859 ExprKind::Log(expr) => {
10860 let val = self.eval_expr(expr)?;
10861 Ok(PerlValue::float(val.to_number().ln()))
10862 }
10863 ExprKind::Rand(upper) => {
10864 let u = match upper {
10865 Some(e) => self.eval_expr(e)?.to_number(),
10866 None => 1.0,
10867 };
10868 Ok(PerlValue::float(self.perl_rand(u)))
10869 }
10870 ExprKind::Srand(seed) => {
10871 let s = match seed {
10872 Some(e) => Some(self.eval_expr(e)?.to_number()),
10873 None => None,
10874 };
10875 Ok(PerlValue::integer(self.perl_srand(s)))
10876 }
10877 ExprKind::Hex(expr) => {
10878 let val = self.eval_expr(expr)?.to_string();
10879 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
10880 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
10881 Ok(PerlValue::integer(n))
10882 }
10883 ExprKind::Oct(expr) => {
10884 let val = self.eval_expr(expr)?.to_string();
10885 let s = val.trim();
10886 let n = if s.starts_with("0x") || s.starts_with("0X") {
10887 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
10888 } else if s.starts_with("0b") || s.starts_with("0B") {
10889 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
10890 } else {
10891 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
10892 };
10893 Ok(PerlValue::integer(n))
10894 }
10895
10896 ExprKind::Lc(expr) => Ok(PerlValue::string(
10898 self.eval_expr(expr)?.to_string().to_lowercase(),
10899 )),
10900 ExprKind::Uc(expr) => Ok(PerlValue::string(
10901 self.eval_expr(expr)?.to_string().to_uppercase(),
10902 )),
10903 ExprKind::Lcfirst(expr) => {
10904 let s = self.eval_expr(expr)?.to_string();
10905 let mut chars = s.chars();
10906 let result = match chars.next() {
10907 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
10908 None => String::new(),
10909 };
10910 Ok(PerlValue::string(result))
10911 }
10912 ExprKind::Ucfirst(expr) => {
10913 let s = self.eval_expr(expr)?.to_string();
10914 let mut chars = s.chars();
10915 let result = match chars.next() {
10916 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
10917 None => String::new(),
10918 };
10919 Ok(PerlValue::string(result))
10920 }
10921 ExprKind::Fc(expr) => Ok(PerlValue::string(default_case_fold_str(
10922 &self.eval_expr(expr)?.to_string(),
10923 ))),
10924 ExprKind::Crypt { plaintext, salt } => {
10925 let p = self.eval_expr(plaintext)?.to_string();
10926 let sl = self.eval_expr(salt)?.to_string();
10927 Ok(PerlValue::string(perl_crypt(&p, &sl)))
10928 }
10929 ExprKind::Pos(e) => {
10930 let key = match e {
10931 None => "_".to_string(),
10932 Some(expr) => match &expr.kind {
10933 ExprKind::ScalarVar(n) => n.clone(),
10934 _ => self.eval_expr(expr)?.to_string(),
10935 },
10936 };
10937 Ok(self
10938 .regex_pos
10939 .get(&key)
10940 .copied()
10941 .flatten()
10942 .map(|p| PerlValue::integer(p as i64))
10943 .unwrap_or(PerlValue::UNDEF))
10944 }
10945 ExprKind::Study(expr) => {
10946 let s = self.eval_expr(expr)?.to_string();
10947 Ok(Self::study_return_value(&s))
10948 }
10949
10950 ExprKind::Defined(expr) => {
10952 if let ExprKind::SubroutineRef(name) = &expr.kind {
10954 let exists = self.resolve_sub_by_name(name).is_some();
10955 return Ok(PerlValue::integer(if exists { 1 } else { 0 }));
10956 }
10957 let val = self.eval_expr(expr)?;
10958 Ok(PerlValue::integer(if val.is_undef() { 0 } else { 1 }))
10959 }
10960 ExprKind::Ref(expr) => {
10961 let val = self.eval_expr(expr)?;
10962 Ok(val.ref_type())
10963 }
10964 ExprKind::ScalarContext(expr) => {
10965 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
10966 Ok(v.scalar_context())
10967 }
10968
10969 ExprKind::Chr(expr) => {
10971 let n = self.eval_expr(expr)?.to_int() as u32;
10972 Ok(PerlValue::string(
10973 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
10974 ))
10975 }
10976 ExprKind::Ord(expr) => {
10977 let s = self.eval_expr(expr)?.to_string();
10978 Ok(PerlValue::integer(
10979 s.chars().next().map(|c| c as i64).unwrap_or(0),
10980 ))
10981 }
10982
10983 ExprKind::OpenMyHandle { .. } => Err(PerlError::runtime(
10985 "internal: `open my $fh` handle used outside open()",
10986 line,
10987 )
10988 .into()),
10989 ExprKind::Open { handle, mode, file } => {
10990 if let ExprKind::OpenMyHandle { name } = &handle.kind {
10991 self.scope
10992 .declare_scalar_frozen(name, PerlValue::UNDEF, false, None)?;
10993 self.english_note_lexical_scalar(name);
10994 let mode_s = self.eval_expr(mode)?.to_string();
10995 let file_opt = if let Some(f) = file {
10996 Some(self.eval_expr(f)?.to_string())
10997 } else {
10998 None
10999 };
11000 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
11001 self.scope.set_scalar(name, ret.clone())?;
11002 return Ok(ret);
11003 }
11004 let handle_s = self.eval_expr(handle)?.to_string();
11005 let handle_name = self.resolve_io_handle_name(&handle_s);
11006 let mode_s = self.eval_expr(mode)?.to_string();
11007 let file_opt = if let Some(f) = file {
11008 Some(self.eval_expr(f)?.to_string())
11009 } else {
11010 None
11011 };
11012 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
11013 .map_err(Into::into)
11014 }
11015 ExprKind::Close(expr) => {
11016 let s = self.eval_expr(expr)?.to_string();
11017 let name = self.resolve_io_handle_name(&s);
11018 self.close_builtin_execute(name).map_err(Into::into)
11019 }
11020 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
11021 self.readline_builtin_execute_list(handle.as_deref())
11022 } else {
11023 self.readline_builtin_execute(handle.as_deref())
11024 }
11025 .map_err(Into::into),
11026 ExprKind::Eof(expr) => match expr {
11027 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
11028 Some(e) => {
11029 let name = self.eval_expr(e)?;
11030 self.eof_builtin_execute(&[name], line).map_err(Into::into)
11031 }
11032 },
11033
11034 ExprKind::Opendir { handle, path } => {
11035 let h = self.eval_expr(handle)?.to_string();
11036 let p = self.eval_expr(path)?.to_string();
11037 Ok(self.opendir_handle(&h, &p))
11038 }
11039 ExprKind::Readdir(e) => {
11040 let h = self.eval_expr(e)?.to_string();
11041 Ok(if ctx == WantarrayCtx::List {
11042 self.readdir_handle_list(&h)
11043 } else {
11044 self.readdir_handle(&h)
11045 })
11046 }
11047 ExprKind::Closedir(e) => {
11048 let h = self.eval_expr(e)?.to_string();
11049 Ok(self.closedir_handle(&h))
11050 }
11051 ExprKind::Rewinddir(e) => {
11052 let h = self.eval_expr(e)?.to_string();
11053 Ok(self.rewinddir_handle(&h))
11054 }
11055 ExprKind::Telldir(e) => {
11056 let h = self.eval_expr(e)?.to_string();
11057 Ok(self.telldir_handle(&h))
11058 }
11059 ExprKind::Seekdir { handle, position } => {
11060 let h = self.eval_expr(handle)?.to_string();
11061 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
11062 Ok(self.seekdir_handle(&h, pos))
11063 }
11064
11065 ExprKind::FileTest { op, expr } => {
11067 let path = self.eval_expr(expr)?.to_string();
11068 if matches!(op, 'M' | 'A' | 'C') {
11070 #[cfg(unix)]
11071 {
11072 return match crate::perl_fs::filetest_age_days(&path, *op) {
11073 Some(days) => Ok(PerlValue::float(days)),
11074 None => Ok(PerlValue::UNDEF),
11075 };
11076 }
11077 #[cfg(not(unix))]
11078 return Ok(PerlValue::UNDEF);
11079 }
11080 if *op == 's' {
11082 return match std::fs::metadata(&path) {
11083 Ok(m) => Ok(PerlValue::integer(m.len() as i64)),
11084 Err(_) => Ok(PerlValue::UNDEF),
11085 };
11086 }
11087 let result = match op {
11088 'e' => std::path::Path::new(&path).exists(),
11089 'f' => std::path::Path::new(&path).is_file(),
11090 'd' => std::path::Path::new(&path).is_dir(),
11091 'l' => std::path::Path::new(&path).is_symlink(),
11092 #[cfg(unix)]
11093 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
11094 #[cfg(not(unix))]
11095 'r' => std::fs::metadata(&path).is_ok(),
11096 #[cfg(unix)]
11097 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
11098 #[cfg(not(unix))]
11099 'w' => std::fs::metadata(&path).is_ok(),
11100 #[cfg(unix)]
11101 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
11102 #[cfg(not(unix))]
11103 'x' => false,
11104 #[cfg(unix)]
11105 'o' => crate::perl_fs::filetest_owned_effective(&path),
11106 #[cfg(not(unix))]
11107 'o' => false,
11108 #[cfg(unix)]
11109 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
11110 #[cfg(not(unix))]
11111 'R' => false,
11112 #[cfg(unix)]
11113 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
11114 #[cfg(not(unix))]
11115 'W' => false,
11116 #[cfg(unix)]
11117 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
11118 #[cfg(not(unix))]
11119 'X' => false,
11120 #[cfg(unix)]
11121 'O' => crate::perl_fs::filetest_owned_real(&path),
11122 #[cfg(not(unix))]
11123 'O' => false,
11124 'z' => std::fs::metadata(&path)
11125 .map(|m| m.len() == 0)
11126 .unwrap_or(true),
11127 't' => crate::perl_fs::filetest_is_tty(&path),
11128 #[cfg(unix)]
11129 'p' => crate::perl_fs::filetest_is_pipe(&path),
11130 #[cfg(not(unix))]
11131 'p' => false,
11132 #[cfg(unix)]
11133 'S' => crate::perl_fs::filetest_is_socket(&path),
11134 #[cfg(not(unix))]
11135 'S' => false,
11136 #[cfg(unix)]
11137 'b' => crate::perl_fs::filetest_is_block_device(&path),
11138 #[cfg(not(unix))]
11139 'b' => false,
11140 #[cfg(unix)]
11141 'c' => crate::perl_fs::filetest_is_char_device(&path),
11142 #[cfg(not(unix))]
11143 'c' => false,
11144 #[cfg(unix)]
11145 'u' => crate::perl_fs::filetest_is_setuid(&path),
11146 #[cfg(not(unix))]
11147 'u' => false,
11148 #[cfg(unix)]
11149 'g' => crate::perl_fs::filetest_is_setgid(&path),
11150 #[cfg(not(unix))]
11151 'g' => false,
11152 #[cfg(unix)]
11153 'k' => crate::perl_fs::filetest_is_sticky(&path),
11154 #[cfg(not(unix))]
11155 'k' => false,
11156 'T' => crate::perl_fs::filetest_is_text(&path),
11157 'B' => crate::perl_fs::filetest_is_binary(&path),
11158 _ => false,
11159 };
11160 Ok(PerlValue::integer(if result { 1 } else { 0 }))
11161 }
11162
11163 ExprKind::System(args) => {
11165 let mut cmd_args = Vec::new();
11166 for a in args {
11167 cmd_args.push(self.eval_expr(a)?.to_string());
11168 }
11169 if cmd_args.is_empty() {
11170 return Ok(PerlValue::integer(-1));
11171 }
11172 let status = Command::new("sh")
11173 .arg("-c")
11174 .arg(cmd_args.join(" "))
11175 .status();
11176 match status {
11177 Ok(s) => {
11178 self.record_child_exit_status(s);
11179 Ok(PerlValue::integer(s.code().unwrap_or(-1) as i64))
11180 }
11181 Err(e) => {
11182 self.apply_io_error_to_errno(&e);
11183 Ok(PerlValue::integer(-1))
11184 }
11185 }
11186 }
11187 ExprKind::Exec(args) => {
11188 let mut cmd_args = Vec::new();
11189 for a in args {
11190 cmd_args.push(self.eval_expr(a)?.to_string());
11191 }
11192 if cmd_args.is_empty() {
11193 return Ok(PerlValue::integer(-1));
11194 }
11195 let status = Command::new("sh")
11196 .arg("-c")
11197 .arg(cmd_args.join(" "))
11198 .status();
11199 match status {
11200 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
11201 Err(e) => {
11202 self.apply_io_error_to_errno(&e);
11203 Ok(PerlValue::integer(-1))
11204 }
11205 }
11206 }
11207 ExprKind::Eval(expr) => {
11208 self.eval_nesting += 1;
11209 let out = match &expr.kind {
11210 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
11211 Ok(v) => {
11212 self.clear_eval_error();
11213 Ok(v)
11214 }
11215 Err(FlowOrError::Error(e)) => {
11216 self.set_eval_error_from_perl_error(&e);
11217 Ok(PerlValue::UNDEF)
11218 }
11219 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
11220 },
11221 _ => {
11222 let code = self.eval_expr(expr)?.to_string();
11223 match crate::parse_and_run_string(&code, self) {
11225 Ok(v) => {
11226 self.clear_eval_error();
11227 Ok(v)
11228 }
11229 Err(e) => {
11230 self.set_eval_error(e.to_string());
11231 Ok(PerlValue::UNDEF)
11232 }
11233 }
11234 }
11235 };
11236 self.eval_nesting -= 1;
11237 out
11238 }
11239 ExprKind::Do(expr) => match &expr.kind {
11240 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
11241 _ => {
11242 let val = self.eval_expr(expr)?;
11243 let filename = val.to_string();
11244 match read_file_text_perl_compat(&filename) {
11245 Ok(code) => {
11246 let code = crate::data_section::strip_perl_end_marker(&code);
11247 match crate::parse_and_run_string_in_file(code, self, &filename) {
11248 Ok(v) => Ok(v),
11249 Err(e) => {
11250 self.set_eval_error(e.to_string());
11251 Ok(PerlValue::UNDEF)
11252 }
11253 }
11254 }
11255 Err(e) => {
11256 self.apply_io_error_to_errno(&e);
11257 Ok(PerlValue::UNDEF)
11258 }
11259 }
11260 }
11261 },
11262 ExprKind::Require(expr) => {
11263 let spec = self.eval_expr(expr)?.to_string();
11264 self.require_execute(&spec, line)
11265 .map_err(FlowOrError::Error)
11266 }
11267 ExprKind::Exit(code) => {
11268 let c = if let Some(e) = code {
11269 self.eval_expr(e)?.to_int() as i32
11270 } else {
11271 0
11272 };
11273 Err(PerlError::new(ErrorKind::Exit(c), "", line, &self.file).into())
11274 }
11275 ExprKind::Chdir(expr) => {
11276 let path = self.eval_expr(expr)?.to_string();
11277 match std::env::set_current_dir(&path) {
11278 Ok(_) => Ok(PerlValue::integer(1)),
11279 Err(e) => {
11280 self.apply_io_error_to_errno(&e);
11281 Ok(PerlValue::integer(0))
11282 }
11283 }
11284 }
11285 ExprKind::Mkdir { path, mode: _ } => {
11286 let p = self.eval_expr(path)?.to_string();
11287 match std::fs::create_dir(&p) {
11288 Ok(_) => Ok(PerlValue::integer(1)),
11289 Err(e) => {
11290 self.apply_io_error_to_errno(&e);
11291 Ok(PerlValue::integer(0))
11292 }
11293 }
11294 }
11295 ExprKind::Unlink(args) => {
11296 let mut count = 0i64;
11297 for a in args {
11298 let path = self.eval_expr(a)?.to_string();
11299 if std::fs::remove_file(&path).is_ok() {
11300 count += 1;
11301 }
11302 }
11303 Ok(PerlValue::integer(count))
11304 }
11305 ExprKind::Rename { old, new } => {
11306 let o = self.eval_expr(old)?.to_string();
11307 let n = self.eval_expr(new)?.to_string();
11308 Ok(crate::perl_fs::rename_paths(&o, &n))
11309 }
11310 ExprKind::Chmod(args) => {
11311 let mode = self.eval_expr(&args[0])?.to_int();
11312 let mut paths = Vec::new();
11313 for a in &args[1..] {
11314 paths.push(self.eval_expr(a)?.to_string());
11315 }
11316 Ok(PerlValue::integer(crate::perl_fs::chmod_paths(
11317 &paths, mode,
11318 )))
11319 }
11320 ExprKind::Chown(args) => {
11321 let uid = self.eval_expr(&args[0])?.to_int();
11322 let gid = self.eval_expr(&args[1])?.to_int();
11323 let mut paths = Vec::new();
11324 for a in &args[2..] {
11325 paths.push(self.eval_expr(a)?.to_string());
11326 }
11327 Ok(PerlValue::integer(crate::perl_fs::chown_paths(
11328 &paths, uid, gid,
11329 )))
11330 }
11331 ExprKind::Stat(e) => {
11332 let path = self.eval_expr(e)?.to_string();
11333 Ok(crate::perl_fs::stat_path(&path, false))
11334 }
11335 ExprKind::Lstat(e) => {
11336 let path = self.eval_expr(e)?.to_string();
11337 Ok(crate::perl_fs::stat_path(&path, true))
11338 }
11339 ExprKind::Link { old, new } => {
11340 let o = self.eval_expr(old)?.to_string();
11341 let n = self.eval_expr(new)?.to_string();
11342 Ok(crate::perl_fs::link_hard(&o, &n))
11343 }
11344 ExprKind::Symlink { old, new } => {
11345 let o = self.eval_expr(old)?.to_string();
11346 let n = self.eval_expr(new)?.to_string();
11347 Ok(crate::perl_fs::link_sym(&o, &n))
11348 }
11349 ExprKind::Readlink(e) => {
11350 let path = self.eval_expr(e)?.to_string();
11351 Ok(crate::perl_fs::read_link(&path))
11352 }
11353 ExprKind::Files(args) => {
11354 let dir = if args.is_empty() {
11355 ".".to_string()
11356 } else {
11357 self.eval_expr(&args[0])?.to_string()
11358 };
11359 Ok(crate::perl_fs::list_files(&dir))
11360 }
11361 ExprKind::Filesf(args) => {
11362 let dir = if args.is_empty() {
11363 ".".to_string()
11364 } else {
11365 self.eval_expr(&args[0])?.to_string()
11366 };
11367 Ok(crate::perl_fs::list_filesf(&dir))
11368 }
11369 ExprKind::FilesfRecursive(args) => {
11370 let dir = if args.is_empty() {
11371 ".".to_string()
11372 } else {
11373 self.eval_expr(&args[0])?.to_string()
11374 };
11375 Ok(PerlValue::iterator(Arc::new(
11376 crate::value::FsWalkIterator::new(&dir, true),
11377 )))
11378 }
11379 ExprKind::Dirs(args) => {
11380 let dir = if args.is_empty() {
11381 ".".to_string()
11382 } else {
11383 self.eval_expr(&args[0])?.to_string()
11384 };
11385 Ok(crate::perl_fs::list_dirs(&dir))
11386 }
11387 ExprKind::DirsRecursive(args) => {
11388 let dir = if args.is_empty() {
11389 ".".to_string()
11390 } else {
11391 self.eval_expr(&args[0])?.to_string()
11392 };
11393 Ok(PerlValue::iterator(Arc::new(
11394 crate::value::FsWalkIterator::new(&dir, false),
11395 )))
11396 }
11397 ExprKind::SymLinks(args) => {
11398 let dir = if args.is_empty() {
11399 ".".to_string()
11400 } else {
11401 self.eval_expr(&args[0])?.to_string()
11402 };
11403 Ok(crate::perl_fs::list_sym_links(&dir))
11404 }
11405 ExprKind::Sockets(args) => {
11406 let dir = if args.is_empty() {
11407 ".".to_string()
11408 } else {
11409 self.eval_expr(&args[0])?.to_string()
11410 };
11411 Ok(crate::perl_fs::list_sockets(&dir))
11412 }
11413 ExprKind::Pipes(args) => {
11414 let dir = if args.is_empty() {
11415 ".".to_string()
11416 } else {
11417 self.eval_expr(&args[0])?.to_string()
11418 };
11419 Ok(crate::perl_fs::list_pipes(&dir))
11420 }
11421 ExprKind::BlockDevices(args) => {
11422 let dir = if args.is_empty() {
11423 ".".to_string()
11424 } else {
11425 self.eval_expr(&args[0])?.to_string()
11426 };
11427 Ok(crate::perl_fs::list_block_devices(&dir))
11428 }
11429 ExprKind::CharDevices(args) => {
11430 let dir = if args.is_empty() {
11431 ".".to_string()
11432 } else {
11433 self.eval_expr(&args[0])?.to_string()
11434 };
11435 Ok(crate::perl_fs::list_char_devices(&dir))
11436 }
11437 ExprKind::Glob(args) => {
11438 let mut pats = Vec::new();
11439 for a in args {
11440 pats.push(self.eval_expr(a)?.to_string());
11441 }
11442 Ok(crate::perl_fs::glob_patterns(&pats))
11443 }
11444 ExprKind::GlobPar { args, progress } => {
11445 let mut pats = Vec::new();
11446 for a in args {
11447 pats.push(self.eval_expr(a)?.to_string());
11448 }
11449 let show_progress = progress
11450 .as_ref()
11451 .map(|p| self.eval_expr(p))
11452 .transpose()?
11453 .map(|v| v.is_true())
11454 .unwrap_or(false);
11455 if show_progress {
11456 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
11457 } else {
11458 Ok(crate::perl_fs::glob_par_patterns(&pats))
11459 }
11460 }
11461 ExprKind::ParSed { args, progress } => {
11462 let has_progress = progress.is_some();
11463 let mut vals: Vec<PerlValue> = Vec::new();
11464 for a in args {
11465 vals.push(self.eval_expr(a)?);
11466 }
11467 if let Some(p) = progress {
11468 vals.push(self.eval_expr(p.as_ref())?);
11469 }
11470 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
11471 }
11472 ExprKind::Bless { ref_expr, class } => {
11473 let val = self.eval_expr(ref_expr)?;
11474 let class_name = if let Some(c) = class {
11475 self.eval_expr(c)?.to_string()
11476 } else {
11477 self.scope.get_scalar("__PACKAGE__").to_string()
11478 };
11479 Ok(PerlValue::blessed(Arc::new(
11480 crate::value::BlessedRef::new_blessed(class_name, val),
11481 )))
11482 }
11483 ExprKind::Caller(_) => {
11484 Ok(PerlValue::array(vec![
11486 PerlValue::string("main".into()),
11487 PerlValue::string(self.file.clone()),
11488 PerlValue::integer(line as i64),
11489 ]))
11490 }
11491 ExprKind::Wantarray => Ok(match self.wantarray_kind {
11492 WantarrayCtx::Void => PerlValue::UNDEF,
11493 WantarrayCtx::Scalar => PerlValue::integer(0),
11494 WantarrayCtx::List => PerlValue::integer(1),
11495 }),
11496
11497 ExprKind::List(exprs) => {
11498 if ctx == WantarrayCtx::Scalar {
11500 if let Some(last) = exprs.last() {
11501 for e in &exprs[..exprs.len() - 1] {
11503 self.eval_expr(e)?;
11504 }
11505 return self.eval_expr(last);
11506 } else {
11507 return Ok(PerlValue::UNDEF);
11508 }
11509 }
11510 let mut vals = Vec::new();
11511 for e in exprs {
11512 let v = self.eval_expr(e)?;
11513 if let Some(items) = v.as_array_vec() {
11514 vals.extend(items);
11515 } else {
11516 vals.push(v);
11517 }
11518 }
11519 if vals.len() == 1 {
11520 Ok(vals.pop().unwrap())
11521 } else {
11522 Ok(PerlValue::array(vals))
11523 }
11524 }
11525
11526 ExprKind::PostfixIf { expr, condition } => {
11528 if self.eval_postfix_condition(condition)? {
11529 self.eval_expr(expr)
11530 } else {
11531 Ok(PerlValue::UNDEF)
11532 }
11533 }
11534 ExprKind::PostfixUnless { expr, condition } => {
11535 if !self.eval_postfix_condition(condition)? {
11536 self.eval_expr(expr)
11537 } else {
11538 Ok(PerlValue::UNDEF)
11539 }
11540 }
11541 ExprKind::PostfixWhile { expr, condition } => {
11542 let is_do_block = matches!(
11545 &expr.kind,
11546 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
11547 );
11548 let mut last = PerlValue::UNDEF;
11549 if is_do_block {
11550 loop {
11551 last = self.eval_expr(expr)?;
11552 if !self.eval_postfix_condition(condition)? {
11553 break;
11554 }
11555 }
11556 } else {
11557 loop {
11558 if !self.eval_postfix_condition(condition)? {
11559 break;
11560 }
11561 last = self.eval_expr(expr)?;
11562 }
11563 }
11564 Ok(last)
11565 }
11566 ExprKind::PostfixUntil { expr, condition } => {
11567 let is_do_block = matches!(
11568 &expr.kind,
11569 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
11570 );
11571 let mut last = PerlValue::UNDEF;
11572 if is_do_block {
11573 loop {
11574 last = self.eval_expr(expr)?;
11575 if self.eval_postfix_condition(condition)? {
11576 break;
11577 }
11578 }
11579 } else {
11580 loop {
11581 if self.eval_postfix_condition(condition)? {
11582 break;
11583 }
11584 last = self.eval_expr(expr)?;
11585 }
11586 }
11587 Ok(last)
11588 }
11589 ExprKind::PostfixForeach { expr, list } => {
11590 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
11591 let mut last = PerlValue::UNDEF;
11592 for item in items {
11593 self.scope.set_topic(item);
11594 last = self.eval_expr(expr)?;
11595 }
11596 Ok(last)
11597 }
11598 }
11599 }
11600
11601 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
11604 match op {
11605 BinOp::Add => Some("+"),
11606 BinOp::Sub => Some("-"),
11607 BinOp::Mul => Some("*"),
11608 BinOp::Div => Some("/"),
11609 BinOp::Mod => Some("%"),
11610 BinOp::Pow => Some("**"),
11611 BinOp::Concat => Some("."),
11612 BinOp::StrEq => Some("eq"),
11613 BinOp::NumEq => Some("=="),
11614 BinOp::StrNe => Some("ne"),
11615 BinOp::NumNe => Some("!="),
11616 BinOp::StrLt => Some("lt"),
11617 BinOp::StrGt => Some("gt"),
11618 BinOp::StrLe => Some("le"),
11619 BinOp::StrGe => Some("ge"),
11620 BinOp::NumLt => Some("<"),
11621 BinOp::NumGt => Some(">"),
11622 BinOp::NumLe => Some("<="),
11623 BinOp::NumGe => Some(">="),
11624 BinOp::Spaceship => Some("<=>"),
11625 BinOp::StrCmp => Some("cmp"),
11626 _ => None,
11627 }
11628 }
11629
11630 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
11632 map.get("").or_else(|| map.get("\"\""))
11633 }
11634
11635 pub(crate) fn stringify_value(
11637 &mut self,
11638 v: PerlValue,
11639 line: usize,
11640 ) -> Result<String, FlowOrError> {
11641 if let Some(r) = self.try_overload_stringify(&v, line) {
11642 let pv = r?;
11643 return Ok(pv.to_string());
11644 }
11645 Ok(v.to_string())
11646 }
11647
11648 pub(crate) fn perl_sprintf_stringify(
11650 &mut self,
11651 fmt: &str,
11652 args: &[PerlValue],
11653 line: usize,
11654 ) -> Result<String, FlowOrError> {
11655 perl_sprintf_format_with(fmt, args, |v| self.stringify_value(v.clone(), line))
11656 }
11657
11658 pub(crate) fn render_format_template(
11660 &mut self,
11661 tmpl: &crate::format::FormatTemplate,
11662 line: usize,
11663 ) -> Result<String, FlowOrError> {
11664 use crate::format::{FormatRecord, PictureSegment};
11665 let mut buf = String::new();
11666 for rec in &tmpl.records {
11667 match rec {
11668 FormatRecord::Literal(s) => {
11669 buf.push_str(s);
11670 buf.push('\n');
11671 }
11672 FormatRecord::Picture { segments, exprs } => {
11673 let mut vals: Vec<String> = Vec::new();
11674 for e in exprs {
11675 let v = self.eval_expr(e)?;
11676 vals.push(self.stringify_value(v, line)?);
11677 }
11678 let mut vi = 0usize;
11679 let mut line_out = String::new();
11680 for seg in segments {
11681 match seg {
11682 PictureSegment::Literal(t) => line_out.push_str(t),
11683 PictureSegment::Field {
11684 width,
11685 align,
11686 kind: _,
11687 } => {
11688 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
11689 vi += 1;
11690 line_out.push_str(&crate::format::pad_field(s, *width, *align));
11691 }
11692 }
11693 }
11694 buf.push_str(line_out.trim_end());
11695 buf.push('\n');
11696 }
11697 }
11698 }
11699 Ok(buf)
11700 }
11701
11702 pub(crate) fn resolve_write_output_handle(
11704 &self,
11705 v: &PerlValue,
11706 line: usize,
11707 ) -> PerlResult<String> {
11708 if let Some(n) = v.as_io_handle_name() {
11709 let n = self.resolve_io_handle_name(&n);
11710 if self.is_bound_handle(&n) {
11711 return Ok(n);
11712 }
11713 }
11714 if let Some(s) = v.as_str() {
11715 if self.is_bound_handle(&s) {
11716 return Ok(self.resolve_io_handle_name(&s));
11717 }
11718 }
11719 let s = v.to_string();
11720 if self.is_bound_handle(&s) {
11721 return Ok(self.resolve_io_handle_name(&s));
11722 }
11723 Err(PerlError::runtime(
11724 format!("write: invalid or unopened filehandle {}", s),
11725 line,
11726 ))
11727 }
11728
11729 pub(crate) fn write_format_execute(
11733 &mut self,
11734 args: &[PerlValue],
11735 line: usize,
11736 ) -> PerlResult<PerlValue> {
11737 let handle_name = match args.len() {
11738 0 => self.default_print_handle.clone(),
11739 1 => self.resolve_write_output_handle(&args[0], line)?,
11740 _ => {
11741 return Err(PerlError::runtime("write: too many arguments", line));
11742 }
11743 };
11744 let pkg = self.current_package();
11745 let mut fmt_name = self.scope.get_scalar("~").to_string();
11746 if fmt_name.is_empty() {
11747 fmt_name = "STDOUT".to_string();
11748 }
11749 let key = format!("{}::{}", pkg, fmt_name);
11750 let tmpl = self
11751 .format_templates
11752 .get(&key)
11753 .map(Arc::clone)
11754 .ok_or_else(|| {
11755 PerlError::runtime(
11756 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
11757 line,
11758 )
11759 })?;
11760 let out = self
11761 .render_format_template(&tmpl, line)
11762 .map_err(|e| match e {
11763 FlowOrError::Error(e) => e,
11764 FlowOrError::Flow(_) => PerlError::runtime("write: unexpected control flow", line),
11765 })?;
11766 self.write_formatted_print(handle_name.as_str(), &out, line)?;
11767 Ok(PerlValue::integer(1))
11768 }
11769
11770 pub(crate) fn try_overload_stringify(
11771 &mut self,
11772 v: &PerlValue,
11773 line: usize,
11774 ) -> Option<ExecResult> {
11775 if let Some(c) = v.as_class_inst() {
11777 let method_name = c
11778 .def
11779 .method("stringify")
11780 .or_else(|| c.def.method("\"\""))
11781 .filter(|m| m.body.is_some())?;
11782 let body = method_name.body.clone().unwrap();
11783 let params = method_name.params.clone();
11784 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
11785 }
11786 let br = v.as_blessed_ref()?;
11787 let class = br.class.clone();
11788 let map = self.overload_table.get(&class)?;
11789 let sub_short = Self::overload_stringify_method(map)?;
11790 let fq = format!("{}::{}", class, sub_short);
11791 let sub = self.subs.get(&fq)?.clone();
11792 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
11793 }
11794
11795 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
11797 match key {
11798 "+" => Some("op_add"),
11799 "-" => Some("op_sub"),
11800 "*" => Some("op_mul"),
11801 "/" => Some("op_div"),
11802 "%" => Some("op_mod"),
11803 "**" => Some("op_pow"),
11804 "." => Some("op_concat"),
11805 "==" => Some("op_eq"),
11806 "!=" => Some("op_ne"),
11807 "<" => Some("op_lt"),
11808 ">" => Some("op_gt"),
11809 "<=" => Some("op_le"),
11810 ">=" => Some("op_ge"),
11811 "<=>" => Some("op_spaceship"),
11812 "eq" => Some("op_str_eq"),
11813 "ne" => Some("op_str_ne"),
11814 "lt" => Some("op_str_lt"),
11815 "gt" => Some("op_str_gt"),
11816 "le" => Some("op_str_le"),
11817 "ge" => Some("op_str_ge"),
11818 "cmp" => Some("op_cmp"),
11819 _ => None,
11820 }
11821 }
11822
11823 pub(crate) fn try_overload_binop(
11824 &mut self,
11825 op: BinOp,
11826 lv: &PerlValue,
11827 rv: &PerlValue,
11828 line: usize,
11829 ) -> Option<ExecResult> {
11830 let key = Self::overload_key_for_binop(op)?;
11831 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
11833 (Some(c.def.clone()), lv.clone(), rv.clone())
11834 } else if let Some(c) = rv.as_class_inst() {
11835 (Some(c.def.clone()), rv.clone(), lv.clone())
11836 } else {
11837 (None, lv.clone(), rv.clone())
11838 };
11839 if let Some(ref def) = ci_def {
11840 if let Some(method_name) = Self::overload_method_name_for_key(key) {
11841 if let Some((m, _)) = self.find_class_method(def, method_name) {
11842 if let Some(ref body) = m.body {
11843 let params = m.params.clone();
11844 return Some(self.call_class_method(
11845 body,
11846 ¶ms,
11847 vec![invocant, other],
11848 line,
11849 ));
11850 }
11851 }
11852 }
11853 }
11854 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
11856 (br.class.clone(), lv.clone(), rv.clone())
11857 } else if let Some(br) = rv.as_blessed_ref() {
11858 (br.class.clone(), rv.clone(), lv.clone())
11859 } else {
11860 return None;
11861 };
11862 let map = self.overload_table.get(&class)?;
11863 let sub_short = if let Some(s) = map.get(key) {
11864 s.clone()
11865 } else if let Some(nm) = map.get("nomethod") {
11866 let fq = format!("{}::{}", class, nm);
11867 let sub = self.subs.get(&fq)?.clone();
11868 return Some(self.call_sub(
11869 &sub,
11870 vec![invocant, other, PerlValue::string(key.to_string())],
11871 WantarrayCtx::Scalar,
11872 line,
11873 ));
11874 } else {
11875 return None;
11876 };
11877 let fq = format!("{}::{}", class, sub_short);
11878 let sub = self.subs.get(&fq)?.clone();
11879 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
11880 }
11881
11882 pub(crate) fn try_overload_unary_dispatch(
11884 &mut self,
11885 op_key: &str,
11886 val: &PerlValue,
11887 line: usize,
11888 ) -> Option<ExecResult> {
11889 if let Some(c) = val.as_class_inst() {
11891 let method_name = match op_key {
11892 "neg" => "op_neg",
11893 "bool" => "op_bool",
11894 "abs" => "op_abs",
11895 "0+" => "op_numify",
11896 _ => return None,
11897 };
11898 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
11899 if let Some(ref body) = m.body {
11900 let params = m.params.clone();
11901 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
11902 }
11903 }
11904 return None;
11905 }
11906 let br = val.as_blessed_ref()?;
11908 let class = br.class.clone();
11909 let map = self.overload_table.get(&class)?;
11910 if let Some(s) = map.get(op_key) {
11911 let fq = format!("{}::{}", class, s);
11912 let sub = self.subs.get(&fq)?.clone();
11913 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
11914 }
11915 if let Some(nm) = map.get("nomethod") {
11916 let fq = format!("{}::{}", class, nm);
11917 let sub = self.subs.get(&fq)?.clone();
11918 return Some(self.call_sub(
11919 &sub,
11920 vec![val.clone(), PerlValue::string(op_key.to_string())],
11921 WantarrayCtx::Scalar,
11922 line,
11923 ));
11924 }
11925 None
11926 }
11927
11928 #[inline]
11929 fn eval_binop(
11930 &mut self,
11931 op: BinOp,
11932 lv: &PerlValue,
11933 rv: &PerlValue,
11934 _line: usize,
11935 ) -> ExecResult {
11936 Ok(match op {
11937 BinOp::Add => {
11940 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
11941 PerlValue::integer(a.wrapping_add(b))
11942 } else {
11943 PerlValue::float(lv.to_number() + rv.to_number())
11944 }
11945 }
11946 BinOp::Sub => {
11947 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
11948 PerlValue::integer(a.wrapping_sub(b))
11949 } else {
11950 PerlValue::float(lv.to_number() - rv.to_number())
11951 }
11952 }
11953 BinOp::Mul => {
11954 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
11955 PerlValue::integer(a.wrapping_mul(b))
11956 } else {
11957 PerlValue::float(lv.to_number() * rv.to_number())
11958 }
11959 }
11960 BinOp::Div => {
11961 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
11962 if b == 0 {
11963 return Err(PerlError::runtime("Illegal division by zero", _line).into());
11964 }
11965 if a % b == 0 {
11966 PerlValue::integer(a / b)
11967 } else {
11968 PerlValue::float(a as f64 / b as f64)
11969 }
11970 } else {
11971 let d = rv.to_number();
11972 if d == 0.0 {
11973 return Err(PerlError::runtime("Illegal division by zero", _line).into());
11974 }
11975 PerlValue::float(lv.to_number() / d)
11976 }
11977 }
11978 BinOp::Mod => {
11979 let d = rv.to_int();
11980 if d == 0 {
11981 return Err(PerlError::runtime("Illegal modulus zero", _line).into());
11982 }
11983 PerlValue::integer(lv.to_int() % d)
11984 }
11985 BinOp::Pow => {
11986 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
11987 let int_pow = (b >= 0)
11988 .then(|| u32::try_from(b).ok())
11989 .flatten()
11990 .and_then(|bu| a.checked_pow(bu))
11991 .map(PerlValue::integer);
11992 int_pow.unwrap_or_else(|| PerlValue::float(lv.to_number().powf(rv.to_number())))
11993 } else {
11994 PerlValue::float(lv.to_number().powf(rv.to_number()))
11995 }
11996 }
11997 BinOp::Concat => {
11998 let mut s = String::new();
11999 lv.append_to(&mut s);
12000 rv.append_to(&mut s);
12001 PerlValue::string(s)
12002 }
12003 BinOp::NumEq => {
12004 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
12006 if a.def.name != b.def.name {
12007 PerlValue::integer(0)
12008 } else {
12009 let av = a.get_values();
12010 let bv = b.get_values();
12011 let eq = av.len() == bv.len()
12012 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
12013 PerlValue::integer(if eq { 1 } else { 0 })
12014 }
12015 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12016 PerlValue::integer(if a == b { 1 } else { 0 })
12017 } else {
12018 PerlValue::integer(if lv.to_number() == rv.to_number() {
12019 1
12020 } else {
12021 0
12022 })
12023 }
12024 }
12025 BinOp::NumNe => {
12026 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12027 PerlValue::integer(if a != b { 1 } else { 0 })
12028 } else {
12029 PerlValue::integer(if lv.to_number() != rv.to_number() {
12030 1
12031 } else {
12032 0
12033 })
12034 }
12035 }
12036 BinOp::NumLt => {
12037 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12038 PerlValue::integer(if a < b { 1 } else { 0 })
12039 } else {
12040 PerlValue::integer(if lv.to_number() < rv.to_number() {
12041 1
12042 } else {
12043 0
12044 })
12045 }
12046 }
12047 BinOp::NumGt => {
12048 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12049 PerlValue::integer(if a > b { 1 } else { 0 })
12050 } else {
12051 PerlValue::integer(if lv.to_number() > rv.to_number() {
12052 1
12053 } else {
12054 0
12055 })
12056 }
12057 }
12058 BinOp::NumLe => {
12059 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12060 PerlValue::integer(if a <= b { 1 } else { 0 })
12061 } else {
12062 PerlValue::integer(if lv.to_number() <= rv.to_number() {
12063 1
12064 } else {
12065 0
12066 })
12067 }
12068 }
12069 BinOp::NumGe => {
12070 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12071 PerlValue::integer(if a >= b { 1 } else { 0 })
12072 } else {
12073 PerlValue::integer(if lv.to_number() >= rv.to_number() {
12074 1
12075 } else {
12076 0
12077 })
12078 }
12079 }
12080 BinOp::Spaceship => {
12081 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
12082 PerlValue::integer(if a < b {
12083 -1
12084 } else if a > b {
12085 1
12086 } else {
12087 0
12088 })
12089 } else {
12090 let a = lv.to_number();
12091 let b = rv.to_number();
12092 PerlValue::integer(if a < b {
12093 -1
12094 } else if a > b {
12095 1
12096 } else {
12097 0
12098 })
12099 }
12100 }
12101 BinOp::StrEq => PerlValue::integer(if lv.to_string() == rv.to_string() {
12102 1
12103 } else {
12104 0
12105 }),
12106 BinOp::StrNe => PerlValue::integer(if lv.to_string() != rv.to_string() {
12107 1
12108 } else {
12109 0
12110 }),
12111 BinOp::StrLt => PerlValue::integer(if lv.to_string() < rv.to_string() {
12112 1
12113 } else {
12114 0
12115 }),
12116 BinOp::StrGt => PerlValue::integer(if lv.to_string() > rv.to_string() {
12117 1
12118 } else {
12119 0
12120 }),
12121 BinOp::StrLe => PerlValue::integer(if lv.to_string() <= rv.to_string() {
12122 1
12123 } else {
12124 0
12125 }),
12126 BinOp::StrGe => PerlValue::integer(if lv.to_string() >= rv.to_string() {
12127 1
12128 } else {
12129 0
12130 }),
12131 BinOp::StrCmp => {
12132 let cmp = lv.to_string().cmp(&rv.to_string());
12133 PerlValue::integer(match cmp {
12134 std::cmp::Ordering::Less => -1,
12135 std::cmp::Ordering::Greater => 1,
12136 std::cmp::Ordering::Equal => 0,
12137 })
12138 }
12139 BinOp::BitAnd => {
12140 if let Some(s) = crate::value::set_intersection(lv, rv) {
12141 s
12142 } else {
12143 PerlValue::integer(lv.to_int() & rv.to_int())
12144 }
12145 }
12146 BinOp::BitOr => {
12147 if let Some(s) = crate::value::set_union(lv, rv) {
12148 s
12149 } else {
12150 PerlValue::integer(lv.to_int() | rv.to_int())
12151 }
12152 }
12153 BinOp::BitXor => PerlValue::integer(lv.to_int() ^ rv.to_int()),
12154 BinOp::ShiftLeft => PerlValue::integer(lv.to_int() << rv.to_int()),
12155 BinOp::ShiftRight => PerlValue::integer(lv.to_int() >> rv.to_int()),
12156 BinOp::LogAnd
12158 | BinOp::LogOr
12159 | BinOp::DefinedOr
12160 | BinOp::LogAndWord
12161 | BinOp::LogOrWord => unreachable!(),
12162 BinOp::BindMatch | BinOp::BindNotMatch => {
12163 unreachable!("regex bind handled in eval_expr BinOp arm")
12164 }
12165 })
12166 }
12167
12168 fn err_modify_symbolic_aggregate_deref_inc_dec(
12172 kind: Sigil,
12173 is_pre: bool,
12174 is_inc: bool,
12175 line: usize,
12176 ) -> FlowOrError {
12177 let agg = match kind {
12178 Sigil::Array => "array",
12179 Sigil::Hash => "hash",
12180 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
12181 };
12182 let op = match (is_pre, is_inc) {
12183 (true, true) => "preincrement (++)",
12184 (true, false) => "predecrement (--)",
12185 (false, true) => "postincrement (++)",
12186 (false, false) => "postdecrement (--)",
12187 };
12188 FlowOrError::Error(PerlError::runtime(
12189 format!("Can't modify {agg} dereference in {op}"),
12190 line,
12191 ))
12192 }
12193
12194 pub(crate) fn symbolic_scalar_ref_postfix(
12196 &mut self,
12197 ref_val: PerlValue,
12198 decrement: bool,
12199 line: usize,
12200 ) -> Result<PerlValue, FlowOrError> {
12201 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
12202 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12203 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
12204 Ok(old)
12205 }
12206
12207 pub(crate) fn assign_scalar_ref_deref(
12210 &mut self,
12211 ref_val: PerlValue,
12212 val: PerlValue,
12213 line: usize,
12214 ) -> ExecResult {
12215 if let Some(name) = ref_val.as_scalar_binding_name() {
12216 self.set_special_var(&name, &val)
12217 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12218 return Ok(PerlValue::UNDEF);
12219 }
12220 if let Some(r) = ref_val.as_scalar_ref() {
12221 *r.write() = val;
12222 return Ok(PerlValue::UNDEF);
12223 }
12224 Err(PerlError::runtime("Can't assign to non-scalar reference", line).into())
12225 }
12226
12227 pub(crate) fn assign_symbolic_array_ref_deref(
12229 &mut self,
12230 ref_val: PerlValue,
12231 val: PerlValue,
12232 line: usize,
12233 ) -> ExecResult {
12234 if let Some(a) = ref_val.as_array_ref() {
12235 *a.write() = val.to_list();
12236 return Ok(PerlValue::UNDEF);
12237 }
12238 if let Some(name) = ref_val.as_array_binding_name() {
12239 self.scope
12240 .set_array(&name, val.to_list())
12241 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12242 return Ok(PerlValue::UNDEF);
12243 }
12244 if let Some(s) = ref_val.as_str() {
12245 if self.strict_refs {
12246 return Err(PerlError::runtime(
12247 format!(
12248 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
12249 s
12250 ),
12251 line,
12252 )
12253 .into());
12254 }
12255 self.scope
12256 .set_array(&s, val.to_list())
12257 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12258 return Ok(PerlValue::UNDEF);
12259 }
12260 Err(PerlError::runtime("Can't assign to non-array reference", line).into())
12261 }
12262
12263 pub(crate) fn assign_symbolic_typeglob_ref_deref(
12266 &mut self,
12267 ref_val: PerlValue,
12268 val: PerlValue,
12269 line: usize,
12270 ) -> ExecResult {
12271 let lhs_name = if let Some(s) = ref_val.as_str() {
12272 if self.strict_refs {
12273 return Err(PerlError::runtime(
12274 format!(
12275 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
12276 s
12277 ),
12278 line,
12279 )
12280 .into());
12281 }
12282 s.to_string()
12283 } else {
12284 return Err(
12285 PerlError::runtime("Can't assign to non-glob symbolic reference", line).into(),
12286 );
12287 };
12288 let is_coderef = val.as_code_ref().is_some()
12289 || val
12290 .as_scalar_ref()
12291 .map(|r| r.read().as_code_ref().is_some())
12292 .unwrap_or(false);
12293 if is_coderef {
12294 return self.assign_typeglob_value(&lhs_name, val, line);
12295 }
12296 let rhs_key = val.to_string();
12297 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
12298 .map_err(FlowOrError::Error)?;
12299 Ok(PerlValue::UNDEF)
12300 }
12301
12302 pub(crate) fn assign_symbolic_hash_ref_deref(
12304 &mut self,
12305 ref_val: PerlValue,
12306 val: PerlValue,
12307 line: usize,
12308 ) -> ExecResult {
12309 let items = val.to_list();
12310 let mut map = IndexMap::new();
12311 let mut i = 0;
12312 while i + 1 < items.len() {
12313 map.insert(items[i].to_string(), items[i + 1].clone());
12314 i += 2;
12315 }
12316 if let Some(h) = ref_val.as_hash_ref() {
12317 *h.write() = map;
12318 return Ok(PerlValue::UNDEF);
12319 }
12320 if let Some(name) = ref_val.as_hash_binding_name() {
12321 self.touch_env_hash(&name);
12322 self.scope
12323 .set_hash(&name, map)
12324 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12325 return Ok(PerlValue::UNDEF);
12326 }
12327 if let Some(s) = ref_val.as_str() {
12328 if self.strict_refs {
12329 return Err(PerlError::runtime(
12330 format!(
12331 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
12332 s
12333 ),
12334 line,
12335 )
12336 .into());
12337 }
12338 self.touch_env_hash(&s);
12339 self.scope
12340 .set_hash(&s, map)
12341 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12342 return Ok(PerlValue::UNDEF);
12343 }
12344 Err(PerlError::runtime("Can't assign to non-hash reference", line).into())
12345 }
12346
12347 pub(crate) fn assign_arrow_hash_deref(
12349 &mut self,
12350 container: PerlValue,
12351 key: String,
12352 val: PerlValue,
12353 line: usize,
12354 ) -> ExecResult {
12355 if let Some(b) = container.as_blessed_ref() {
12356 let mut data = b.data.write();
12357 if let Some(r) = data.as_hash_ref() {
12358 r.write().insert(key, val);
12359 return Ok(PerlValue::UNDEF);
12360 }
12361 if let Some(mut map) = data.as_hash_map() {
12362 map.insert(key, val);
12363 *data = PerlValue::hash(map);
12364 return Ok(PerlValue::UNDEF);
12365 }
12366 return Err(PerlError::runtime("Can't assign into non-hash blessed ref", line).into());
12367 }
12368 if let Some(r) = container.as_hash_ref() {
12369 r.write().insert(key, val);
12370 return Ok(PerlValue::UNDEF);
12371 }
12372 if let Some(name) = container.as_hash_binding_name() {
12373 self.touch_env_hash(&name);
12374 self.scope
12375 .set_hash_element(&name, &key, val)
12376 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12377 return Ok(PerlValue::UNDEF);
12378 }
12379 Err(PerlError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
12380 }
12381
12382 pub(crate) fn eval_arrow_array_base(
12385 &mut self,
12386 expr: &Expr,
12387 _line: usize,
12388 ) -> Result<PerlValue, FlowOrError> {
12389 match &expr.kind {
12390 ExprKind::Deref {
12391 expr: inner,
12392 kind: Sigil::Array | Sigil::Scalar,
12393 } => self.eval_expr(inner),
12394 _ => self.eval_expr(expr),
12395 }
12396 }
12397
12398 pub(crate) fn eval_arrow_hash_base(
12400 &mut self,
12401 expr: &Expr,
12402 _line: usize,
12403 ) -> Result<PerlValue, FlowOrError> {
12404 match &expr.kind {
12405 ExprKind::Deref {
12406 expr: inner,
12407 kind: Sigil::Scalar,
12408 } => self.eval_expr(inner),
12409 _ => self.eval_expr(expr),
12410 }
12411 }
12412
12413 pub(crate) fn read_arrow_array_element(
12415 &self,
12416 container: PerlValue,
12417 idx: i64,
12418 line: usize,
12419 ) -> Result<PerlValue, FlowOrError> {
12420 if let Some(a) = container.as_array_ref() {
12421 let arr = a.read();
12422 let i = if idx < 0 {
12423 (arr.len() as i64 + idx) as usize
12424 } else {
12425 idx as usize
12426 };
12427 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12428 }
12429 if let Some(name) = container.as_array_binding_name() {
12430 return Ok(self.scope.get_array_element(&name, idx));
12431 }
12432 if let Some(arr) = container.as_array_vec() {
12433 let i = if idx < 0 {
12434 (arr.len() as i64 + idx) as usize
12435 } else {
12436 idx as usize
12437 };
12438 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12439 }
12440 if let Some(b) = container.as_blessed_ref() {
12444 let inner = b.data.read().clone();
12445 if let Some(a) = inner.as_array_ref() {
12446 let arr = a.read();
12447 let i = if idx < 0 {
12448 (arr.len() as i64 + idx) as usize
12449 } else {
12450 idx as usize
12451 };
12452 return Ok(arr.get(i).cloned().unwrap_or(PerlValue::UNDEF));
12453 }
12454 }
12455 Err(PerlError::runtime("Can't use arrow deref on non-array-ref", line).into())
12456 }
12457
12458 pub(crate) fn read_arrow_hash_element(
12460 &mut self,
12461 container: PerlValue,
12462 key: &str,
12463 line: usize,
12464 ) -> Result<PerlValue, FlowOrError> {
12465 if let Some(r) = container.as_hash_ref() {
12466 let h = r.read();
12467 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12468 }
12469 if let Some(name) = container.as_hash_binding_name() {
12470 self.touch_env_hash(&name);
12471 return Ok(self.scope.get_hash_element(&name, key));
12472 }
12473 if let Some(b) = container.as_blessed_ref() {
12474 let data = b.data.read();
12475 if let Some(v) = data.hash_get(key) {
12476 return Ok(v);
12477 }
12478 if let Some(r) = data.as_hash_ref() {
12479 let h = r.read();
12480 return Ok(h.get(key).cloned().unwrap_or(PerlValue::UNDEF));
12481 }
12482 return Err(PerlError::runtime(
12483 "Can't access hash field on non-hash blessed ref",
12484 line,
12485 )
12486 .into());
12487 }
12488 Err(PerlError::runtime("Can't use arrow deref on non-hash-ref", line).into())
12489 }
12490
12491 pub(crate) fn arrow_array_postfix(
12493 &mut self,
12494 container: PerlValue,
12495 idx: i64,
12496 decrement: bool,
12497 line: usize,
12498 ) -> Result<PerlValue, FlowOrError> {
12499 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
12500 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12501 self.assign_arrow_array_deref(container, idx, new_val, line)?;
12502 Ok(old)
12503 }
12504
12505 pub(crate) fn arrow_hash_postfix(
12507 &mut self,
12508 container: PerlValue,
12509 key: String,
12510 decrement: bool,
12511 line: usize,
12512 ) -> Result<PerlValue, FlowOrError> {
12513 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
12514 let new_val = PerlValue::integer(old.to_int() + if decrement { -1 } else { 1 });
12515 self.assign_arrow_hash_deref(container, key, new_val, line)?;
12516 Ok(old)
12517 }
12518
12519 pub(crate) fn resolve_bareword_rvalue(
12527 &mut self,
12528 name: &str,
12529 want: WantarrayCtx,
12530 line: usize,
12531 ) -> Result<PerlValue, FlowOrError> {
12532 if name == "__PACKAGE__" {
12533 return Ok(PerlValue::string(self.current_package()));
12534 }
12535 if let Some(sub) = self.resolve_sub_by_name(name) {
12536 return self.call_sub(&sub, vec![], want, line);
12537 }
12538 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
12540 return r.map_err(Into::into);
12541 }
12542 Ok(PerlValue::string(name.to_string()))
12543 }
12544
12545 pub(crate) fn arrow_array_slice_values(
12549 &mut self,
12550 container: PerlValue,
12551 indices: &[i64],
12552 line: usize,
12553 ) -> Result<PerlValue, FlowOrError> {
12554 let mut out = Vec::with_capacity(indices.len());
12555 for &idx in indices {
12556 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
12557 out.push(v);
12558 }
12559 Ok(PerlValue::array(out))
12560 }
12561
12562 pub(crate) fn assign_arrow_array_slice(
12566 &mut self,
12567 container: PerlValue,
12568 indices: Vec<i64>,
12569 val: PerlValue,
12570 line: usize,
12571 ) -> Result<PerlValue, FlowOrError> {
12572 if indices.is_empty() {
12573 return Err(PerlError::runtime("assign to empty array slice", line).into());
12574 }
12575 let vals = val.to_list();
12576 for (i, idx) in indices.iter().enumerate() {
12577 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
12578 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
12579 }
12580 Ok(PerlValue::UNDEF)
12581 }
12582
12583 pub(crate) fn flatten_array_slice_index_specs(
12585 &mut self,
12586 indices: &[Expr],
12587 ) -> Result<Vec<i64>, FlowOrError> {
12588 let mut out = Vec::new();
12589 for idx_expr in indices {
12590 let v = if matches!(idx_expr.kind, ExprKind::Range { .. }) {
12591 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
12592 } else {
12593 self.eval_expr(idx_expr)?
12594 };
12595 if let Some(list) = v.as_array_vec() {
12596 for idx in list {
12597 out.push(idx.to_int());
12598 }
12599 } else {
12600 out.push(v.to_int());
12601 }
12602 }
12603 Ok(out)
12604 }
12605
12606 pub(crate) fn assign_named_array_slice(
12608 &mut self,
12609 stash_array_name: &str,
12610 indices: Vec<i64>,
12611 val: PerlValue,
12612 line: usize,
12613 ) -> Result<PerlValue, FlowOrError> {
12614 if indices.is_empty() {
12615 return Err(PerlError::runtime("assign to empty array slice", line).into());
12616 }
12617 let vals = val.to_list();
12618 for (i, idx) in indices.iter().enumerate() {
12619 let v = vals.get(i).cloned().unwrap_or(PerlValue::UNDEF);
12620 self.scope
12621 .set_array_element(stash_array_name, *idx, v)
12622 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12623 }
12624 Ok(PerlValue::UNDEF)
12625 }
12626
12627 pub(crate) fn compound_assign_arrow_array_slice(
12630 &mut self,
12631 container: PerlValue,
12632 indices: Vec<i64>,
12633 op: BinOp,
12634 rhs: PerlValue,
12635 line: usize,
12636 ) -> Result<PerlValue, FlowOrError> {
12637 if indices.is_empty() {
12638 return Err(PerlError::runtime("assign to empty array slice", line).into());
12639 }
12640 let last_idx = *indices.last().expect("non-empty indices");
12641 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
12642 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
12643 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
12644 Ok(new_val)
12645 }
12646
12647 pub(crate) fn arrow_array_slice_inc_dec(
12652 &mut self,
12653 container: PerlValue,
12654 indices: Vec<i64>,
12655 kind: u8,
12656 line: usize,
12657 ) -> Result<PerlValue, FlowOrError> {
12658 if indices.is_empty() {
12659 return Err(
12660 PerlError::runtime("array slice increment needs at least one index", line).into(),
12661 );
12662 }
12663 let last_idx = *indices.last().expect("non-empty indices");
12664 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
12665 let new_val = if kind & 1 == 0 {
12666 PerlValue::integer(last_old.to_int() + 1)
12667 } else {
12668 PerlValue::integer(last_old.to_int() - 1)
12669 };
12670 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
12671 Ok(if kind < 2 { new_val } else { last_old })
12672 }
12673
12674 pub(crate) fn named_array_slice_inc_dec(
12677 &mut self,
12678 stash_array_name: &str,
12679 indices: Vec<i64>,
12680 kind: u8,
12681 line: usize,
12682 ) -> Result<PerlValue, FlowOrError> {
12683 let last_idx = *indices.last().ok_or_else(|| {
12684 PerlError::runtime("array slice increment needs at least one index", line)
12685 })?;
12686 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
12687 let new_val = if kind & 1 == 0 {
12688 PerlValue::integer(last_old.to_int() + 1)
12689 } else {
12690 PerlValue::integer(last_old.to_int() - 1)
12691 };
12692 self.scope
12693 .set_array_element(stash_array_name, last_idx, new_val.clone())
12694 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12695 Ok(if kind < 2 { new_val } else { last_old })
12696 }
12697
12698 pub(crate) fn compound_assign_named_array_slice(
12700 &mut self,
12701 stash_array_name: &str,
12702 indices: Vec<i64>,
12703 op: BinOp,
12704 rhs: PerlValue,
12705 line: usize,
12706 ) -> Result<PerlValue, FlowOrError> {
12707 if indices.is_empty() {
12708 return Err(PerlError::runtime("assign to empty array slice", line).into());
12709 }
12710 let last_idx = *indices.last().expect("non-empty indices");
12711 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
12712 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
12713 self.scope
12714 .set_array_element(stash_array_name, last_idx, new_val.clone())
12715 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12716 Ok(new_val)
12717 }
12718
12719 pub(crate) fn assign_arrow_array_deref(
12721 &mut self,
12722 container: PerlValue,
12723 idx: i64,
12724 val: PerlValue,
12725 line: usize,
12726 ) -> ExecResult {
12727 if let Some(a) = container.as_array_ref() {
12728 let mut arr = a.write();
12729 let i = if idx < 0 {
12730 (arr.len() as i64 + idx) as usize
12731 } else {
12732 idx as usize
12733 };
12734 if i >= arr.len() {
12735 arr.resize(i + 1, PerlValue::UNDEF);
12736 }
12737 arr[i] = val;
12738 return Ok(PerlValue::UNDEF);
12739 }
12740 if let Some(name) = container.as_array_binding_name() {
12741 self.scope
12742 .set_array_element(&name, idx, val)
12743 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
12744 return Ok(PerlValue::UNDEF);
12745 }
12746 Err(PerlError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
12747 }
12748
12749 pub(crate) fn assign_typeglob_value(
12751 &mut self,
12752 name: &str,
12753 val: PerlValue,
12754 line: usize,
12755 ) -> ExecResult {
12756 let sub = if let Some(c) = val.as_code_ref() {
12757 Some(c)
12758 } else if let Some(r) = val.as_scalar_ref() {
12759 r.read().as_code_ref().map(|c| Arc::clone(&c))
12760 } else {
12761 None
12762 };
12763 if let Some(sub) = sub {
12764 let lhs_sub = self.qualify_typeglob_sub_key(name);
12765 self.subs.insert(lhs_sub, sub);
12766 return Ok(PerlValue::UNDEF);
12767 }
12768 Err(PerlError::runtime(
12769 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
12770 line,
12771 )
12772 .into())
12773 }
12774
12775 fn assign_value(&mut self, target: &Expr, val: PerlValue) -> ExecResult {
12776 match &target.kind {
12777 ExprKind::ScalarVar(name) => {
12778 let stor = self.tree_scalar_storage_name(name);
12779 if self.scope.is_scalar_frozen(&stor) {
12780 return Err(FlowOrError::Error(PerlError::runtime(
12781 format!("Modification of a frozen value: ${}", name),
12782 target.line,
12783 )));
12784 }
12785 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
12786 let class = obj
12787 .as_blessed_ref()
12788 .map(|b| b.class.clone())
12789 .unwrap_or_default();
12790 let full = format!("{}::STORE", class);
12791 if let Some(sub) = self.subs.get(&full).cloned() {
12792 let arg_vals = vec![obj, val];
12793 return match self.call_sub(
12794 &sub,
12795 arg_vals,
12796 WantarrayCtx::Scalar,
12797 target.line,
12798 ) {
12799 Ok(_) => Ok(PerlValue::UNDEF),
12800 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
12801 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
12802 };
12803 }
12804 }
12805 self.set_special_var(&stor, &val)
12806 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
12807 Ok(PerlValue::UNDEF)
12808 }
12809 ExprKind::ArrayVar(name) => {
12810 if self.scope.is_array_frozen(name) {
12811 return Err(PerlError::runtime(
12812 format!("Modification of a frozen value: @{}", name),
12813 target.line,
12814 )
12815 .into());
12816 }
12817 if self.strict_vars
12818 && !name.contains("::")
12819 && !self.scope.array_binding_exists(name)
12820 {
12821 return Err(PerlError::runtime(
12822 format!(
12823 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
12824 name, name
12825 ),
12826 target.line,
12827 )
12828 .into());
12829 }
12830 self.scope.set_array(name, val.to_list())?;
12831 Ok(PerlValue::UNDEF)
12832 }
12833 ExprKind::HashVar(name) => {
12834 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
12835 {
12836 return Err(PerlError::runtime(
12837 format!(
12838 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
12839 name, name
12840 ),
12841 target.line,
12842 )
12843 .into());
12844 }
12845 let items = val.to_list();
12846 let mut map = IndexMap::new();
12847 let mut i = 0;
12848 while i + 1 < items.len() {
12849 map.insert(items[i].to_string(), items[i + 1].clone());
12850 i += 2;
12851 }
12852 self.scope.set_hash(name, map)?;
12853 Ok(PerlValue::UNDEF)
12854 }
12855 ExprKind::ArrayElement { array, index } => {
12856 if self.strict_vars
12857 && !array.contains("::")
12858 && !self.scope.array_binding_exists(array)
12859 {
12860 return Err(PerlError::runtime(
12861 format!(
12862 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
12863 array, array
12864 ),
12865 target.line,
12866 )
12867 .into());
12868 }
12869 if self.scope.is_array_frozen(array) {
12870 return Err(PerlError::runtime(
12871 format!("Modification of a frozen value: @{}", array),
12872 target.line,
12873 )
12874 .into());
12875 }
12876 let idx = self.eval_expr(index)?.to_int();
12877 let aname = self.stash_array_name_for_package(array);
12878 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
12879 let class = obj
12880 .as_blessed_ref()
12881 .map(|b| b.class.clone())
12882 .unwrap_or_default();
12883 let full = format!("{}::STORE", class);
12884 if let Some(sub) = self.subs.get(&full).cloned() {
12885 let arg_vals = vec![obj, PerlValue::integer(idx), val];
12886 return match self.call_sub(
12887 &sub,
12888 arg_vals,
12889 WantarrayCtx::Scalar,
12890 target.line,
12891 ) {
12892 Ok(_) => Ok(PerlValue::UNDEF),
12893 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
12894 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
12895 };
12896 }
12897 }
12898 self.scope.set_array_element(&aname, idx, val)?;
12899 Ok(PerlValue::UNDEF)
12900 }
12901 ExprKind::ArraySlice { array, indices } => {
12902 if indices.is_empty() {
12903 return Err(
12904 PerlError::runtime("assign to empty array slice", target.line).into(),
12905 );
12906 }
12907 self.check_strict_array_var(array, target.line)?;
12908 if self.scope.is_array_frozen(array) {
12909 return Err(PerlError::runtime(
12910 format!("Modification of a frozen value: @{}", array),
12911 target.line,
12912 )
12913 .into());
12914 }
12915 let aname = self.stash_array_name_for_package(array);
12916 let flat = self.flatten_array_slice_index_specs(indices)?;
12917 self.assign_named_array_slice(&aname, flat, val, target.line)
12918 }
12919 ExprKind::HashElement { hash, key } => {
12920 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
12921 {
12922 return Err(PerlError::runtime(
12923 format!(
12924 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
12925 hash, hash
12926 ),
12927 target.line,
12928 )
12929 .into());
12930 }
12931 if self.scope.is_hash_frozen(hash) {
12932 return Err(PerlError::runtime(
12933 format!("Modification of a frozen value: %%{}", hash),
12934 target.line,
12935 )
12936 .into());
12937 }
12938 let k = self.eval_expr(key)?.to_string();
12939 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
12940 let class = obj
12941 .as_blessed_ref()
12942 .map(|b| b.class.clone())
12943 .unwrap_or_default();
12944 let full = format!("{}::STORE", class);
12945 if let Some(sub) = self.subs.get(&full).cloned() {
12946 let arg_vals = vec![obj, PerlValue::string(k), val];
12947 return match self.call_sub(
12948 &sub,
12949 arg_vals,
12950 WantarrayCtx::Scalar,
12951 target.line,
12952 ) {
12953 Ok(_) => Ok(PerlValue::UNDEF),
12954 Err(FlowOrError::Flow(_)) => Ok(PerlValue::UNDEF),
12955 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
12956 };
12957 }
12958 }
12959 self.scope.set_hash_element(hash, &k, val)?;
12960 Ok(PerlValue::UNDEF)
12961 }
12962 ExprKind::HashSlice { hash, keys } => {
12963 if keys.is_empty() {
12964 return Err(
12965 PerlError::runtime("assign to empty hash slice", target.line).into(),
12966 );
12967 }
12968 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
12969 {
12970 return Err(PerlError::runtime(
12971 format!(
12972 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
12973 hash, hash
12974 ),
12975 target.line,
12976 )
12977 .into());
12978 }
12979 if self.scope.is_hash_frozen(hash) {
12980 return Err(PerlError::runtime(
12981 format!("Modification of a frozen value: %%{}", hash),
12982 target.line,
12983 )
12984 .into());
12985 }
12986 let mut key_vals = Vec::with_capacity(keys.len());
12987 for key_expr in keys {
12988 let v = if matches!(key_expr.kind, ExprKind::Range { .. }) {
12989 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
12990 } else {
12991 self.eval_expr(key_expr)?
12992 };
12993 key_vals.push(v);
12994 }
12995 self.assign_named_hash_slice(hash, key_vals, val, target.line)
12996 }
12997 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
12998 ExprKind::TypeglobExpr(e) => {
12999 let name = self.eval_expr(e)?.to_string();
13000 let synthetic = Expr {
13001 kind: ExprKind::Typeglob(name),
13002 line: target.line,
13003 };
13004 self.assign_value(&synthetic, val)
13005 }
13006 ExprKind::AnonymousListSlice { source, indices } => {
13007 if let ExprKind::Deref {
13008 expr: inner,
13009 kind: Sigil::Array,
13010 } = &source.kind
13011 {
13012 let container = self.eval_arrow_array_base(inner, target.line)?;
13013 let vals = val.to_list();
13014 let n = indices.len().min(vals.len());
13015 for i in 0..n {
13016 let idx = self.eval_expr(&indices[i])?.to_int();
13017 self.assign_arrow_array_deref(
13018 container.clone(),
13019 idx,
13020 vals[i].clone(),
13021 target.line,
13022 )?;
13023 }
13024 return Ok(PerlValue::UNDEF);
13025 }
13026 Err(
13027 PerlError::runtime("assign to list slice: unsupported base", target.line)
13028 .into(),
13029 )
13030 }
13031 ExprKind::ArrowDeref {
13032 expr,
13033 index,
13034 kind: DerefKind::Hash,
13035 } => {
13036 let key = self.eval_expr(index)?.to_string();
13037 let container = self.eval_expr(expr)?;
13038 self.assign_arrow_hash_deref(container, key, val, target.line)
13039 }
13040 ExprKind::ArrowDeref {
13041 expr,
13042 index,
13043 kind: DerefKind::Array,
13044 } => {
13045 let container = self.eval_arrow_array_base(expr, target.line)?;
13046 if let ExprKind::List(indices) = &index.kind {
13047 let vals = val.to_list();
13048 let n = indices.len().min(vals.len());
13049 for i in 0..n {
13050 let idx = self.eval_expr(&indices[i])?.to_int();
13051 self.assign_arrow_array_deref(
13052 container.clone(),
13053 idx,
13054 vals[i].clone(),
13055 target.line,
13056 )?;
13057 }
13058 return Ok(PerlValue::UNDEF);
13059 }
13060 let idx = self.eval_expr(index)?.to_int();
13061 self.assign_arrow_array_deref(container, idx, val, target.line)
13062 }
13063 ExprKind::HashSliceDeref { container, keys } => {
13064 let href = self.eval_expr(container)?;
13065 let mut key_vals = Vec::with_capacity(keys.len());
13066 for key_expr in keys {
13067 key_vals.push(self.eval_expr(key_expr)?);
13068 }
13069 self.assign_hash_slice_deref(href, key_vals, val, target.line)
13070 }
13071 ExprKind::Deref {
13072 expr,
13073 kind: Sigil::Scalar,
13074 } => {
13075 let ref_val = self.eval_expr(expr)?;
13076 self.assign_scalar_ref_deref(ref_val, val, target.line)
13077 }
13078 ExprKind::Deref {
13079 expr,
13080 kind: Sigil::Array,
13081 } => {
13082 let ref_val = self.eval_expr(expr)?;
13083 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
13084 }
13085 ExprKind::Deref {
13086 expr,
13087 kind: Sigil::Hash,
13088 } => {
13089 let ref_val = self.eval_expr(expr)?;
13090 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
13091 }
13092 ExprKind::Deref {
13093 expr,
13094 kind: Sigil::Typeglob,
13095 } => {
13096 let ref_val = self.eval_expr(expr)?;
13097 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
13098 }
13099 ExprKind::Pos(inner) => {
13100 let key = match inner {
13101 None => "_".to_string(),
13102 Some(expr) => match &expr.kind {
13103 ExprKind::ScalarVar(n) => n.clone(),
13104 _ => self.eval_expr(expr)?.to_string(),
13105 },
13106 };
13107 if val.is_undef() {
13108 self.regex_pos.insert(key, None);
13109 } else {
13110 let u = val.to_int().max(0) as usize;
13111 self.regex_pos.insert(key, Some(u));
13112 }
13113 Ok(PerlValue::UNDEF)
13114 }
13115 ExprKind::Assign { target, .. } => self.assign_value(target, val),
13118 _ => Ok(PerlValue::UNDEF),
13119 }
13120 }
13121
13122 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
13124 (name.starts_with('#') && name.len() > 1)
13125 || name.starts_with('^')
13126 || matches!(
13127 name,
13128 "$$" | "0"
13129 | "!"
13130 | "@"
13131 | "/"
13132 | "\\"
13133 | ","
13134 | "."
13135 | "]"
13136 | ";"
13137 | "ARGV"
13138 | "^I"
13139 | "^D"
13140 | "^P"
13141 | "^S"
13142 | "^W"
13143 | "^O"
13144 | "^T"
13145 | "^V"
13146 | "^E"
13147 | "^H"
13148 | "^WARNING_BITS"
13149 | "^GLOBAL_PHASE"
13150 | "^MATCH"
13151 | "^PREMATCH"
13152 | "^POSTMATCH"
13153 | "^LAST_SUBMATCH_RESULT"
13154 | "<"
13155 | ">"
13156 | "("
13157 | ")"
13158 | "?"
13159 | "|"
13160 | "\""
13161 | "+"
13162 | "%"
13163 | "="
13164 | "-"
13165 | ":"
13166 | "*"
13167 | "INC"
13168 )
13169 || crate::english::is_known_alias(name)
13170 }
13171
13172 #[inline]
13177 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
13178 if !self.english_enabled {
13179 return name;
13180 }
13181 if self
13182 .english_lexical_scalars
13183 .iter()
13184 .any(|s| s.contains(name))
13185 {
13186 return name;
13187 }
13188 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
13189 return short;
13190 }
13191 name
13192 }
13193
13194 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
13196 name.starts_with('^')
13197 || matches!(
13198 name,
13199 "0" | "/"
13200 | "\\"
13201 | ","
13202 | ";"
13203 | "\""
13204 | "%"
13205 | "="
13206 | "-"
13207 | ":"
13208 | "*"
13209 | "INC"
13210 | "^I"
13211 | "^D"
13212 | "^P"
13213 | "^W"
13214 | "^H"
13215 | "^WARNING_BITS"
13216 | "$$"
13217 | "]"
13218 | "^S"
13219 | "ARGV"
13220 | "|"
13221 | "+"
13222 | "?"
13223 | "!"
13224 | "@"
13225 | "."
13226 )
13227 || crate::english::is_known_alias(name)
13228 }
13229
13230 pub(crate) fn get_special_var(&self, name: &str) -> PerlValue {
13231 let name = if !crate::compat_mode() {
13233 match name {
13234 "NR" => ".",
13235 "RS" => "/",
13236 "OFS" => ",",
13237 "ORS" => "\\",
13238 "NF" => {
13239 let len = self.scope.array_len("F");
13240 return PerlValue::integer(len as i64);
13241 }
13242 _ => self.english_scalar_name(name),
13243 }
13244 } else {
13245 self.english_scalar_name(name)
13246 };
13247 match name {
13248 "$$" => PerlValue::integer(std::process::id() as i64),
13249 "_" => self.scope.get_scalar("_"),
13250 "^MATCH" => PerlValue::string(self.last_match.clone()),
13251 "^PREMATCH" => PerlValue::string(self.prematch.clone()),
13252 "^POSTMATCH" => PerlValue::string(self.postmatch.clone()),
13253 "^LAST_SUBMATCH_RESULT" => PerlValue::string(self.last_paren_match.clone()),
13254 "0" => PerlValue::string(self.program_name.clone()),
13255 "!" => PerlValue::errno_dual(self.errno_code, self.errno.clone()),
13256 "@" => {
13257 if let Some(ref v) = self.eval_error_value {
13258 v.clone()
13259 } else {
13260 PerlValue::errno_dual(self.eval_error_code, self.eval_error.clone())
13261 }
13262 }
13263 "/" => match &self.irs {
13264 Some(s) => PerlValue::string(s.clone()),
13265 None => PerlValue::UNDEF,
13266 },
13267 "\\" => PerlValue::string(self.ors.clone()),
13268 "," => PerlValue::string(self.ofs.clone()),
13269 "." => {
13270 if self.last_readline_handle.is_empty() {
13272 if self.line_number == 0 {
13273 PerlValue::UNDEF
13274 } else {
13275 PerlValue::integer(self.line_number)
13276 }
13277 } else {
13278 PerlValue::integer(
13279 *self
13280 .handle_line_numbers
13281 .get(&self.last_readline_handle)
13282 .unwrap_or(&0),
13283 )
13284 }
13285 }
13286 "]" => PerlValue::float(perl_bracket_version()),
13287 ";" => PerlValue::string(self.subscript_sep.clone()),
13288 "ARGV" => PerlValue::string(self.argv_current_file.clone()),
13289 "^I" => PerlValue::string(self.inplace_edit.clone()),
13290 "^D" => PerlValue::integer(self.debug_flags),
13291 "^P" => PerlValue::integer(self.perl_debug_flags),
13292 "^S" => PerlValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
13293 "^W" => PerlValue::integer(if self.warnings { 1 } else { 0 }),
13294 "^O" => PerlValue::string(perl_osname()),
13295 "^T" => PerlValue::integer(self.script_start_time),
13296 "^V" => PerlValue::string(perl_version_v_string()),
13297 "^E" => PerlValue::string(extended_os_error_string()),
13298 "^H" => PerlValue::integer(self.compile_hints),
13299 "^WARNING_BITS" => PerlValue::integer(self.warning_bits),
13300 "^GLOBAL_PHASE" => PerlValue::string(self.global_phase.clone()),
13301 "<" | ">" => PerlValue::integer(unix_id_for_special(name)),
13302 "(" | ")" => PerlValue::string(unix_group_list_for_special(name)),
13303 "?" => PerlValue::integer(self.child_exit_status),
13304 "|" => PerlValue::integer(if self.output_autoflush { 1 } else { 0 }),
13305 "\"" => PerlValue::string(self.list_separator.clone()),
13306 "+" => PerlValue::string(self.last_paren_match.clone()),
13307 "%" => PerlValue::integer(self.format_page_number),
13308 "=" => PerlValue::integer(self.format_lines_per_page),
13309 "-" => PerlValue::integer(self.format_lines_left),
13310 ":" => PerlValue::string(self.format_line_break_chars.clone()),
13311 "*" => PerlValue::integer(if self.multiline_match { 1 } else { 0 }),
13312 "^" => PerlValue::string(self.format_top_name.clone()),
13313 "INC" => PerlValue::integer(self.inc_hook_index),
13314 "^A" => PerlValue::string(self.accumulator_format.clone()),
13315 "^C" => PerlValue::integer(if self.sigint_pending_caret.replace(false) {
13316 1
13317 } else {
13318 0
13319 }),
13320 "^F" => PerlValue::integer(self.max_system_fd),
13321 "^L" => PerlValue::string(self.formfeed_string.clone()),
13322 "^M" => PerlValue::string(self.emergency_memory.clone()),
13323 "^N" => PerlValue::string(self.last_subpattern_name.clone()),
13324 "^X" => PerlValue::string(self.executable_path.clone()),
13325 "^TAINT" | "^TAINTED" => PerlValue::integer(0),
13327 "^UNICODE" => PerlValue::integer(if self.utf8_pragma { 1 } else { 0 }),
13328 "^OPEN" => PerlValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
13329 "^UTF8LOCALE" => PerlValue::integer(0),
13330 "^UTF8CACHE" => PerlValue::integer(-1),
13331 _ if name.starts_with('^') && name.len() > 1 => self
13332 .special_caret_scalars
13333 .get(name)
13334 .cloned()
13335 .unwrap_or(PerlValue::UNDEF),
13336 _ if name.starts_with('#') && name.len() > 1 => {
13337 let arr = &name[1..];
13338 let aname = self.stash_array_name_for_package(arr);
13339 let len = self.scope.array_len(&aname);
13340 PerlValue::integer(len as i64 - 1)
13341 }
13342 _ => self.scope.get_scalar(name),
13343 }
13344 }
13345
13346 pub(crate) fn set_special_var(&mut self, name: &str, val: &PerlValue) -> Result<(), PerlError> {
13347 let name = self.english_scalar_name(name);
13348 match name {
13349 "!" => {
13350 let code = val.to_int() as i32;
13351 self.errno_code = code;
13352 self.errno = if code == 0 {
13353 String::new()
13354 } else {
13355 std::io::Error::from_raw_os_error(code).to_string()
13356 };
13357 }
13358 "@" => {
13359 if let Some((code, msg)) = val.errno_dual_parts() {
13360 self.eval_error_code = code;
13361 self.eval_error = msg;
13362 } else {
13363 self.eval_error = val.to_string();
13364 let mut code = val.to_int() as i32;
13365 if code == 0 && !self.eval_error.is_empty() {
13366 code = 1;
13367 }
13368 self.eval_error_code = code;
13369 }
13370 }
13371 "." => {
13372 let n = val.to_int();
13375 if self.last_readline_handle.is_empty() {
13376 self.line_number = n;
13377 } else {
13378 self.handle_line_numbers
13379 .insert(self.last_readline_handle.clone(), n);
13380 }
13381 }
13382 "0" => self.program_name = val.to_string(),
13383 "/" => {
13384 self.irs = if val.is_undef() {
13385 None
13386 } else {
13387 Some(val.to_string())
13388 }
13389 }
13390 "\\" => self.ors = val.to_string(),
13391 "," => self.ofs = val.to_string(),
13392 ";" => self.subscript_sep = val.to_string(),
13393 "\"" => self.list_separator = val.to_string(),
13394 "%" => self.format_page_number = val.to_int(),
13395 "=" => self.format_lines_per_page = val.to_int(),
13396 "-" => self.format_lines_left = val.to_int(),
13397 ":" => self.format_line_break_chars = val.to_string(),
13398 "*" => self.multiline_match = val.to_int() != 0,
13399 "^" => self.format_top_name = val.to_string(),
13400 "INC" => self.inc_hook_index = val.to_int(),
13401 "^A" => self.accumulator_format = val.to_string(),
13402 "^F" => self.max_system_fd = val.to_int(),
13403 "^L" => self.formfeed_string = val.to_string(),
13404 "^M" => self.emergency_memory = val.to_string(),
13405 "^I" => self.inplace_edit = val.to_string(),
13406 "^D" => self.debug_flags = val.to_int(),
13407 "^P" => self.perl_debug_flags = val.to_int(),
13408 "^W" => self.warnings = val.to_int() != 0,
13409 "^H" => self.compile_hints = val.to_int(),
13410 "^WARNING_BITS" => self.warning_bits = val.to_int(),
13411 "|" => {
13412 self.output_autoflush = val.to_int() != 0;
13413 if self.output_autoflush {
13414 let _ = io::stdout().flush();
13415 }
13416 }
13417 "$$"
13419 | "]"
13420 | "^S"
13421 | "ARGV"
13422 | "?"
13423 | "^O"
13424 | "^T"
13425 | "^V"
13426 | "^E"
13427 | "^GLOBAL_PHASE"
13428 | "^MATCH"
13429 | "^PREMATCH"
13430 | "^POSTMATCH"
13431 | "^LAST_SUBMATCH_RESULT"
13432 | "^C"
13433 | "^N"
13434 | "^X"
13435 | "^TAINT"
13436 | "^TAINTED"
13437 | "^UNICODE"
13438 | "^UTF8LOCALE"
13439 | "^UTF8CACHE"
13440 | "+"
13441 | "<"
13442 | ">"
13443 | "("
13444 | ")" => {}
13445 _ if name.starts_with('^') && name.len() > 1 => {
13446 self.special_caret_scalars
13447 .insert(name.to_string(), val.clone());
13448 }
13449 _ => self.scope.set_scalar(name, val.clone())?,
13450 }
13451 Ok(())
13452 }
13453
13454 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
13455 match &expr.kind {
13456 ExprKind::ArrayVar(name) => Ok(name.clone()),
13457 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(PerlError::runtime("Expected array", expr.line).into()),
13459 }
13460 }
13461
13462 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
13464 match &expr.kind {
13465 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
13466 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
13467 _ => expr,
13468 }
13469 }
13470
13471 fn try_eval_array_deref_container(
13473 &mut self,
13474 expr: &Expr,
13475 ) -> Result<Option<PerlValue>, FlowOrError> {
13476 let e = Self::peel_array_builtin_operand(expr);
13477 if let ExprKind::Deref {
13478 expr: inner,
13479 kind: Sigil::Array,
13480 } = &e.kind
13481 {
13482 return Ok(Some(self.eval_expr(inner)?));
13483 }
13484 Ok(None)
13485 }
13486
13487 fn current_package(&self) -> String {
13489 let s = self.scope.get_scalar("__PACKAGE__").to_string();
13490 if s.is_empty() {
13491 "main".to_string()
13492 } else {
13493 s
13494 }
13495 }
13496
13497 pub(crate) fn package_version_scalar(
13500 &mut self,
13501 package: &str,
13502 ) -> PerlResult<Option<PerlValue>> {
13503 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
13504 let _ = self
13505 .scope
13506 .set_scalar("__PACKAGE__", PerlValue::string(package.to_string()));
13507 let ver = self.get_special_var("VERSION");
13508 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
13509 Ok(if ver.is_undef() { None } else { Some(ver) })
13510 }
13511
13512 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<PerlSub>> {
13514 let root = if start_package.is_empty() {
13515 "main"
13516 } else {
13517 start_package
13518 };
13519 for pkg in self.mro_linearize(root) {
13520 let key = if pkg == "main" {
13521 "AUTOLOAD".to_string()
13522 } else {
13523 format!("{}::AUTOLOAD", pkg)
13524 };
13525 if let Some(s) = self.subs.get(&key) {
13526 return Some(s.clone());
13527 }
13528 }
13529 None
13530 }
13531
13532 pub(crate) fn try_autoload_call(
13537 &mut self,
13538 missing_name: &str,
13539 args: Vec<PerlValue>,
13540 line: usize,
13541 want: WantarrayCtx,
13542 method_invocant_class: Option<&str>,
13543 ) -> Option<ExecResult> {
13544 let pkg = self.current_package();
13545 let full = if missing_name.contains("::") {
13546 missing_name.to_string()
13547 } else {
13548 format!("{}::{}", pkg, missing_name)
13549 };
13550 let start_pkg = method_invocant_class.unwrap_or_else(|| {
13551 full.rsplit_once("::")
13552 .map(|(p, _)| p)
13553 .filter(|p| !p.is_empty())
13554 .unwrap_or("main")
13555 });
13556 let sub = self.resolve_autoload_sub(start_pkg)?;
13557 if let Err(e) = self
13558 .scope
13559 .set_scalar("AUTOLOAD", PerlValue::string(full.clone()))
13560 {
13561 return Some(Err(e.into()));
13562 }
13563 Some(self.call_sub(&sub, args, want, line))
13564 }
13565
13566 pub(crate) fn with_topic_default_args(&self, args: Vec<PerlValue>) -> Vec<PerlValue> {
13567 if args.is_empty() {
13568 vec![self.scope.get_scalar("_").clone()]
13569 } else {
13570 args
13571 }
13572 }
13573
13574 pub(crate) fn dispatch_indirect_call(
13577 &mut self,
13578 target: PerlValue,
13579 arg_vals: Vec<PerlValue>,
13580 want: WantarrayCtx,
13581 line: usize,
13582 ) -> ExecResult {
13583 if let Some(sub) = target.as_code_ref() {
13584 return self.call_sub(&sub, arg_vals, want, line);
13585 }
13586 if let Some(name) = target.as_str() {
13587 return self.call_named_sub(&name, arg_vals, line, want);
13588 }
13589 Err(PerlError::runtime("Can't use non-code reference as a subroutine", line).into())
13590 }
13591
13592 pub(crate) fn call_bare_list_util(
13598 &mut self,
13599 name: &str,
13600 args: Vec<PerlValue>,
13601 line: usize,
13602 want: WantarrayCtx,
13603 ) -> ExecResult {
13604 crate::list_util::ensure_list_util(self);
13605 let fq = match name {
13606 "uniq" | "distinct" | "uq" => "List::Util::uniq",
13607 "uniqstr" => "List::Util::uniqstr",
13608 "uniqint" => "List::Util::uniqint",
13609 "uniqnum" => "List::Util::uniqnum",
13610 "shuffle" | "shuf" => "List::Util::shuffle",
13611 "sample" => "List::Util::sample",
13612 "chunked" | "chk" => "List::Util::chunked",
13613 "windowed" | "win" => "List::Util::windowed",
13614 "zip" | "zp" => "List::Util::zip",
13615 "zip_longest" => "List::Util::zip_longest",
13616 "zip_shortest" => "List::Util::zip_shortest",
13617 "mesh" => "List::Util::mesh",
13618 "mesh_longest" => "List::Util::mesh_longest",
13619 "mesh_shortest" => "List::Util::mesh_shortest",
13620 "any" => "List::Util::any",
13621 "all" => "List::Util::all",
13622 "none" => "List::Util::none",
13623 "notall" => "List::Util::notall",
13624 "first" | "fst" => "List::Util::first",
13625 "reduce" | "rd" => "List::Util::reduce",
13626 "reductions" => "List::Util::reductions",
13627 "sum" => "List::Util::sum",
13628 "sum0" => "List::Util::sum0",
13629 "product" => "List::Util::product",
13630 "min" => "List::Util::min",
13631 "max" => "List::Util::max",
13632 "minstr" => "List::Util::minstr",
13633 "maxstr" => "List::Util::maxstr",
13634 "mean" => "List::Util::mean",
13635 "median" | "med" => "List::Util::median",
13636 "mode" => "List::Util::mode",
13637 "stddev" | "std" => "List::Util::stddev",
13638 "variance" | "var" => "List::Util::variance",
13639 "pairs" => "List::Util::pairs",
13640 "unpairs" => "List::Util::unpairs",
13641 "pairkeys" => "List::Util::pairkeys",
13642 "pairvalues" => "List::Util::pairvalues",
13643 "pairgrep" => "List::Util::pairgrep",
13644 "pairmap" => "List::Util::pairmap",
13645 "pairfirst" => "List::Util::pairfirst",
13646 _ => {
13647 return Err(PerlError::runtime(
13648 format!("internal: not a bare list-util alias: {name}"),
13649 line,
13650 )
13651 .into());
13652 }
13653 };
13654 let Some(sub) = self.subs.get(fq).cloned() else {
13655 return Err(PerlError::runtime(
13656 format!("internal: missing native stub for {fq}"),
13657 line,
13658 )
13659 .into());
13660 };
13661 let args = self.with_topic_default_args(args);
13662 self.call_sub(&sub, args, want, line)
13663 }
13664
13665 fn call_named_sub(
13666 &mut self,
13667 name: &str,
13668 args: Vec<PerlValue>,
13669 line: usize,
13670 want: WantarrayCtx,
13671 ) -> ExecResult {
13672 if let Some(sub) = self.resolve_sub_by_name(name) {
13673 let args = self.with_topic_default_args(args);
13674 return self.call_sub(&sub, args, want, line);
13675 }
13676 match name {
13677 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
13678 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
13679 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
13680 | "none" | "notall" | "first" | "fst" | "reduce" | "rd" | "reductions" | "sum"
13681 | "sum0" | "product" | "min" | "max" | "minstr" | "maxstr" | "mean" | "median"
13682 | "med" | "mode" | "stddev" | "std" | "variance" | "var" | "pairs" | "unpairs"
13683 | "pairkeys" | "pairvalues" | "pairgrep" | "pairmap" | "pairfirst" => {
13684 self.call_bare_list_util(name, args, line, want)
13685 }
13686 "deque" => {
13687 if !args.is_empty() {
13688 return Err(PerlError::runtime("deque() takes no arguments", line).into());
13689 }
13690 Ok(PerlValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
13691 }
13692 "defer__internal" => {
13693 if args.len() != 1 {
13694 return Err(PerlError::runtime(
13695 "defer__internal expects one coderef argument",
13696 line,
13697 )
13698 .into());
13699 }
13700 self.scope.push_defer(args[0].clone());
13701 Ok(PerlValue::UNDEF)
13702 }
13703 "heap" => {
13704 if args.len() != 1 {
13705 return Err(
13706 PerlError::runtime("heap() expects one comparator sub", line).into(),
13707 );
13708 }
13709 if let Some(sub) = args[0].as_code_ref() {
13710 Ok(PerlValue::heap(Arc::new(Mutex::new(PerlHeap {
13711 items: Vec::new(),
13712 cmp: Arc::clone(&sub),
13713 }))))
13714 } else {
13715 Err(PerlError::runtime("heap() requires a code reference", line).into())
13716 }
13717 }
13718 "pipeline" => {
13719 let mut items = Vec::new();
13720 for v in args {
13721 if let Some(a) = v.as_array_vec() {
13722 items.extend(a);
13723 } else {
13724 items.push(v);
13725 }
13726 }
13727 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
13728 source: items,
13729 ops: Vec::new(),
13730 has_scalar_terminal: false,
13731 par_stream: false,
13732 streaming: false,
13733 streaming_workers: 0,
13734 streaming_buffer: 256,
13735 }))))
13736 }
13737 "par_pipeline" => {
13738 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
13739 return crate::par_pipeline::run_par_pipeline(self, &args, line)
13740 .map_err(Into::into);
13741 }
13742 Ok(self.builtin_par_pipeline_stream(&args, line)?)
13743 }
13744 "par_pipeline_stream" => {
13745 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
13746 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
13747 .map_err(Into::into);
13748 }
13749 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
13750 }
13751 "ppool" => {
13752 if args.len() != 1 {
13753 return Err(PerlError::runtime(
13754 "ppool() expects one argument (worker count)",
13755 line,
13756 )
13757 .into());
13758 }
13759 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
13760 }
13761 "barrier" => {
13762 if args.len() != 1 {
13763 return Err(PerlError::runtime(
13764 "barrier() expects one argument (party count)",
13765 line,
13766 )
13767 .into());
13768 }
13769 let n = args[0].to_int().max(1) as usize;
13770 Ok(PerlValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
13771 }
13772 "cluster" => {
13773 let items = if args.len() == 1 {
13774 args[0].to_list()
13775 } else {
13776 args.to_vec()
13777 };
13778 let c = RemoteCluster::from_list_args(&items)
13779 .map_err(|msg| PerlError::runtime(msg, line))?;
13780 Ok(PerlValue::remote_cluster(Arc::new(c)))
13781 }
13782 _ => {
13783 if let Some(method_name) = name.strip_prefix("static::") {
13785 let self_val = self.scope.get_scalar("self");
13786 if let Some(c) = self_val.as_class_inst() {
13787 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
13788 if let Some(ref body) = m.body {
13789 let params = m.params.clone();
13790 let mut call_args = vec![self_val.clone()];
13791 call_args.extend(args);
13792 return match self.call_class_method(body, ¶ms, call_args, line)
13793 {
13794 Ok(v) => Ok(v),
13795 Err(FlowOrError::Error(e)) => Err(e.into()),
13796 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
13797 Err(e) => Err(e),
13798 };
13799 }
13800 }
13801 return Err(PerlError::runtime(
13802 format!(
13803 "static::{} — method not found on class {}",
13804 method_name, c.def.name
13805 ),
13806 line,
13807 )
13808 .into());
13809 }
13810 return Err(PerlError::runtime(
13811 "static:: can only be used inside a class method",
13812 line,
13813 )
13814 .into());
13815 }
13816 if let Some(def) = self.struct_defs.get(name).cloned() {
13818 return self.struct_construct(&def, args, line);
13819 }
13820 if let Some(def) = self.class_defs.get(name).cloned() {
13822 return self.class_construct(&def, args, line);
13823 }
13824 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
13826 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
13827 return self.enum_construct(&def, variant_name, args, line);
13828 }
13829 }
13830 if let Some((class_name, member_name)) = name.rsplit_once("::") {
13832 if let Some(def) = self.class_defs.get(class_name).cloned() {
13833 if let Some(m) = def.method(member_name) {
13835 if m.is_static {
13836 if let Some(ref body) = m.body {
13837 let params = m.params.clone();
13838 return match self.call_static_class_method(
13839 body,
13840 ¶ms,
13841 args.clone(),
13842 line,
13843 ) {
13844 Ok(v) => Ok(v),
13845 Err(FlowOrError::Error(e)) => Err(e.into()),
13846 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
13847 Err(e) => Err(e),
13848 };
13849 }
13850 }
13851 }
13852 if def.static_fields.iter().any(|sf| sf.name == member_name) {
13854 let key = format!("{}::{}", class_name, member_name);
13855 match args.len() {
13856 0 => {
13857 let val = self.scope.get_scalar(&key);
13858 return Ok(val);
13859 }
13860 1 => {
13861 let _ = self.scope.set_scalar(&key, args[0].clone());
13862 return Ok(args[0].clone());
13863 }
13864 _ => {
13865 return Err(PerlError::runtime(
13866 format!(
13867 "static field `{}::{}` takes 0 or 1 arguments",
13868 class_name, member_name
13869 ),
13870 line,
13871 )
13872 .into());
13873 }
13874 }
13875 }
13876 }
13877 }
13878 let args = self.with_topic_default_args(args);
13879 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
13880 return r;
13881 }
13882 Err(PerlError::runtime(self.undefined_subroutine_call_message(name), line).into())
13883 }
13884 }
13885 }
13886
13887 pub(crate) fn struct_construct(
13889 &mut self,
13890 def: &Arc<StructDef>,
13891 args: Vec<PerlValue>,
13892 line: usize,
13893 ) -> ExecResult {
13894 let is_named = args.len() >= 2
13897 && args.len().is_multiple_of(2)
13898 && args.iter().step_by(2).all(|v| {
13899 let s = v.to_string();
13900 def.field_index(&s).is_some()
13901 });
13902
13903 let provided = if is_named {
13904 let mut pairs = Vec::new();
13906 let mut i = 0;
13907 while i + 1 < args.len() {
13908 let k = args[i].to_string();
13909 let v = args[i + 1].clone();
13910 pairs.push((k, v));
13911 i += 2;
13912 }
13913 pairs
13914 } else {
13915 def.fields
13917 .iter()
13918 .zip(args.iter())
13919 .map(|(f, v)| (f.name.clone(), v.clone()))
13920 .collect()
13921 };
13922
13923 let mut defaults = Vec::with_capacity(def.fields.len());
13925 for field in &def.fields {
13926 if let Some(ref expr) = field.default {
13927 let val = self.eval_expr(expr)?;
13928 defaults.push(Some(val));
13929 } else {
13930 defaults.push(None);
13931 }
13932 }
13933
13934 Ok(crate::native_data::struct_new_with_defaults(
13935 def, &provided, &defaults, line,
13936 )?)
13937 }
13938
13939 pub(crate) fn class_construct(
13941 &mut self,
13942 def: &Arc<ClassDef>,
13943 args: Vec<PerlValue>,
13944 _line: usize,
13945 ) -> ExecResult {
13946 use crate::value::ClassInstance;
13947
13948 if def.is_abstract {
13950 return Err(PerlError::runtime(
13951 format!("cannot instantiate abstract class `{}`", def.name),
13952 _line,
13953 )
13954 .into());
13955 }
13956
13957 let all_fields = self.collect_class_fields(def);
13959
13960 let is_named = args.len() >= 2
13962 && args.len().is_multiple_of(2)
13963 && args.iter().step_by(2).all(|v| {
13964 let s = v.to_string();
13965 all_fields.iter().any(|(name, _, _)| name == &s)
13966 });
13967
13968 let provided: Vec<(String, PerlValue)> = if is_named {
13969 let mut pairs = Vec::new();
13970 let mut i = 0;
13971 while i + 1 < args.len() {
13972 let k = args[i].to_string();
13973 let v = args[i + 1].clone();
13974 pairs.push((k, v));
13975 i += 2;
13976 }
13977 pairs
13978 } else {
13979 all_fields
13980 .iter()
13981 .zip(args.iter())
13982 .map(|((name, _, _), v)| (name.clone(), v.clone()))
13983 .collect()
13984 };
13985
13986 let mut values = Vec::with_capacity(all_fields.len());
13988 for (name, default, ty) in &all_fields {
13989 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
13990 val.clone()
13991 } else if let Some(ref expr) = default {
13992 self.eval_expr(expr)?
13993 } else {
13994 PerlValue::UNDEF
13995 };
13996 ty.check_value(&val).map_err(|msg| {
13997 PerlError::type_error(
13998 format!("class {} field `{}`: {}", def.name, name, msg),
13999 _line,
14000 )
14001 })?;
14002 values.push(val);
14003 }
14004
14005 let instance = PerlValue::class_inst(Arc::new(ClassInstance::new(Arc::clone(def), values)));
14006
14007 let build_chain = self.collect_build_chain(def);
14009 if !build_chain.is_empty() {
14010 for (body, params) in &build_chain {
14011 let call_args = vec![instance.clone()];
14012 match self.call_class_method(body, params, call_args, _line) {
14013 Ok(_) => {}
14014 Err(FlowOrError::Flow(Flow::Return(_))) => {}
14015 Err(e) => return Err(e),
14016 }
14017 }
14018 }
14019
14020 Ok(instance)
14021 }
14022
14023 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14025 let mut chain = Vec::new();
14026 for parent_name in &def.extends {
14028 if let Some(parent_def) = self.class_defs.get(parent_name) {
14029 chain.extend(self.collect_build_chain(parent_def));
14030 }
14031 }
14032 if let Some(m) = def.method("BUILD") {
14034 if let Some(ref body) = m.body {
14035 chain.push((body.clone(), m.params.clone()));
14036 }
14037 }
14038 chain
14039 }
14040
14041 fn collect_class_fields(
14044 &self,
14045 def: &ClassDef,
14046 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
14047 self.collect_class_fields_full(def)
14048 .into_iter()
14049 .map(|(name, default, ty, _, _)| (name, default, ty))
14050 .collect()
14051 }
14052
14053 fn collect_class_fields_full(
14055 &self,
14056 def: &ClassDef,
14057 ) -> Vec<(
14058 String,
14059 Option<Expr>,
14060 crate::ast::PerlTypeName,
14061 crate::ast::Visibility,
14062 String,
14063 )> {
14064 let mut all_fields = Vec::new();
14065
14066 for parent_name in &def.extends {
14067 if let Some(parent_def) = self.class_defs.get(parent_name) {
14068 let parent_fields = self.collect_class_fields_full(parent_def);
14069 all_fields.extend(parent_fields);
14070 }
14071 }
14072
14073 for field in &def.fields {
14074 all_fields.push((
14075 field.name.clone(),
14076 field.default.clone(),
14077 field.ty.clone(),
14078 field.visibility,
14079 def.name.clone(),
14080 ));
14081 }
14082
14083 all_fields
14084 }
14085
14086 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
14088 for parent_name in &def.extends {
14090 if let Some(parent_def) = self.class_defs.get(parent_name) {
14091 self.collect_class_method_names(parent_def, names);
14092 }
14093 }
14094 for m in &def.methods {
14096 if !m.is_static && !names.contains(&m.name) {
14097 names.push(m.name.clone());
14098 }
14099 }
14100 }
14101
14102 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
14104 let mut chain = Vec::new();
14105 if let Some(m) = def.method("DESTROY") {
14107 if let Some(ref body) = m.body {
14108 chain.push((body.clone(), m.params.clone()));
14109 }
14110 }
14111 for parent_name in &def.extends {
14113 if let Some(parent_def) = self.class_defs.get(parent_name) {
14114 chain.extend(self.collect_destroy_chain(parent_def));
14115 }
14116 }
14117 chain
14118 }
14119
14120 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
14122 if let Some(def) = self.class_defs.get(child) {
14123 for parent in &def.extends {
14124 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
14125 return true;
14126 }
14127 }
14128 }
14129 false
14130 }
14131
14132 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
14134 if let Some(m) = def.method(method) {
14136 return Some((m.clone(), def.name.clone()));
14137 }
14138 for parent_name in &def.extends {
14140 if let Some(parent_def) = self.class_defs.get(parent_name) {
14141 if let Some(result) = self.find_class_method(parent_def, method) {
14142 return Some(result);
14143 }
14144 }
14145 }
14146 None
14147 }
14148
14149 pub(crate) fn enum_construct(
14151 &mut self,
14152 def: &Arc<EnumDef>,
14153 variant_name: &str,
14154 args: Vec<PerlValue>,
14155 line: usize,
14156 ) -> ExecResult {
14157 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
14158 FlowOrError::Error(PerlError::runtime(
14159 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
14160 line,
14161 ))
14162 })?;
14163 let variant = &def.variants[variant_idx];
14164 let data = if variant.ty.is_some() {
14165 if args.is_empty() {
14166 return Err(PerlError::runtime(
14167 format!(
14168 "enum variant `{}::{}` requires data",
14169 def.name, variant_name
14170 ),
14171 line,
14172 )
14173 .into());
14174 }
14175 if args.len() == 1 {
14176 args.into_iter().next().unwrap()
14177 } else {
14178 PerlValue::array(args)
14179 }
14180 } else {
14181 if !args.is_empty() {
14182 return Err(PerlError::runtime(
14183 format!(
14184 "enum variant `{}::{}` does not take data",
14185 def.name, variant_name
14186 ),
14187 line,
14188 )
14189 .into());
14190 }
14191 PerlValue::UNDEF
14192 };
14193 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
14194 Ok(PerlValue::enum_inst(Arc::new(inst)))
14195 }
14196
14197 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
14199 matches!(name, "STDIN" | "STDOUT" | "STDERR")
14200 || self.input_handles.contains_key(name)
14201 || self.output_handles.contains_key(name)
14202 || self.io_file_slots.contains_key(name)
14203 || self.pipe_children.contains_key(name)
14204 }
14205
14206 pub(crate) fn io_handle_method(
14208 &mut self,
14209 name: &str,
14210 method: &str,
14211 args: &[PerlValue],
14212 line: usize,
14213 ) -> PerlResult<PerlValue> {
14214 match method {
14215 "print" => self.io_handle_print(name, args, false, line),
14216 "say" => self.io_handle_print(name, args, true, line),
14217 "printf" => self.io_handle_printf(name, args, line),
14218 "getline" | "readline" => {
14219 if !args.is_empty() {
14220 return Err(PerlError::runtime(
14221 format!("{}: too many arguments", method),
14222 line,
14223 ));
14224 }
14225 self.readline_builtin_execute(Some(name))
14226 }
14227 "close" => {
14228 if !args.is_empty() {
14229 return Err(PerlError::runtime("close: too many arguments", line));
14230 }
14231 self.close_builtin_execute(name.to_string())
14232 }
14233 "eof" => {
14234 if !args.is_empty() {
14235 return Err(PerlError::runtime("eof: too many arguments", line));
14236 }
14237 let at_eof = !self.has_input_handle(name);
14238 Ok(PerlValue::integer(if at_eof { 1 } else { 0 }))
14239 }
14240 "getc" => {
14241 if !args.is_empty() {
14242 return Err(PerlError::runtime("getc: too many arguments", line));
14243 }
14244 match crate::builtins::try_builtin(
14245 self,
14246 "getc",
14247 &[PerlValue::string(name.to_string())],
14248 line,
14249 ) {
14250 Some(r) => r,
14251 None => Err(PerlError::runtime("getc: not available", line)),
14252 }
14253 }
14254 "binmode" => match crate::builtins::try_builtin(
14255 self,
14256 "binmode",
14257 &[PerlValue::string(name.to_string())],
14258 line,
14259 ) {
14260 Some(r) => r,
14261 None => Err(PerlError::runtime("binmode: not available", line)),
14262 },
14263 "fileno" => match crate::builtins::try_builtin(
14264 self,
14265 "fileno",
14266 &[PerlValue::string(name.to_string())],
14267 line,
14268 ) {
14269 Some(r) => r,
14270 None => Err(PerlError::runtime("fileno: not available", line)),
14271 },
14272 "flush" => {
14273 if !args.is_empty() {
14274 return Err(PerlError::runtime("flush: too many arguments", line));
14275 }
14276 self.io_handle_flush(name, line)
14277 }
14278 _ => Err(PerlError::runtime(
14279 format!("Unknown method for filehandle: {}", method),
14280 line,
14281 )),
14282 }
14283 }
14284
14285 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> PerlResult<PerlValue> {
14286 match handle_name {
14287 "STDOUT" => {
14288 let _ = IoWrite::flush(&mut io::stdout());
14289 }
14290 "STDERR" => {
14291 let _ = IoWrite::flush(&mut io::stderr());
14292 }
14293 name => {
14294 if let Some(writer) = self.output_handles.get_mut(name) {
14295 let _ = IoWrite::flush(&mut *writer);
14296 } else {
14297 return Err(PerlError::runtime(
14298 format!("flush on unopened filehandle {}", name),
14299 line,
14300 ));
14301 }
14302 }
14303 }
14304 Ok(PerlValue::integer(1))
14305 }
14306
14307 fn io_handle_print(
14308 &mut self,
14309 handle_name: &str,
14310 args: &[PerlValue],
14311 newline: bool,
14312 line: usize,
14313 ) -> PerlResult<PerlValue> {
14314 if newline && (self.feature_bits & FEAT_SAY) == 0 {
14315 return Err(PerlError::runtime(
14316 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
14317 line,
14318 ));
14319 }
14320 let mut output = String::new();
14321 if args.is_empty() {
14322 output.push_str(&self.scope.get_scalar("_").to_string());
14324 } else {
14325 for (i, val) in args.iter().enumerate() {
14326 if i > 0 && !self.ofs.is_empty() {
14327 output.push_str(&self.ofs);
14328 }
14329 output.push_str(&val.to_string());
14330 }
14331 }
14332 if newline {
14333 output.push('\n');
14334 }
14335 output.push_str(&self.ors);
14336
14337 self.write_formatted_print(handle_name, &output, line)?;
14338 Ok(PerlValue::integer(1))
14339 }
14340
14341 pub(crate) fn write_formatted_print(
14344 &mut self,
14345 handle_name: &str,
14346 output: &str,
14347 line: usize,
14348 ) -> PerlResult<()> {
14349 match handle_name {
14350 "STDOUT" => {
14351 if !self.suppress_stdout {
14352 print!("{}", output);
14353 if self.output_autoflush {
14354 let _ = io::stdout().flush();
14355 }
14356 }
14357 }
14358 "STDERR" => {
14359 eprint!("{}", output);
14360 let _ = io::stderr().flush();
14361 }
14362 name => {
14363 if let Some(writer) = self.output_handles.get_mut(name) {
14364 let _ = writer.write_all(output.as_bytes());
14365 if self.output_autoflush {
14366 let _ = writer.flush();
14367 }
14368 } else {
14369 return Err(PerlError::runtime(
14370 format!("print on unopened filehandle {}", name),
14371 line,
14372 ));
14373 }
14374 }
14375 }
14376 Ok(())
14377 }
14378
14379 fn io_handle_printf(
14380 &mut self,
14381 handle_name: &str,
14382 args: &[PerlValue],
14383 line: usize,
14384 ) -> PerlResult<PerlValue> {
14385 let (fmt, rest): (String, &[PerlValue]) = if args.is_empty() {
14386 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
14387 Ok(s) => s,
14388 Err(FlowOrError::Error(e)) => return Err(e),
14389 Err(FlowOrError::Flow(_)) => {
14390 return Err(PerlError::runtime(
14391 "printf: unexpected control flow in sprintf",
14392 line,
14393 ));
14394 }
14395 };
14396 (s, &[])
14397 } else {
14398 (args[0].to_string(), &args[1..])
14399 };
14400 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
14401 Ok(s) => s,
14402 Err(FlowOrError::Error(e)) => return Err(e),
14403 Err(FlowOrError::Flow(_)) => {
14404 return Err(PerlError::runtime(
14405 "printf: unexpected control flow in sprintf",
14406 line,
14407 ));
14408 }
14409 };
14410 match handle_name {
14411 "STDOUT" => {
14412 if !self.suppress_stdout {
14413 print!("{}", output);
14414 if self.output_autoflush {
14415 let _ = IoWrite::flush(&mut io::stdout());
14416 }
14417 }
14418 }
14419 "STDERR" => {
14420 eprint!("{}", output);
14421 let _ = IoWrite::flush(&mut io::stderr());
14422 }
14423 name => {
14424 if let Some(writer) = self.output_handles.get_mut(name) {
14425 let _ = writer.write_all(output.as_bytes());
14426 if self.output_autoflush {
14427 let _ = writer.flush();
14428 }
14429 } else {
14430 return Err(PerlError::runtime(
14431 format!("printf on unopened filehandle {}", name),
14432 line,
14433 ));
14434 }
14435 }
14436 }
14437 Ok(PerlValue::integer(1))
14438 }
14439
14440 pub(crate) fn try_native_method(
14442 &mut self,
14443 receiver: &PerlValue,
14444 method: &str,
14445 args: &[PerlValue],
14446 line: usize,
14447 ) -> Option<PerlResult<PerlValue>> {
14448 if let Some(name) = receiver.as_io_handle_name() {
14449 return Some(self.io_handle_method(&name, method, args, line));
14450 }
14451 if let Some(ref s) = receiver.as_str() {
14452 if self.is_bound_handle(s) {
14453 return Some(self.io_handle_method(s, method, args, line));
14454 }
14455 }
14456 if let Some(c) = receiver.as_sqlite_conn() {
14457 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
14458 }
14459 if let Some(s) = receiver.as_struct_inst() {
14460 if let Some(idx) = s.def.field_index(method) {
14462 match args.len() {
14463 0 => {
14464 return Some(Ok(s.get_field(idx).unwrap_or(PerlValue::UNDEF)));
14465 }
14466 1 => {
14467 let field = &s.def.fields[idx];
14468 let new_val = args[0].clone();
14469 if let Err(msg) = field.ty.check_value(&new_val) {
14470 return Some(Err(PerlError::type_error(
14471 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
14472 line,
14473 )));
14474 }
14475 s.set_field(idx, new_val.clone());
14476 return Some(Ok(new_val));
14477 }
14478 _ => {
14479 return Some(Err(PerlError::runtime(
14480 format!(
14481 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
14482 method,
14483 args.len()
14484 ),
14485 line,
14486 )));
14487 }
14488 }
14489 }
14490 match method {
14492 "with" => {
14493 let mut new_values = s.get_values();
14495 let mut i = 0;
14496 while i + 1 < args.len() {
14497 let k = args[i].to_string();
14498 let v = args[i + 1].clone();
14499 if let Some(idx) = s.def.field_index(&k) {
14500 let field = &s.def.fields[idx];
14501 if let Err(msg) = field.ty.check_value(&v) {
14502 return Some(Err(PerlError::type_error(
14503 format!(
14504 "struct {} field `{}`: {}",
14505 s.def.name, field.name, msg
14506 ),
14507 line,
14508 )));
14509 }
14510 new_values[idx] = v;
14511 } else {
14512 return Some(Err(PerlError::runtime(
14513 format!("struct {}: unknown field `{}`", s.def.name, k),
14514 line,
14515 )));
14516 }
14517 i += 2;
14518 }
14519 return Some(Ok(PerlValue::struct_inst(Arc::new(
14520 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
14521 ))));
14522 }
14523 "to_hash" => {
14524 if !args.is_empty() {
14526 return Some(Err(PerlError::runtime(
14527 "struct to_hash takes no arguments",
14528 line,
14529 )));
14530 }
14531 let mut map = IndexMap::new();
14532 let values = s.get_values();
14533 for (i, field) in s.def.fields.iter().enumerate() {
14534 map.insert(field.name.clone(), values[i].clone());
14535 }
14536 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
14537 }
14538 "fields" => {
14539 if !args.is_empty() {
14541 return Some(Err(PerlError::runtime(
14542 "struct fields takes no arguments",
14543 line,
14544 )));
14545 }
14546 let names: Vec<PerlValue> = s
14547 .def
14548 .fields
14549 .iter()
14550 .map(|f| PerlValue::string(f.name.clone()))
14551 .collect();
14552 return Some(Ok(PerlValue::array(names)));
14553 }
14554 "clone" => {
14555 if !args.is_empty() {
14557 return Some(Err(PerlError::runtime(
14558 "struct clone takes no arguments",
14559 line,
14560 )));
14561 }
14562 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
14563 return Some(Ok(PerlValue::struct_inst(Arc::new(
14564 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
14565 ))));
14566 }
14567 _ => {}
14568 }
14569 if let Some(m) = s.def.method(method) {
14571 let body = m.body.clone();
14572 let params = m.params.clone();
14573 let mut call_args = vec![receiver.clone()];
14575 call_args.extend(args.iter().cloned());
14576 return Some(
14577 match self.call_struct_method(&body, ¶ms, call_args, line) {
14578 Ok(v) => Ok(v),
14579 Err(FlowOrError::Error(e)) => Err(e),
14580 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14581 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
14582 "unexpected control flow in struct method",
14583 line,
14584 )),
14585 },
14586 );
14587 }
14588 return None;
14589 }
14590 if let Some(c) = receiver.as_class_inst() {
14592 let all_fields_full = self.collect_class_fields_full(&c.def);
14594 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
14595 .iter()
14596 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
14597 .collect();
14598
14599 if let Some(idx) = all_fields_full
14601 .iter()
14602 .position(|(name, _, _, _, _)| name == method)
14603 {
14604 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
14605
14606 match vis {
14608 crate::ast::Visibility::Private => {
14609 let caller_class = self
14611 .scope
14612 .get_scalar("self")
14613 .as_class_inst()
14614 .map(|ci| ci.def.name.clone());
14615 if caller_class.as_deref() != Some(owner_class.as_str()) {
14616 return Some(Err(PerlError::runtime(
14617 format!("field `{}` of class {} is private", method, owner_class),
14618 line,
14619 )));
14620 }
14621 }
14622 crate::ast::Visibility::Protected => {
14623 let caller_class = self
14625 .scope
14626 .get_scalar("self")
14627 .as_class_inst()
14628 .map(|ci| ci.def.name.clone());
14629 let allowed = caller_class.as_deref().is_some_and(|caller| {
14630 caller == owner_class || self.class_inherits_from(caller, owner_class)
14631 });
14632 if !allowed {
14633 return Some(Err(PerlError::runtime(
14634 format!("field `{}` of class {} is protected", method, owner_class),
14635 line,
14636 )));
14637 }
14638 }
14639 crate::ast::Visibility::Public => {}
14640 }
14641
14642 match args.len() {
14643 0 => {
14644 return Some(Ok(c.get_field(idx).unwrap_or(PerlValue::UNDEF)));
14645 }
14646 1 => {
14647 let new_val = args[0].clone();
14648 if let Err(msg) = ty.check_value(&new_val) {
14649 return Some(Err(PerlError::type_error(
14650 format!("class {} field `{}`: {}", c.def.name, method, msg),
14651 line,
14652 )));
14653 }
14654 c.set_field(idx, new_val.clone());
14655 return Some(Ok(new_val));
14656 }
14657 _ => {
14658 return Some(Err(PerlError::runtime(
14659 format!(
14660 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
14661 method,
14662 args.len()
14663 ),
14664 line,
14665 )));
14666 }
14667 }
14668 }
14669 match method {
14671 "with" => {
14672 let mut new_values = c.get_values();
14673 let mut i = 0;
14674 while i + 1 < args.len() {
14675 let k = args[i].to_string();
14676 let v = args[i + 1].clone();
14677 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
14678 let (_, _, ref ty) = all_fields[idx];
14679 if let Err(msg) = ty.check_value(&v) {
14680 return Some(Err(PerlError::type_error(
14681 format!("class {} field `{}`: {}", c.def.name, k, msg),
14682 line,
14683 )));
14684 }
14685 new_values[idx] = v;
14686 } else {
14687 return Some(Err(PerlError::runtime(
14688 format!("class {}: unknown field `{}`", c.def.name, k),
14689 line,
14690 )));
14691 }
14692 i += 2;
14693 }
14694 return Some(Ok(PerlValue::class_inst(Arc::new(
14695 crate::value::ClassInstance::new(Arc::clone(&c.def), new_values),
14696 ))));
14697 }
14698 "to_hash" => {
14699 if !args.is_empty() {
14700 return Some(Err(PerlError::runtime(
14701 "class to_hash takes no arguments",
14702 line,
14703 )));
14704 }
14705 let mut map = IndexMap::new();
14706 let values = c.get_values();
14707 for (i, (name, _, _)) in all_fields.iter().enumerate() {
14708 if let Some(v) = values.get(i) {
14709 map.insert(name.clone(), v.clone());
14710 }
14711 }
14712 return Some(Ok(PerlValue::hash_ref(Arc::new(RwLock::new(map)))));
14713 }
14714 "fields" => {
14715 if !args.is_empty() {
14716 return Some(Err(PerlError::runtime(
14717 "class fields takes no arguments",
14718 line,
14719 )));
14720 }
14721 let names: Vec<PerlValue> = all_fields
14722 .iter()
14723 .map(|(name, _, _)| PerlValue::string(name.clone()))
14724 .collect();
14725 return Some(Ok(PerlValue::array(names)));
14726 }
14727 "clone" => {
14728 if !args.is_empty() {
14729 return Some(Err(PerlError::runtime(
14730 "class clone takes no arguments",
14731 line,
14732 )));
14733 }
14734 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
14735 return Some(Ok(PerlValue::class_inst(Arc::new(
14736 crate::value::ClassInstance::new(Arc::clone(&c.def), new_values),
14737 ))));
14738 }
14739 "isa" => {
14740 if args.len() != 1 {
14741 return Some(Err(PerlError::runtime("isa requires one argument", line)));
14742 }
14743 let class_name = args[0].to_string();
14744 let is_a = c.def.name == class_name || c.def.extends.contains(&class_name);
14745 return Some(Ok(if is_a {
14746 PerlValue::integer(1)
14747 } else {
14748 PerlValue::string(String::new())
14749 }));
14750 }
14751 "does" => {
14752 if args.len() != 1 {
14753 return Some(Err(PerlError::runtime("does requires one argument", line)));
14754 }
14755 let trait_name = args[0].to_string();
14756 let implements = c.def.implements.contains(&trait_name);
14757 return Some(Ok(if implements {
14758 PerlValue::integer(1)
14759 } else {
14760 PerlValue::string(String::new())
14761 }));
14762 }
14763 "methods" => {
14764 if !args.is_empty() {
14765 return Some(Err(PerlError::runtime("methods takes no arguments", line)));
14766 }
14767 let mut names = Vec::new();
14768 self.collect_class_method_names(&c.def, &mut names);
14769 let values: Vec<PerlValue> = names.into_iter().map(PerlValue::string).collect();
14770 return Some(Ok(PerlValue::array(values)));
14771 }
14772 "superclass" => {
14773 if !args.is_empty() {
14774 return Some(Err(PerlError::runtime(
14775 "superclass takes no arguments",
14776 line,
14777 )));
14778 }
14779 let parents: Vec<PerlValue> = c
14780 .def
14781 .extends
14782 .iter()
14783 .map(|s| PerlValue::string(s.clone()))
14784 .collect();
14785 return Some(Ok(PerlValue::array(parents)));
14786 }
14787 "destroy" => {
14788 let destroy_chain = self.collect_destroy_chain(&c.def);
14790 for (body, params) in &destroy_chain {
14791 let call_args = vec![receiver.clone()];
14792 match self.call_class_method(body, params, call_args, line) {
14793 Ok(_) => {}
14794 Err(FlowOrError::Flow(Flow::Return(_))) => {}
14795 Err(FlowOrError::Error(e)) => return Some(Err(e)),
14796 Err(_) => {}
14797 }
14798 }
14799 return Some(Ok(PerlValue::UNDEF));
14800 }
14801 _ => {}
14802 }
14803 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
14805 match m.visibility {
14807 crate::ast::Visibility::Private => {
14808 let caller_class = self
14809 .scope
14810 .get_scalar("self")
14811 .as_class_inst()
14812 .map(|ci| ci.def.name.clone());
14813 if caller_class.as_deref() != Some(owner_class.as_str()) {
14814 return Some(Err(PerlError::runtime(
14815 format!("method `{}` of class {} is private", method, owner_class),
14816 line,
14817 )));
14818 }
14819 }
14820 crate::ast::Visibility::Protected => {
14821 let caller_class = self
14822 .scope
14823 .get_scalar("self")
14824 .as_class_inst()
14825 .map(|ci| ci.def.name.clone());
14826 let allowed = caller_class.as_deref().is_some_and(|caller| {
14827 caller == owner_class.as_str()
14828 || self.class_inherits_from(caller, owner_class)
14829 });
14830 if !allowed {
14831 return Some(Err(PerlError::runtime(
14832 format!(
14833 "method `{}` of class {} is protected",
14834 method, owner_class
14835 ),
14836 line,
14837 )));
14838 }
14839 }
14840 crate::ast::Visibility::Public => {}
14841 }
14842 if let Some(ref body) = m.body {
14843 let params = m.params.clone();
14844 let mut call_args = vec![receiver.clone()];
14845 call_args.extend(args.iter().cloned());
14846 return Some(
14847 match self.call_class_method(body, ¶ms, call_args, line) {
14848 Ok(v) => Ok(v),
14849 Err(FlowOrError::Error(e)) => Err(e),
14850 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
14851 Err(FlowOrError::Flow(_)) => Err(PerlError::runtime(
14852 "unexpected control flow in class method",
14853 line,
14854 )),
14855 },
14856 );
14857 }
14858 }
14859 return None;
14860 }
14861 if let Some(d) = receiver.as_dataframe() {
14862 return Some(self.dataframe_method(d, method, args, line));
14863 }
14864 if let Some(s) = crate::value::set_payload(receiver) {
14865 return Some(self.set_method(s, method, args, line));
14866 }
14867 if let Some(d) = receiver.as_deque() {
14868 return Some(self.deque_method(d, method, args, line));
14869 }
14870 if let Some(h) = receiver.as_heap_pq() {
14871 return Some(self.heap_method(h, method, args, line));
14872 }
14873 if let Some(p) = receiver.as_pipeline() {
14874 return Some(self.pipeline_method(p, method, args, line));
14875 }
14876 if let Some(c) = receiver.as_capture() {
14877 return Some(self.capture_method(c, method, args, line));
14878 }
14879 if let Some(p) = receiver.as_ppool() {
14880 return Some(self.ppool_method(p, method, args, line));
14881 }
14882 if let Some(b) = receiver.as_barrier() {
14883 return Some(self.barrier_method(b, method, args, line));
14884 }
14885 if let Some(g) = receiver.as_generator() {
14886 if method == "next" {
14887 if !args.is_empty() {
14888 return Some(Err(PerlError::runtime(
14889 "generator->next takes no arguments",
14890 line,
14891 )));
14892 }
14893 return Some(self.generator_next(&g));
14894 }
14895 return None;
14896 }
14897 if let Some(arc) = receiver.as_atomic_arc() {
14898 let inner = arc.lock().clone();
14899 if let Some(d) = inner.as_deque() {
14900 return Some(self.deque_method(d, method, args, line));
14901 }
14902 if let Some(h) = inner.as_heap_pq() {
14903 return Some(self.heap_method(h, method, args, line));
14904 }
14905 }
14906 None
14907 }
14908
14909 fn dataframe_method(
14911 &mut self,
14912 d: Arc<Mutex<PerlDataFrame>>,
14913 method: &str,
14914 args: &[PerlValue],
14915 line: usize,
14916 ) -> PerlResult<PerlValue> {
14917 match method {
14918 "nrow" | "nrows" => {
14919 if !args.is_empty() {
14920 return Err(PerlError::runtime(
14921 format!("dataframe {} takes no arguments", method),
14922 line,
14923 ));
14924 }
14925 Ok(PerlValue::integer(d.lock().nrows() as i64))
14926 }
14927 "ncol" | "ncols" => {
14928 if !args.is_empty() {
14929 return Err(PerlError::runtime(
14930 format!("dataframe {} takes no arguments", method),
14931 line,
14932 ));
14933 }
14934 Ok(PerlValue::integer(d.lock().ncols() as i64))
14935 }
14936 "filter" => {
14937 if args.len() != 1 {
14938 return Err(PerlError::runtime(
14939 "dataframe filter expects 1 argument (sub)",
14940 line,
14941 ));
14942 }
14943 let Some(sub) = args[0].as_code_ref() else {
14944 return Err(PerlError::runtime(
14945 "dataframe filter expects a code reference",
14946 line,
14947 ));
14948 };
14949 let df_guard = d.lock();
14950 let n = df_guard.nrows();
14951 let mut keep = vec![false; n];
14952 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
14953 let row = df_guard.row_hashref(r);
14954 self.scope_push_hook();
14955 self.scope.set_topic(row);
14956 if let Some(ref env) = sub.closure_env {
14957 self.scope.restore_capture(env);
14958 }
14959 let pass = match self.exec_block_no_scope(&sub.body) {
14960 Ok(v) => v.is_true(),
14961 Err(_) => false,
14962 };
14963 self.scope_pop_hook();
14964 *row_keep = pass;
14965 }
14966 let columns = df_guard.columns.clone();
14967 let cols: Vec<Vec<PerlValue>> = (0..df_guard.ncols())
14968 .map(|i| {
14969 let mut out = Vec::new();
14970 for (r, pass_row) in keep.iter().enumerate().take(n) {
14971 if *pass_row {
14972 out.push(df_guard.cols[i][r].clone());
14973 }
14974 }
14975 out
14976 })
14977 .collect();
14978 let group_by = df_guard.group_by.clone();
14979 drop(df_guard);
14980 let new_df = PerlDataFrame {
14981 columns,
14982 cols,
14983 group_by,
14984 };
14985 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
14986 }
14987 "group_by" => {
14988 if args.len() != 1 {
14989 return Err(PerlError::runtime(
14990 "dataframe group_by expects 1 column name",
14991 line,
14992 ));
14993 }
14994 let key = args[0].to_string();
14995 let inner = d.lock();
14996 if inner.col_index(&key).is_none() {
14997 return Err(PerlError::runtime(
14998 format!("dataframe group_by: unknown column \"{}\"", key),
14999 line,
15000 ));
15001 }
15002 let new_df = PerlDataFrame {
15003 columns: inner.columns.clone(),
15004 cols: inner.cols.clone(),
15005 group_by: Some(key),
15006 };
15007 Ok(PerlValue::dataframe(Arc::new(Mutex::new(new_df))))
15008 }
15009 "sum" => {
15010 if args.len() != 1 {
15011 return Err(PerlError::runtime(
15012 "dataframe sum expects 1 column name",
15013 line,
15014 ));
15015 }
15016 let col_name = args[0].to_string();
15017 let inner = d.lock();
15018 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
15019 PerlError::runtime(
15020 format!("dataframe sum: unknown column \"{}\"", col_name),
15021 line,
15022 )
15023 })?;
15024 match &inner.group_by {
15025 Some(gcol) => {
15026 let gi = inner.col_index(gcol).ok_or_else(|| {
15027 PerlError::runtime(
15028 format!("dataframe sum: unknown group column \"{}\"", gcol),
15029 line,
15030 )
15031 })?;
15032 let mut acc: IndexMap<String, f64> = IndexMap::new();
15033 for r in 0..inner.nrows() {
15034 let k = inner.cols[gi][r].to_string();
15035 let v = inner.cols[val_idx][r].to_number();
15036 *acc.entry(k).or_insert(0.0) += v;
15037 }
15038 let keys: Vec<String> = acc.keys().cloned().collect();
15039 let sums: Vec<f64> = acc.values().copied().collect();
15040 let cols = vec![
15041 keys.into_iter().map(PerlValue::string).collect(),
15042 sums.into_iter().map(PerlValue::float).collect(),
15043 ];
15044 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
15045 let out = PerlDataFrame {
15046 columns,
15047 cols,
15048 group_by: None,
15049 };
15050 Ok(PerlValue::dataframe(Arc::new(Mutex::new(out))))
15051 }
15052 None => {
15053 let total: f64 = (0..inner.nrows())
15054 .map(|r| inner.cols[val_idx][r].to_number())
15055 .sum();
15056 Ok(PerlValue::float(total))
15057 }
15058 }
15059 }
15060 _ => Err(PerlError::runtime(
15061 format!("Unknown method for dataframe: {}", method),
15062 line,
15063 )),
15064 }
15065 }
15066
15067 fn set_method(
15069 &self,
15070 s: Arc<crate::value::PerlSet>,
15071 method: &str,
15072 args: &[PerlValue],
15073 line: usize,
15074 ) -> PerlResult<PerlValue> {
15075 match method {
15076 "has" | "contains" | "member" => {
15077 if args.len() != 1 {
15078 return Err(PerlError::runtime(
15079 "set->has expects one argument (element)",
15080 line,
15081 ));
15082 }
15083 let k = crate::value::set_member_key(&args[0]);
15084 Ok(PerlValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
15085 }
15086 "size" | "len" | "count" => {
15087 if !args.is_empty() {
15088 return Err(PerlError::runtime("set->size takes no arguments", line));
15089 }
15090 Ok(PerlValue::integer(s.len() as i64))
15091 }
15092 "values" | "list" | "elements" => {
15093 if !args.is_empty() {
15094 return Err(PerlError::runtime("set->values takes no arguments", line));
15095 }
15096 Ok(PerlValue::array(s.values().cloned().collect()))
15097 }
15098 _ => Err(PerlError::runtime(
15099 format!("Unknown method for set: {}", method),
15100 line,
15101 )),
15102 }
15103 }
15104
15105 fn deque_method(
15106 &mut self,
15107 d: Arc<Mutex<VecDeque<PerlValue>>>,
15108 method: &str,
15109 args: &[PerlValue],
15110 line: usize,
15111 ) -> PerlResult<PerlValue> {
15112 match method {
15113 "push_back" => {
15114 if args.len() != 1 {
15115 return Err(PerlError::runtime("push_back expects 1 argument", line));
15116 }
15117 d.lock().push_back(args[0].clone());
15118 Ok(PerlValue::integer(d.lock().len() as i64))
15119 }
15120 "push_front" => {
15121 if args.len() != 1 {
15122 return Err(PerlError::runtime("push_front expects 1 argument", line));
15123 }
15124 d.lock().push_front(args[0].clone());
15125 Ok(PerlValue::integer(d.lock().len() as i64))
15126 }
15127 "pop_back" => Ok(d.lock().pop_back().unwrap_or(PerlValue::UNDEF)),
15128 "pop_front" => Ok(d.lock().pop_front().unwrap_or(PerlValue::UNDEF)),
15129 "size" | "len" => Ok(PerlValue::integer(d.lock().len() as i64)),
15130 _ => Err(PerlError::runtime(
15131 format!("Unknown method for deque: {}", method),
15132 line,
15133 )),
15134 }
15135 }
15136
15137 fn heap_method(
15138 &mut self,
15139 h: Arc<Mutex<PerlHeap>>,
15140 method: &str,
15141 args: &[PerlValue],
15142 line: usize,
15143 ) -> PerlResult<PerlValue> {
15144 match method {
15145 "push" => {
15146 if args.len() != 1 {
15147 return Err(PerlError::runtime("heap push expects 1 argument", line));
15148 }
15149 let mut g = h.lock();
15150 let n = g.items.len();
15151 g.items.push(args[0].clone());
15152 let cmp = g.cmp.clone();
15153 drop(g);
15154 let mut g = h.lock();
15155 self.heap_sift_up(&mut g.items, &cmp, n);
15156 Ok(PerlValue::integer(g.items.len() as i64))
15157 }
15158 "pop" => {
15159 let mut g = h.lock();
15160 if g.items.is_empty() {
15161 return Ok(PerlValue::UNDEF);
15162 }
15163 let cmp = g.cmp.clone();
15164 let n = g.items.len();
15165 g.items.swap(0, n - 1);
15166 let v = g.items.pop().unwrap();
15167 if !g.items.is_empty() {
15168 self.heap_sift_down(&mut g.items, &cmp, 0);
15169 }
15170 Ok(v)
15171 }
15172 "peek" => Ok(h.lock().items.first().cloned().unwrap_or(PerlValue::UNDEF)),
15173 _ => Err(PerlError::runtime(
15174 format!("Unknown method for heap: {}", method),
15175 line,
15176 )),
15177 }
15178 }
15179
15180 fn ppool_method(
15181 &mut self,
15182 pool: PerlPpool,
15183 method: &str,
15184 args: &[PerlValue],
15185 line: usize,
15186 ) -> PerlResult<PerlValue> {
15187 match method {
15188 "submit" => pool.submit(self, args, line),
15189 "collect" => {
15190 if !args.is_empty() {
15191 return Err(PerlError::runtime("collect() takes no arguments", line));
15192 }
15193 pool.collect(line)
15194 }
15195 _ => Err(PerlError::runtime(
15196 format!("Unknown method for ppool: {}", method),
15197 line,
15198 )),
15199 }
15200 }
15201
15202 fn barrier_method(
15203 &self,
15204 barrier: PerlBarrier,
15205 method: &str,
15206 args: &[PerlValue],
15207 line: usize,
15208 ) -> PerlResult<PerlValue> {
15209 match method {
15210 "wait" => {
15211 if !args.is_empty() {
15212 return Err(PerlError::runtime("wait() takes no arguments", line));
15213 }
15214 let _ = barrier.0.wait();
15215 Ok(PerlValue::integer(1))
15216 }
15217 _ => Err(PerlError::runtime(
15218 format!("Unknown method for barrier: {}", method),
15219 line,
15220 )),
15221 }
15222 }
15223
15224 fn capture_method(
15225 &self,
15226 c: Arc<CaptureResult>,
15227 method: &str,
15228 args: &[PerlValue],
15229 line: usize,
15230 ) -> PerlResult<PerlValue> {
15231 if !args.is_empty() {
15232 return Err(PerlError::runtime(
15233 format!("capture: {} takes no arguments", method),
15234 line,
15235 ));
15236 }
15237 match method {
15238 "stdout" => Ok(PerlValue::string(c.stdout.clone())),
15239 "stderr" => Ok(PerlValue::string(c.stderr.clone())),
15240 "exitcode" => Ok(PerlValue::integer(c.exitcode)),
15241 "failed" => Ok(PerlValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
15242 _ => Err(PerlError::runtime(
15243 format!("Unknown method for capture: {}", method),
15244 line,
15245 )),
15246 }
15247 }
15248
15249 pub(crate) fn builtin_par_pipeline_stream(
15250 &mut self,
15251 args: &[PerlValue],
15252 _line: usize,
15253 ) -> PerlResult<PerlValue> {
15254 let mut items = Vec::new();
15255 for v in args {
15256 if let Some(a) = v.as_array_vec() {
15257 items.extend(a);
15258 } else {
15259 items.push(v.clone());
15260 }
15261 }
15262 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15263 source: items,
15264 ops: Vec::new(),
15265 has_scalar_terminal: false,
15266 par_stream: true,
15267 streaming: false,
15268 streaming_workers: 0,
15269 streaming_buffer: 256,
15270 }))))
15271 }
15272
15273 pub(crate) fn builtin_par_pipeline_stream_new(
15276 &mut self,
15277 args: &[PerlValue],
15278 _line: usize,
15279 ) -> PerlResult<PerlValue> {
15280 let mut items = Vec::new();
15281 let mut workers: usize = 0;
15282 let mut buffer: usize = 256;
15283 let mut i = 0;
15285 while i < args.len() {
15286 let s = args[i].to_string();
15287 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
15288 let val = args[i + 1].to_int().max(1) as usize;
15289 if s == "workers" {
15290 workers = val;
15291 } else {
15292 buffer = val;
15293 }
15294 i += 2;
15295 } else if let Some(a) = args[i].as_array_vec() {
15296 items.extend(a);
15297 i += 1;
15298 } else {
15299 items.push(args[i].clone());
15300 i += 1;
15301 }
15302 }
15303 Ok(PerlValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15304 source: items,
15305 ops: Vec::new(),
15306 has_scalar_terminal: false,
15307 par_stream: false,
15308 streaming: true,
15309 streaming_workers: workers,
15310 streaming_buffer: buffer,
15311 }))))
15312 }
15313
15314 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<PerlSub> {
15316 let line = 1usize;
15317 let body = vec![Statement {
15318 label: None,
15319 kind: StmtKind::Expression(Expr {
15320 kind: ExprKind::BinOp {
15321 left: Box::new(Expr {
15322 kind: ExprKind::ScalarVar("_".into()),
15323 line,
15324 }),
15325 op: BinOp::Mul,
15326 right: Box::new(Expr {
15327 kind: ExprKind::Integer(k),
15328 line,
15329 }),
15330 },
15331 line,
15332 }),
15333 line,
15334 }];
15335 Arc::new(PerlSub {
15336 name: "__pipeline_int_mul__".into(),
15337 params: vec![],
15338 body,
15339 closure_env: None,
15340 prototype: None,
15341 fib_like: None,
15342 })
15343 }
15344
15345 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<PerlSub> {
15346 let captured = self.scope.capture();
15347 Arc::new(PerlSub {
15348 name: "__ANON__".into(),
15349 params: vec![],
15350 body: block.clone(),
15351 closure_env: Some(captured),
15352 prototype: None,
15353 fib_like: None,
15354 })
15355 }
15356
15357 pub(crate) fn builtin_collect_execute(
15358 &mut self,
15359 args: &[PerlValue],
15360 line: usize,
15361 ) -> PerlResult<PerlValue> {
15362 if args.is_empty() {
15363 return Err(PerlError::runtime(
15364 "collect() expects at least one argument",
15365 line,
15366 ));
15367 }
15368 if args.len() == 1 {
15371 if let Some(p) = args[0].as_pipeline() {
15372 return self.pipeline_collect(&p, line);
15373 }
15374 return Ok(PerlValue::array(args[0].to_list()));
15375 }
15376 Ok(PerlValue::array(args.to_vec()))
15377 }
15378
15379 pub(crate) fn pipeline_push(
15380 &self,
15381 p: &Arc<Mutex<PipelineInner>>,
15382 op: PipelineOp,
15383 line: usize,
15384 ) -> PerlResult<()> {
15385 let mut g = p.lock();
15386 if g.has_scalar_terminal {
15387 return Err(PerlError::runtime(
15388 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
15389 line,
15390 ));
15391 }
15392 if matches!(
15393 &op,
15394 PipelineOp::PReduce { .. }
15395 | PipelineOp::PReduceInit { .. }
15396 | PipelineOp::PMapReduce { .. }
15397 ) {
15398 g.has_scalar_terminal = true;
15399 }
15400 g.ops.push(op);
15401 Ok(())
15402 }
15403
15404 fn pipeline_parse_sub_progress(
15405 args: &[PerlValue],
15406 line: usize,
15407 name: &str,
15408 ) -> PerlResult<(Arc<PerlSub>, bool)> {
15409 if args.is_empty() {
15410 return Err(PerlError::runtime(
15411 format!("pipeline {}: expects at least 1 argument (code ref)", name),
15412 line,
15413 ));
15414 }
15415 let Some(sub) = args[0].as_code_ref() else {
15416 return Err(PerlError::runtime(
15417 format!("pipeline {}: first argument must be a code reference", name),
15418 line,
15419 ));
15420 };
15421 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
15422 if args.len() > 2 {
15423 return Err(PerlError::runtime(
15424 format!(
15425 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
15426 name
15427 ),
15428 line,
15429 ));
15430 }
15431 Ok((sub, progress))
15432 }
15433
15434 pub(crate) fn pipeline_method(
15435 &mut self,
15436 p: Arc<Mutex<PipelineInner>>,
15437 method: &str,
15438 args: &[PerlValue],
15439 line: usize,
15440 ) -> PerlResult<PerlValue> {
15441 match method {
15442 "filter" | "f" | "grep" => {
15443 if args.len() != 1 {
15444 return Err(PerlError::runtime(
15445 "pipeline filter/grep expects 1 argument (sub)",
15446 line,
15447 ));
15448 }
15449 let Some(sub) = args[0].as_code_ref() else {
15450 return Err(PerlError::runtime(
15451 "pipeline filter/grep expects a code reference",
15452 line,
15453 ));
15454 };
15455 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
15456 Ok(PerlValue::pipeline(Arc::clone(&p)))
15457 }
15458 "map" => {
15459 if args.len() != 1 {
15460 return Err(PerlError::runtime(
15461 "pipeline map expects 1 argument (sub)",
15462 line,
15463 ));
15464 }
15465 let Some(sub) = args[0].as_code_ref() else {
15466 return Err(PerlError::runtime(
15467 "pipeline map expects a code reference",
15468 line,
15469 ));
15470 };
15471 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
15472 Ok(PerlValue::pipeline(Arc::clone(&p)))
15473 }
15474 "tap" | "peek" => {
15475 if args.len() != 1 {
15476 return Err(PerlError::runtime(
15477 "pipeline tap/peek expects 1 argument (sub)",
15478 line,
15479 ));
15480 }
15481 let Some(sub) = args[0].as_code_ref() else {
15482 return Err(PerlError::runtime(
15483 "pipeline tap/peek expects a code reference",
15484 line,
15485 ));
15486 };
15487 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
15488 Ok(PerlValue::pipeline(Arc::clone(&p)))
15489 }
15490 "take" => {
15491 if args.len() != 1 {
15492 return Err(PerlError::runtime("pipeline take expects 1 argument", line));
15493 }
15494 let n = args[0].to_int();
15495 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
15496 Ok(PerlValue::pipeline(Arc::clone(&p)))
15497 }
15498 "pmap" => {
15499 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
15500 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
15501 Ok(PerlValue::pipeline(Arc::clone(&p)))
15502 }
15503 "pgrep" => {
15504 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
15505 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
15506 Ok(PerlValue::pipeline(Arc::clone(&p)))
15507 }
15508 "pfor" => {
15509 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
15510 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
15511 Ok(PerlValue::pipeline(Arc::clone(&p)))
15512 }
15513 "pmap_chunked" => {
15514 if args.len() < 2 {
15515 return Err(PerlError::runtime(
15516 "pipeline pmap_chunked expects chunk size and a code reference",
15517 line,
15518 ));
15519 }
15520 let chunk = args[0].to_int().max(1);
15521 let Some(sub) = args[1].as_code_ref() else {
15522 return Err(PerlError::runtime(
15523 "pipeline pmap_chunked: second argument must be a code reference",
15524 line,
15525 ));
15526 };
15527 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15528 if args.len() > 3 {
15529 return Err(PerlError::runtime(
15530 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
15531 line,
15532 ));
15533 }
15534 self.pipeline_push(
15535 &p,
15536 PipelineOp::PMapChunked {
15537 chunk,
15538 sub,
15539 progress,
15540 },
15541 line,
15542 )?;
15543 Ok(PerlValue::pipeline(Arc::clone(&p)))
15544 }
15545 "psort" => {
15546 let (cmp, progress) = match args.len() {
15547 0 => (None, false),
15548 1 => {
15549 if let Some(s) = args[0].as_code_ref() {
15550 (Some(s), false)
15551 } else {
15552 (None, args[0].is_true())
15553 }
15554 }
15555 2 => {
15556 let Some(s) = args[0].as_code_ref() else {
15557 return Err(PerlError::runtime(
15558 "pipeline psort: with two arguments, the first must be a comparator sub",
15559 line,
15560 ));
15561 };
15562 (Some(s), args[1].is_true())
15563 }
15564 _ => {
15565 return Err(PerlError::runtime(
15566 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
15567 line,
15568 ));
15569 }
15570 };
15571 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
15572 Ok(PerlValue::pipeline(Arc::clone(&p)))
15573 }
15574 "pcache" => {
15575 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
15576 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
15577 Ok(PerlValue::pipeline(Arc::clone(&p)))
15578 }
15579 "preduce" => {
15580 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
15581 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
15582 Ok(PerlValue::pipeline(Arc::clone(&p)))
15583 }
15584 "preduce_init" => {
15585 if args.len() < 2 {
15586 return Err(PerlError::runtime(
15587 "pipeline preduce_init expects init value and a code reference",
15588 line,
15589 ));
15590 }
15591 let init = args[0].clone();
15592 let Some(sub) = args[1].as_code_ref() else {
15593 return Err(PerlError::runtime(
15594 "pipeline preduce_init: second argument must be a code reference",
15595 line,
15596 ));
15597 };
15598 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15599 if args.len() > 3 {
15600 return Err(PerlError::runtime(
15601 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
15602 line,
15603 ));
15604 }
15605 self.pipeline_push(
15606 &p,
15607 PipelineOp::PReduceInit {
15608 init,
15609 sub,
15610 progress,
15611 },
15612 line,
15613 )?;
15614 Ok(PerlValue::pipeline(Arc::clone(&p)))
15615 }
15616 "pmap_reduce" => {
15617 if args.len() < 2 {
15618 return Err(PerlError::runtime(
15619 "pipeline pmap_reduce expects map sub and reduce sub",
15620 line,
15621 ));
15622 }
15623 let Some(map) = args[0].as_code_ref() else {
15624 return Err(PerlError::runtime(
15625 "pipeline pmap_reduce: first argument must be a code reference (map)",
15626 line,
15627 ));
15628 };
15629 let Some(reduce) = args[1].as_code_ref() else {
15630 return Err(PerlError::runtime(
15631 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
15632 line,
15633 ));
15634 };
15635 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
15636 if args.len() > 3 {
15637 return Err(PerlError::runtime(
15638 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
15639 line,
15640 ));
15641 }
15642 self.pipeline_push(
15643 &p,
15644 PipelineOp::PMapReduce {
15645 map,
15646 reduce,
15647 progress,
15648 },
15649 line,
15650 )?;
15651 Ok(PerlValue::pipeline(Arc::clone(&p)))
15652 }
15653 "collect" => {
15654 if !args.is_empty() {
15655 return Err(PerlError::runtime(
15656 "pipeline collect takes no arguments",
15657 line,
15658 ));
15659 }
15660 self.pipeline_collect(&p, line)
15661 }
15662 _ => {
15663 if let Some(sub) = self.resolve_sub_by_name(method) {
15666 if !args.is_empty() {
15667 return Err(PerlError::runtime(
15668 format!(
15669 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
15670 method
15671 ),
15672 line,
15673 ));
15674 }
15675 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
15676 Ok(PerlValue::pipeline(Arc::clone(&p)))
15677 } else {
15678 Err(PerlError::runtime(
15679 format!("Unknown method for pipeline: {}", method),
15680 line,
15681 ))
15682 }
15683 }
15684 }
15685 }
15686
15687 fn pipeline_parallel_map(
15688 &mut self,
15689 items: Vec<PerlValue>,
15690 sub: &Arc<PerlSub>,
15691 progress: bool,
15692 ) -> Vec<PerlValue> {
15693 let subs = self.subs.clone();
15694 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
15695 let pmap_progress = PmapProgress::new(progress, items.len());
15696 let results: Vec<PerlValue> = items
15697 .into_par_iter()
15698 .map(|item| {
15699 let mut local_interp = Interpreter::new();
15700 local_interp.subs = subs.clone();
15701 local_interp.scope.restore_capture(&scope_capture);
15702 local_interp
15703 .scope
15704 .restore_atomics(&atomic_arrays, &atomic_hashes);
15705 local_interp.enable_parallel_guard();
15706 local_interp.scope.set_topic(item);
15707 local_interp.scope_push_hook();
15708 let val = match local_interp.exec_block_no_scope(&sub.body) {
15709 Ok(val) => val,
15710 Err(_) => PerlValue::UNDEF,
15711 };
15712 local_interp.scope_pop_hook();
15713 pmap_progress.tick();
15714 val
15715 })
15716 .collect();
15717 pmap_progress.finish();
15718 results
15719 }
15720
15721 fn pipeline_par_stream_filter(
15723 &mut self,
15724 items: Vec<PerlValue>,
15725 sub: &Arc<PerlSub>,
15726 ) -> Vec<PerlValue> {
15727 if items.is_empty() {
15728 return items;
15729 }
15730 let subs = self.subs.clone();
15731 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
15732 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
15733 let mut kept: Vec<(usize, PerlValue)> = indexed
15734 .into_par_iter()
15735 .filter_map(|(i, item)| {
15736 let mut local_interp = Interpreter::new();
15737 local_interp.subs = subs.clone();
15738 local_interp.scope.restore_capture(&scope_capture);
15739 local_interp
15740 .scope
15741 .restore_atomics(&atomic_arrays, &atomic_hashes);
15742 local_interp.enable_parallel_guard();
15743 local_interp.scope.set_topic(item.clone());
15744 local_interp.scope_push_hook();
15745 let keep = match local_interp.exec_block_no_scope(&sub.body) {
15746 Ok(val) => val.is_true(),
15747 Err(_) => false,
15748 };
15749 local_interp.scope_pop_hook();
15750 if keep {
15751 Some((i, item))
15752 } else {
15753 None
15754 }
15755 })
15756 .collect();
15757 kept.sort_by_key(|(i, _)| *i);
15758 kept.into_iter().map(|(_, x)| x).collect()
15759 }
15760
15761 fn pipeline_par_stream_map(
15763 &mut self,
15764 items: Vec<PerlValue>,
15765 sub: &Arc<PerlSub>,
15766 ) -> Vec<PerlValue> {
15767 if items.is_empty() {
15768 return items;
15769 }
15770 let subs = self.subs.clone();
15771 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
15772 let indexed: Vec<(usize, PerlValue)> = items.into_iter().enumerate().collect();
15773 let mut mapped: Vec<(usize, PerlValue)> = indexed
15774 .into_par_iter()
15775 .map(|(i, item)| {
15776 let mut local_interp = Interpreter::new();
15777 local_interp.subs = subs.clone();
15778 local_interp.scope.restore_capture(&scope_capture);
15779 local_interp
15780 .scope
15781 .restore_atomics(&atomic_arrays, &atomic_hashes);
15782 local_interp.enable_parallel_guard();
15783 local_interp.scope.set_topic(item);
15784 local_interp.scope_push_hook();
15785 let val = match local_interp.exec_block_no_scope(&sub.body) {
15786 Ok(val) => val,
15787 Err(_) => PerlValue::UNDEF,
15788 };
15789 local_interp.scope_pop_hook();
15790 (i, val)
15791 })
15792 .collect();
15793 mapped.sort_by_key(|(i, _)| *i);
15794 mapped.into_iter().map(|(_, x)| x).collect()
15795 }
15796
15797 fn pipeline_collect(
15798 &mut self,
15799 p: &Arc<Mutex<PipelineInner>>,
15800 line: usize,
15801 ) -> PerlResult<PerlValue> {
15802 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
15803 let g = p.lock();
15804 (
15805 g.source.clone(),
15806 g.ops.clone(),
15807 g.par_stream,
15808 g.streaming,
15809 g.streaming_workers,
15810 g.streaming_buffer,
15811 )
15812 };
15813 if streaming {
15814 return self.pipeline_collect_streaming(
15815 v,
15816 &ops,
15817 streaming_workers,
15818 streaming_buffer,
15819 line,
15820 );
15821 }
15822 for op in ops {
15823 match op {
15824 PipelineOp::Filter(sub) => {
15825 if par_stream {
15826 v = self.pipeline_par_stream_filter(v, &sub);
15827 } else {
15828 let mut out = Vec::new();
15829 for item in v {
15830 self.scope_push_hook();
15831 self.scope.set_topic(item.clone());
15832 if let Some(ref env) = sub.closure_env {
15833 self.scope.restore_capture(env);
15834 }
15835 let keep = match self.exec_block_no_scope(&sub.body) {
15836 Ok(val) => val.is_true(),
15837 Err(_) => false,
15838 };
15839 self.scope_pop_hook();
15840 if keep {
15841 out.push(item);
15842 }
15843 }
15844 v = out;
15845 }
15846 }
15847 PipelineOp::Map(sub) => {
15848 if par_stream {
15849 v = self.pipeline_par_stream_map(v, &sub);
15850 } else {
15851 let mut out = Vec::new();
15852 for item in v {
15853 self.scope_push_hook();
15854 self.scope.set_topic(item);
15855 if let Some(ref env) = sub.closure_env {
15856 self.scope.restore_capture(env);
15857 }
15858 let mapped = match self.exec_block_no_scope(&sub.body) {
15859 Ok(val) => val,
15860 Err(_) => PerlValue::UNDEF,
15861 };
15862 self.scope_pop_hook();
15863 out.push(mapped);
15864 }
15865 v = out;
15866 }
15867 }
15868 PipelineOp::Tap(sub) => {
15869 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
15870 Ok(_) => {}
15871 Err(FlowOrError::Error(e)) => return Err(e),
15872 Err(FlowOrError::Flow(_)) => {
15873 return Err(PerlError::runtime(
15874 "tap: unsupported control flow in block",
15875 line,
15876 ));
15877 }
15878 }
15879 }
15880 PipelineOp::Take(n) => {
15881 let n = n.max(0) as usize;
15882 if v.len() > n {
15883 v.truncate(n);
15884 }
15885 }
15886 PipelineOp::PMap { sub, progress } => {
15887 v = self.pipeline_parallel_map(v, &sub, progress);
15888 }
15889 PipelineOp::PGrep { sub, progress } => {
15890 let subs = self.subs.clone();
15891 let (scope_capture, atomic_arrays, atomic_hashes) =
15892 self.scope.capture_with_atomics();
15893 let pmap_progress = PmapProgress::new(progress, v.len());
15894 v = v
15895 .into_par_iter()
15896 .filter_map(|item| {
15897 let mut local_interp = Interpreter::new();
15898 local_interp.subs = subs.clone();
15899 local_interp.scope.restore_capture(&scope_capture);
15900 local_interp
15901 .scope
15902 .restore_atomics(&atomic_arrays, &atomic_hashes);
15903 local_interp.enable_parallel_guard();
15904 local_interp.scope.set_topic(item.clone());
15905 local_interp.scope_push_hook();
15906 let keep = match local_interp.exec_block_no_scope(&sub.body) {
15907 Ok(val) => val.is_true(),
15908 Err(_) => false,
15909 };
15910 local_interp.scope_pop_hook();
15911 pmap_progress.tick();
15912 if keep {
15913 Some(item)
15914 } else {
15915 None
15916 }
15917 })
15918 .collect();
15919 pmap_progress.finish();
15920 }
15921 PipelineOp::PFor { sub, progress } => {
15922 let subs = self.subs.clone();
15923 let (scope_capture, atomic_arrays, atomic_hashes) =
15924 self.scope.capture_with_atomics();
15925 let pmap_progress = PmapProgress::new(progress, v.len());
15926 let first_err: Arc<Mutex<Option<PerlError>>> = Arc::new(Mutex::new(None));
15927 v.clone().into_par_iter().for_each(|item| {
15928 if first_err.lock().is_some() {
15929 return;
15930 }
15931 let mut local_interp = Interpreter::new();
15932 local_interp.subs = subs.clone();
15933 local_interp.scope.restore_capture(&scope_capture);
15934 local_interp
15935 .scope
15936 .restore_atomics(&atomic_arrays, &atomic_hashes);
15937 local_interp.enable_parallel_guard();
15938 local_interp.scope.set_topic(item);
15939 local_interp.scope_push_hook();
15940 match local_interp.exec_block_no_scope(&sub.body) {
15941 Ok(_) => {}
15942 Err(e) => {
15943 let stryke = match e {
15944 FlowOrError::Error(stryke) => stryke,
15945 FlowOrError::Flow(_) => PerlError::runtime(
15946 "return/last/next/redo not supported inside pipeline pfor block",
15947 line,
15948 ),
15949 };
15950 let mut g = first_err.lock();
15951 if g.is_none() {
15952 *g = Some(stryke);
15953 }
15954 }
15955 }
15956 local_interp.scope_pop_hook();
15957 pmap_progress.tick();
15958 });
15959 pmap_progress.finish();
15960 let pfor_err = first_err.lock().take();
15961 if let Some(e) = pfor_err {
15962 return Err(e);
15963 }
15964 }
15965 PipelineOp::PMapChunked {
15966 chunk,
15967 sub,
15968 progress,
15969 } => {
15970 let chunk_n = chunk.max(1) as usize;
15971 let subs = self.subs.clone();
15972 let (scope_capture, atomic_arrays, atomic_hashes) =
15973 self.scope.capture_with_atomics();
15974 let indexed_chunks: Vec<(usize, Vec<PerlValue>)> = v
15975 .chunks(chunk_n)
15976 .enumerate()
15977 .map(|(i, c)| (i, c.to_vec()))
15978 .collect();
15979 let n_chunks = indexed_chunks.len();
15980 let pmap_progress = PmapProgress::new(progress, n_chunks);
15981 let mut chunk_results: Vec<(usize, Vec<PerlValue>)> = indexed_chunks
15982 .into_par_iter()
15983 .map(|(chunk_idx, chunk)| {
15984 let mut local_interp = Interpreter::new();
15985 local_interp.subs = subs.clone();
15986 local_interp.scope.restore_capture(&scope_capture);
15987 local_interp
15988 .scope
15989 .restore_atomics(&atomic_arrays, &atomic_hashes);
15990 local_interp.enable_parallel_guard();
15991 let mut out = Vec::with_capacity(chunk.len());
15992 for item in chunk {
15993 local_interp.scope.set_topic(item);
15994 local_interp.scope_push_hook();
15995 match local_interp.exec_block_no_scope(&sub.body) {
15996 Ok(val) => {
15997 local_interp.scope_pop_hook();
15998 out.push(val);
15999 }
16000 Err(_) => {
16001 local_interp.scope_pop_hook();
16002 out.push(PerlValue::UNDEF);
16003 }
16004 }
16005 }
16006 pmap_progress.tick();
16007 (chunk_idx, out)
16008 })
16009 .collect();
16010 pmap_progress.finish();
16011 chunk_results.sort_by_key(|(i, _)| *i);
16012 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
16013 }
16014 PipelineOp::PSort { cmp, progress } => {
16015 let pmap_progress = PmapProgress::new(progress, 2);
16016 pmap_progress.tick();
16017 match cmp {
16018 Some(cmp_block) => {
16019 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
16020 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
16021 } else {
16022 let subs = self.subs.clone();
16023 let scope_capture = self.scope.capture();
16024 v.par_sort_by(|a, b| {
16025 let mut local_interp = Interpreter::new();
16026 local_interp.subs = subs.clone();
16027 local_interp.scope.restore_capture(&scope_capture);
16028 local_interp.enable_parallel_guard();
16029 let _ = local_interp.scope.set_scalar("a", a.clone());
16030 let _ = local_interp.scope.set_scalar("b", b.clone());
16031 let _ = local_interp.scope.set_scalar("_0", a.clone());
16032 let _ = local_interp.scope.set_scalar("_1", b.clone());
16033 local_interp.scope_push_hook();
16034 let ord =
16035 match local_interp.exec_block_no_scope(&cmp_block.body) {
16036 Ok(v) => {
16037 let n = v.to_int();
16038 if n < 0 {
16039 std::cmp::Ordering::Less
16040 } else if n > 0 {
16041 std::cmp::Ordering::Greater
16042 } else {
16043 std::cmp::Ordering::Equal
16044 }
16045 }
16046 Err(_) => std::cmp::Ordering::Equal,
16047 };
16048 local_interp.scope_pop_hook();
16049 ord
16050 });
16051 }
16052 }
16053 None => {
16054 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
16055 }
16056 }
16057 pmap_progress.tick();
16058 pmap_progress.finish();
16059 }
16060 PipelineOp::PCache { sub, progress } => {
16061 let subs = self.subs.clone();
16062 let scope_capture = self.scope.capture();
16063 let cache = &*crate::pcache::GLOBAL_PCACHE;
16064 let pmap_progress = PmapProgress::new(progress, v.len());
16065 v = v
16066 .into_par_iter()
16067 .map(|item| {
16068 let k = crate::pcache::cache_key(&item);
16069 if let Some(cached) = cache.get(&k) {
16070 pmap_progress.tick();
16071 return cached.clone();
16072 }
16073 let mut local_interp = Interpreter::new();
16074 local_interp.subs = subs.clone();
16075 local_interp.scope.restore_capture(&scope_capture);
16076 local_interp.enable_parallel_guard();
16077 local_interp.scope.set_topic(item.clone());
16078 local_interp.scope_push_hook();
16079 let val = match local_interp.exec_block_no_scope(&sub.body) {
16080 Ok(v) => v,
16081 Err(_) => PerlValue::UNDEF,
16082 };
16083 local_interp.scope_pop_hook();
16084 cache.insert(k, val.clone());
16085 pmap_progress.tick();
16086 val
16087 })
16088 .collect();
16089 pmap_progress.finish();
16090 }
16091 PipelineOp::PReduce { sub, progress } => {
16092 if v.is_empty() {
16093 return Ok(PerlValue::UNDEF);
16094 }
16095 if v.len() == 1 {
16096 return Ok(v.into_iter().next().unwrap());
16097 }
16098 let block = sub.body.clone();
16099 let subs = self.subs.clone();
16100 let scope_capture = self.scope.capture();
16101 let pmap_progress = PmapProgress::new(progress, v.len());
16102 let result = v
16103 .into_par_iter()
16104 .map(|x| {
16105 pmap_progress.tick();
16106 x
16107 })
16108 .reduce_with(|a, b| {
16109 let mut local_interp = Interpreter::new();
16110 local_interp.subs = subs.clone();
16111 local_interp.scope.restore_capture(&scope_capture);
16112 local_interp.enable_parallel_guard();
16113 let _ = local_interp.scope.set_scalar("a", a.clone());
16114 let _ = local_interp.scope.set_scalar("b", b.clone());
16115 let _ = local_interp.scope.set_scalar("_0", a);
16116 let _ = local_interp.scope.set_scalar("_1", b);
16117 match local_interp.exec_block(&block) {
16118 Ok(val) => val,
16119 Err(_) => PerlValue::UNDEF,
16120 }
16121 });
16122 pmap_progress.finish();
16123 return Ok(result.unwrap_or(PerlValue::UNDEF));
16124 }
16125 PipelineOp::PReduceInit {
16126 init,
16127 sub,
16128 progress,
16129 } => {
16130 if v.is_empty() {
16131 return Ok(init);
16132 }
16133 let block = sub.body.clone();
16134 let subs = self.subs.clone();
16135 let scope_capture = self.scope.capture();
16136 let cap: &[(String, PerlValue)] = scope_capture.as_slice();
16137 if v.len() == 1 {
16138 return Ok(fold_preduce_init_step(
16139 &subs,
16140 cap,
16141 &block,
16142 preduce_init_fold_identity(&init),
16143 v.into_iter().next().unwrap(),
16144 ));
16145 }
16146 let pmap_progress = PmapProgress::new(progress, v.len());
16147 let result = v
16148 .into_par_iter()
16149 .fold(
16150 || preduce_init_fold_identity(&init),
16151 |acc, item| {
16152 pmap_progress.tick();
16153 fold_preduce_init_step(&subs, cap, &block, acc, item)
16154 },
16155 )
16156 .reduce(
16157 || preduce_init_fold_identity(&init),
16158 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
16159 );
16160 pmap_progress.finish();
16161 return Ok(result);
16162 }
16163 PipelineOp::PMapReduce {
16164 map,
16165 reduce,
16166 progress,
16167 } => {
16168 if v.is_empty() {
16169 return Ok(PerlValue::UNDEF);
16170 }
16171 let map_block = map.body.clone();
16172 let reduce_block = reduce.body.clone();
16173 let subs = self.subs.clone();
16174 let scope_capture = self.scope.capture();
16175 if v.len() == 1 {
16176 let mut local_interp = Interpreter::new();
16177 local_interp.subs = subs.clone();
16178 local_interp.scope.restore_capture(&scope_capture);
16179 local_interp.scope.set_topic(v[0].clone());
16180 return match local_interp.exec_block_no_scope(&map_block) {
16181 Ok(val) => Ok(val),
16182 Err(_) => Ok(PerlValue::UNDEF),
16183 };
16184 }
16185 let pmap_progress = PmapProgress::new(progress, v.len());
16186 let result = v
16187 .into_par_iter()
16188 .map(|item| {
16189 let mut local_interp = Interpreter::new();
16190 local_interp.subs = subs.clone();
16191 local_interp.scope.restore_capture(&scope_capture);
16192 local_interp.scope.set_topic(item);
16193 let val = match local_interp.exec_block_no_scope(&map_block) {
16194 Ok(val) => val,
16195 Err(_) => PerlValue::UNDEF,
16196 };
16197 pmap_progress.tick();
16198 val
16199 })
16200 .reduce_with(|a, b| {
16201 let mut local_interp = Interpreter::new();
16202 local_interp.subs = subs.clone();
16203 local_interp.scope.restore_capture(&scope_capture);
16204 let _ = local_interp.scope.set_scalar("a", a.clone());
16205 let _ = local_interp.scope.set_scalar("b", b.clone());
16206 let _ = local_interp.scope.set_scalar("_0", a);
16207 let _ = local_interp.scope.set_scalar("_1", b);
16208 match local_interp.exec_block_no_scope(&reduce_block) {
16209 Ok(val) => val,
16210 Err(_) => PerlValue::UNDEF,
16211 }
16212 });
16213 pmap_progress.finish();
16214 return Ok(result.unwrap_or(PerlValue::UNDEF));
16215 }
16216 }
16217 }
16218 Ok(PerlValue::array(v))
16219 }
16220
16221 fn pipeline_collect_streaming(
16224 &mut self,
16225 source: Vec<PerlValue>,
16226 ops: &[PipelineOp],
16227 workers_per_stage: usize,
16228 buffer: usize,
16229 line: usize,
16230 ) -> PerlResult<PerlValue> {
16231 use crossbeam::channel::{bounded, Receiver, Sender};
16232
16233 for op in ops {
16235 match op {
16236 PipelineOp::PSort { .. }
16237 | PipelineOp::PReduce { .. }
16238 | PipelineOp::PReduceInit { .. }
16239 | PipelineOp::PMapReduce { .. }
16240 | PipelineOp::PMapChunked { .. } => {
16241 return Err(PerlError::runtime(
16242 format!(
16243 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
16244 std::mem::discriminant(op)
16245 ),
16246 line,
16247 ));
16248 }
16249 _ => {}
16250 }
16251 }
16252
16253 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
16256 if streamable_ops.is_empty() {
16257 return Ok(PerlValue::array(source));
16258 }
16259
16260 let n_stages = streamable_ops.len();
16261 let wn = if workers_per_stage > 0 {
16262 workers_per_stage
16263 } else {
16264 self.parallel_thread_count()
16265 };
16266 let subs = self.subs.clone();
16267 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
16268
16269 let mut channels: Vec<(Sender<PerlValue>, Receiver<PerlValue>)> =
16274 (0..=n_stages).map(|_| bounded(buffer)).collect();
16275
16276 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
16277 let take_done: Arc<std::sync::atomic::AtomicBool> =
16278 Arc::new(std::sync::atomic::AtomicBool::new(false));
16279
16280 let source_tx = channels[0].0.clone();
16283 let result_rx = channels[n_stages].1.clone();
16284 let results: Arc<Mutex<Vec<PerlValue>>> = Arc::new(Mutex::new(Vec::new()));
16285
16286 std::thread::scope(|scope| {
16287 let result_rx_c = result_rx.clone();
16290 let results_c = Arc::clone(&results);
16291 scope.spawn(move || {
16292 while let Ok(item) = result_rx_c.recv() {
16293 results_c.lock().push(item);
16294 }
16295 });
16296
16297 let err_s = Arc::clone(&err);
16299 let take_done_s = Arc::clone(&take_done);
16300 scope.spawn(move || {
16301 for item in source {
16302 if err_s.lock().is_some()
16303 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
16304 {
16305 break;
16306 }
16307 if source_tx.send(item).is_err() {
16308 break;
16309 }
16310 }
16311 });
16312
16313 for (stage_idx, op) in streamable_ops.iter().enumerate() {
16315 let rx = channels[stage_idx].1.clone();
16316 let tx = channels[stage_idx + 1].0.clone();
16317
16318 for _ in 0..wn {
16319 let rx = rx.clone();
16320 let tx = tx.clone();
16321 let subs = subs.clone();
16322 let capture = capture.clone();
16323 let atomic_arrays = atomic_arrays.clone();
16324 let atomic_hashes = atomic_hashes.clone();
16325 let err_w = Arc::clone(&err);
16326 let take_done_w = Arc::clone(&take_done);
16327
16328 match *op {
16329 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
16330 let sub = Arc::clone(sub);
16331 scope.spawn(move || {
16332 while let Ok(item) = rx.recv() {
16333 if err_w.lock().is_some() {
16334 break;
16335 }
16336 let mut interp = Interpreter::new();
16337 interp.subs = subs.clone();
16338 interp.scope.restore_capture(&capture);
16339 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16340 interp.enable_parallel_guard();
16341 interp.scope.set_topic(item.clone());
16342 interp.scope_push_hook();
16343 let keep = match interp.exec_block_no_scope(&sub.body) {
16344 Ok(val) => val.is_true(),
16345 Err(_) => false,
16346 };
16347 interp.scope_pop_hook();
16348 if keep && tx.send(item).is_err() {
16349 break;
16350 }
16351 }
16352 });
16353 }
16354 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
16355 let sub = Arc::clone(sub);
16356 scope.spawn(move || {
16357 while let Ok(item) = rx.recv() {
16358 if err_w.lock().is_some() {
16359 break;
16360 }
16361 let mut interp = Interpreter::new();
16362 interp.subs = subs.clone();
16363 interp.scope.restore_capture(&capture);
16364 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
16365 interp.enable_parallel_guard();
16366 interp.scope.set_topic(item);
16367 interp.scope_push_hook();
16368 let mapped = match interp.exec_block_no_scope(&sub.body) {
16369 Ok(val) => val,
16370 Err(_) => PerlValue::UNDEF,
16371 };
16372 interp.scope_pop_hook();
16373 if tx.send(mapped).is_err() {
16374 break;
16375 }
16376 }
16377 });
16378 }
16379 PipelineOp::Take(n) => {
16380 let limit = (*n).max(0) as usize;
16381 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
16382 let count_w = Arc::clone(&count);
16383 scope.spawn(move || {
16384 while let Ok(item) = rx.recv() {
16385 let prev =
16386 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
16387 if prev >= limit {
16388 take_done_w
16389 .store(true, std::sync::atomic::Ordering::Relaxed);
16390 break;
16391 }
16392 if tx.send(item).is_err() {
16393 break;
16394 }
16395 }
16396 });
16397 break;
16399 }
16400 PipelineOp::PFor { ref sub, .. } => {
16401 let sub = Arc::clone(sub);
16402 scope.spawn(move || {
16403 while let Ok(item) = rx.recv() {
16404 if err_w.lock().is_some() {
16405 break;
16406 }
16407 let mut interp = Interpreter::new();
16408 interp.subs = subs.clone();
16409 interp.scope.restore_capture(&capture);
16410 interp
16411 .scope
16412 .restore_atomics(&atomic_arrays, &atomic_hashes);
16413 interp.enable_parallel_guard();
16414 interp.scope.set_topic(item.clone());
16415 interp.scope_push_hook();
16416 match interp.exec_block_no_scope(&sub.body) {
16417 Ok(_) => {}
16418 Err(e) => {
16419 let msg = match e {
16420 FlowOrError::Error(stryke) => stryke.to_string(),
16421 FlowOrError::Flow(_) => {
16422 "unexpected control flow in par_pipeline_stream pfor".into()
16423 }
16424 };
16425 let mut g = err_w.lock();
16426 if g.is_none() {
16427 *g = Some(msg);
16428 }
16429 interp.scope_pop_hook();
16430 break;
16431 }
16432 }
16433 interp.scope_pop_hook();
16434 if tx.send(item).is_err() {
16435 break;
16436 }
16437 }
16438 });
16439 }
16440 PipelineOp::Tap(ref sub) => {
16441 let sub = Arc::clone(sub);
16442 scope.spawn(move || {
16443 while let Ok(item) = rx.recv() {
16444 if err_w.lock().is_some() {
16445 break;
16446 }
16447 let mut interp = Interpreter::new();
16448 interp.subs = subs.clone();
16449 interp.scope.restore_capture(&capture);
16450 interp
16451 .scope
16452 .restore_atomics(&atomic_arrays, &atomic_hashes);
16453 interp.enable_parallel_guard();
16454 match interp.call_sub(
16455 &sub,
16456 vec![item.clone()],
16457 WantarrayCtx::Void,
16458 line,
16459 )
16460 {
16461 Ok(_) => {}
16462 Err(e) => {
16463 let msg = match e {
16464 FlowOrError::Error(stryke) => stryke.to_string(),
16465 FlowOrError::Flow(_) => {
16466 "unexpected control flow in par_pipeline_stream tap"
16467 .into()
16468 }
16469 };
16470 let mut g = err_w.lock();
16471 if g.is_none() {
16472 *g = Some(msg);
16473 }
16474 break;
16475 }
16476 }
16477 if tx.send(item).is_err() {
16478 break;
16479 }
16480 }
16481 });
16482 }
16483 PipelineOp::PCache { ref sub, .. } => {
16484 let sub = Arc::clone(sub);
16485 scope.spawn(move || {
16486 while let Ok(item) = rx.recv() {
16487 if err_w.lock().is_some() {
16488 break;
16489 }
16490 let k = crate::pcache::cache_key(&item);
16491 let val = if let Some(cached) =
16492 crate::pcache::GLOBAL_PCACHE.get(&k)
16493 {
16494 cached.clone()
16495 } else {
16496 let mut interp = Interpreter::new();
16497 interp.subs = subs.clone();
16498 interp.scope.restore_capture(&capture);
16499 interp
16500 .scope
16501 .restore_atomics(&atomic_arrays, &atomic_hashes);
16502 interp.enable_parallel_guard();
16503 interp.scope.set_topic(item);
16504 interp.scope_push_hook();
16505 let v = match interp.exec_block_no_scope(&sub.body) {
16506 Ok(v) => v,
16507 Err(_) => PerlValue::UNDEF,
16508 };
16509 interp.scope_pop_hook();
16510 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
16511 v
16512 };
16513 if tx.send(val).is_err() {
16514 break;
16515 }
16516 }
16517 });
16518 }
16519 _ => unreachable!(),
16521 }
16522 }
16523 }
16524
16525 channels.clear();
16529 drop(result_rx);
16530 });
16531
16532 if let Some(msg) = err.lock().take() {
16533 return Err(PerlError::runtime(msg, line));
16534 }
16535
16536 let results = std::mem::take(&mut *results.lock());
16537 Ok(PerlValue::array(results))
16538 }
16539
16540 fn heap_compare(&mut self, cmp: &Arc<PerlSub>, a: &PerlValue, b: &PerlValue) -> Ordering {
16541 self.scope_push_hook();
16542 if let Some(ref env) = cmp.closure_env {
16543 self.scope.restore_capture(env);
16544 }
16545 let _ = self.scope.set_scalar("a", a.clone());
16546 let _ = self.scope.set_scalar("b", b.clone());
16547 let _ = self.scope.set_scalar("_0", a.clone());
16548 let _ = self.scope.set_scalar("_1", b.clone());
16549 let ord = match self.exec_block_no_scope(&cmp.body) {
16550 Ok(v) => {
16551 let n = v.to_int();
16552 if n < 0 {
16553 Ordering::Less
16554 } else if n > 0 {
16555 Ordering::Greater
16556 } else {
16557 Ordering::Equal
16558 }
16559 }
16560 Err(_) => Ordering::Equal,
16561 };
16562 self.scope_pop_hook();
16563 ord
16564 }
16565
16566 fn heap_sift_up(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
16567 while i > 0 {
16568 let p = (i - 1) / 2;
16569 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
16570 break;
16571 }
16572 items.swap(i, p);
16573 i = p;
16574 }
16575 }
16576
16577 fn heap_sift_down(&mut self, items: &mut [PerlValue], cmp: &Arc<PerlSub>, mut i: usize) {
16578 let n = items.len();
16579 loop {
16580 let mut sm = i;
16581 let l = 2 * i + 1;
16582 let r = 2 * i + 2;
16583 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
16584 sm = l;
16585 }
16586 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
16587 sm = r;
16588 }
16589 if sm == i {
16590 break;
16591 }
16592 items.swap(i, sm);
16593 i = sm;
16594 }
16595 }
16596
16597 fn hash_for_signature_destruct(
16598 &mut self,
16599 v: &PerlValue,
16600 line: usize,
16601 ) -> PerlResult<IndexMap<String, PerlValue>> {
16602 let Some(m) = self.match_subject_as_hash(v) else {
16603 return Err(PerlError::runtime(
16604 format!(
16605 "sub signature hash destruct: expected HASH or HASH reference, got {}",
16606 v.ref_type()
16607 ),
16608 line,
16609 ));
16610 };
16611 Ok(m)
16612 }
16613
16614 pub(crate) fn apply_sub_signature(
16616 &mut self,
16617 sub: &PerlSub,
16618 argv: &[PerlValue],
16619 line: usize,
16620 ) -> PerlResult<()> {
16621 if sub.params.is_empty() {
16622 return Ok(());
16623 }
16624 let mut i = 0usize;
16625 for p in &sub.params {
16626 match p {
16627 SubSigParam::Scalar(name, ty) => {
16628 let val = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
16629 i += 1;
16630 if let Some(t) = ty {
16631 if let Err(e) = t.check_value(&val) {
16632 return Err(PerlError::runtime(
16633 format!("sub parameter ${}: {}", name, e),
16634 line,
16635 ));
16636 }
16637 }
16638 let n = self.english_scalar_name(name);
16639 self.scope.declare_scalar(n, val);
16640 }
16641 SubSigParam::ArrayDestruct(elems) => {
16642 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
16643 i += 1;
16644 let Some(arr) = self.match_subject_as_array(&arg) else {
16645 return Err(PerlError::runtime(
16646 format!(
16647 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
16648 arg.ref_type()
16649 ),
16650 line,
16651 ));
16652 };
16653 let binds = self
16654 .match_array_pattern_elems(&arr, elems, line)
16655 .map_err(|e| match e {
16656 FlowOrError::Error(stryke) => stryke,
16657 FlowOrError::Flow(_) => PerlError::runtime(
16658 "unexpected flow in sub signature array destruct",
16659 line,
16660 ),
16661 })?;
16662 let Some(binds) = binds else {
16663 return Err(PerlError::runtime(
16664 "sub signature array destruct: length or element mismatch",
16665 line,
16666 ));
16667 };
16668 for b in binds {
16669 match b {
16670 PatternBinding::Scalar(name, v) => {
16671 let n = self.english_scalar_name(&name);
16672 self.scope.declare_scalar(n, v);
16673 }
16674 PatternBinding::Array(name, elems) => {
16675 self.scope.declare_array(&name, elems);
16676 }
16677 }
16678 }
16679 }
16680 SubSigParam::HashDestruct(pairs) => {
16681 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
16682 i += 1;
16683 let map = self.hash_for_signature_destruct(&arg, line)?;
16684 for (key, varname) in pairs {
16685 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
16686 let n = self.english_scalar_name(varname);
16687 self.scope.declare_scalar(n, v);
16688 }
16689 }
16690 }
16691 }
16692 Ok(())
16693 }
16694
16695 pub(crate) fn try_hof_dispatch(
16699 &mut self,
16700 sub: &PerlSub,
16701 args: &[PerlValue],
16702 want: WantarrayCtx,
16703 line: usize,
16704 ) -> Option<ExecResult> {
16705 let env = sub.closure_env.as_ref()?;
16706 fn env_get<'a>(env: &'a [(String, PerlValue)], key: &str) -> Option<&'a PerlValue> {
16707 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
16708 }
16709
16710 match sub.name.as_str() {
16711 "__comp__" => {
16713 let fns = env_get(env, "__comp_fns__")?.to_list();
16714 let mut val = args.first().cloned().unwrap_or(PerlValue::UNDEF);
16715 for f in fns.iter().rev() {
16716 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
16717 Ok(v) => val = v,
16718 Err(e) => return Some(Err(e)),
16719 }
16720 }
16721 Some(Ok(val))
16722 }
16723 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
16725 "__juxt__" => {
16727 let fns = env_get(env, "__juxt_fns__")?.to_list();
16728 let mut results = Vec::with_capacity(fns.len());
16729 for f in &fns {
16730 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
16731 Ok(v) => results.push(v),
16732 Err(e) => return Some(Err(e)),
16733 }
16734 }
16735 Some(Ok(PerlValue::array(results)))
16736 }
16737 "__partial__" => {
16739 let fn_val = env_get(env, "__partial_fn__")?.clone();
16740 let bound = env_get(env, "__partial_args__")?.to_list();
16741 let mut all_args = bound;
16742 all_args.extend_from_slice(args);
16743 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
16744 }
16745 "__complement__" => {
16747 let fn_val = env_get(env, "__complement_fn__")?.clone();
16748 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
16749 Ok(v) => Some(Ok(PerlValue::integer(if v.is_true() { 0 } else { 1 }))),
16750 Err(e) => Some(Err(e)),
16751 }
16752 }
16753 "__fnil__" => {
16755 let fn_val = env_get(env, "__fnil_fn__")?.clone();
16756 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
16757 let mut patched = args.to_vec();
16758 for (i, d) in defaults.iter().enumerate() {
16759 if i < patched.len() {
16760 if patched[i].is_undef() {
16761 patched[i] = d.clone();
16762 }
16763 } else {
16764 patched.push(d.clone());
16765 }
16766 }
16767 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
16768 }
16769 "__memoize__" => {
16771 let fn_val = env_get(env, "__memoize_fn__")?.clone();
16772 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
16773 let key = args
16774 .iter()
16775 .map(|a| a.to_string())
16776 .collect::<Vec<_>>()
16777 .join("\x00");
16778 if let Some(href) = cache_ref.as_hash_ref() {
16779 if let Some(cached) = href.read().get(&key) {
16780 return Some(Ok(cached.clone()));
16781 }
16782 }
16783 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
16784 Ok(v) => {
16785 if let Some(href) = cache_ref.as_hash_ref() {
16786 href.write().insert(key, v.clone());
16787 }
16788 Some(Ok(v))
16789 }
16790 Err(e) => Some(Err(e)),
16791 }
16792 }
16793 "__curry__" => {
16795 let fn_val = env_get(env, "__curry_fn__")?.clone();
16796 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
16797 let bound = env_get(env, "__curry_bound__")?.to_list();
16798 let mut all = bound;
16799 all.extend_from_slice(args);
16800 if all.len() >= arity {
16801 Some(self.dispatch_indirect_call(fn_val, all, want, line))
16802 } else {
16803 let curry_sub = PerlSub {
16804 name: "__curry__".to_string(),
16805 params: vec![],
16806 body: vec![],
16807 closure_env: Some(vec![
16808 ("__curry_fn__".to_string(), fn_val),
16809 (
16810 "__curry_arity__".to_string(),
16811 PerlValue::integer(arity as i64),
16812 ),
16813 ("__curry_bound__".to_string(), PerlValue::array(all)),
16814 ]),
16815 prototype: None,
16816 fib_like: None,
16817 };
16818 Some(Ok(PerlValue::code_ref(Arc::new(curry_sub))))
16819 }
16820 }
16821 "__once__" => {
16823 let cache_ref = env_get(env, "__once_cache__")?.clone();
16824 if let Some(href) = cache_ref.as_hash_ref() {
16825 let r = href.read();
16826 if r.contains_key("done") {
16827 return Some(Ok(r.get("val").cloned().unwrap_or(PerlValue::UNDEF)));
16828 }
16829 }
16830 let fn_val = env_get(env, "__once_fn__")?.clone();
16831 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
16832 Ok(v) => {
16833 if let Some(href) = cache_ref.as_hash_ref() {
16834 let mut w = href.write();
16835 w.insert("done".to_string(), PerlValue::integer(1));
16836 w.insert("val".to_string(), v.clone());
16837 }
16838 Some(Ok(v))
16839 }
16840 Err(e) => Some(Err(e)),
16841 }
16842 }
16843 _ => None,
16844 }
16845 }
16846
16847 pub(crate) fn call_sub(
16848 &mut self,
16849 sub: &PerlSub,
16850 args: Vec<PerlValue>,
16851 want: WantarrayCtx,
16852 _line: usize,
16853 ) -> ExecResult {
16854 self.current_sub_stack.push(Arc::new(sub.clone()));
16856
16857 self.scope_push_hook();
16860 self.scope.declare_array("_", args.clone());
16861 if let Some(ref env) = sub.closure_env {
16862 self.scope.restore_capture(env);
16863 }
16864 self.scope.set_closure_args(&args);
16868 let argv = self.scope.take_sub_underscore().unwrap_or_default();
16870 self.apply_sub_signature(sub, &argv, _line)?;
16871 let saved = self.wantarray_kind;
16872 self.wantarray_kind = want;
16873 if let Some(r) = crate::list_util::native_dispatch(self, sub, &argv, want) {
16874 self.wantarray_kind = saved;
16875 self.scope_pop_hook();
16876 self.current_sub_stack.pop();
16877 return match r {
16878 Ok(v) => Ok(v),
16879 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16880 Err(e) => Err(e),
16881 };
16882 }
16883 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
16884 self.wantarray_kind = saved;
16885 self.scope_pop_hook();
16886 self.current_sub_stack.pop();
16887 return match r {
16888 Ok(v) => Ok(v),
16889 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16890 Err(e) => Err(e),
16891 };
16892 }
16893 if let Some(pat) = sub.fib_like.as_ref() {
16894 if argv.len() == 1 {
16895 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
16896 let t0 = self.profiler.is_some().then(std::time::Instant::now);
16897 if let Some(p) = &mut self.profiler {
16898 p.enter_sub(&sub.name);
16899 }
16900 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
16901 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
16902 p.exit_sub(t0.elapsed());
16903 }
16904 self.wantarray_kind = saved;
16905 self.scope_pop_hook();
16906 self.current_sub_stack.pop();
16907 return Ok(PerlValue::integer(n));
16908 }
16909 }
16910 }
16911 self.scope.declare_array("_", argv.clone());
16912 let t0 = self.profiler.is_some().then(std::time::Instant::now);
16915 if let Some(p) = &mut self.profiler {
16916 p.enter_sub(&sub.name);
16917 }
16918 let result = self.exec_block_no_scope_with_tail(&sub.body, self.wantarray_kind);
16920 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
16921 p.exit_sub(t0.elapsed());
16922 }
16923 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
16925 Some(self.scope.get_array("_"))
16926 } else {
16927 None
16928 };
16929 self.wantarray_kind = saved;
16930 self.scope_pop_hook();
16931 self.current_sub_stack.pop();
16932 match result {
16933 Ok(v) => Ok(v),
16934 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16935 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
16936 let goto_args = goto_args.unwrap_or_default();
16938 let fqn = if target_name.contains("::") {
16939 target_name.clone()
16940 } else {
16941 format!("{}::{}", self.current_package(), target_name)
16942 };
16943 if let Some(target_sub) = self
16944 .subs
16945 .get(&fqn)
16946 .cloned()
16947 .or_else(|| self.subs.get(&target_name).cloned())
16948 {
16949 self.call_sub(&target_sub, goto_args, want, _line)
16950 } else {
16951 Err(
16952 PerlError::runtime(format!("Undefined subroutine &{}", target_name), _line)
16953 .into(),
16954 )
16955 }
16956 }
16957 Err(FlowOrError::Flow(Flow::Yield(_))) => {
16958 Err(PerlError::runtime("yield is only valid inside gen { }", 0).into())
16959 }
16960 Err(e) => Err(e),
16961 }
16962 }
16963
16964 fn call_struct_method(
16966 &mut self,
16967 body: &Block,
16968 params: &[SubSigParam],
16969 args: Vec<PerlValue>,
16970 line: usize,
16971 ) -> ExecResult {
16972 self.scope_push_hook();
16973 self.scope.declare_array("_", args.clone());
16974 if let Some(self_val) = args.first() {
16976 self.scope.declare_scalar("self", self_val.clone());
16977 }
16978 self.scope.set_closure_args(&args);
16980 let user_args: Vec<PerlValue> = args.iter().skip(1).cloned().collect();
16982 self.apply_params_to_argv(params, &user_args, line)?;
16983 let result = self.exec_block_no_scope(body);
16984 self.scope_pop_hook();
16985 match result {
16986 Ok(v) => Ok(v),
16987 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16988 Err(e) => Err(e),
16989 }
16990 }
16991
16992 pub(crate) fn call_class_method(
16994 &mut self,
16995 body: &Block,
16996 params: &[SubSigParam],
16997 args: Vec<PerlValue>,
16998 line: usize,
16999 ) -> ExecResult {
17000 self.call_class_method_inner(body, params, args, line, false)
17001 }
17002
17003 pub(crate) fn call_static_class_method(
17005 &mut self,
17006 body: &Block,
17007 params: &[SubSigParam],
17008 args: Vec<PerlValue>,
17009 line: usize,
17010 ) -> ExecResult {
17011 self.call_class_method_inner(body, params, args, line, true)
17012 }
17013
17014 fn call_class_method_inner(
17015 &mut self,
17016 body: &Block,
17017 params: &[SubSigParam],
17018 args: Vec<PerlValue>,
17019 line: usize,
17020 is_static: bool,
17021 ) -> ExecResult {
17022 self.scope_push_hook();
17023 self.scope.declare_array("_", args.clone());
17024 if !is_static {
17025 if let Some(self_val) = args.first() {
17027 self.scope.declare_scalar("self", self_val.clone());
17028 }
17029 }
17030 self.scope.set_closure_args(&args);
17032 let user_args: Vec<PerlValue> = if is_static {
17034 args.clone()
17035 } else {
17036 args.iter().skip(1).cloned().collect()
17037 };
17038 self.apply_params_to_argv(params, &user_args, line)?;
17039 let result = self.exec_block_no_scope(body);
17040 self.scope_pop_hook();
17041 match result {
17042 Ok(v) => Ok(v),
17043 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17044 Err(e) => Err(e),
17045 }
17046 }
17047
17048 fn apply_params_to_argv(
17050 &mut self,
17051 params: &[SubSigParam],
17052 argv: &[PerlValue],
17053 line: usize,
17054 ) -> PerlResult<()> {
17055 let mut i = 0;
17056 for param in params {
17057 match param {
17058 SubSigParam::Scalar(name, ty_opt) => {
17059 let v = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17060 i += 1;
17061 if let Some(ty) = ty_opt {
17062 ty.check_value(&v).map_err(|msg| {
17063 PerlError::type_error(
17064 format!("method parameter ${}: {}", name, msg),
17065 line,
17066 )
17067 })?;
17068 }
17069 let n = self.english_scalar_name(name);
17070 self.scope.declare_scalar(n, v);
17071 }
17072 SubSigParam::ArrayDestruct(elems) => {
17073 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17074 i += 1;
17075 let Some(arr) = self.match_subject_as_array(&arg) else {
17076 return Err(PerlError::runtime(
17077 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
17078 line,
17079 ));
17080 };
17081 let binds = self
17082 .match_array_pattern_elems(&arr, elems, line)
17083 .map_err(|e| match e {
17084 FlowOrError::Error(stryke) => stryke,
17085 FlowOrError::Flow(_) => {
17086 PerlError::runtime("unexpected flow in method array destruct", line)
17087 }
17088 })?;
17089 let Some(binds) = binds else {
17090 return Err(PerlError::runtime(
17091 format!(
17092 "method parameter: array destructure failed at position {}",
17093 i
17094 ),
17095 line,
17096 ));
17097 };
17098 for b in binds {
17099 match b {
17100 PatternBinding::Scalar(name, v) => {
17101 let n = self.english_scalar_name(&name);
17102 self.scope.declare_scalar(n, v);
17103 }
17104 PatternBinding::Array(name, elems) => {
17105 self.scope.declare_array(&name, elems);
17106 }
17107 }
17108 }
17109 }
17110 SubSigParam::HashDestruct(pairs) => {
17111 let arg = argv.get(i).cloned().unwrap_or(PerlValue::UNDEF);
17112 i += 1;
17113 let map = self.hash_for_signature_destruct(&arg, line)?;
17114 for (key, varname) in pairs {
17115 let v = map.get(key).cloned().unwrap_or(PerlValue::UNDEF);
17116 let n = self.english_scalar_name(varname);
17117 self.scope.declare_scalar(n, v);
17118 }
17119 }
17120 }
17121 }
17122 Ok(())
17123 }
17124
17125 fn builtin_new(&mut self, class: &str, args: Vec<PerlValue>, line: usize) -> ExecResult {
17126 if class == "Set" {
17127 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
17128 }
17129 if let Some(def) = self.struct_defs.get(class).cloned() {
17130 let mut provided = Vec::new();
17131 let mut i = 1;
17132 while i + 1 < args.len() {
17133 let k = args[i].to_string();
17134 let v = args[i + 1].clone();
17135 provided.push((k, v));
17136 i += 2;
17137 }
17138 let mut defaults = Vec::with_capacity(def.fields.len());
17139 for field in &def.fields {
17140 if let Some(ref expr) = field.default {
17141 let val = self.eval_expr(expr)?;
17142 defaults.push(Some(val));
17143 } else {
17144 defaults.push(None);
17145 }
17146 }
17147 return Ok(crate::native_data::struct_new_with_defaults(
17148 &def, &provided, &defaults, line,
17149 )?);
17150 }
17151 let mut map = IndexMap::new();
17153 let mut i = 1; while i + 1 < args.len() {
17155 let k = args[i].to_string();
17156 let v = args[i + 1].clone();
17157 map.insert(k, v);
17158 i += 2;
17159 }
17160 Ok(PerlValue::blessed(Arc::new(
17161 crate::value::BlessedRef::new_blessed(class.to_string(), PerlValue::hash(map)),
17162 )))
17163 }
17164
17165 fn exec_print(
17166 &mut self,
17167 handle: Option<&str>,
17168 args: &[Expr],
17169 newline: bool,
17170 line: usize,
17171 ) -> ExecResult {
17172 if newline && (self.feature_bits & FEAT_SAY) == 0 {
17173 return Err(PerlError::runtime(
17174 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
17175 line,
17176 )
17177 .into());
17178 }
17179 let mut output = String::new();
17180 if args.is_empty() {
17181 let topic = self.scope.get_scalar("_").clone();
17183 let s = self.stringify_value(topic, line)?;
17184 output.push_str(&s);
17185 } else {
17186 for (i, a) in args.iter().enumerate() {
17189 if i > 0 {
17190 output.push_str(&self.ofs);
17191 }
17192 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17193 for item in val.to_list() {
17194 let s = self.stringify_value(item, line)?;
17195 output.push_str(&s);
17196 }
17197 }
17198 }
17199 if newline {
17200 output.push('\n');
17201 }
17202 output.push_str(&self.ors);
17203
17204 let handle_name =
17205 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17206 self.write_formatted_print(handle_name.as_str(), &output, line)?;
17207 Ok(PerlValue::integer(1))
17208 }
17209
17210 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
17211 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
17212 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
17214 (s, &[])
17215 } else {
17216 (self.eval_expr(&args[0])?.to_string(), &args[1..])
17217 };
17218 let mut arg_vals = Vec::new();
17222 for a in rest {
17223 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
17224 if let Some(items) = v.as_array_vec() {
17225 arg_vals.extend(items);
17226 } else {
17227 arg_vals.push(v);
17228 }
17229 }
17230 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
17231 let handle_name =
17232 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
17233 match handle_name.as_str() {
17234 "STDOUT" => {
17235 if !self.suppress_stdout {
17236 print!("{}", output);
17237 if self.output_autoflush {
17238 let _ = io::stdout().flush();
17239 }
17240 }
17241 }
17242 "STDERR" => {
17243 eprint!("{}", output);
17244 let _ = io::stderr().flush();
17245 }
17246 name => {
17247 if let Some(writer) = self.output_handles.get_mut(name) {
17248 let _ = writer.write_all(output.as_bytes());
17249 if self.output_autoflush {
17250 let _ = writer.flush();
17251 }
17252 }
17253 }
17254 }
17255 Ok(PerlValue::integer(1))
17256 }
17257
17258 pub(crate) fn eval_substr_expr(
17260 &mut self,
17261 string: &Expr,
17262 offset: &Expr,
17263 length: Option<&Expr>,
17264 replacement: Option<&Expr>,
17265 _line: usize,
17266 ) -> Result<PerlValue, FlowOrError> {
17267 let s = self.eval_expr(string)?.to_string();
17268 let off = self.eval_expr(offset)?.to_int();
17269 let start = if off < 0 {
17270 (s.len() as i64 + off).max(0) as usize
17271 } else {
17272 off as usize
17273 };
17274 let len = if let Some(l) = length {
17275 self.eval_expr(l)?.to_int() as usize
17276 } else {
17277 s.len().saturating_sub(start)
17278 };
17279 let end = (start + len).min(s.len());
17280 let result = s.get(start..end).unwrap_or("").to_string();
17281 if let Some(rep) = replacement {
17282 let rep_s = self.eval_expr(rep)?.to_string();
17283 let mut new_s = String::new();
17284 new_s.push_str(&s[..start]);
17285 new_s.push_str(&rep_s);
17286 new_s.push_str(&s[end..]);
17287 self.assign_value(string, PerlValue::string(new_s))?;
17288 }
17289 Ok(PerlValue::string(result))
17290 }
17291
17292 pub(crate) fn eval_push_expr(
17293 &mut self,
17294 array: &Expr,
17295 values: &[Expr],
17296 line: usize,
17297 ) -> Result<PerlValue, FlowOrError> {
17298 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17299 for v in values {
17300 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17301 self.push_array_deref_value(aref.clone(), val, line)?;
17302 }
17303 let len = self.array_deref_len(aref, line)?;
17304 return Ok(PerlValue::integer(len));
17305 }
17306 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17307 if self.scope.is_array_frozen(&arr_name) {
17308 return Err(PerlError::runtime(
17309 format!("Modification of a frozen value: @{}", arr_name),
17310 line,
17311 )
17312 .into());
17313 }
17314 for v in values {
17315 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17316 if let Some(items) = val.as_array_vec() {
17317 for item in items {
17318 self.scope
17319 .push_to_array(&arr_name, item)
17320 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17321 }
17322 } else {
17323 self.scope
17324 .push_to_array(&arr_name, val)
17325 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17326 }
17327 }
17328 let len = self.scope.array_len(&arr_name);
17329 Ok(PerlValue::integer(len as i64))
17330 }
17331
17332 pub(crate) fn eval_pop_expr(
17333 &mut self,
17334 array: &Expr,
17335 line: usize,
17336 ) -> Result<PerlValue, FlowOrError> {
17337 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17338 return self.pop_array_deref(aref, line);
17339 }
17340 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17341 self.scope
17342 .pop_from_array(&arr_name)
17343 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17344 }
17345
17346 pub(crate) fn eval_shift_expr(
17347 &mut self,
17348 array: &Expr,
17349 line: usize,
17350 ) -> Result<PerlValue, FlowOrError> {
17351 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17352 return self.shift_array_deref(aref, line);
17353 }
17354 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17355 self.scope
17356 .shift_from_array(&arr_name)
17357 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17358 }
17359
17360 pub(crate) fn eval_unshift_expr(
17361 &mut self,
17362 array: &Expr,
17363 values: &[Expr],
17364 line: usize,
17365 ) -> Result<PerlValue, FlowOrError> {
17366 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17367 let mut vals = Vec::new();
17368 for v in values {
17369 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17370 if let Some(items) = val.as_array_vec() {
17371 vals.extend(items);
17372 } else {
17373 vals.push(val);
17374 }
17375 }
17376 let len = self.unshift_array_deref_multi(aref, vals, line)?;
17377 return Ok(PerlValue::integer(len));
17378 }
17379 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17380 let mut vals = Vec::new();
17381 for v in values {
17382 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
17383 if let Some(items) = val.as_array_vec() {
17384 vals.extend(items);
17385 } else {
17386 vals.push(val);
17387 }
17388 }
17389 let arr = self
17390 .scope
17391 .get_array_mut(&arr_name)
17392 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17393 for (i, v) in vals.into_iter().enumerate() {
17394 arr.insert(i, v);
17395 }
17396 let len = arr.len();
17397 Ok(PerlValue::integer(len as i64))
17398 }
17399
17400 pub(crate) fn push_array_deref_value(
17402 &mut self,
17403 arr_ref: PerlValue,
17404 val: PerlValue,
17405 line: usize,
17406 ) -> Result<(), FlowOrError> {
17407 if let Some(r) = arr_ref.as_array_ref() {
17408 let mut w = r.write();
17409 if let Some(items) = val.as_array_vec() {
17410 w.extend(items.iter().cloned());
17411 } else {
17412 w.push(val);
17413 }
17414 return Ok(());
17415 }
17416 if let Some(name) = arr_ref.as_array_binding_name() {
17417 if let Some(items) = val.as_array_vec() {
17418 for item in items {
17419 self.scope
17420 .push_to_array(&name, item)
17421 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17422 }
17423 } else {
17424 self.scope
17425 .push_to_array(&name, val)
17426 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17427 }
17428 return Ok(());
17429 }
17430 if let Some(s) = arr_ref.as_str() {
17431 if self.strict_refs {
17432 return Err(PerlError::runtime(
17433 format!(
17434 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17435 s
17436 ),
17437 line,
17438 )
17439 .into());
17440 }
17441 let name = s.to_string();
17442 if let Some(items) = val.as_array_vec() {
17443 for item in items {
17444 self.scope
17445 .push_to_array(&name, item)
17446 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17447 }
17448 } else {
17449 self.scope
17450 .push_to_array(&name, val)
17451 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17452 }
17453 return Ok(());
17454 }
17455 Err(PerlError::runtime("push argument is not an ARRAY reference", line).into())
17456 }
17457
17458 pub(crate) fn array_deref_len(
17459 &self,
17460 arr_ref: PerlValue,
17461 line: usize,
17462 ) -> Result<i64, FlowOrError> {
17463 if let Some(r) = arr_ref.as_array_ref() {
17464 return Ok(r.read().len() as i64);
17465 }
17466 if let Some(name) = arr_ref.as_array_binding_name() {
17467 return Ok(self.scope.array_len(&name) as i64);
17468 }
17469 if let Some(s) = arr_ref.as_str() {
17470 if self.strict_refs {
17471 return Err(PerlError::runtime(
17472 format!(
17473 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17474 s
17475 ),
17476 line,
17477 )
17478 .into());
17479 }
17480 return Ok(self.scope.array_len(&s) as i64);
17481 }
17482 Err(PerlError::runtime("argument is not an ARRAY reference", line).into())
17483 }
17484
17485 pub(crate) fn pop_array_deref(
17486 &mut self,
17487 arr_ref: PerlValue,
17488 line: usize,
17489 ) -> Result<PerlValue, FlowOrError> {
17490 if let Some(r) = arr_ref.as_array_ref() {
17491 let mut w = r.write();
17492 return Ok(w.pop().unwrap_or(PerlValue::UNDEF));
17493 }
17494 if let Some(name) = arr_ref.as_array_binding_name() {
17495 return self
17496 .scope
17497 .pop_from_array(&name)
17498 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17499 }
17500 if let Some(s) = arr_ref.as_str() {
17501 if self.strict_refs {
17502 return Err(PerlError::runtime(
17503 format!(
17504 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17505 s
17506 ),
17507 line,
17508 )
17509 .into());
17510 }
17511 return self
17512 .scope
17513 .pop_from_array(&s)
17514 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17515 }
17516 Err(PerlError::runtime("pop argument is not an ARRAY reference", line).into())
17517 }
17518
17519 pub(crate) fn shift_array_deref(
17520 &mut self,
17521 arr_ref: PerlValue,
17522 line: usize,
17523 ) -> Result<PerlValue, FlowOrError> {
17524 if let Some(r) = arr_ref.as_array_ref() {
17525 let mut w = r.write();
17526 return Ok(if w.is_empty() {
17527 PerlValue::UNDEF
17528 } else {
17529 w.remove(0)
17530 });
17531 }
17532 if let Some(name) = arr_ref.as_array_binding_name() {
17533 return self
17534 .scope
17535 .shift_from_array(&name)
17536 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17537 }
17538 if let Some(s) = arr_ref.as_str() {
17539 if self.strict_refs {
17540 return Err(PerlError::runtime(
17541 format!(
17542 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17543 s
17544 ),
17545 line,
17546 )
17547 .into());
17548 }
17549 return self
17550 .scope
17551 .shift_from_array(&s)
17552 .map_err(|e| FlowOrError::Error(e.at_line(line)));
17553 }
17554 Err(PerlError::runtime("shift argument is not an ARRAY reference", line).into())
17555 }
17556
17557 pub(crate) fn unshift_array_deref_multi(
17558 &mut self,
17559 arr_ref: PerlValue,
17560 vals: Vec<PerlValue>,
17561 line: usize,
17562 ) -> Result<i64, FlowOrError> {
17563 let mut flat: Vec<PerlValue> = Vec::new();
17564 for v in vals {
17565 if let Some(items) = v.as_array_vec() {
17566 flat.extend(items);
17567 } else {
17568 flat.push(v);
17569 }
17570 }
17571 if let Some(r) = arr_ref.as_array_ref() {
17572 let mut w = r.write();
17573 for (i, v) in flat.into_iter().enumerate() {
17574 w.insert(i, v);
17575 }
17576 return Ok(w.len() as i64);
17577 }
17578 if let Some(name) = arr_ref.as_array_binding_name() {
17579 let arr = self
17580 .scope
17581 .get_array_mut(&name)
17582 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17583 for (i, v) in flat.into_iter().enumerate() {
17584 arr.insert(i, v);
17585 }
17586 return Ok(arr.len() as i64);
17587 }
17588 if let Some(s) = arr_ref.as_str() {
17589 if self.strict_refs {
17590 return Err(PerlError::runtime(
17591 format!(
17592 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17593 s
17594 ),
17595 line,
17596 )
17597 .into());
17598 }
17599 let name = s.to_string();
17600 let arr = self
17601 .scope
17602 .get_array_mut(&name)
17603 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17604 for (i, v) in flat.into_iter().enumerate() {
17605 arr.insert(i, v);
17606 }
17607 return Ok(arr.len() as i64);
17608 }
17609 Err(PerlError::runtime("unshift argument is not an ARRAY reference", line).into())
17610 }
17611
17612 pub(crate) fn splice_array_deref(
17615 &mut self,
17616 aref: PerlValue,
17617 offset_val: PerlValue,
17618 length_val: PerlValue,
17619 rep_vals: Vec<PerlValue>,
17620 line: usize,
17621 ) -> Result<PerlValue, FlowOrError> {
17622 let ctx = self.wantarray_kind;
17623 if let Some(r) = aref.as_array_ref() {
17624 let arr_len = r.read().len();
17625 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17626 let mut w = r.write();
17627 let removed: Vec<PerlValue> = w.drain(off..end).collect();
17628 for (i, v) in rep_vals.into_iter().enumerate() {
17629 w.insert(off + i, v);
17630 }
17631 return Ok(match ctx {
17632 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17633 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17634 });
17635 }
17636 if let Some(name) = aref.as_array_binding_name() {
17637 let arr_len = self.scope.array_len(&name);
17638 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17639 let arr = self
17640 .scope
17641 .get_array_mut(&name)
17642 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17643 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
17644 for (i, v) in rep_vals.into_iter().enumerate() {
17645 arr.insert(off + i, v);
17646 }
17647 return Ok(match ctx {
17648 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17649 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17650 });
17651 }
17652 if let Some(s) = aref.as_str() {
17653 if self.strict_refs {
17654 return Err(PerlError::runtime(
17655 format!(
17656 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
17657 s
17658 ),
17659 line,
17660 )
17661 .into());
17662 }
17663 let arr_len = self.scope.array_len(&s);
17664 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17665 let arr = self
17666 .scope
17667 .get_array_mut(&s)
17668 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17669 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
17670 for (i, v) in rep_vals.into_iter().enumerate() {
17671 arr.insert(off + i, v);
17672 }
17673 return Ok(match ctx {
17674 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17675 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17676 });
17677 }
17678 Err(PerlError::runtime("splice argument is not an ARRAY reference", line).into())
17679 }
17680
17681 pub(crate) fn eval_splice_expr(
17682 &mut self,
17683 array: &Expr,
17684 offset: Option<&Expr>,
17685 length: Option<&Expr>,
17686 replacement: &[Expr],
17687 ctx: WantarrayCtx,
17688 line: usize,
17689 ) -> Result<PerlValue, FlowOrError> {
17690 if let Some(aref) = self.try_eval_array_deref_container(array)? {
17691 let offset_val = if let Some(o) = offset {
17692 self.eval_expr(o)?
17693 } else {
17694 PerlValue::integer(0)
17695 };
17696 let length_val = if let Some(l) = length {
17697 self.eval_expr(l)?
17698 } else {
17699 PerlValue::UNDEF
17700 };
17701 let mut rep_vals = Vec::new();
17702 for r in replacement {
17703 rep_vals.push(self.eval_expr(r)?);
17704 }
17705 let saved = self.wantarray_kind;
17706 self.wantarray_kind = ctx;
17707 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
17708 self.wantarray_kind = saved;
17709 return out;
17710 }
17711 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
17712 let arr_len = self.scope.array_len(&arr_name);
17713 let offset_val = if let Some(o) = offset {
17714 self.eval_expr(o)?
17715 } else {
17716 PerlValue::integer(0)
17717 };
17718 let length_val = if let Some(l) = length {
17719 self.eval_expr(l)?
17720 } else {
17721 PerlValue::UNDEF
17722 };
17723 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
17724 let mut rep_vals = Vec::new();
17725 for r in replacement {
17726 rep_vals.push(self.eval_expr(r)?);
17727 }
17728 let arr = self
17729 .scope
17730 .get_array_mut(&arr_name)
17731 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
17732 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
17733 for (i, v) in rep_vals.into_iter().enumerate() {
17734 arr.insert(off + i, v);
17735 }
17736 Ok(match ctx {
17737 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(PerlValue::UNDEF),
17738 WantarrayCtx::List | WantarrayCtx::Void => PerlValue::array(removed),
17739 })
17740 }
17741
17742 pub(crate) fn keys_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
17744 if let Some(h) = val.as_hash_map() {
17745 Ok(PerlValue::array(
17746 h.keys().map(|k| PerlValue::string(k.clone())).collect(),
17747 ))
17748 } else if let Some(r) = val.as_hash_ref() {
17749 Ok(PerlValue::array(
17750 r.read()
17751 .keys()
17752 .map(|k| PerlValue::string(k.clone()))
17753 .collect(),
17754 ))
17755 } else {
17756 Err(PerlError::runtime("keys requires hash", line).into())
17757 }
17758 }
17759
17760 pub(crate) fn eval_keys_expr(
17761 &mut self,
17762 expr: &Expr,
17763 line: usize,
17764 ) -> Result<PerlValue, FlowOrError> {
17765 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
17768 Self::keys_from_value(val, line)
17769 }
17770
17771 pub(crate) fn values_from_value(val: PerlValue, line: usize) -> Result<PerlValue, FlowOrError> {
17773 if let Some(h) = val.as_hash_map() {
17774 Ok(PerlValue::array(h.values().cloned().collect()))
17775 } else if let Some(r) = val.as_hash_ref() {
17776 Ok(PerlValue::array(r.read().values().cloned().collect()))
17777 } else {
17778 Err(PerlError::runtime("values requires hash", line).into())
17779 }
17780 }
17781
17782 pub(crate) fn eval_values_expr(
17783 &mut self,
17784 expr: &Expr,
17785 line: usize,
17786 ) -> Result<PerlValue, FlowOrError> {
17787 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
17788 Self::values_from_value(val, line)
17789 }
17790
17791 pub(crate) fn eval_delete_operand(
17792 &mut self,
17793 expr: &Expr,
17794 line: usize,
17795 ) -> Result<PerlValue, FlowOrError> {
17796 match &expr.kind {
17797 ExprKind::HashElement { hash, key } => {
17798 let k = self.eval_expr(key)?.to_string();
17799 self.touch_env_hash(hash);
17800 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
17801 let class = obj
17802 .as_blessed_ref()
17803 .map(|b| b.class.clone())
17804 .unwrap_or_default();
17805 let full = format!("{}::DELETE", class);
17806 if let Some(sub) = self.subs.get(&full).cloned() {
17807 return self.call_sub(
17808 &sub,
17809 vec![obj, PerlValue::string(k)],
17810 WantarrayCtx::Scalar,
17811 line,
17812 );
17813 }
17814 }
17815 self.scope
17816 .delete_hash_element(hash, &k)
17817 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17818 }
17819 ExprKind::ArrayElement { array, index } => {
17820 self.check_strict_array_var(array, line)?;
17821 let idx = self.eval_expr(index)?.to_int();
17822 let aname = self.stash_array_name_for_package(array);
17823 self.scope
17824 .delete_array_element(&aname, idx)
17825 .map_err(|e| FlowOrError::Error(e.at_line(line)))
17826 }
17827 ExprKind::ArrowDeref {
17828 expr: inner,
17829 index,
17830 kind: DerefKind::Hash,
17831 } => {
17832 let k = self.eval_expr(index)?.to_string();
17833 let container = self.eval_expr(inner)?;
17834 self.delete_arrow_hash_element(container, &k, line)
17835 .map_err(Into::into)
17836 }
17837 ExprKind::ArrowDeref {
17838 expr: inner,
17839 index,
17840 kind: DerefKind::Array,
17841 } => {
17842 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
17843 return Err(PerlError::runtime(
17844 "delete on array element needs scalar subscript",
17845 line,
17846 )
17847 .into());
17848 }
17849 let container = self.eval_expr(inner)?;
17850 let idx = self.eval_expr(index)?.to_int();
17851 self.delete_arrow_array_element(container, idx, line)
17852 .map_err(Into::into)
17853 }
17854 _ => Err(PerlError::runtime("delete requires hash or array element", line).into()),
17855 }
17856 }
17857
17858 pub(crate) fn eval_exists_operand(
17859 &mut self,
17860 expr: &Expr,
17861 line: usize,
17862 ) -> Result<PerlValue, FlowOrError> {
17863 match &expr.kind {
17864 ExprKind::HashElement { hash, key } => {
17865 let k = self.eval_expr(key)?.to_string();
17866 self.touch_env_hash(hash);
17867 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
17868 let class = obj
17869 .as_blessed_ref()
17870 .map(|b| b.class.clone())
17871 .unwrap_or_default();
17872 let full = format!("{}::EXISTS", class);
17873 if let Some(sub) = self.subs.get(&full).cloned() {
17874 return self.call_sub(
17875 &sub,
17876 vec![obj, PerlValue::string(k)],
17877 WantarrayCtx::Scalar,
17878 line,
17879 );
17880 }
17881 }
17882 Ok(PerlValue::integer(
17883 if self.scope.exists_hash_element(hash, &k) {
17884 1
17885 } else {
17886 0
17887 },
17888 ))
17889 }
17890 ExprKind::ArrayElement { array, index } => {
17891 self.check_strict_array_var(array, line)?;
17892 let idx = self.eval_expr(index)?.to_int();
17893 let aname = self.stash_array_name_for_package(array);
17894 Ok(PerlValue::integer(
17895 if self.scope.exists_array_element(&aname, idx) {
17896 1
17897 } else {
17898 0
17899 },
17900 ))
17901 }
17902 ExprKind::ArrowDeref {
17903 expr: inner,
17904 index,
17905 kind: DerefKind::Hash,
17906 } => {
17907 let k = self.eval_expr(index)?.to_string();
17908 let container = self.eval_expr(inner)?;
17909 let yes = self.exists_arrow_hash_element(container, &k, line)?;
17910 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
17911 }
17912 ExprKind::ArrowDeref {
17913 expr: inner,
17914 index,
17915 kind: DerefKind::Array,
17916 } => {
17917 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
17918 return Err(PerlError::runtime(
17919 "exists on array element needs scalar subscript",
17920 line,
17921 )
17922 .into());
17923 }
17924 let container = self.eval_expr(inner)?;
17925 let idx = self.eval_expr(index)?.to_int();
17926 let yes = self.exists_arrow_array_element(container, idx, line)?;
17927 Ok(PerlValue::integer(if yes { 1 } else { 0 }))
17928 }
17929 _ => Err(PerlError::runtime("exists requires hash or array element", line).into()),
17930 }
17931 }
17932
17933 pub(crate) fn eval_pmap_remote(
17941 &mut self,
17942 cluster_pv: PerlValue,
17943 list_pv: PerlValue,
17944 show_progress: bool,
17945 block: &Block,
17946 flat_outputs: bool,
17947 line: usize,
17948 ) -> Result<PerlValue, FlowOrError> {
17949 let Some(cluster) = cluster_pv.as_remote_cluster() else {
17950 return Err(PerlError::runtime("pmap_on: expected cluster(...) value", line).into());
17951 };
17952 let items = list_pv.to_list();
17953 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
17954 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
17955 return Err(PerlError::runtime(
17956 "pmap_on: mysync/atomic capture is not supported for remote workers",
17957 line,
17958 )
17959 .into());
17960 }
17961 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
17962 .map_err(|e| PerlError::runtime(e, line))?;
17963 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
17964 let block_src = crate::fmt::format_block(block);
17965 let item_jsons =
17966 crate::cluster::perl_items_to_json(&items).map_err(|e| PerlError::runtime(e, line))?;
17967
17968 let pmap_progress = PmapProgress::new(show_progress, items.len());
17971 let result_values =
17972 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
17973 .map_err(|e| PerlError::runtime(format!("pmap_on remote: {e}"), line))?;
17974 for _ in 0..result_values.len() {
17975 pmap_progress.tick();
17976 }
17977 pmap_progress.finish();
17978
17979 if flat_outputs {
17980 let flattened: Vec<PerlValue> = result_values
17981 .into_iter()
17982 .flat_map(|v| v.map_flatten_outputs(true))
17983 .collect();
17984 Ok(PerlValue::array(flattened))
17985 } else {
17986 Ok(PerlValue::array(result_values))
17987 }
17988 }
17989
17990 pub(crate) fn eval_par_lines_expr(
17992 &mut self,
17993 path: &Expr,
17994 callback: &Expr,
17995 progress: Option<&Expr>,
17996 line: usize,
17997 ) -> Result<PerlValue, FlowOrError> {
17998 let show_progress = progress
17999 .map(|p| self.eval_expr(p))
18000 .transpose()?
18001 .map(|v| v.is_true())
18002 .unwrap_or(false);
18003 let path_s = self.eval_expr(path)?.to_string();
18004 let cb_val = self.eval_expr(callback)?;
18005 let sub = if let Some(s) = cb_val.as_code_ref() {
18006 s
18007 } else {
18008 return Err(PerlError::runtime(
18009 "par_lines: second argument must be a code reference",
18010 line,
18011 )
18012 .into());
18013 };
18014 let subs = self.subs.clone();
18015 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18016 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
18017 FlowOrError::Error(PerlError::runtime(format!("par_lines: {}", e), line))
18018 })?;
18019 let mmap = unsafe {
18020 memmap2::Mmap::map(&file).map_err(|e| {
18021 FlowOrError::Error(PerlError::runtime(format!("par_lines: mmap: {}", e), line))
18022 })?
18023 };
18024 let data: &[u8] = &mmap;
18025 if data.is_empty() {
18026 return Ok(PerlValue::UNDEF);
18027 }
18028 let line_total = crate::par_lines::line_count_bytes(data);
18029 let pmap_progress = PmapProgress::new(show_progress, line_total);
18030 if self.num_threads == 0 {
18031 self.num_threads = rayon::current_num_threads();
18032 }
18033 let num_chunks = self.num_threads.saturating_mul(8).max(1);
18034 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
18035 chunks.into_par_iter().try_for_each(|(start, end)| {
18036 let slice = &data[start..end];
18037 let mut s = 0usize;
18038 while s < slice.len() {
18039 let e = slice[s..]
18040 .iter()
18041 .position(|&b| b == b'\n')
18042 .map(|p| s + p)
18043 .unwrap_or(slice.len());
18044 let line_bytes = &slice[s..e];
18045 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
18046 let mut local_interp = Interpreter::new();
18047 local_interp.subs = subs.clone();
18048 local_interp.scope.restore_capture(&scope_capture);
18049 local_interp
18050 .scope
18051 .restore_atomics(&atomic_arrays, &atomic_hashes);
18052 local_interp.enable_parallel_guard();
18053 local_interp.scope.set_topic(PerlValue::string(line_str));
18054 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
18055 Ok(_) => {}
18056 Err(e) => return Err(e),
18057 }
18058 pmap_progress.tick();
18059 if e >= slice.len() {
18060 break;
18061 }
18062 s = e + 1;
18063 }
18064 Ok(())
18065 })?;
18066 pmap_progress.finish();
18067 Ok(PerlValue::UNDEF)
18068 }
18069
18070 pub(crate) fn eval_par_walk_expr(
18072 &mut self,
18073 path: &Expr,
18074 callback: &Expr,
18075 progress: Option<&Expr>,
18076 line: usize,
18077 ) -> Result<PerlValue, FlowOrError> {
18078 let show_progress = progress
18079 .map(|p| self.eval_expr(p))
18080 .transpose()?
18081 .map(|v| v.is_true())
18082 .unwrap_or(false);
18083 let path_val = self.eval_expr(path)?;
18084 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
18085 arr.into_iter()
18086 .map(|v| PathBuf::from(v.to_string()))
18087 .collect()
18088 } else {
18089 vec![PathBuf::from(path_val.to_string())]
18090 };
18091 let cb_val = self.eval_expr(callback)?;
18092 let sub = if let Some(s) = cb_val.as_code_ref() {
18093 s
18094 } else {
18095 return Err(PerlError::runtime(
18096 "par_walk: second argument must be a code reference",
18097 line,
18098 )
18099 .into());
18100 };
18101 let subs = self.subs.clone();
18102 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18103
18104 if show_progress {
18105 let paths = crate::par_walk::collect_paths(&roots);
18106 let pmap_progress = PmapProgress::new(true, paths.len());
18107 paths.into_par_iter().try_for_each(|p| {
18108 let s = p.to_string_lossy().into_owned();
18109 let mut local_interp = Interpreter::new();
18110 local_interp.subs = subs.clone();
18111 local_interp.scope.restore_capture(&scope_capture);
18112 local_interp
18113 .scope
18114 .restore_atomics(&atomic_arrays, &atomic_hashes);
18115 local_interp.enable_parallel_guard();
18116 local_interp.scope.set_topic(PerlValue::string(s));
18117 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
18118 Ok(_) => {}
18119 Err(e) => return Err(e),
18120 }
18121 pmap_progress.tick();
18122 Ok(())
18123 })?;
18124 pmap_progress.finish();
18125 } else {
18126 for r in &roots {
18127 par_walk_recursive(
18128 r.as_path(),
18129 &sub,
18130 &subs,
18131 &scope_capture,
18132 &atomic_arrays,
18133 &atomic_hashes,
18134 line,
18135 )?;
18136 }
18137 }
18138 Ok(PerlValue::UNDEF)
18139 }
18140
18141 pub(crate) fn builtin_par_sed(
18143 &mut self,
18144 args: &[PerlValue],
18145 line: usize,
18146 has_progress: bool,
18147 ) -> PerlResult<PerlValue> {
18148 let show_progress = if has_progress {
18149 args.last().map(|v| v.is_true()).unwrap_or(false)
18150 } else {
18151 false
18152 };
18153 let slice = if has_progress {
18154 &args[..args.len().saturating_sub(1)]
18155 } else {
18156 args
18157 };
18158 if slice.len() < 3 {
18159 return Err(PerlError::runtime(
18160 "par_sed: need pattern, replacement, and at least one file path",
18161 line,
18162 ));
18163 }
18164 let pat_val = &slice[0];
18165 let repl = slice[1].to_string();
18166 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
18167
18168 let re = if let Some(rx) = pat_val.as_regex() {
18169 rx
18170 } else {
18171 let pattern = pat_val.to_string();
18172 match self.compile_regex(&pattern, "g", line) {
18173 Ok(r) => r,
18174 Err(FlowOrError::Error(e)) => return Err(e),
18175 Err(FlowOrError::Flow(f)) => {
18176 return Err(PerlError::runtime(format!("par_sed: {:?}", f), line))
18177 }
18178 }
18179 };
18180
18181 let pmap = PmapProgress::new(show_progress, files.len());
18182 let touched = AtomicUsize::new(0);
18183 files.par_iter().try_for_each(|path| {
18184 let content = read_file_text_perl_compat(path)
18185 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18186 let new_s = re.replace_all(&content, &repl);
18187 if new_s != content {
18188 std::fs::write(path, new_s.as_bytes())
18189 .map_err(|e| PerlError::runtime(format!("par_sed {}: {}", path, e), line))?;
18190 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
18191 }
18192 pmap.tick();
18193 Ok(())
18194 })?;
18195 pmap.finish();
18196 Ok(PerlValue::integer(
18197 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
18198 ))
18199 }
18200
18201 pub(crate) fn eval_pwatch_expr(
18203 &mut self,
18204 path: &Expr,
18205 callback: &Expr,
18206 line: usize,
18207 ) -> Result<PerlValue, FlowOrError> {
18208 let pattern_s = self.eval_expr(path)?.to_string();
18209 let cb_val = self.eval_expr(callback)?;
18210 let sub = if let Some(s) = cb_val.as_code_ref() {
18211 s
18212 } else {
18213 return Err(PerlError::runtime(
18214 "pwatch: second argument must be a code reference",
18215 line,
18216 )
18217 .into());
18218 };
18219 let subs = self.subs.clone();
18220 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18221 crate::pwatch::run_pwatch(
18222 &pattern_s,
18223 sub,
18224 subs,
18225 scope_capture,
18226 atomic_arrays,
18227 atomic_hashes,
18228 line,
18229 )
18230 .map_err(FlowOrError::Error)
18231 }
18232
18233 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
18235 let mut out = String::with_capacity(pattern.len());
18236 let chars: Vec<char> = pattern.chars().collect();
18237 let mut i = 0;
18238 while i < chars.len() {
18239 if chars[i] == '\\' && i + 1 < chars.len() {
18240 out.push(chars[i]);
18242 out.push(chars[i + 1]);
18243 i += 2;
18244 continue;
18245 }
18246 if chars[i] == '$' && i + 1 < chars.len() {
18247 i += 1;
18248 if i >= chars.len()
18250 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
18251 {
18252 out.push('$');
18253 continue;
18254 }
18255 let mut name = String::new();
18256 if chars[i] == '{' {
18257 i += 1;
18258 while i < chars.len() && chars[i] != '}' {
18259 name.push(chars[i]);
18260 i += 1;
18261 }
18262 if i < chars.len() {
18263 i += 1;
18264 } } else {
18266 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
18267 name.push(chars[i]);
18268 i += 1;
18269 }
18270 }
18271 if !name.is_empty() {
18272 let val = self.scope.get_scalar(&name);
18273 out.push_str(&val.to_string());
18274 } else {
18275 out.push('$');
18276 }
18277 continue;
18278 }
18279 out.push(chars[i]);
18280 i += 1;
18281 }
18282 out
18283 }
18284
18285 pub(crate) fn compile_regex(
18286 &mut self,
18287 pattern: &str,
18288 flags: &str,
18289 line: usize,
18290 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
18291 let pattern = if pattern.contains('$') || pattern.contains('@') {
18293 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
18294 } else {
18295 std::borrow::Cow::Borrowed(pattern)
18296 };
18297 let pattern = pattern.as_ref();
18298 let multiline = self.multiline_match;
18301 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
18302 if lp == pattern && lf == flags && *lm == multiline {
18303 return Ok(lr.clone());
18304 }
18305 }
18306 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
18308 if let Some(cached) = self.regex_cache.get(&key) {
18309 self.regex_last = Some((
18310 pattern.to_string(),
18311 flags.to_string(),
18312 multiline,
18313 cached.clone(),
18314 ));
18315 return Ok(cached.clone());
18316 }
18317 let expanded = expand_perl_regex_quotemeta(pattern);
18318 let expanded = expand_perl_regex_octal_escapes(&expanded);
18319 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
18320 let mut re_str = String::new();
18321 if flags.contains('i') {
18322 re_str.push_str("(?i)");
18323 }
18324 if flags.contains('s') {
18325 re_str.push_str("(?s)");
18326 }
18327 if flags.contains('m') {
18328 re_str.push_str("(?m)");
18329 }
18330 if flags.contains('x') {
18331 re_str.push_str("(?x)");
18332 }
18333 if multiline {
18335 re_str.push_str("(?s)");
18336 }
18337 re_str.push_str(&expanded);
18338 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
18339 FlowOrError::Error(PerlError::runtime(
18340 format!("Invalid regex /{}/: {}", pattern, e),
18341 line,
18342 ))
18343 })?;
18344 let arc = re;
18345 self.regex_last = Some((
18346 pattern.to_string(),
18347 flags.to_string(),
18348 multiline,
18349 arc.clone(),
18350 ));
18351 self.regex_cache.insert(key, arc.clone());
18352 Ok(arc)
18353 }
18354
18355 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
18357 if self.last_readline_handle.is_empty() {
18358 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
18359 }
18360 let n = *self
18361 .handle_line_numbers
18362 .get(&self.last_readline_handle)
18363 .unwrap_or(&0);
18364 if n <= 0 {
18365 return None;
18366 }
18367 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
18368 {
18369 return Some(("<>".to_string(), n));
18370 }
18371 if self.last_readline_handle == "STDIN" {
18372 return Some((self.last_stdin_die_bracket.clone(), n));
18373 }
18374 Some((format!("<{}>", self.last_readline_handle), n))
18375 }
18376
18377 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
18379 let mut s = format!(" at {} line {}", self.file, source_line);
18380 if let Some((bracket, n)) = self.die_warn_io_annotation() {
18381 s.push_str(&format!(", {} line {}.", bracket, n));
18382 } else {
18383 s.push('.');
18384 }
18385 s
18386 }
18387
18388 pub fn process_line(
18393 &mut self,
18394 line_str: &str,
18395 program: &Program,
18396 is_last_input_line: bool,
18397 ) -> PerlResult<Option<String>> {
18398 self.line_mode_eof_pending = is_last_input_line;
18399 let result: PerlResult<Option<String>> = (|| {
18400 self.line_number += 1;
18401 self.scope
18402 .set_topic(PerlValue::string(line_str.to_string()));
18403
18404 if self.auto_split {
18405 let sep = self.field_separator.as_deref().unwrap_or(" ");
18406 let re = regex::Regex::new(sep).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
18407 let fields: Vec<PerlValue> = re
18408 .split(line_str)
18409 .map(|s| PerlValue::string(s.to_string()))
18410 .collect();
18411 self.scope.set_array("F", fields)?;
18412 }
18413
18414 for stmt in &program.statements {
18415 match &stmt.kind {
18416 StmtKind::SubDecl { .. }
18417 | StmtKind::Begin(_)
18418 | StmtKind::UnitCheck(_)
18419 | StmtKind::Check(_)
18420 | StmtKind::Init(_)
18421 | StmtKind::End(_) => continue,
18422 _ => match self.exec_statement(stmt) {
18423 Ok(_) => {}
18424 Err(FlowOrError::Error(e)) => return Err(e),
18425 Err(FlowOrError::Flow(_)) => {}
18426 },
18427 }
18428 }
18429
18430 let mut out = self.scope.get_scalar("_").to_string();
18432 out.push_str(&self.ors);
18433 Ok(Some(out))
18434 })();
18435 self.line_mode_eof_pending = false;
18436 result
18437 }
18438}
18439
18440fn par_walk_invoke_entry(
18441 path: &Path,
18442 sub: &Arc<PerlSub>,
18443 subs: &HashMap<String, Arc<PerlSub>>,
18444 scope_capture: &[(String, PerlValue)],
18445 atomic_arrays: &[(String, crate::scope::AtomicArray)],
18446 atomic_hashes: &[(String, crate::scope::AtomicHash)],
18447 line: usize,
18448) -> Result<(), FlowOrError> {
18449 let s = path.to_string_lossy().into_owned();
18450 let mut local_interp = Interpreter::new();
18451 local_interp.subs = subs.clone();
18452 local_interp.scope.restore_capture(scope_capture);
18453 local_interp
18454 .scope
18455 .restore_atomics(atomic_arrays, atomic_hashes);
18456 local_interp.enable_parallel_guard();
18457 local_interp.scope.set_topic(PerlValue::string(s));
18458 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
18459 Ok(())
18460}
18461
18462fn par_walk_recursive(
18463 path: &Path,
18464 sub: &Arc<PerlSub>,
18465 subs: &HashMap<String, Arc<PerlSub>>,
18466 scope_capture: &[(String, PerlValue)],
18467 atomic_arrays: &[(String, crate::scope::AtomicArray)],
18468 atomic_hashes: &[(String, crate::scope::AtomicHash)],
18469 line: usize,
18470) -> Result<(), FlowOrError> {
18471 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
18472 return par_walk_invoke_entry(
18473 path,
18474 sub,
18475 subs,
18476 scope_capture,
18477 atomic_arrays,
18478 atomic_hashes,
18479 line,
18480 );
18481 }
18482 if !path.is_dir() {
18483 return Ok(());
18484 }
18485 par_walk_invoke_entry(
18486 path,
18487 sub,
18488 subs,
18489 scope_capture,
18490 atomic_arrays,
18491 atomic_hashes,
18492 line,
18493 )?;
18494 let read = match std::fs::read_dir(path) {
18495 Ok(r) => r,
18496 Err(_) => return Ok(()),
18497 };
18498 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
18499 entries.par_iter().try_for_each(|e| {
18500 par_walk_recursive(
18501 &e.path(),
18502 sub,
18503 subs,
18504 scope_capture,
18505 atomic_arrays,
18506 atomic_hashes,
18507 line,
18508 )
18509 })?;
18510 Ok(())
18511}
18512
18513pub(crate) fn perl_sprintf_format_with<F>(
18515 fmt: &str,
18516 args: &[PerlValue],
18517 mut string_for_s: F,
18518) -> Result<String, FlowOrError>
18519where
18520 F: FnMut(&PerlValue) -> Result<String, FlowOrError>,
18521{
18522 let mut result = String::new();
18523 let mut arg_idx = 0;
18524 let chars: Vec<char> = fmt.chars().collect();
18525 let mut i = 0;
18526
18527 while i < chars.len() {
18528 if chars[i] == '%' {
18529 i += 1;
18530 if i >= chars.len() {
18531 break;
18532 }
18533 if chars[i] == '%' {
18534 result.push('%');
18535 i += 1;
18536 continue;
18537 }
18538
18539 let mut flags = String::new();
18541 while i < chars.len() && "-+ #0".contains(chars[i]) {
18542 flags.push(chars[i]);
18543 i += 1;
18544 }
18545 let mut width = String::new();
18546 while i < chars.len() && chars[i].is_ascii_digit() {
18547 width.push(chars[i]);
18548 i += 1;
18549 }
18550 let mut precision = String::new();
18551 if i < chars.len() && chars[i] == '.' {
18552 i += 1;
18553 while i < chars.len() && chars[i].is_ascii_digit() {
18554 precision.push(chars[i]);
18555 i += 1;
18556 }
18557 }
18558 if i >= chars.len() {
18559 break;
18560 }
18561 let spec = chars[i];
18562 i += 1;
18563
18564 let arg = args.get(arg_idx).cloned().unwrap_or(PerlValue::UNDEF);
18565 arg_idx += 1;
18566
18567 let w: usize = width.parse().unwrap_or(0);
18568 let p: usize = precision.parse().unwrap_or(6);
18569
18570 let zero_pad = flags.contains('0') && !flags.contains('-');
18571 let left_align = flags.contains('-');
18572 let formatted = match spec {
18573 'd' | 'i' => {
18574 if zero_pad {
18575 format!("{:0width$}", arg.to_int(), width = w)
18576 } else if left_align {
18577 format!("{:<width$}", arg.to_int(), width = w)
18578 } else {
18579 format!("{:width$}", arg.to_int(), width = w)
18580 }
18581 }
18582 'u' => {
18583 if zero_pad {
18584 format!("{:0width$}", arg.to_int() as u64, width = w)
18585 } else {
18586 format!("{:width$}", arg.to_int() as u64, width = w)
18587 }
18588 }
18589 'f' => format!("{:width$.prec$}", arg.to_number(), width = w, prec = p),
18590 'e' => format!("{:width$.prec$e}", arg.to_number(), width = w, prec = p),
18591 'g' => {
18592 let n = arg.to_number();
18593 if n.abs() >= 1e-4 && n.abs() < 1e15 {
18594 format!("{:width$.prec$}", n, width = w, prec = p)
18595 } else {
18596 format!("{:width$.prec$e}", n, width = w, prec = p)
18597 }
18598 }
18599 's' => {
18600 let s = string_for_s(&arg)?;
18601 if !precision.is_empty() {
18602 let truncated: String = s.chars().take(p).collect();
18603 if flags.contains('-') {
18604 format!("{:<width$}", truncated, width = w)
18605 } else {
18606 format!("{:>width$}", truncated, width = w)
18607 }
18608 } else if flags.contains('-') {
18609 format!("{:<width$}", s, width = w)
18610 } else {
18611 format!("{:>width$}", s, width = w)
18612 }
18613 }
18614 'x' => {
18615 let v = arg.to_int();
18616 if zero_pad && w > 0 {
18617 format!("{:0width$x}", v, width = w)
18618 } else if left_align {
18619 format!("{:<width$x}", v, width = w)
18620 } else if w > 0 {
18621 format!("{:width$x}", v, width = w)
18622 } else {
18623 format!("{:x}", v)
18624 }
18625 }
18626 'X' => {
18627 let v = arg.to_int();
18628 if zero_pad && w > 0 {
18629 format!("{:0width$X}", v, width = w)
18630 } else if left_align {
18631 format!("{:<width$X}", v, width = w)
18632 } else if w > 0 {
18633 format!("{:width$X}", v, width = w)
18634 } else {
18635 format!("{:X}", v)
18636 }
18637 }
18638 'o' => {
18639 let v = arg.to_int();
18640 if zero_pad && w > 0 {
18641 format!("{:0width$o}", v, width = w)
18642 } else if left_align {
18643 format!("{:<width$o}", v, width = w)
18644 } else if w > 0 {
18645 format!("{:width$o}", v, width = w)
18646 } else {
18647 format!("{:o}", v)
18648 }
18649 }
18650 'b' => {
18651 let v = arg.to_int();
18652 if zero_pad && w > 0 {
18653 format!("{:0width$b}", v, width = w)
18654 } else if left_align {
18655 format!("{:<width$b}", v, width = w)
18656 } else if w > 0 {
18657 format!("{:width$b}", v, width = w)
18658 } else {
18659 format!("{:b}", v)
18660 }
18661 }
18662 'c' => char::from_u32(arg.to_int() as u32)
18663 .map(|c| c.to_string())
18664 .unwrap_or_default(),
18665 _ => arg.to_string(),
18666 };
18667
18668 result.push_str(&formatted);
18669 } else {
18670 result.push(chars[i]);
18671 i += 1;
18672 }
18673 }
18674 Ok(result)
18675}
18676
18677#[cfg(test)]
18678mod regex_expand_tests {
18679 use super::Interpreter;
18680
18681 #[test]
18682 fn compile_regex_quotemeta_qe_matches_literal() {
18683 let mut i = Interpreter::new();
18684 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
18685 assert!(re.is_match("a.c"));
18686 assert!(!re.is_match("abc"));
18687 }
18688
18689 #[test]
18692 fn compile_regex_char_class_leading_close_bracket_is_literal() {
18693 let mut i = Interpreter::new();
18694 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
18695 assert!(re.is_match("$"));
18696 assert!(re.is_match("]"));
18697 assert!(!re.is_match("x"));
18698 }
18699}
18700
18701#[cfg(test)]
18702mod special_scalar_name_tests {
18703 use super::Interpreter;
18704
18705 #[test]
18706 fn special_scalar_name_for_get_matches_magic_globals() {
18707 assert!(Interpreter::is_special_scalar_name_for_get("0"));
18708 assert!(Interpreter::is_special_scalar_name_for_get("!"));
18709 assert!(Interpreter::is_special_scalar_name_for_get("^W"));
18710 assert!(Interpreter::is_special_scalar_name_for_get("^O"));
18711 assert!(Interpreter::is_special_scalar_name_for_get("^MATCH"));
18712 assert!(Interpreter::is_special_scalar_name_for_get("<"));
18713 assert!(Interpreter::is_special_scalar_name_for_get("?"));
18714 assert!(Interpreter::is_special_scalar_name_for_get("|"));
18715 assert!(Interpreter::is_special_scalar_name_for_get("^UNICODE"));
18716 assert!(Interpreter::is_special_scalar_name_for_get("\""));
18717 assert!(!Interpreter::is_special_scalar_name_for_get("foo"));
18718 assert!(!Interpreter::is_special_scalar_name_for_get("plainvar"));
18719 }
18720
18721 #[test]
18722 fn special_scalar_name_for_set_matches_set_special_var_arms() {
18723 assert!(Interpreter::is_special_scalar_name_for_set("0"));
18724 assert!(Interpreter::is_special_scalar_name_for_set("^D"));
18725 assert!(Interpreter::is_special_scalar_name_for_set("^H"));
18726 assert!(Interpreter::is_special_scalar_name_for_set("^WARNING_BITS"));
18727 assert!(Interpreter::is_special_scalar_name_for_set("ARGV"));
18728 assert!(Interpreter::is_special_scalar_name_for_set("|"));
18729 assert!(Interpreter::is_special_scalar_name_for_set("?"));
18730 assert!(Interpreter::is_special_scalar_name_for_set("^UNICODE"));
18731 assert!(Interpreter::is_special_scalar_name_for_set("."));
18732 assert!(!Interpreter::is_special_scalar_name_for_set("foo"));
18733 assert!(!Interpreter::is_special_scalar_name_for_set("__PACKAGE__"));
18734 }
18735
18736 #[test]
18737 fn caret_and_id_specials_roundtrip_get() {
18738 let i = Interpreter::new();
18739 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
18740 assert_eq!(
18741 i.get_special_var("^V").to_string(),
18742 format!("v{}", env!("CARGO_PKG_VERSION"))
18743 );
18744 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
18745 assert!(i.get_special_var("^T").to_int() >= 0);
18746 #[cfg(unix)]
18747 {
18748 assert!(i.get_special_var("<").to_int() >= 0);
18749 }
18750 }
18751
18752 #[test]
18753 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
18754 let mut i = Interpreter::new();
18755 i.last_readline_handle.clear();
18756 i.line_number = 3;
18757 i.prepare_flip_flop_vm_slots(1);
18758 assert_eq!(
18759 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
18760 1
18761 );
18762 assert!(i.flip_flop_active[0]);
18763 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
18764 assert_eq!(
18766 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
18767 1
18768 );
18769 assert!(i.flip_flop_active[0]);
18770 }
18771
18772 #[test]
18773 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
18774 let mut i = Interpreter::new();
18775 i.last_readline_handle.clear();
18776 i.line_number = 2;
18777 i.prepare_flip_flop_vm_slots(1);
18778 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
18779 assert!(i.flip_flop_active[0]);
18780 i.line_number = 3;
18781 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
18782 assert!(!i.flip_flop_active[0]);
18783 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
18784 }
18785}