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::StrykeSocket;
25use crate::crypt_util::perl_crypt;
26use crate::error::{ErrorKind, StrykeError, StrykeResult};
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, PerlBarrier, PerlDataFrame, PerlGenerator, PerlHeap,
37 PerlPpool, PipelineInner, PipelineOp, RemoteCluster, StrykeAsyncTask, StrykeSub, StrykeValue,
38};
39
40pub(crate) fn preduce_init_merge_maps(
43 mut acc: IndexMap<String, StrykeValue>,
44 b: IndexMap<String, StrykeValue>,
45) -> StrykeValue {
46 for (k, v2) in b {
47 acc.entry(k)
48 .and_modify(|v1| *v1 = StrykeValue::float(v1.to_number() + v2.to_number()))
49 .or_insert(v2);
50 }
51 StrykeValue::hash_ref(Arc::new(RwLock::new(acc)))
52}
53
54#[inline]
56fn splice_compute_range(
57 arr_len: usize,
58 offset_val: &StrykeValue,
59 length_val: &StrykeValue,
60) -> (usize, usize) {
61 let off_i = offset_val.to_int();
62 let off = if off_i < 0 {
63 arr_len.saturating_sub((-off_i) as usize)
64 } else {
65 (off_i as usize).min(arr_len)
66 };
67 let rest = arr_len.saturating_sub(off);
68 let take = if length_val.is_undef() {
69 rest
70 } else {
71 let l = length_val.to_int();
72 if l < 0 {
73 rest.saturating_sub((-l) as usize)
74 } else {
75 (l as usize).min(rest)
76 }
77 };
78 let end = (off + take).min(arr_len);
79 (off, end)
80}
81
82pub(crate) fn merge_preduce_init_partials(
85 a: StrykeValue,
86 b: StrykeValue,
87 block: &Block,
88 subs: &HashMap<String, Arc<StrykeSub>>,
89 scope_capture: &[(String, StrykeValue)],
90) -> StrykeValue {
91 if let (Some(m1), Some(m2)) = (a.as_hash_map(), b.as_hash_map()) {
92 return preduce_init_merge_maps(m1, m2);
93 }
94 if let (Some(r1), Some(r2)) = (a.as_hash_ref(), b.as_hash_ref()) {
95 let m1 = r1.read().clone();
96 let m2 = r2.read().clone();
97 return preduce_init_merge_maps(m1, m2);
98 }
99 if let Some(m1) = a.as_hash_map() {
100 if let Some(r2) = b.as_hash_ref() {
101 let m2 = r2.read().clone();
102 return preduce_init_merge_maps(m1, m2);
103 }
104 }
105 if let Some(r1) = a.as_hash_ref() {
106 if let Some(m2) = b.as_hash_map() {
107 let m1 = r1.read().clone();
108 return preduce_init_merge_maps(m1, m2);
109 }
110 }
111 let mut local_interp = VMHelper::new();
112 local_interp.subs = subs.clone();
113 local_interp.scope.restore_capture(scope_capture);
114 local_interp.enable_parallel_guard();
115 local_interp
116 .scope
117 .declare_array("_", vec![a.clone(), b.clone()]);
118 local_interp.scope.set_sort_pair(a, b);
119 match local_interp.exec_block(block) {
120 Ok(val) => val,
121 Err(_) => StrykeValue::UNDEF,
122 }
123}
124
125pub(crate) fn preduce_init_fold_identity(init: &StrykeValue) -> StrykeValue {
128 if let Some(m) = init.as_hash_map() {
129 return StrykeValue::hash(m.clone());
130 }
131 if let Some(r) = init.as_hash_ref() {
132 return StrykeValue::hash_ref(Arc::new(RwLock::new(r.read().clone())));
133 }
134 init.clone()
135}
136
137pub(crate) fn fold_preduce_init_step(
138 subs: &HashMap<String, Arc<StrykeSub>>,
139 scope_capture: &[(String, StrykeValue)],
140 block: &Block,
141 acc: StrykeValue,
142 item: StrykeValue,
143) -> StrykeValue {
144 let mut local_interp = VMHelper::new();
145 local_interp.subs = subs.clone();
146 local_interp.scope.restore_capture(scope_capture);
147 local_interp.enable_parallel_guard();
148 local_interp
149 .scope
150 .declare_array("_", vec![acc.clone(), item.clone()]);
151 local_interp.scope.set_sort_pair(acc, item);
152 match local_interp.exec_block(block) {
153 Ok(val) => val,
154 Err(_) => StrykeValue::UNDEF,
155 }
156}
157
158pub const FEAT_SAY: u64 = 1 << 0;
160pub const FEAT_STATE: u64 = 1 << 1;
162pub const FEAT_SWITCH: u64 = 1 << 2;
164pub const FEAT_UNICODE_STRINGS: u64 = 1 << 3;
166
167#[derive(Debug)]
169pub(crate) enum Flow {
170 Return(StrykeValue),
171 Last(Option<String>),
172 Next(Option<String>),
173 Redo(Option<String>),
174 Yield(StrykeValue),
175 GotoSub(String),
177}
178
179pub(crate) type ExecResult = Result<StrykeValue, FlowOrError>;
180
181#[derive(Debug)]
182pub(crate) enum FlowOrError {
183 Flow(Flow),
184 Error(StrykeError),
185}
186
187impl From<StrykeError> for FlowOrError {
188 fn from(e: StrykeError) -> Self {
189 FlowOrError::Error(e)
190 }
191}
192
193impl From<Flow> for FlowOrError {
194 fn from(f: Flow) -> Self {
195 FlowOrError::Flow(f)
196 }
197}
198
199enum PatternBinding {
201 Scalar(String, StrykeValue),
202 Array(String, Vec<StrykeValue>),
203}
204
205pub fn perl_bracket_version() -> f64 {
208 const PERL_EMUL_MINOR: u32 = 38;
209 const PERL_EMUL_PATCH: u32 = 0;
210 5.0 + (PERL_EMUL_MINOR as f64) / 1000.0 + (PERL_EMUL_PATCH as f64) / 1_000_000.0
211}
212
213#[inline]
215fn fast_rng_seed() -> u64 {
216 let local: u8 = 0;
217 let addr = &local as *const u8 as u64;
218 (std::process::id() as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ addr
219}
220
221fn cached_executable_path() -> String {
223 static CACHED: OnceLock<String> = OnceLock::new();
224 CACHED
225 .get_or_init(|| {
226 std::env::current_exe()
227 .map(|p| p.to_string_lossy().into_owned())
228 .unwrap_or_else(|_| "stryke".to_string())
229 })
230 .clone()
231}
232
233fn build_term_hash() -> IndexMap<String, StrykeValue> {
234 let mut m = IndexMap::new();
235 m.insert(
236 "TERM".into(),
237 StrykeValue::string(std::env::var("TERM").unwrap_or_default()),
238 );
239 m.insert(
240 "COLORTERM".into(),
241 StrykeValue::string(std::env::var("COLORTERM").unwrap_or_default()),
242 );
243
244 let (rows, cols) = term_size();
245 m.insert("rows".into(), StrykeValue::integer(rows));
246 m.insert("cols".into(), StrykeValue::integer(cols));
247
248 #[cfg(unix)]
249 let is_tty = unsafe { libc::isatty(1) != 0 };
250 #[cfg(not(unix))]
251 let is_tty = false;
252 m.insert(
253 "is_tty".into(),
254 StrykeValue::integer(if is_tty { 1 } else { 0 }),
255 );
256
257 m
258}
259
260fn term_size() -> (i64, i64) {
261 #[cfg(unix)]
262 {
263 unsafe {
264 let mut ws: libc::winsize = std::mem::zeroed();
265 if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 {
266 return (ws.ws_row as i64, ws.ws_col as i64);
267 }
268 }
269 }
270 let rows = std::env::var("LINES")
271 .ok()
272 .and_then(|s| s.parse().ok())
273 .unwrap_or(24);
274 let cols = std::env::var("COLUMNS")
275 .ok()
276 .and_then(|s| s.parse().ok())
277 .unwrap_or(80);
278 (rows, cols)
279}
280
281#[cfg(unix)]
282fn build_uname_hash() -> IndexMap<String, StrykeValue> {
283 fn uts_field(slice: &[libc::c_char]) -> String {
284 let n = slice.iter().take_while(|&&c| c != 0).count();
285 let bytes: Vec<u8> = slice[..n].iter().map(|&c| c as u8).collect();
286 String::from_utf8_lossy(&bytes).into_owned()
287 }
288 let mut m = IndexMap::new();
289 let mut uts: libc::utsname = unsafe { std::mem::zeroed() };
290 if unsafe { libc::uname(&mut uts) } == 0 {
291 m.insert(
292 "sysname".into(),
293 StrykeValue::string(uts_field(uts.sysname.as_slice())),
294 );
295 m.insert(
296 "nodename".into(),
297 StrykeValue::string(uts_field(uts.nodename.as_slice())),
298 );
299 m.insert(
300 "release".into(),
301 StrykeValue::string(uts_field(uts.release.as_slice())),
302 );
303 m.insert(
304 "version".into(),
305 StrykeValue::string(uts_field(uts.version.as_slice())),
306 );
307 m.insert(
308 "machine".into(),
309 StrykeValue::string(uts_field(uts.machine.as_slice())),
310 );
311 }
312 m
313}
314
315#[cfg(unix)]
316fn build_limits_hash() -> IndexMap<String, StrykeValue> {
317 use libc::{getrlimit, rlimit, RLIM_INFINITY};
318 #[cfg(target_os = "linux")]
319 type RlimitResource = libc::__rlimit_resource_t;
320 #[cfg(not(target_os = "linux"))]
321 type RlimitResource = libc::c_int;
322 fn get_limit(resource: RlimitResource) -> (i64, i64) {
323 let mut rlim = rlimit {
324 rlim_cur: 0,
325 rlim_max: 0,
326 };
327 if unsafe { getrlimit(resource, &mut rlim) } == 0 {
328 let cur = if rlim.rlim_cur == RLIM_INFINITY {
329 -1
330 } else {
331 rlim.rlim_cur as i64
332 };
333 let max = if rlim.rlim_max == RLIM_INFINITY {
334 -1
335 } else {
336 rlim.rlim_max as i64
337 };
338 (cur, max)
339 } else {
340 (-1, -1)
341 }
342 }
343 let mut m = IndexMap::new();
344 let (cur, max) = get_limit(libc::RLIMIT_NOFILE);
345 m.insert("nofile".into(), StrykeValue::integer(cur));
346 m.insert("nofile_max".into(), StrykeValue::integer(max));
347 let (cur, max) = get_limit(libc::RLIMIT_STACK);
348 m.insert("stack".into(), StrykeValue::integer(cur));
349 m.insert("stack_max".into(), StrykeValue::integer(max));
350 let (cur, max) = get_limit(libc::RLIMIT_AS);
351 m.insert("as".into(), StrykeValue::integer(cur));
352 m.insert("as_max".into(), StrykeValue::integer(max));
353 let (cur, max) = get_limit(libc::RLIMIT_DATA);
354 m.insert("data".into(), StrykeValue::integer(cur));
355 m.insert("data_max".into(), StrykeValue::integer(max));
356 let (cur, max) = get_limit(libc::RLIMIT_FSIZE);
357 m.insert("fsize".into(), StrykeValue::integer(cur));
358 m.insert("fsize_max".into(), StrykeValue::integer(max));
359 let (cur, max) = get_limit(libc::RLIMIT_CORE);
360 m.insert("core".into(), StrykeValue::integer(cur));
361 m.insert("core_max".into(), StrykeValue::integer(max));
362 let (cur, max) = get_limit(libc::RLIMIT_CPU);
363 m.insert("cpu".into(), StrykeValue::integer(cur));
364 m.insert("cpu_max".into(), StrykeValue::integer(max));
365 let (cur, max) = get_limit(libc::RLIMIT_NPROC);
366 m.insert("nproc".into(), StrykeValue::integer(cur));
367 m.insert("nproc_max".into(), StrykeValue::integer(max));
368 #[cfg(target_os = "linux")]
369 {
370 let (cur, max) = get_limit(libc::RLIMIT_MEMLOCK);
371 m.insert("memlock".into(), StrykeValue::integer(cur));
372 m.insert("memlock_max".into(), StrykeValue::integer(max));
373 }
374 m
375}
376
377#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
379pub(crate) enum WantarrayCtx {
380 #[default]
381 Scalar,
382 List,
383 Void,
384}
385
386impl WantarrayCtx {
387 #[inline]
388 pub(crate) fn from_byte(b: u8) -> Self {
389 match b {
390 1 => Self::List,
391 2 => Self::Void,
392 _ => Self::Scalar,
393 }
394 }
395
396 #[inline]
397 pub(crate) fn as_byte(self) -> u8 {
398 match self {
399 Self::Scalar => 0,
400 Self::List => 1,
401 Self::Void => 2,
402 }
403 }
404}
405
406#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
408pub(crate) enum LogLevelFilter {
409 Trace,
410 Debug,
411 Info,
412 Warn,
413 Error,
414}
415
416impl LogLevelFilter {
417 pub(crate) fn parse(s: &str) -> Option<Self> {
418 match s.trim().to_ascii_lowercase().as_str() {
419 "trace" => Some(Self::Trace),
420 "debug" => Some(Self::Debug),
421 "info" => Some(Self::Info),
422 "warn" | "warning" => Some(Self::Warn),
423 "error" => Some(Self::Error),
424 _ => None,
425 }
426 }
427
428 pub(crate) fn as_str(self) -> &'static str {
429 match self {
430 Self::Trace => "trace",
431 Self::Debug => "debug",
432 Self::Info => "info",
433 Self::Warn => "warn",
434 Self::Error => "error",
435 }
436 }
437}
438
439fn arrow_deref_array_assign_rhs_list_ctx(index: &Expr) -> bool {
441 match &index.kind {
442 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => true,
443 ExprKind::QW(ws) => ws.len() > 1,
444 ExprKind::List(el) => {
445 if el.len() > 1 {
446 true
447 } else if el.len() == 1 {
448 arrow_deref_array_assign_rhs_list_ctx(&el[0])
449 } else {
450 false
451 }
452 }
453 _ => false,
454 }
455}
456
457pub(crate) fn assign_rhs_wantarray(target: &Expr) -> WantarrayCtx {
460 match &target.kind {
461 ExprKind::ArrayVar(_) | ExprKind::HashVar(_) => WantarrayCtx::List,
462 ExprKind::ScalarVar(_) | ExprKind::ArrayElement { .. } | ExprKind::HashElement { .. } => {
463 WantarrayCtx::Scalar
464 }
465 ExprKind::Deref { kind, .. } => match kind {
466 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
467 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
468 },
469 ExprKind::ArrowDeref {
470 index,
471 kind: DerefKind::Array,
472 ..
473 } => {
474 if arrow_deref_array_assign_rhs_list_ctx(index) {
475 WantarrayCtx::List
476 } else {
477 WantarrayCtx::Scalar
478 }
479 }
480 ExprKind::ArrowDeref {
481 kind: DerefKind::Hash,
482 ..
483 }
484 | ExprKind::ArrowDeref {
485 kind: DerefKind::Call,
486 ..
487 } => WantarrayCtx::Scalar,
488 ExprKind::HashSliceDeref { .. }
489 | ExprKind::HashSlice { .. }
490 | ExprKind::HashKvSlice { .. } => WantarrayCtx::List,
491 ExprKind::ArraySlice { indices, .. } => {
492 if indices.len() > 1 {
493 WantarrayCtx::List
494 } else if indices.len() == 1 {
495 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
496 WantarrayCtx::List
497 } else {
498 WantarrayCtx::Scalar
499 }
500 } else {
501 WantarrayCtx::Scalar
502 }
503 }
504 ExprKind::AnonymousListSlice { indices, .. } => {
505 if indices.len() > 1 {
506 WantarrayCtx::List
507 } else if indices.len() == 1 {
508 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
509 WantarrayCtx::List
510 } else {
511 WantarrayCtx::Scalar
512 }
513 } else {
514 WantarrayCtx::Scalar
515 }
516 }
517 ExprKind::Typeglob(_) | ExprKind::TypeglobExpr(_) => WantarrayCtx::Scalar,
518 ExprKind::List(_) => WantarrayCtx::List,
519 _ => WantarrayCtx::Scalar,
520 }
521}
522
523#[derive(Clone)]
528pub(crate) struct RegexMatchMemo {
529 pub pattern: String,
530 pub flags: String,
531 pub multiline: bool,
532 pub haystack: String,
533 pub result: StrykeValue,
534}
535
536#[derive(Clone, Copy, Default)]
538struct FlipFlopTreeState {
539 active: bool,
540 exclusive_left_line: Option<i64>,
544}
545
546#[derive(Clone)]
548pub(crate) struct IoSharedFile(pub Arc<Mutex<File>>);
549
550impl Read for IoSharedFile {
551 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
552 self.0.lock().read(buf)
553 }
554}
555
556pub(crate) struct IoSharedFileWrite(pub Arc<Mutex<File>>);
557
558impl IoWrite for IoSharedFileWrite {
559 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
560 self.0.lock().write(buf)
561 }
562
563 fn flush(&mut self) -> io::Result<()> {
564 self.0.lock().flush()
565 }
566}
567
568pub struct VMHelper {
570 pub scope: Scope,
571 pub(crate) subs: HashMap<String, Arc<StrykeSub>>,
572 pub(crate) intercepts: Vec<crate::aop::Intercept>,
574 pub(crate) next_intercept_id: u32,
576 pub(crate) intercept_ctx_stack: Vec<crate::aop::InterceptCtx>,
578 pub(crate) intercept_active_names: Vec<String>,
582 pub(crate) file: String,
583 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
585 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
586 pub ofs: String,
588 pub ors: String,
590 pub irs: Option<String>,
593 pub errno: String,
595 pub errno_code: i32,
597 pub eval_error: String,
599 pub eval_error_code: i32,
601 pub eval_error_value: Option<StrykeValue>,
603 pub argv: Vec<String>,
605 pub env: IndexMap<String, StrykeValue>,
607 pub env_materialized: bool,
609 pub program_name: String,
611 pub line_number: i64,
613 pub last_readline_handle: String,
615 pub(crate) last_stdin_die_bracket: String,
617 pub handle_line_numbers: HashMap<String, i64>,
619 pub(crate) flip_flop_active: Vec<bool>,
623 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
626 pub(crate) flip_flop_sequence: Vec<i64>,
630 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
634 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
636 pub sigint_pending_caret: Cell<bool>,
638 pub auto_split: bool,
640 pub field_separator: Option<String>,
642 begin_blocks: Vec<Block>,
644 unit_check_blocks: Vec<Block>,
646 check_blocks: Vec<Block>,
648 init_blocks: Vec<Block>,
650 end_blocks: Vec<Block>,
652 pub warnings: bool,
654 pub output_autoflush: bool,
656 pub default_print_handle: String,
658 pub suppress_stdout: bool,
660 pub test_pass_count: std::sync::atomic::AtomicUsize,
667 pub test_fail_count: std::sync::atomic::AtomicUsize,
668 pub test_skip_count: std::sync::atomic::AtomicUsize,
669 pub test_pass_total: std::sync::atomic::AtomicUsize,
679 pub test_fail_total: std::sync::atomic::AtomicUsize,
680 pub test_skip_total: std::sync::atomic::AtomicUsize,
681 pub test_run_failed: std::sync::atomic::AtomicBool,
687 pub child_exit_status: i64,
689 pub last_match: String,
691 pub prematch: String,
693 pub postmatch: String,
695 pub last_paren_match: String,
697 pub list_separator: String,
699 pub script_start_time: i64,
701 pub compile_hints: i64,
703 pub warning_bits: i64,
705 pub global_phase: String,
707 pub subscript_sep: String,
709 pub inplace_edit: String,
712 pub debug_flags: i64,
714 pub perl_debug_flags: i64,
716 pub eval_nesting: u32,
718 pub argv_current_file: String,
720 pub(crate) diamond_next_idx: usize,
722 pub(crate) diamond_reader: Option<BufReader<File>>,
724 pub strict_refs: bool,
726 pub strict_subs: bool,
727 pub strict_vars: bool,
728 pub utf8_pragma: bool,
730 pub open_pragma_utf8: bool,
732 pub feature_bits: u64,
734 pub num_threads: usize,
736 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
738 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
741 regex_match_memo: Option<RegexMatchMemo>,
750 regex_capture_scope_fresh: bool,
754 pub(crate) regex_pos: HashMap<String, Option<usize>>,
756 pub(crate) state_vars: HashMap<String, StrykeValue>,
758 pub(crate) state_bindings_stack: Vec<Vec<(String, String)>>,
760 pub(crate) rand_rng: StdRng,
762 pub(crate) dir_handles: HashMap<String, DirHandleState>,
764 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
766 pub(crate) pipe_children: HashMap<String, Child>,
768 pub(crate) socket_handles: HashMap<String, StrykeSocket>,
770 pub(crate) wantarray_kind: WantarrayCtx,
772 pub struct_defs: HashMap<String, Arc<StructDef>>,
774 pub enum_defs: HashMap<String, Arc<EnumDef>>,
776 pub class_defs: HashMap<String, Arc<ClassDef>>,
778 pub trait_defs: HashMap<String, Arc<TraitDef>>,
780 pub profiler: Option<Profiler>,
783 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
785 pub(crate) virtual_modules: HashMap<String, String>,
787 pub(crate) tied_hashes: HashMap<String, StrykeValue>,
789 pub(crate) tied_scalars: HashMap<String, StrykeValue>,
791 pub(crate) tied_arrays: HashMap<String, StrykeValue>,
793 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
795 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
797 pub(crate) special_caret_scalars: HashMap<String, StrykeValue>,
799 pub format_page_number: i64,
801 pub format_lines_per_page: i64,
803 pub format_lines_left: i64,
805 pub format_line_break_chars: String,
807 pub format_top_name: String,
809 pub accumulator_format: String,
811 pub max_system_fd: i64,
813 pub emergency_memory: String,
815 pub last_subpattern_name: String,
817 pub inc_hook_index: i64,
819 pub multiline_match: bool,
821 pub executable_path: String,
823 pub formfeed_string: String,
825 pub(crate) glob_handle_alias: HashMap<String, String>,
827 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
829 pub(crate) special_var_restore_frames: Vec<Vec<(String, StrykeValue)>>,
834 pub(crate) reflection_hashes_ready: bool,
838 pub(crate) english_enabled: bool,
839 pub(crate) english_no_match_vars: bool,
841 pub(crate) english_match_vars_ever_enabled: bool,
845 english_lexical_scalars: Vec<HashSet<String>>,
847 our_lexical_scalars: Vec<HashSet<String>>,
849 pub vm_jit_enabled: bool,
852 pub disasm_bytecode: bool,
854 pub cached_chunk: Option<crate::bytecode::Chunk>,
858 pub cache_script_path: Option<std::path::PathBuf>,
860 pub(crate) stryke_pwd: PathBuf,
862 pub(crate) in_generator: bool,
864 pub line_mode_skip_main: bool,
866 pub line_mode_chunk: Option<crate::bytecode::Chunk>,
869 pub(crate) line_mode_eof_pending: bool,
873 pub line_mode_stdin_pending: VecDeque<String>,
876 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
878 pub(crate) log_level_override: Option<LogLevelFilter>,
880 pub(crate) current_sub_stack: Vec<Arc<StrykeSub>>,
883 pub debugger: Option<crate::debugger::Debugger>,
885 pub(crate) debug_call_stack: Vec<(String, usize)>,
887}
888
889#[derive(Debug, Clone, Default)]
891pub struct ReplCompletionSnapshot {
892 pub subs: Vec<String>,
893 pub blessed_scalars: HashMap<String, String>,
894 pub isa_for_class: HashMap<String, Vec<String>>,
895}
896
897impl ReplCompletionSnapshot {
898 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
900 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
901 let mro = linearize_c3(class, &parents, 0);
902 let mut names = HashSet::new();
903 for pkg in &mro {
904 if pkg == "UNIVERSAL" {
905 continue;
906 }
907 let prefix = format!("{}::", pkg);
908 for k in &self.subs {
909 if k.starts_with(&prefix) {
910 let rest = &k[prefix.len()..];
911 if !rest.contains("::") {
912 names.insert(rest.to_string());
913 }
914 }
915 }
916 }
917 for k in &self.subs {
918 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
919 if !rest.contains("::") {
920 names.insert(rest.to_string());
921 }
922 }
923 }
924 let mut v: Vec<String> = names.into_iter().collect();
925 v.sort();
926 v
927 }
928}
929
930fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
931 let left = left.trim_end();
932 if left.is_empty() {
933 return None;
934 }
935 if let Some(i) = left.rfind('$') {
936 let name = left[i + 1..].trim();
937 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
938 return state.blessed_scalars.get(name).cloned();
939 }
940 }
941 let tok = left.split_whitespace().last()?;
942 if tok.contains("::") {
943 return Some(tok.to_string());
944 }
945 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
946 return Some(tok.to_string());
947 }
948 None
949}
950
951pub fn repl_arrow_method_completions(
953 state: &ReplCompletionSnapshot,
954 line: &str,
955 pos: usize,
956) -> Option<(usize, Vec<String>)> {
957 let pos = pos.min(line.len());
958 let before = &line[..pos];
959 let arrow_idx = before.rfind("->")?;
960 let after_arrow = &before[arrow_idx + 2..];
961 let rest = after_arrow.trim_start();
962 let ws_len = after_arrow.len() - rest.len();
963 let method_start = arrow_idx + 2 + ws_len;
964 let method_prefix = &line[method_start..pos];
965 if !method_prefix
966 .chars()
967 .all(|c| c.is_alphanumeric() || c == '_')
968 {
969 return None;
970 }
971 let left = line[..arrow_idx].trim_end();
972 let class = repl_resolve_class_for_arrow(state, left)?;
973 let mut methods = state.methods_for_class(&class);
974 methods.retain(|m| m.starts_with(method_prefix));
975 Some((method_start, methods))
976}
977
978#[derive(Debug, Clone, Default)]
980pub(crate) struct ModuleExportLists {
981 pub export: Vec<String>,
983 pub export_ok: Vec<String>,
985}
986
987fn piped_shell_command(cmd: &str) -> Command {
989 if cfg!(windows) {
990 let mut c = Command::new("cmd");
991 c.arg("/C").arg(cmd);
992 c
993 } else {
994 let mut c = Command::new("sh");
995 c.arg("-c").arg(cmd);
996 c
997 }
998}
999
1000fn expand_perl_regex_octal_escapes(pat: &str) -> String {
1007 let mut out = String::with_capacity(pat.len());
1008 let mut it = pat.chars().peekable();
1009 while let Some(c) = it.next() {
1010 if c == '\\' {
1011 if let Some(&'0') = it.peek() {
1012 let mut oct = String::new();
1014 while oct.len() < 3 {
1015 if let Some(&d) = it.peek() {
1016 if ('0'..='7').contains(&d) {
1017 oct.push(d);
1018 it.next();
1019 } else {
1020 break;
1021 }
1022 } else {
1023 break;
1024 }
1025 }
1026 if let Ok(val) = u8::from_str_radix(&oct, 8) {
1027 out.push_str(&format!("\\x{:02x}", val));
1028 } else {
1029 out.push('\\');
1030 out.push_str(&oct);
1031 }
1032 continue;
1033 }
1034 }
1035 out.push(c);
1036 }
1037 out
1038}
1039
1040fn expand_perl_regex_quotemeta(pat: &str) -> String {
1041 let mut out = String::with_capacity(pat.len().saturating_mul(2));
1042 let mut it = pat.chars().peekable();
1043 let mut in_q = false;
1044 while let Some(c) = it.next() {
1045 if in_q {
1046 if c == '\\' && it.peek() == Some(&'E') {
1047 it.next();
1048 in_q = false;
1049 continue;
1050 }
1051 out.push_str(&perl_quotemeta(&c.to_string()));
1052 continue;
1053 }
1054 if c == '\\' && it.peek() == Some(&'Q') {
1055 it.next();
1056 in_q = true;
1057 continue;
1058 }
1059 out.push(c);
1060 }
1061 out
1062}
1063
1064pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
1070 let mut out = String::with_capacity(replacement.len() + 8);
1071 let mut it = replacement.chars().peekable();
1072 while let Some(c) = it.next() {
1073 if c == '\\' {
1074 match it.peek() {
1075 Some(&d) if d.is_ascii_digit() => {
1076 it.next();
1077 out.push_str("${");
1078 out.push(d);
1079 while let Some(&d2) = it.peek() {
1080 if !d2.is_ascii_digit() {
1081 break;
1082 }
1083 it.next();
1084 out.push(d2);
1085 }
1086 out.push('}');
1087 }
1088 Some(&'\\') => {
1089 it.next();
1090 out.push('\\');
1091 }
1092 _ => out.push('\\'),
1093 }
1094 } else if c == '$' {
1095 match it.peek() {
1096 Some(&d) if d.is_ascii_digit() => {
1097 it.next();
1098 out.push_str("${");
1099 out.push(d);
1100 while let Some(&d2) = it.peek() {
1101 if !d2.is_ascii_digit() {
1102 break;
1103 }
1104 it.next();
1105 out.push(d2);
1106 }
1107 out.push('}');
1108 }
1109 Some(&'{') => {
1110 out.push('$');
1112 }
1113 _ => out.push('$'),
1114 }
1115 } else {
1116 out.push(c);
1117 }
1118 }
1119 out
1120}
1121
1122fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
1125 debug_assert_eq!(chars.get(i), Some(&'['));
1126 out.push('[');
1127 i += 1;
1128 if i < chars.len() && chars[i] == '^' {
1129 out.push('^');
1130 i += 1;
1131 }
1132 if i >= chars.len() {
1133 return i;
1134 }
1135 if chars[i] == ']' {
1139 if i + 1 < chars.len() && chars[i + 1] == ']' {
1140 out.push(']');
1142 i += 1;
1143 } else {
1144 let mut scan = i + 1;
1145 let mut found_closing = false;
1146 while scan < chars.len() {
1147 if chars[scan] == '\\' && scan + 1 < chars.len() {
1148 scan += 2;
1149 continue;
1150 }
1151 if chars[scan] == ']' {
1152 found_closing = true;
1153 break;
1154 }
1155 scan += 1;
1156 }
1157 if found_closing {
1158 out.push(']');
1159 i += 1;
1160 } else {
1161 out.push(']');
1162 return i + 1;
1163 }
1164 }
1165 }
1166 while i < chars.len() && chars[i] != ']' {
1167 if chars[i] == '\\' && i + 1 < chars.len() {
1168 out.push(chars[i]);
1169 out.push(chars[i + 1]);
1170 i += 2;
1171 continue;
1172 }
1173 out.push(chars[i]);
1174 i += 1;
1175 }
1176 if i < chars.len() {
1177 out.push(']');
1178 i += 1;
1179 }
1180 i
1181}
1182
1183fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1188 if multiline_flag {
1189 return pat.to_string();
1190 }
1191 let chars: Vec<char> = pat.chars().collect();
1192 let mut out = String::with_capacity(pat.len().saturating_add(16));
1193 let mut i = 0usize;
1194 while i < chars.len() {
1195 let c = chars[i];
1196 if c == '\\' && i + 1 < chars.len() {
1197 out.push(c);
1198 out.push(chars[i + 1]);
1199 i += 2;
1200 continue;
1201 }
1202 if c == '[' {
1203 i = copy_regex_char_class(&chars, i, &mut out);
1204 continue;
1205 }
1206 if c == '$' {
1207 if let Some(&next) = chars.get(i + 1) {
1208 if next.is_ascii_digit() {
1209 out.push(c);
1210 i += 1;
1211 continue;
1212 }
1213 if next == '{' {
1214 out.push(c);
1215 i += 1;
1216 continue;
1217 }
1218 if next.is_ascii_alphanumeric() || next == '_' {
1219 out.push(c);
1220 i += 1;
1221 continue;
1222 }
1223 }
1224 out.push_str("(?=\\n?\\z)");
1225 i += 1;
1226 continue;
1227 }
1228 out.push(c);
1229 i += 1;
1230 }
1231 out
1232}
1233
1234#[derive(Debug, Clone)]
1236pub(crate) struct DirHandleState {
1237 pub entries: Vec<String>,
1238 pub pos: usize,
1239}
1240
1241pub(crate) fn perl_osname() -> String {
1243 match std::env::consts::OS {
1244 "linux" => "linux".to_string(),
1245 "macos" => "darwin".to_string(),
1246 "windows" => "MSWin32".to_string(),
1247 other => other.to_string(),
1248 }
1249}
1250
1251fn perl_version_v_string() -> String {
1252 format!("v{}", env!("CARGO_PKG_VERSION"))
1253}
1254
1255fn extended_os_error_string() -> String {
1256 std::io::Error::last_os_error().to_string()
1257}
1258
1259#[cfg(unix)]
1260fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1261 unsafe {
1262 (
1263 libc::getuid() as i64,
1264 libc::geteuid() as i64,
1265 libc::getgid() as i64,
1266 libc::getegid() as i64,
1267 )
1268 }
1269}
1270
1271#[cfg(not(unix))]
1272fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1273 (0, 0, 0, 0)
1274}
1275
1276fn unix_id_for_special(name: &str) -> i64 {
1277 let (r, e, _, _) = unix_real_effective_ids();
1278 match name {
1279 "<" => r,
1280 ">" => e,
1281 _ => 0,
1282 }
1283}
1284
1285#[cfg(unix)]
1286fn unix_group_list_string(primary: libc::gid_t) -> String {
1287 let mut buf = vec![0 as libc::gid_t; 256];
1288 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1289 if n <= 0 {
1290 return format!("{}", primary);
1291 }
1292 let mut parts = vec![format!("{}", primary)];
1293 for g in buf.iter().take(n as usize) {
1294 parts.push(format!("{}", g));
1295 }
1296 parts.join(" ")
1297}
1298
1299#[cfg(unix)]
1301fn unix_group_list_for_special(name: &str) -> String {
1302 let (_, _, gid, egid) = unix_real_effective_ids();
1303 match name {
1304 "(" => unix_group_list_string(gid as libc::gid_t),
1305 ")" => unix_group_list_string(egid as libc::gid_t),
1306 _ => String::new(),
1307 }
1308}
1309
1310#[cfg(not(unix))]
1311fn unix_group_list_for_special(_name: &str) -> String {
1312 String::new()
1313}
1314
1315#[cfg(unix)]
1318fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1319 use libc::{getpwuid_r, getuid};
1320 use std::ffi::CStr;
1321 use std::os::unix::ffi::OsStringExt;
1322 let uid = unsafe { getuid() };
1323 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1324 let mut result: *mut libc::passwd = std::ptr::null_mut();
1325 let mut buf = vec![0u8; 16_384];
1326 let rc = unsafe {
1327 getpwuid_r(
1328 uid,
1329 &mut pw,
1330 buf.as_mut_ptr().cast::<libc::c_char>(),
1331 buf.len(),
1332 &mut result,
1333 )
1334 };
1335 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1336 return None;
1337 }
1338 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1339 if bytes.is_empty() {
1340 return None;
1341 }
1342 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1343}
1344
1345#[cfg(unix)]
1347fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1348 use libc::getpwnam_r;
1349 use std::ffi::{CStr, CString};
1350 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1351 let bytes = login.as_bytes();
1352 if bytes.is_empty() || bytes.contains(&0) {
1353 return None;
1354 }
1355 let cname = CString::new(bytes).ok()?;
1356 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1357 let mut result: *mut libc::passwd = std::ptr::null_mut();
1358 let mut buf = vec![0u8; 16_384];
1359 let rc = unsafe {
1360 getpwnam_r(
1361 cname.as_ptr(),
1362 &mut pw,
1363 buf.as_mut_ptr().cast::<libc::c_char>(),
1364 buf.len(),
1365 &mut result,
1366 )
1367 };
1368 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1369 return None;
1370 }
1371 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1372 if dir_bytes.is_empty() {
1373 return None;
1374 }
1375 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1376}
1377
1378impl Default for VMHelper {
1379 fn default() -> Self {
1380 Self::new()
1381 }
1382}
1383
1384#[derive(Clone, Copy)]
1386pub(crate) enum CaptureAllMode {
1387 Empty,
1389 Append,
1391 Skip,
1393}
1394
1395impl VMHelper {
1396 pub fn new() -> Self {
1397 let mut scope = Scope::new();
1398 scope.declare_array("INC", vec![StrykeValue::string(".".to_string())]);
1399 scope.declare_hash("INC", IndexMap::new());
1400 scope.declare_array("ARGV", vec![]);
1401 scope.declare_array("_", vec![]);
1402
1403 let path_vec: Vec<StrykeValue> = std::env::var("PATH")
1405 .unwrap_or_default()
1406 .split(if cfg!(windows) { ';' } else { ':' })
1407 .filter(|s| !s.is_empty())
1408 .map(|p| StrykeValue::string(p.to_string()))
1409 .collect();
1410 scope.declare_array_frozen("path", path_vec.clone(), true);
1411 scope.declare_array_frozen("p", path_vec, true);
1412
1413 let fpath_vec: Vec<StrykeValue> = std::env::var("FPATH")
1415 .unwrap_or_default()
1416 .split(':')
1417 .filter(|s| !s.is_empty())
1418 .map(|p| StrykeValue::string(p.to_string()))
1419 .collect();
1420 scope.declare_array_frozen("fpath", fpath_vec.clone(), true);
1421 scope.declare_array_frozen("f", fpath_vec, true);
1422 scope.declare_hash("ENV", IndexMap::new());
1423 scope.declare_hash("SIG", IndexMap::new());
1424
1425 let term_map = build_term_hash();
1427 scope.declare_hash_global_frozen("term", term_map);
1428
1429 #[cfg(unix)]
1431 {
1432 let uname_map = build_uname_hash();
1433 scope.declare_hash_global_frozen("uname", uname_map);
1434 }
1435 #[cfg(not(unix))]
1436 {
1437 scope.declare_hash_global_frozen("uname", IndexMap::new());
1438 }
1439
1440 #[cfg(unix)]
1442 {
1443 let limits_map = build_limits_hash();
1444 scope.declare_hash_global_frozen("limits", limits_map);
1445 }
1446 #[cfg(not(unix))]
1447 {
1448 scope.declare_hash_global_frozen("limits", IndexMap::new());
1449 }
1450
1451 scope.declare_scalar(
1474 "stryke::VERSION",
1475 StrykeValue::string(env!("CARGO_PKG_VERSION").to_string()),
1476 );
1477 scope.declare_array("-", vec![]);
1478 scope.declare_array("+", vec![]);
1479 scope.declare_array("^CAPTURE", vec![]);
1480 scope.declare_array("^CAPTURE_ALL", vec![]);
1481 scope.declare_hash("^HOOK", IndexMap::new());
1482 scope.declare_scalar("~", StrykeValue::string("STDOUT".to_string()));
1483
1484 let script_start_time = std::time::SystemTime::now()
1485 .duration_since(std::time::UNIX_EPOCH)
1486 .map(|d| d.as_secs() as i64)
1487 .unwrap_or(0);
1488
1489 let executable_path = cached_executable_path();
1490
1491 let stryke_pwd_init = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1492 let stryke_pwd = std::fs::canonicalize(&stryke_pwd_init).unwrap_or(stryke_pwd_init);
1493
1494 let mut special_caret_scalars: HashMap<String, StrykeValue> = HashMap::new();
1495 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1496 special_caret_scalars.insert(format!("^{}", name), StrykeValue::UNDEF);
1497 }
1498
1499 let mut s = Self {
1500 scope,
1501 subs: HashMap::new(),
1502 intercepts: Vec::new(),
1503 next_intercept_id: 1,
1504 intercept_ctx_stack: Vec::new(),
1505 intercept_active_names: Vec::new(),
1506 struct_defs: HashMap::new(),
1507 enum_defs: HashMap::new(),
1508 class_defs: HashMap::new(),
1509 trait_defs: HashMap::new(),
1510 file: "-e".to_string(),
1511 output_handles: HashMap::new(),
1512 input_handles: HashMap::new(),
1513 ofs: String::new(),
1514 ors: String::new(),
1515 irs: Some("\n".to_string()),
1516 errno: String::new(),
1517 errno_code: 0,
1518 eval_error: String::new(),
1519 eval_error_code: 0,
1520 eval_error_value: None,
1521 argv: Vec::new(),
1522 env: IndexMap::new(),
1523 env_materialized: false,
1524 program_name: "stryke".to_string(),
1525 line_number: 0,
1526 last_readline_handle: String::new(),
1527 last_stdin_die_bracket: "<STDIN>".to_string(),
1528 handle_line_numbers: HashMap::new(),
1529 flip_flop_active: Vec::new(),
1530 flip_flop_exclusive_left_line: Vec::new(),
1531 flip_flop_sequence: Vec::new(),
1532 flip_flop_last_dot: Vec::new(),
1533 flip_flop_tree: HashMap::new(),
1534 sigint_pending_caret: Cell::new(false),
1535 auto_split: false,
1536 field_separator: None,
1537 begin_blocks: Vec::new(),
1538 unit_check_blocks: Vec::new(),
1539 check_blocks: Vec::new(),
1540 init_blocks: Vec::new(),
1541 end_blocks: Vec::new(),
1542 warnings: false,
1543 output_autoflush: false,
1544 default_print_handle: "STDOUT".to_string(),
1545 suppress_stdout: false,
1546 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1547 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1548 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1549 test_pass_total: std::sync::atomic::AtomicUsize::new(0),
1550 test_fail_total: std::sync::atomic::AtomicUsize::new(0),
1551 test_skip_total: std::sync::atomic::AtomicUsize::new(0),
1552 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1553 child_exit_status: 0,
1554 last_match: String::new(),
1555 prematch: String::new(),
1556 postmatch: String::new(),
1557 last_paren_match: String::new(),
1558 list_separator: " ".to_string(),
1559 script_start_time,
1560 compile_hints: 0,
1561 warning_bits: 0,
1562 global_phase: "RUN".to_string(),
1563 subscript_sep: "\x1c".to_string(),
1564 inplace_edit: String::new(),
1565 debug_flags: 0,
1566 perl_debug_flags: 0,
1567 eval_nesting: 0,
1568 argv_current_file: String::new(),
1569 diamond_next_idx: 0,
1570 diamond_reader: None,
1571 strict_refs: false,
1572 strict_subs: false,
1573 strict_vars: false,
1574 utf8_pragma: false,
1575 open_pragma_utf8: false,
1576 feature_bits: FEAT_SAY,
1578 num_threads: 0, regex_cache: HashMap::new(),
1580 regex_last: None,
1581 regex_match_memo: None,
1582 regex_capture_scope_fresh: false,
1583 regex_pos: HashMap::new(),
1584 state_vars: HashMap::new(),
1585 state_bindings_stack: Vec::new(),
1586 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1587 dir_handles: HashMap::new(),
1588 io_file_slots: HashMap::new(),
1589 pipe_children: HashMap::new(),
1590 socket_handles: HashMap::new(),
1591 wantarray_kind: WantarrayCtx::Scalar,
1592 profiler: None,
1593 module_export_lists: HashMap::new(),
1594 virtual_modules: HashMap::new(),
1595 tied_hashes: HashMap::new(),
1596 tied_scalars: HashMap::new(),
1597 tied_arrays: HashMap::new(),
1598 overload_table: HashMap::new(),
1599 format_templates: HashMap::new(),
1600 special_caret_scalars,
1601 format_page_number: 0,
1602 format_lines_per_page: 60,
1603 format_lines_left: 0,
1604 format_line_break_chars: "\n".to_string(),
1605 format_top_name: String::new(),
1606 accumulator_format: String::new(),
1607 max_system_fd: 2,
1608 emergency_memory: String::new(),
1609 last_subpattern_name: String::new(),
1610 inc_hook_index: 0,
1611 multiline_match: false,
1612 executable_path,
1613 formfeed_string: "\x0c".to_string(),
1614 glob_handle_alias: HashMap::new(),
1615 glob_restore_frames: vec![Vec::new()],
1616 special_var_restore_frames: vec![Vec::new()],
1617 reflection_hashes_ready: false,
1618 english_enabled: false,
1619 english_no_match_vars: false,
1620 english_match_vars_ever_enabled: false,
1621 english_lexical_scalars: vec![HashSet::new()],
1622 our_lexical_scalars: vec![HashSet::new()],
1623 vm_jit_enabled: !matches!(
1624 std::env::var("STRYKE_NO_JIT"),
1625 Ok(v)
1626 if v == "1"
1627 || v.eq_ignore_ascii_case("true")
1628 || v.eq_ignore_ascii_case("yes")
1629 ),
1630 disasm_bytecode: false,
1631 cached_chunk: None,
1632 cache_script_path: None,
1633 stryke_pwd,
1634 in_generator: false,
1635 line_mode_skip_main: false,
1636 line_mode_chunk: None,
1637 line_mode_eof_pending: false,
1638 line_mode_stdin_pending: VecDeque::new(),
1639 rate_limit_slots: Vec::new(),
1640 log_level_override: None,
1641 current_sub_stack: Vec::new(),
1642 debugger: None,
1643 debug_call_stack: Vec::new(),
1644 };
1645 s.install_overload_pragma_stubs();
1646 s
1647 }
1648
1649 pub(crate) fn ensure_reflection_hashes(&mut self) {
1653 if self.reflection_hashes_ready {
1654 return;
1655 }
1656 self.reflection_hashes_ready = true;
1657 self.refresh_package_stashes();
1660 if crate::compat_mode() {
1664 return;
1665 }
1666 let builtins_map = crate::builtins::builtins_hash_map();
1667 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1668 let extensions_map = crate::builtins::extensions_hash_map();
1669 let aliases_map = crate::builtins::aliases_hash_map();
1670 let descriptions_map = crate::builtins::descriptions_hash_map();
1671 let categories_map = crate::builtins::categories_hash_map();
1672 let primaries_map = crate::builtins::primaries_hash_map();
1673 let keywords_map = crate::builtins::keywords_hash_map();
1674 let operators_map = crate::builtins::operators_hash_map();
1675 let special_vars_map = crate::builtins::special_vars_hash_map();
1676 let all_map = crate::builtins::all_hash_map();
1677 self.scope
1678 .declare_hash_global_frozen("stryke::builtins", builtins_map.clone());
1679 self.scope
1680 .declare_hash_global_frozen("stryke::perl_compats", perl_compats_map.clone());
1681 self.scope
1682 .declare_hash_global_frozen("stryke::extensions", extensions_map.clone());
1683 self.scope
1684 .declare_hash_global_frozen("stryke::aliases", aliases_map.clone());
1685 self.scope
1686 .declare_hash_global_frozen("stryke::descriptions", descriptions_map.clone());
1687 self.scope
1688 .declare_hash_global_frozen("stryke::categories", categories_map.clone());
1689 self.scope
1690 .declare_hash_global_frozen("stryke::primaries", primaries_map.clone());
1691 self.scope
1692 .declare_hash_global_frozen("stryke::keywords", keywords_map.clone());
1693 self.scope
1694 .declare_hash_global_frozen("stryke::operators", operators_map.clone());
1695 self.scope
1696 .declare_hash_global_frozen("stryke::special_vars", special_vars_map.clone());
1697 self.scope
1698 .declare_hash_global_frozen("stryke::all", all_map.clone());
1699 for (name, val) in [
1702 ("b", builtins_map),
1703 ("pc", perl_compats_map),
1704 ("e", extensions_map),
1705 ("a", aliases_map),
1706 ("d", descriptions_map),
1707 ("c", categories_map),
1708 ("p", primaries_map),
1709 ("k", keywords_map),
1710 ("o", operators_map),
1711 ("v", special_vars_map),
1712 ("all", all_map),
1713 ] {
1714 if !self.scope.any_frame_has_hash(name) {
1715 self.scope.declare_hash_global_frozen(name, val);
1716 }
1717 }
1718 if !self.scope.has_lexical_hash("parameters") {
1721 self.refresh_parameters_hash();
1722 }
1723 }
1724
1725 pub fn refresh_parameters_hash(&mut self) {
1734 let pairs = self.scope.parameters_pairs();
1735 let mut h: indexmap::IndexMap<String, StrykeValue> =
1736 indexmap::IndexMap::with_capacity(pairs.len());
1737 for (name, kind) in pairs {
1738 h.insert(name, StrykeValue::string(kind.to_string()));
1739 }
1740 self.scope.declare_hash_global_frozen("parameters", h);
1743 }
1744
1745 pub fn refresh_package_stashes(&mut self) {
1755 use indexmap::IndexMap;
1756
1757 let mut by_pkg: std::collections::HashMap<String, IndexMap<String, StrykeValue>> =
1758 std::collections::HashMap::new();
1759
1760 let record = |pkg: &str,
1761 sym: &str,
1762 kind: &str,
1763 map: &mut std::collections::HashMap<
1764 String,
1765 IndexMap<String, StrykeValue>,
1766 >| {
1767 map.entry(pkg.to_string())
1768 .or_default()
1769 .insert(sym.to_string(), StrykeValue::string(kind.to_string()));
1770 };
1771
1772 for key in self.subs.keys() {
1774 if let Some(idx) = key.rfind("::") {
1775 let (pkg, rest) = key.split_at(idx);
1776 let sym = &rest[2..];
1777 if pkg.is_empty() || sym.is_empty() {
1778 continue;
1779 }
1780 record(pkg, sym, "sub", &mut by_pkg);
1781 } else {
1782 record("main", key, "sub", &mut by_pkg);
1784 }
1785 }
1786
1787 for frame in self.scope.frames_for_introspection() {
1789 let (scalars, arrays, hashes) = frame;
1790 for name in scalars {
1791 if let Some(idx) = name.rfind("::") {
1792 let (pkg, rest) = name.split_at(idx);
1793 let sym = &rest[2..];
1794 if !pkg.is_empty() && !sym.is_empty() {
1795 record(pkg, sym, "scalar", &mut by_pkg);
1796 }
1797 }
1798 }
1799 for name in arrays {
1800 if let Some(idx) = name.rfind("::") {
1801 let (pkg, rest) = name.split_at(idx);
1802 let sym = &rest[2..];
1803 if !pkg.is_empty() && !sym.is_empty() {
1804 record(pkg, sym, "array", &mut by_pkg);
1805 }
1806 }
1807 }
1808 for name in hashes {
1809 if let Some(idx) = name.rfind("::") {
1810 let (pkg, rest) = name.split_at(idx);
1811 let sym = &rest[2..];
1812 if !pkg.is_empty() && !sym.is_empty() {
1813 record(pkg, sym, "hash", &mut by_pkg);
1814 }
1815 }
1816 }
1817 }
1818
1819 for (pkg, mut entries) in by_pkg {
1823 entries.sort_keys();
1824 let key = format!("{}::", pkg);
1825 self.scope.declare_hash_global_frozen(&key, entries);
1826 }
1827 }
1828
1829 fn install_overload_pragma_stubs(&mut self) {
1833 let empty: Block = vec![];
1834 for key in ["overload::import", "overload::unimport"] {
1835 let name = key.to_string();
1836 self.subs.insert(
1837 name.clone(),
1838 Arc::new(StrykeSub {
1839 name,
1840 params: vec![],
1841 body: empty.clone(),
1842 prototype: None,
1843 closure_env: None,
1844 fib_like: None,
1845 }),
1846 );
1847 }
1848 }
1849
1850 pub fn line_mode_worker_clone(&self) -> VMHelper {
1853 VMHelper {
1854 scope: self.scope.clone(),
1855 subs: self.subs.clone(),
1856 intercepts: self.intercepts.clone(),
1857 next_intercept_id: self.next_intercept_id,
1858 intercept_ctx_stack: self.intercept_ctx_stack.clone(),
1859 intercept_active_names: self.intercept_active_names.clone(),
1860 struct_defs: self.struct_defs.clone(),
1861 enum_defs: self.enum_defs.clone(),
1862 class_defs: self.class_defs.clone(),
1863 trait_defs: self.trait_defs.clone(),
1864 file: self.file.clone(),
1865 output_handles: HashMap::new(),
1866 input_handles: HashMap::new(),
1867 ofs: self.ofs.clone(),
1868 ors: self.ors.clone(),
1869 irs: self.irs.clone(),
1870 errno: self.errno.clone(),
1871 errno_code: self.errno_code,
1872 eval_error: self.eval_error.clone(),
1873 eval_error_code: self.eval_error_code,
1874 eval_error_value: self.eval_error_value.clone(),
1875 argv: self.argv.clone(),
1876 env: self.env.clone(),
1877 env_materialized: self.env_materialized,
1878 program_name: self.program_name.clone(),
1879 line_number: 0,
1880 last_readline_handle: String::new(),
1881 last_stdin_die_bracket: "<STDIN>".to_string(),
1882 handle_line_numbers: HashMap::new(),
1883 flip_flop_active: Vec::new(),
1884 flip_flop_exclusive_left_line: Vec::new(),
1885 flip_flop_sequence: Vec::new(),
1886 flip_flop_last_dot: Vec::new(),
1887 flip_flop_tree: HashMap::new(),
1888 sigint_pending_caret: Cell::new(false),
1889 auto_split: self.auto_split,
1890 field_separator: self.field_separator.clone(),
1891 begin_blocks: self.begin_blocks.clone(),
1892 unit_check_blocks: self.unit_check_blocks.clone(),
1893 check_blocks: self.check_blocks.clone(),
1894 init_blocks: self.init_blocks.clone(),
1895 end_blocks: self.end_blocks.clone(),
1896 warnings: self.warnings,
1897 output_autoflush: self.output_autoflush,
1898 default_print_handle: self.default_print_handle.clone(),
1899 suppress_stdout: self.suppress_stdout,
1900 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1904 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1905 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1906 test_pass_total: std::sync::atomic::AtomicUsize::new(0),
1907 test_fail_total: std::sync::atomic::AtomicUsize::new(0),
1908 test_skip_total: std::sync::atomic::AtomicUsize::new(0),
1909 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1910 child_exit_status: self.child_exit_status,
1911 last_match: self.last_match.clone(),
1912 prematch: self.prematch.clone(),
1913 postmatch: self.postmatch.clone(),
1914 last_paren_match: self.last_paren_match.clone(),
1915 list_separator: self.list_separator.clone(),
1916 script_start_time: self.script_start_time,
1917 compile_hints: self.compile_hints,
1918 warning_bits: self.warning_bits,
1919 global_phase: self.global_phase.clone(),
1920 subscript_sep: self.subscript_sep.clone(),
1921 inplace_edit: self.inplace_edit.clone(),
1922 debug_flags: self.debug_flags,
1923 perl_debug_flags: self.perl_debug_flags,
1924 eval_nesting: self.eval_nesting,
1925 argv_current_file: String::new(),
1926 diamond_next_idx: 0,
1927 diamond_reader: None,
1928 strict_refs: self.strict_refs,
1929 strict_subs: self.strict_subs,
1930 strict_vars: self.strict_vars,
1931 utf8_pragma: self.utf8_pragma,
1932 open_pragma_utf8: self.open_pragma_utf8,
1933 feature_bits: self.feature_bits,
1934 num_threads: 0,
1935 regex_cache: self.regex_cache.clone(),
1936 regex_last: self.regex_last.clone(),
1937 regex_match_memo: self.regex_match_memo.clone(),
1938 regex_capture_scope_fresh: false,
1939 regex_pos: self.regex_pos.clone(),
1940 state_vars: self.state_vars.clone(),
1941 state_bindings_stack: Vec::new(),
1942 rand_rng: self.rand_rng.clone(),
1943 dir_handles: HashMap::new(),
1944 io_file_slots: HashMap::new(),
1945 pipe_children: HashMap::new(),
1946 socket_handles: HashMap::new(),
1947 wantarray_kind: self.wantarray_kind,
1948 profiler: None,
1949 module_export_lists: self.module_export_lists.clone(),
1950 virtual_modules: self.virtual_modules.clone(),
1951 tied_hashes: self.tied_hashes.clone(),
1952 tied_scalars: self.tied_scalars.clone(),
1953 tied_arrays: self.tied_arrays.clone(),
1954 overload_table: self.overload_table.clone(),
1955 format_templates: self.format_templates.clone(),
1956 special_caret_scalars: self.special_caret_scalars.clone(),
1957 format_page_number: self.format_page_number,
1958 format_lines_per_page: self.format_lines_per_page,
1959 format_lines_left: self.format_lines_left,
1960 format_line_break_chars: self.format_line_break_chars.clone(),
1961 format_top_name: self.format_top_name.clone(),
1962 accumulator_format: self.accumulator_format.clone(),
1963 max_system_fd: self.max_system_fd,
1964 emergency_memory: self.emergency_memory.clone(),
1965 last_subpattern_name: self.last_subpattern_name.clone(),
1966 inc_hook_index: self.inc_hook_index,
1967 multiline_match: self.multiline_match,
1968 executable_path: self.executable_path.clone(),
1969 formfeed_string: self.formfeed_string.clone(),
1970 glob_handle_alias: self.glob_handle_alias.clone(),
1971 glob_restore_frames: self.glob_restore_frames.clone(),
1972 special_var_restore_frames: self.special_var_restore_frames.clone(),
1973 reflection_hashes_ready: self.reflection_hashes_ready,
1974 english_enabled: self.english_enabled,
1975 english_no_match_vars: self.english_no_match_vars,
1976 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1977 english_lexical_scalars: self.english_lexical_scalars.clone(),
1978 our_lexical_scalars: self.our_lexical_scalars.clone(),
1979 vm_jit_enabled: self.vm_jit_enabled,
1980 disasm_bytecode: self.disasm_bytecode,
1981 cached_chunk: None,
1983 cache_script_path: None,
1984 stryke_pwd: self.stryke_pwd.clone(),
1985 in_generator: false,
1986 line_mode_skip_main: false,
1987 line_mode_chunk: self.line_mode_chunk.clone(),
1988 line_mode_eof_pending: false,
1989 line_mode_stdin_pending: VecDeque::new(),
1990 rate_limit_slots: Vec::new(),
1991 log_level_override: self.log_level_override,
1992 current_sub_stack: Vec::new(),
1993 debugger: None,
1994 debug_call_stack: Vec::new(),
1995 }
1996 }
1997
1998 pub(crate) fn parallel_thread_count(&mut self) -> usize {
2000 if self.num_threads == 0 {
2001 self.num_threads = rayon::current_num_threads();
2002 }
2003 self.num_threads
2004 }
2005
2006 pub(crate) fn eval_par_list_call(
2008 &mut self,
2009 name: &str,
2010 args: &[StrykeValue],
2011 ctx: WantarrayCtx,
2012 line: usize,
2013 ) -> StrykeResult<StrykeValue> {
2014 match name {
2015 "puniq" => {
2016 let (list_src, show_prog) = match args.len() {
2017 0 => return Err(StrykeError::runtime("puniq: expected LIST", line)),
2018 1 => (&args[0], false),
2019 2 => (&args[0], args[1].is_true()),
2020 _ => {
2021 return Err(StrykeError::runtime(
2022 "puniq: expected LIST [, progress => EXPR]",
2023 line,
2024 ));
2025 }
2026 };
2027 let list = list_src.to_list();
2028 let n_threads = self.parallel_thread_count();
2029 let pmap_progress = PmapProgress::new(show_prog, list.len());
2030 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
2031 pmap_progress.finish();
2032 if ctx == WantarrayCtx::List {
2033 Ok(StrykeValue::array(out))
2034 } else {
2035 Ok(StrykeValue::integer(out.len() as i64))
2036 }
2037 }
2038 "pfirst" => {
2039 let (code_val, list_src, show_prog) = match args.len() {
2040 2 => (&args[0], &args[1], false),
2041 3 => (&args[0], &args[1], args[2].is_true()),
2042 _ => {
2043 return Err(StrykeError::runtime(
2044 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
2045 line,
2046 ));
2047 }
2048 };
2049 let Some(sub) = code_val.as_code_ref() else {
2050 return Err(StrykeError::runtime(
2051 "pfirst: first argument must be a code reference",
2052 line,
2053 ));
2054 };
2055 let sub = sub.clone();
2056 let list = list_src.to_list();
2057 if list.is_empty() {
2058 return Ok(StrykeValue::UNDEF);
2059 }
2060 let pmap_progress = PmapProgress::new(show_prog, list.len());
2061 let subs = self.subs.clone();
2062 let (scope_capture, atomic_arrays, atomic_hashes) =
2063 self.scope.capture_with_atomics();
2064 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
2065 let mut local_interp = VMHelper::new();
2066 local_interp.subs = subs.clone();
2067 local_interp.scope.restore_capture(&scope_capture);
2068 local_interp
2069 .scope
2070 .restore_atomics(&atomic_arrays, &atomic_hashes);
2071 local_interp.enable_parallel_guard();
2072 local_interp.scope.set_topic(item);
2073 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2074 Ok(v) => v.is_true(),
2075 Err(_) => false,
2076 }
2077 });
2078 pmap_progress.finish();
2079 Ok(out.unwrap_or(StrykeValue::UNDEF))
2080 }
2081 "pany" => {
2082 let (code_val, list_src, show_prog) = match args.len() {
2083 2 => (&args[0], &args[1], false),
2084 3 => (&args[0], &args[1], args[2].is_true()),
2085 _ => {
2086 return Err(StrykeError::runtime(
2087 "pany: expected BLOCK, LIST [, progress => EXPR]",
2088 line,
2089 ));
2090 }
2091 };
2092 let Some(sub) = code_val.as_code_ref() else {
2093 return Err(StrykeError::runtime(
2094 "pany: first argument must be a code reference",
2095 line,
2096 ));
2097 };
2098 let sub = sub.clone();
2099 let list = list_src.to_list();
2100 let pmap_progress = PmapProgress::new(show_prog, list.len());
2101 let subs = self.subs.clone();
2102 let (scope_capture, atomic_arrays, atomic_hashes) =
2103 self.scope.capture_with_atomics();
2104 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
2105 let mut local_interp = VMHelper::new();
2106 local_interp.subs = subs.clone();
2107 local_interp.scope.restore_capture(&scope_capture);
2108 local_interp
2109 .scope
2110 .restore_atomics(&atomic_arrays, &atomic_hashes);
2111 local_interp.enable_parallel_guard();
2112 local_interp.scope.set_topic(item);
2113 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2114 Ok(v) => v.is_true(),
2115 Err(_) => false,
2116 }
2117 });
2118 pmap_progress.finish();
2119 Ok(StrykeValue::integer(if b { 1 } else { 0 }))
2120 }
2121 _ => Err(StrykeError::runtime(
2122 format!("internal: unknown par_list builtin {name}"),
2123 line,
2124 )),
2125 }
2126 }
2127
2128 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
2129 #[cfg(unix)]
2130 if let Some(sig) = s.signal() {
2131 return sig as i64 & 0x7f;
2132 }
2133 let code = s.code().unwrap_or(0) as i64;
2134 code << 8
2135 }
2136
2137 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
2138 self.child_exit_status = self.encode_exit_status(s);
2139 }
2140
2141 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
2143 let s = e.to_string();
2146 let stripped = s
2147 .rfind(" (os error ")
2148 .map(|i| s[..i].to_string())
2149 .unwrap_or(s);
2150 self.errno = stripped;
2151 self.errno_code = e.raw_os_error().unwrap_or(0);
2152 }
2153
2154 pub(crate) fn ssh_builtin_execute(
2165 &mut self,
2166 args: &[StrykeValue],
2167 ) -> StrykeResult<StrykeValue> {
2168 use std::process::Command;
2169 let mut cmd = Command::new("ssh");
2170 #[cfg(unix)]
2171 {
2172 use libc::geteuid;
2173 let home_for_ssh = if unsafe { geteuid() } == 0 {
2174 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
2175 } else {
2176 None
2177 };
2178 if let Some(h) = home_for_ssh {
2179 cmd.env("HOME", h);
2180 } else if std::env::var_os("HOME").is_none() {
2181 if let Some(h) = pw_home_dir_for_current_uid() {
2182 cmd.env("HOME", h);
2183 }
2184 }
2185 }
2186 for a in args {
2187 cmd.arg(a.to_string());
2188 }
2189 match cmd.status() {
2190 Ok(s) => {
2191 self.record_child_exit_status(s);
2192 Ok(StrykeValue::integer(s.code().unwrap_or(-1) as i64))
2193 }
2194 Err(e) => {
2195 self.apply_io_error_to_errno(&e);
2196 Ok(StrykeValue::integer(-1))
2197 }
2198 }
2199 }
2200
2201 pub(crate) fn set_eval_error(&mut self, msg: String) {
2203 self.eval_error = msg;
2204 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2205 self.eval_error_value = None;
2206 }
2207
2208 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &StrykeError) {
2209 self.eval_error = e.to_string();
2210 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2211 self.eval_error_value = e.die_value.clone();
2212 }
2213
2214 pub(crate) fn clear_eval_error(&mut self) {
2215 self.eval_error = String::new();
2216 self.eval_error_code = 0;
2217 self.eval_error_value = None;
2218 }
2219
2220 fn bump_line_for_handle(&mut self, handle_key: &str) {
2222 self.last_readline_handle = handle_key.to_string();
2223 *self
2224 .handle_line_numbers
2225 .entry(handle_key.to_string())
2226 .or_insert(0) += 1;
2227 }
2228
2229 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
2231 if name.starts_with('^') {
2232 return name.to_string();
2233 }
2234 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
2235 let pkg = self.current_package();
2236 if !pkg.is_empty() && pkg != "main" {
2237 return format!("{}::{}", pkg, name);
2238 }
2239 }
2240 name.to_string()
2241 }
2242
2243 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
2245 if name.contains("::") {
2246 return name.to_string();
2247 }
2248 let pkg = self.current_package();
2249 if pkg.is_empty() || pkg == "main" {
2250 format!("main::{}", name)
2251 } else {
2252 format!("{}::{}", pkg, name)
2253 }
2254 }
2255
2256 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
2258 if name.contains("::") {
2259 return name.to_string();
2260 }
2261 for (lex, our) in self
2262 .english_lexical_scalars
2263 .iter()
2264 .zip(self.our_lexical_scalars.iter())
2265 .rev()
2266 {
2267 if lex.contains(name) {
2268 if our.contains(name) {
2269 return self.stash_scalar_name_for_package(name);
2270 }
2271 return name.to_string();
2272 }
2273 }
2274 name.to_string()
2275 }
2276
2277 pub(crate) fn tie_execute(
2279 &mut self,
2280 target_kind: u8,
2281 target_name: &str,
2282 class_and_args: Vec<StrykeValue>,
2283 line: usize,
2284 ) -> StrykeResult<StrykeValue> {
2285 let mut it = class_and_args.into_iter();
2286 let class = it.next().unwrap_or(StrykeValue::UNDEF);
2287 let pkg = class.to_string();
2288 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
2289 let tie_ctor = match target_kind {
2290 0 => "TIESCALAR",
2291 1 => "TIEARRAY",
2292 2 => "TIEHASH",
2293 _ => return Err(StrykeError::runtime("tie: invalid target kind", line)),
2294 };
2295 let tie_fn = format!("{}::{}", pkg, tie_ctor);
2296 let sub =
2297 self.subs.get(&tie_fn).cloned().ok_or_else(|| {
2298 StrykeError::runtime(format!("tie: cannot find &{}", tie_fn), line)
2299 })?;
2300 let mut call_args = vec![StrykeValue::string(pkg.clone())];
2301 call_args.extend(it);
2302 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
2303 Ok(v) => v,
2304 Err(FlowOrError::Flow(_)) => StrykeValue::UNDEF,
2305 Err(FlowOrError::Error(e)) => return Err(e),
2306 };
2307 match target_kind {
2308 0 => {
2309 self.tied_scalars.insert(target_name.to_string(), obj);
2310 }
2311 1 => {
2312 let key = self.stash_array_name_for_package(target_name);
2313 self.tied_arrays.insert(key, obj);
2314 }
2315 2 => {
2316 self.tied_hashes.insert(target_name.to_string(), obj);
2317 }
2318 _ => return Err(StrykeError::runtime("tie: invalid target kind", line)),
2319 }
2320 Ok(StrykeValue::UNDEF)
2321 }
2322
2323 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
2325 let key = format!("{}::ISA", class);
2326 self.scope
2327 .get_array(&key)
2328 .into_iter()
2329 .map(|v| v.to_string())
2330 .collect()
2331 }
2332
2333 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
2334 let p = |c: &str| self.parents_of_class(c);
2335 linearize_c3(class, &p, 0)
2336 }
2337
2338 pub(crate) fn resolve_method_full_name(
2340 &self,
2341 invocant_class: &str,
2342 method: &str,
2343 super_mode: bool,
2344 ) -> Option<String> {
2345 let mro = self.mro_linearize(invocant_class);
2346 let start = if super_mode {
2350 mro.iter()
2351 .position(|p| p == invocant_class)
2352 .map(|i| i + 1)
2353 .unwrap_or(1)
2356 } else {
2357 0
2358 };
2359 for pkg in mro.iter().skip(start) {
2360 if pkg == "UNIVERSAL" {
2361 continue;
2362 }
2363 let fq = format!("{}::{}", pkg, method);
2364 if self.subs.contains_key(&fq) {
2365 return Some(fq);
2366 }
2367 }
2368 mro.iter()
2369 .skip(start)
2370 .find(|p| *p != "UNIVERSAL")
2371 .map(|pkg| format!("{}::{}", pkg, method))
2372 }
2373
2374 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
2375 if let Some(alias) = self.glob_handle_alias.get(name) {
2376 return alias.clone();
2377 }
2378 if let Some(var_name) = name.strip_prefix('$') {
2381 let val = self.scope.get_scalar(var_name);
2382 let s = val.to_string();
2383 if !s.is_empty() {
2384 return self.resolve_io_handle_name(&s);
2385 }
2386 }
2387 name.to_string()
2388 }
2389
2390 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2392 if name.contains("::") {
2393 name.to_string()
2394 } else {
2395 self.qualify_sub_key(name)
2396 }
2397 }
2398
2399 pub(crate) fn copy_typeglob_slots(
2401 &mut self,
2402 lhs: &str,
2403 rhs: &str,
2404 line: usize,
2405 ) -> StrykeResult<()> {
2406 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2407 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2408 match self.subs.get(&rhs_sub).cloned() {
2409 Some(s) => {
2410 self.subs.insert(lhs_sub, s);
2411 }
2412 None => {
2413 self.subs.remove(&lhs_sub);
2414 }
2415 }
2416 let sv = self.scope.get_scalar(rhs);
2417 self.scope
2418 .set_scalar(lhs, sv.clone())
2419 .map_err(|e| e.at_line(line))?;
2420 let lhs_an = self.stash_array_name_for_package(lhs);
2421 let rhs_an = self.stash_array_name_for_package(rhs);
2422 let av = self.scope.get_array(&rhs_an);
2423 self.scope
2424 .set_array(&lhs_an, av.clone())
2425 .map_err(|e| e.at_line(line))?;
2426 let hv = self.scope.get_hash(rhs);
2427 self.scope
2428 .set_hash(lhs, hv.clone())
2429 .map_err(|e| e.at_line(line))?;
2430 match self.glob_handle_alias.get(rhs).cloned() {
2431 Some(t) => {
2432 self.glob_handle_alias.insert(lhs.to_string(), t);
2433 }
2434 None => {
2435 self.glob_handle_alias.remove(lhs);
2436 }
2437 }
2438 Ok(())
2439 }
2440
2441 pub(crate) fn install_format_decl(
2443 &mut self,
2444 basename: &str,
2445 lines: &[String],
2446 line: usize,
2447 ) -> StrykeResult<()> {
2448 let pkg = self.current_package();
2449 let key = format!("{}::{}", pkg, basename);
2450 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2451 self.format_templates.insert(key, Arc::new(tmpl));
2452 Ok(())
2453 }
2454
2455 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2461 let pkg = self.current_package();
2462 for (_, v) in pairs {
2463 if v.starts_with("__overload_anon_") {
2464 let pkg_key = format!("{}::{}", pkg, v);
2471 if !self.subs.contains_key(&pkg_key) {
2472 let src = if let Some(s) = self.subs.get(v) {
2473 Some(s.clone())
2474 } else {
2475 self.subs.get(&format!("main::{}", v)).cloned()
2476 };
2477 if let Some(sub) = src {
2478 self.subs.insert(pkg_key, sub);
2479 }
2480 }
2481 }
2482 }
2483 let ent = self.overload_table.entry(pkg).or_default();
2484 for (k, v) in pairs {
2485 ent.insert(k.clone(), v.clone());
2486 }
2487 }
2488
2489 pub(crate) fn local_declare_typeglob(
2492 &mut self,
2493 lhs: &str,
2494 rhs: Option<&str>,
2495 line: usize,
2496 ) -> StrykeResult<()> {
2497 let old = self.glob_handle_alias.remove(lhs);
2498 let Some(frame) = self.glob_restore_frames.last_mut() else {
2499 return Err(StrykeError::runtime(
2500 "internal: no glob restore frame for local *GLOB",
2501 line,
2502 ));
2503 };
2504 frame.push((lhs.to_string(), old));
2505 if let Some(r) = rhs {
2506 self.glob_handle_alias
2507 .insert(lhs.to_string(), r.to_string());
2508 }
2509 Ok(())
2510 }
2511
2512 pub(crate) fn scope_push_hook(&mut self) {
2513 self.scope.push_frame();
2514 self.glob_restore_frames.push(Vec::new());
2515 self.special_var_restore_frames.push(Vec::new());
2516 self.english_lexical_scalars.push(HashSet::new());
2517 self.our_lexical_scalars.push(HashSet::new());
2518 self.state_bindings_stack.push(Vec::new());
2519 }
2520
2521 #[inline]
2522 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2523 if let Some(s) = self.english_lexical_scalars.last_mut() {
2524 s.insert(name.to_string());
2525 }
2526 }
2527
2528 #[inline]
2531 pub(crate) fn english_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2532 self.english_lexical_scalars.clone()
2533 }
2534
2535 #[inline]
2538 pub(crate) fn our_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2539 self.our_lexical_scalars.clone()
2540 }
2541
2542 #[inline]
2544 pub(crate) fn set_english_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2545 self.english_lexical_scalars = v;
2546 }
2547
2548 #[inline]
2550 pub(crate) fn set_our_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2551 self.our_lexical_scalars = v;
2552 }
2553
2554 #[inline]
2555 fn note_our_scalar(&mut self, bare_name: &str) {
2556 if let Some(s) = self.our_lexical_scalars.last_mut() {
2557 s.insert(bare_name.to_string());
2558 }
2559 }
2560
2561 #[inline]
2565 pub(crate) fn english_note_lexical_scalar_pub(&mut self, name: &str) {
2566 self.english_note_lexical_scalar(name);
2567 }
2568
2569 #[inline]
2571 pub(crate) fn note_our_scalar_pub(&mut self, bare_name: &str) {
2572 self.note_our_scalar(bare_name);
2573 }
2574
2575 pub(crate) fn scope_pop_hook(&mut self) {
2576 if !self.scope.can_pop_frame() {
2577 return;
2578 }
2579 let defers = self.scope.take_defers();
2583 for coderef in defers {
2584 if let Some(sub) = coderef.as_code_ref() {
2585 let saved_wa = self.wantarray_kind;
2589 self.wantarray_kind = WantarrayCtx::Void;
2590 let _ = self.exec_block_no_scope(&sub.body);
2591 self.wantarray_kind = saved_wa;
2592 }
2593 }
2594 if let Some(bindings) = self.state_bindings_stack.pop() {
2596 for (var_name, state_key) in &bindings {
2597 let val = self.scope.get_scalar(var_name).clone();
2598 self.state_vars.insert(state_key.clone(), val);
2599 }
2600 }
2601 if let Some(entries) = self.special_var_restore_frames.pop() {
2604 for (name, old) in entries.into_iter().rev() {
2605 let _ = self.set_special_var(&name, &old);
2606 }
2607 }
2608 if let Some(entries) = self.glob_restore_frames.pop() {
2609 for (name, old) in entries.into_iter().rev() {
2610 match old {
2611 Some(s) => {
2612 self.glob_handle_alias.insert(name, s);
2613 }
2614 None => {
2615 self.glob_handle_alias.remove(&name);
2616 }
2617 }
2618 }
2619 }
2620 self.scope.pop_frame();
2621 let _ = self.english_lexical_scalars.pop();
2622 let _ = self.our_lexical_scalars.pop();
2623 }
2624
2625 #[inline]
2628 pub(crate) fn enable_parallel_guard(&mut self) {
2629 self.scope.set_parallel_guard(true);
2630 }
2631
2632 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2634 self.begin_blocks.clear();
2635 self.unit_check_blocks.clear();
2636 self.check_blocks.clear();
2637 self.init_blocks.clear();
2638 self.end_blocks.clear();
2639 }
2640
2641 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2647 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2648 self.scope_pop_hook();
2649 }
2650 }
2651
2652 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> StrykeResult<()> {
2659 self.touch_env_hash("SIG");
2660 let v = self.scope.get_hash_element("SIG", sig);
2661 if v.is_undef() {
2662 return Self::default_sig_action(sig);
2663 }
2664 if let Some(s) = v.as_str() {
2665 if s == "IGNORE" {
2666 return Ok(());
2667 }
2668 if s == "DEFAULT" {
2669 return Self::default_sig_action(sig);
2670 }
2671 }
2672 if let Some(sub) = v.as_code_ref() {
2673 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2674 Ok(_) => Ok(()),
2675 Err(FlowOrError::Flow(_)) => Ok(()),
2676 Err(FlowOrError::Error(e)) => Err(e),
2677 }
2678 } else {
2679 Self::default_sig_action(sig)
2680 }
2681 }
2682
2683 pub(crate) fn fire_pseudosig_warn(&mut self, msg: &str, line: usize) -> StrykeResult<()> {
2687 self.touch_env_hash("SIG");
2688 let slot = self.scope.get_hash_element("SIG", "__WARN__");
2689 if let Some(sub) = slot.as_code_ref() {
2690 let prev = slot;
2691 let _ = self
2692 .scope
2693 .set_hash_element("SIG", "__WARN__", StrykeValue::UNDEF);
2694 let arg = StrykeValue::string(msg.to_string());
2695 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2696 let _ = self.scope.set_hash_element("SIG", "__WARN__", prev);
2697 return match r {
2698 Ok(_) => Ok(()),
2699 Err(FlowOrError::Flow(_)) => Ok(()),
2700 Err(FlowOrError::Error(e)) => Err(e),
2701 };
2702 }
2703 eprint!("{}", msg);
2704 Ok(())
2705 }
2706
2707 pub(crate) fn fire_pseudosig_die(&mut self, msg: &str, line: usize) -> StrykeResult<()> {
2713 self.touch_env_hash("SIG");
2714 let slot = self.scope.get_hash_element("SIG", "__DIE__");
2715 if let Some(sub) = slot.as_code_ref() {
2716 let prev = slot;
2717 let _ = self
2718 .scope
2719 .set_hash_element("SIG", "__DIE__", StrykeValue::UNDEF);
2720 let arg = StrykeValue::string(msg.to_string());
2721 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2722 let _ = self.scope.set_hash_element("SIG", "__DIE__", prev);
2723 return match r {
2724 Ok(_) => Ok(()),
2725 Err(FlowOrError::Flow(_)) => Ok(()),
2726 Err(FlowOrError::Error(e)) => Err(e),
2727 };
2728 }
2729 Ok(())
2730 }
2731
2732 #[inline]
2734 fn default_sig_action(sig: &str) -> StrykeResult<()> {
2735 match sig {
2736 "INT" => std::process::exit(130),
2738 "TERM" => std::process::exit(143),
2739 "ALRM" => std::process::exit(142),
2740 "CHLD" => Ok(()),
2742 _ => Ok(()),
2743 }
2744 }
2745
2746 #[inline]
2751 pub fn debugger_enter_sub(&mut self, name: &str) {
2752 if let Some(dbg) = &mut self.debugger {
2753 dbg.enter_sub(name);
2754 }
2755 }
2756
2757 #[inline]
2759 pub fn debugger_leave_sub(&mut self) {
2760 if let Some(dbg) = &mut self.debugger {
2761 dbg.leave_sub();
2762 }
2763 }
2764
2765 pub fn materialize_env_if_needed(&mut self) {
2768 if self.env_materialized {
2769 return;
2770 }
2771 self.env = std::env::vars()
2772 .map(|(k, v)| (k, StrykeValue::string(v)))
2773 .collect();
2774 self.scope
2775 .set_hash("ENV", self.env.clone())
2776 .expect("set %ENV");
2777 self.env_materialized = true;
2778 }
2779
2780 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2782 self.materialize_env_if_needed();
2783 if let Some(x) = self.log_level_override {
2784 return x;
2785 }
2786 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2787 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2788 }
2789
2790 pub(crate) fn no_color_effective(&mut self) -> bool {
2792 self.materialize_env_if_needed();
2793 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2794 if v.is_undef() {
2795 return false;
2796 }
2797 !v.to_string().is_empty()
2798 }
2799
2800 #[inline]
2801 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2802 let hash_name: &str = crate::scope::strip_main_prefix(hash_name).unwrap_or(hash_name);
2809 if hash_name == "ENV" {
2810 self.materialize_env_if_needed();
2811 } else if hash_name == "parameters"
2812 && !crate::compat_mode()
2813 && !self.scope.has_lexical_hash("parameters")
2814 {
2815 self.ensure_reflection_hashes();
2821 self.refresh_parameters_hash();
2822 } else if hash_name.ends_with("::") && hash_name.len() > 2 {
2823 self.refresh_package_stashes();
2828 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2829 match hash_name {
2830 "b"
2831 | "pc"
2832 | "e"
2833 | "a"
2834 | "d"
2835 | "c"
2836 | "p"
2837 | "k"
2838 | "o"
2839 | "v"
2840 | "all"
2841 | "stryke::builtins"
2842 | "stryke::perl_compats"
2843 | "stryke::extensions"
2844 | "stryke::aliases"
2845 | "stryke::descriptions"
2846 | "stryke::categories"
2847 | "stryke::primaries"
2848 | "stryke::keywords"
2849 | "stryke::operators"
2850 | "stryke::special_vars"
2851 | "stryke::all" => {
2852 self.ensure_reflection_hashes();
2853 }
2854 _ => {}
2855 }
2856 }
2857 }
2858
2859 pub(crate) fn exists_arrow_hash_element(
2861 &self,
2862 container: StrykeValue,
2863 key: &str,
2864 line: usize,
2865 ) -> StrykeResult<bool> {
2866 if let Some(r) = container.as_hash_ref() {
2867 return Ok(r.read().contains_key(key));
2868 }
2869 if let Some(b) = container.as_blessed_ref() {
2870 let data = b.data.read();
2871 if let Some(r) = data.as_hash_ref() {
2872 return Ok(r.read().contains_key(key));
2873 }
2874 if let Some(hm) = data.as_hash_map() {
2875 return Ok(hm.contains_key(key));
2876 }
2877 return Err(StrykeError::runtime(
2878 "exists argument is not a HASH reference",
2879 line,
2880 ));
2881 }
2882 let _ = line;
2886 Ok(false)
2887 }
2888
2889 pub(crate) fn delete_arrow_hash_element(
2891 &self,
2892 container: StrykeValue,
2893 key: &str,
2894 line: usize,
2895 ) -> StrykeResult<StrykeValue> {
2896 if let Some(r) = container.as_hash_ref() {
2897 return Ok(r.write().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2898 }
2899 if let Some(b) = container.as_blessed_ref() {
2900 let mut data = b.data.write();
2901 if let Some(r) = data.as_hash_ref() {
2902 return Ok(r.write().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2903 }
2904 if let Some(mut map) = data.as_hash_map() {
2905 let v = map.shift_remove(key).unwrap_or(StrykeValue::UNDEF);
2906 *data = StrykeValue::hash(map);
2907 return Ok(v);
2908 }
2909 return Err(StrykeError::runtime(
2910 "delete argument is not a HASH reference",
2911 line,
2912 ));
2913 }
2914 Err(StrykeError::runtime(
2915 "delete argument is not a HASH reference",
2916 line,
2917 ))
2918 }
2919
2920 pub(crate) fn exists_arrow_array_element(
2922 &self,
2923 container: StrykeValue,
2924 idx: i64,
2925 line: usize,
2926 ) -> StrykeResult<bool> {
2927 if let Some(a) = container.as_array_ref() {
2928 let arr = a.read();
2929 let i = if idx < 0 {
2930 (arr.len() as i64 + idx) as usize
2931 } else {
2932 idx as usize
2933 };
2934 return Ok(i < arr.len());
2935 }
2936 let _ = line;
2939 Ok(false)
2940 }
2941
2942 pub(crate) fn delete_arrow_array_element(
2944 &self,
2945 container: StrykeValue,
2946 idx: i64,
2947 line: usize,
2948 ) -> StrykeResult<StrykeValue> {
2949 if let Some(a) = container.as_array_ref() {
2950 let mut arr = a.write();
2951 let i = if idx < 0 {
2952 (arr.len() as i64 + idx) as usize
2953 } else {
2954 idx as usize
2955 };
2956 if i >= arr.len() {
2957 return Ok(StrykeValue::UNDEF);
2958 }
2959 let old = arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
2960 arr[i] = StrykeValue::UNDEF;
2961 return Ok(old);
2962 }
2963 Err(StrykeError::runtime(
2964 "delete argument is not an ARRAY reference",
2965 line,
2966 ))
2967 }
2968
2969 pub(crate) fn inc_directories(&self) -> Vec<String> {
2971 let mut v: Vec<String> = self
2972 .scope
2973 .get_array("INC")
2974 .into_iter()
2975 .map(|x| x.to_string())
2976 .filter(|s| !s.is_empty())
2977 .collect();
2978 if v.is_empty() {
2979 v.push(".".to_string());
2980 }
2981 v
2982 }
2983
2984 #[inline]
2985 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
2986 matches!(
2987 name,
2988 "_" | "0"
2989 | "!"
2990 | "@"
2991 | "/"
2992 | "\\"
2993 | ","
2994 | "."
2995 | "__PACKAGE__"
2996 | "$$"
2997 | "|"
2998 | "?"
2999 | "\""
3000 | "&"
3001 | "`"
3002 | "'"
3003 | "+"
3004 | "<"
3005 | ">"
3006 | "("
3007 | ")"
3008 | "]"
3009 | ";"
3010 | "ARGV"
3011 | "%"
3012 | "="
3013 | "-"
3014 | ":"
3015 | "*"
3016 | "INC"
3017 | "a"
3021 | "b"
3022 ) || name.chars().all(|c| c.is_ascii_digit())
3023 || name.starts_with('^')
3024 || (name.starts_with('#') && name.len() > 1)
3025 || (name.starts_with('_')
3032 && name.len() > 1
3033 && name[1..].chars().all(|c| c.is_ascii_digit()))
3034 }
3035
3036 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3037 if !self.strict_vars
3038 || Self::strict_scalar_exempt(name)
3039 || name.contains("::")
3040 || self.scope.scalar_binding_exists(name)
3041 {
3042 return Ok(());
3043 }
3044 Err(StrykeError::runtime(
3045 format!(
3046 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
3047 name, name
3048 ),
3049 line,
3050 )
3051 .into())
3052 }
3053
3054 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3055 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
3056 return Ok(());
3057 }
3058 Err(StrykeError::runtime(
3059 format!(
3060 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
3061 name, name
3062 ),
3063 line,
3064 )
3065 .into())
3066 }
3067
3068 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3069 if !self.strict_vars
3071 || name.contains("::")
3072 || self.scope.hash_binding_exists(name)
3073 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
3074 {
3075 return Ok(());
3076 }
3077 Err(StrykeError::runtime(
3078 format!(
3079 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
3080 name, name
3081 ),
3082 line,
3083 )
3084 .into())
3085 }
3086
3087 fn looks_like_version_only(spec: &str) -> bool {
3088 let t = spec.trim();
3089 !t.is_empty()
3090 && !t.contains('/')
3091 && !t.contains('\\')
3092 && !t.contains("::")
3093 && t.chars()
3094 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
3095 && t.chars().any(|c| c.is_ascii_digit())
3096 }
3097
3098 fn module_spec_to_relpath(spec: &str) -> String {
3099 let t = spec.trim();
3100 if t.contains("::") {
3101 format!("{}.pm", t.replace("::", "/"))
3102 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
3103 t.replace('\\', "/")
3104 } else {
3105 format!("{}.pm", t)
3106 }
3107 }
3108
3109 fn try_resolve_via_lockfile(relpath: &str) -> Option<std::path::PathBuf> {
3116 let cwd = std::env::current_dir().ok()?;
3117 let project_root = crate::pkg::commands::find_project_root(&cwd)?;
3118
3119 let stem = relpath
3122 .strip_suffix(".pm")
3123 .or_else(|| relpath.strip_suffix(".pl"))
3124 .or_else(|| relpath.strip_suffix(".stk"))
3125 .unwrap_or(relpath);
3126 let logical = stem.replace('/', "::");
3127
3128 crate::pkg::commands::resolve_module(&project_root, &logical).unwrap_or_default()
3129 }
3130
3131 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
3136 if name.contains("::") {
3137 return name.to_string();
3138 }
3139 let pkg = self.current_package();
3140 if pkg.is_empty() || pkg == "main" {
3141 name.to_string()
3142 } else {
3143 format!("{}::{}", pkg, name)
3144 }
3145 }
3146
3147 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
3149 let mut msg = format!("Undefined subroutine &{}", name);
3150 if self.strict_subs {
3151 msg.push_str(
3152 " (strict subs: declare the sub or use a fully qualified name before calling)",
3153 );
3154 }
3155 msg
3156 }
3157
3158 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
3160 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
3161 if self.strict_subs {
3162 msg.push_str(
3163 " (strict subs: declare the sub or use a fully qualified name before calling)",
3164 );
3165 }
3166 msg
3167 }
3168
3169 fn import_alias_key(&self, short: &str) -> String {
3171 self.qualify_sub_key(short)
3172 }
3173
3174 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
3176 if imports.len() == 1 {
3177 match &imports[0].kind {
3178 ExprKind::QW(ws) => return ws.is_empty(),
3179 ExprKind::List(xs) => return xs.is_empty(),
3181 _ => {}
3182 }
3183 }
3184 false
3185 }
3186
3187 fn apply_module_import(
3189 &mut self,
3190 module: &str,
3191 imports: &[Expr],
3192 line: usize,
3193 ) -> StrykeResult<()> {
3194 if imports.is_empty() {
3195 return self.import_all_from_module(module, line);
3196 }
3197 if Self::is_explicit_empty_import_list(imports) {
3198 return Ok(());
3199 }
3200 let names = Self::pragma_import_strings(imports, line)?;
3201 if names.is_empty() {
3202 return Ok(());
3203 }
3204 for name in names {
3205 self.import_one_symbol(module, &name, line)?;
3206 }
3207 Ok(())
3208 }
3209
3210 fn import_all_from_module(&mut self, module: &str, line: usize) -> StrykeResult<()> {
3211 if let Some(lists) = self.module_export_lists.get(module) {
3212 let export: Vec<String> = lists.export.clone();
3213 for short in export {
3214 self.import_named_sub(module, &short, line)?;
3215 }
3216 return Ok(());
3217 }
3218 let prefix = format!("{}::", module);
3220 let keys: Vec<String> = self
3221 .subs
3222 .keys()
3223 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
3224 .cloned()
3225 .collect();
3226 for k in keys {
3227 let short = k[prefix.len()..].to_string();
3228 if let Some(sub) = self.subs.get(&k).cloned() {
3229 let alias = self.import_alias_key(&short);
3230 self.subs.insert(alias, sub);
3231 }
3232 }
3233 Ok(())
3234 }
3235
3236 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> StrykeResult<()> {
3238 let qual = format!("{}::{}", module, short);
3239 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
3240 StrykeError::runtime(
3241 format!(
3242 "`{}` is not defined in module `{}` (expected `{}`)",
3243 short, module, qual
3244 ),
3245 line,
3246 )
3247 })?;
3248 let alias = self.import_alias_key(short);
3249 self.subs.insert(alias, sub);
3250 Ok(())
3251 }
3252
3253 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> StrykeResult<()> {
3254 if let Some(lists) = self.module_export_lists.get(module) {
3255 let allowed: HashSet<&str> = lists
3256 .export
3257 .iter()
3258 .map(|s| s.as_str())
3259 .chain(lists.export_ok.iter().map(|s| s.as_str()))
3260 .collect();
3261 if !allowed.contains(export) {
3262 return Err(StrykeError::runtime(
3263 format!(
3264 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
3265 export, module
3266 ),
3267 line,
3268 ));
3269 }
3270 }
3271 self.import_named_sub(module, export, line)
3272 }
3273
3274 fn record_exporter_our_array_name(&mut self, name: &str, items: &[StrykeValue]) {
3276 if name != "EXPORT" && name != "EXPORT_OK" {
3277 return;
3278 }
3279 let pkg = self.current_package();
3280 if pkg.is_empty() || pkg == "main" {
3281 return;
3282 }
3283 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
3284 let ent = self.module_export_lists.entry(pkg).or_default();
3285 if name == "EXPORT" {
3286 ent.export = names;
3287 } else {
3288 ent.export_ok = names;
3289 }
3290 }
3291
3292 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
3296 let key = self.qualify_sub_key(name);
3297 let Some(sub) = self.subs.get(&key).cloned() else {
3298 return;
3299 };
3300 let captured = self.scope.capture();
3301 let closure_env = if captured.is_empty() {
3302 None
3303 } else {
3304 Some(captured)
3305 };
3306 let mut new_sub = (*sub).clone();
3307 new_sub.closure_env = closure_env;
3308 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
3309 self.subs.insert(key, Arc::new(new_sub));
3310 }
3311
3312 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<StrykeSub>> {
3313 if let Some(s) = self.subs.get(name) {
3314 return Some(s.clone());
3315 }
3316 if !name.contains("::") {
3317 let pkg = self.current_package();
3319 if !pkg.is_empty() && pkg != "main" {
3320 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
3321 q.push_str(&pkg);
3322 q.push_str("::");
3323 q.push_str(name);
3324 return self.subs.get(&q).cloned();
3325 }
3326 return None;
3327 }
3328 if let Some(rest) = name.strip_prefix("main::") {
3333 if !rest.contains("::") {
3334 return self.subs.get(rest).cloned();
3335 }
3336 }
3337 None
3338 }
3339
3340 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
3343 if let Some(first) = imports.first() {
3344 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
3345 return &imports[1..];
3346 }
3347 }
3348 imports
3349 }
3350
3351 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> StrykeResult<Vec<String>> {
3353 let mut out = Vec::new();
3354 for e in imports {
3355 match &e.kind {
3356 ExprKind::String(s) => out.push(s.clone()),
3357 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
3358 ExprKind::Integer(n) => out.push(n.to_string()),
3359 ExprKind::InterpolatedString(parts) => {
3362 let mut s = String::new();
3363 for p in parts {
3364 match p {
3365 StringPart::Literal(l) => s.push_str(l),
3366 StringPart::ScalarVar(v) => {
3367 s.push('$');
3368 s.push_str(v);
3369 }
3370 StringPart::ArrayVar(v) => {
3371 s.push('@');
3372 s.push_str(v);
3373 }
3374 _ => {
3375 return Err(StrykeError::runtime(
3376 "pragma import must be a compile-time string, qw(), or integer",
3377 e.line.max(default_line),
3378 ));
3379 }
3380 }
3381 }
3382 out.push(s);
3383 }
3384 _ => {
3385 return Err(StrykeError::runtime(
3386 "pragma import must be a compile-time string, qw(), or integer",
3387 e.line.max(default_line),
3388 ));
3389 }
3390 }
3391 }
3392 Ok(out)
3393 }
3394
3395 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3396 if imports.is_empty() {
3397 self.strict_refs = true;
3398 self.strict_subs = true;
3399 self.strict_vars = true;
3400 return Ok(());
3401 }
3402 let names = Self::pragma_import_strings(imports, line)?;
3403 for name in names {
3404 match name.as_str() {
3405 "refs" => self.strict_refs = true,
3406 "subs" => self.strict_subs = true,
3407 "vars" => self.strict_vars = true,
3408 _ => {
3409 return Err(StrykeError::runtime(
3410 format!("Unknown strict mode `{}`", name),
3411 line,
3412 ));
3413 }
3414 }
3415 }
3416 Ok(())
3417 }
3418
3419 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3420 if imports.is_empty() {
3421 self.strict_refs = false;
3422 self.strict_subs = false;
3423 self.strict_vars = false;
3424 return Ok(());
3425 }
3426 let names = Self::pragma_import_strings(imports, line)?;
3427 for name in names {
3428 match name.as_str() {
3429 "refs" => self.strict_refs = false,
3430 "subs" => self.strict_subs = false,
3431 "vars" => self.strict_vars = false,
3432 _ => {
3433 return Err(StrykeError::runtime(
3434 format!("Unknown strict mode `{}`", name),
3435 line,
3436 ));
3437 }
3438 }
3439 }
3440 Ok(())
3441 }
3442
3443 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3444 let items = Self::pragma_import_strings(imports, line)?;
3445 if items.is_empty() {
3446 return Err(StrykeError::runtime(
3447 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
3448 line,
3449 ));
3450 }
3451 for item in items {
3452 let s = item.trim();
3453 if let Some(rest) = s.strip_prefix(':') {
3454 self.apply_feature_bundle(rest, line)?;
3455 } else {
3456 self.apply_feature_name(s, true, line)?;
3457 }
3458 }
3459 Ok(())
3460 }
3461
3462 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3463 if imports.is_empty() {
3464 self.feature_bits = 0;
3465 return Ok(());
3466 }
3467 let items = Self::pragma_import_strings(imports, line)?;
3468 for item in items {
3469 let s = item.trim();
3470 if let Some(rest) = s.strip_prefix(':') {
3471 self.clear_feature_bundle(rest);
3472 } else {
3473 self.apply_feature_name(s, false, line)?;
3474 }
3475 }
3476 Ok(())
3477 }
3478
3479 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> StrykeResult<()> {
3480 let key = v.trim();
3481 match key {
3482 "5.10" | "5.010" | "5.10.0" => {
3483 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3484 }
3485 "5.12" | "5.012" | "5.12.0" => {
3486 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3487 }
3488 _ => {
3489 return Err(StrykeError::runtime(
3490 format!("unsupported feature bundle :{}", key),
3491 line,
3492 ));
3493 }
3494 }
3495 Ok(())
3496 }
3497
3498 fn clear_feature_bundle(&mut self, v: &str) {
3499 let key = v.trim();
3500 if matches!(
3501 key,
3502 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
3503 ) {
3504 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
3505 }
3506 }
3507
3508 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> StrykeResult<()> {
3509 let bit = match name {
3510 "say" => FEAT_SAY,
3511 "state" => FEAT_STATE,
3512 "switch" => FEAT_SWITCH,
3513 "unicode_strings" => FEAT_UNICODE_STRINGS,
3514 "postderef"
3518 | "postderef_qq"
3519 | "evalbytes"
3520 | "current_sub"
3521 | "fc"
3522 | "lexical_subs"
3523 | "signatures"
3524 | "refaliasing"
3525 | "bitwise"
3526 | "isa"
3527 | "indirect"
3528 | "multidimensional"
3529 | "bareword_filehandles"
3530 | "try"
3531 | "defer"
3532 | "extra_paired_delimiters"
3533 | "module_true"
3534 | "class"
3535 | "array_base" => return Ok(()),
3536 _ => {
3537 return Err(StrykeError::runtime(
3538 format!("unknown feature `{}`", name),
3539 line,
3540 ));
3541 }
3542 };
3543 if enable {
3544 self.feature_bits |= bit;
3545 } else {
3546 self.feature_bits &= !bit;
3547 }
3548 Ok(())
3549 }
3550
3551 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> StrykeResult<StrykeValue> {
3553 let t = spec.trim();
3554 if t.is_empty() {
3555 return Err(StrykeError::runtime("require: empty argument", line));
3556 }
3557 match t {
3558 "strict" => {
3559 self.apply_use_strict(&[], line)?;
3560 return Ok(StrykeValue::integer(1));
3561 }
3562 "utf8" => {
3563 self.utf8_pragma = true;
3564 return Ok(StrykeValue::integer(1));
3565 }
3566 "feature" | "v5" => {
3567 return Ok(StrykeValue::integer(1));
3568 }
3569 "warnings" => {
3570 self.warnings = true;
3571 return Ok(StrykeValue::integer(1));
3572 }
3573 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
3574 return Ok(StrykeValue::integer(1));
3575 }
3576 _ => {}
3577 }
3578 let p = Path::new(t);
3579 if p.is_absolute() {
3580 return self.require_absolute_path(p, line);
3581 }
3582 if t.starts_with("./") || t.starts_with("../") {
3583 return self.require_relative_path(p, line);
3584 }
3585 if Self::looks_like_version_only(t) {
3586 return Ok(StrykeValue::integer(1));
3587 }
3588 let relpath = Self::module_spec_to_relpath(t);
3589 self.require_from_inc(&relpath, line)
3590 }
3591
3592 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> StrykeResult<()> {
3594 let v = self.scope.get_hash_element("^HOOK", key);
3595 if v.is_undef() {
3596 return Ok(());
3597 }
3598 let Some(sub) = v.as_code_ref() else {
3599 return Ok(());
3600 };
3601 let r = self.call_sub(
3602 sub.as_ref(),
3603 vec![StrykeValue::string(path.to_string())],
3604 WantarrayCtx::Scalar,
3605 line,
3606 );
3607 match r {
3608 Ok(_) => Ok(()),
3609 Err(FlowOrError::Error(e)) => Err(e),
3610 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3611 Err(FlowOrError::Flow(other)) => Err(StrykeError::runtime(
3612 format!(
3613 "require hook {:?} returned unexpected control flow: {:?}",
3614 key, other
3615 ),
3616 line,
3617 )),
3618 }
3619 }
3620
3621 fn require_absolute_path(&mut self, path: &Path, line: usize) -> StrykeResult<StrykeValue> {
3622 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3623 let key = canon.to_string_lossy().into_owned();
3624 if self.scope.exists_hash_element("INC", &key) {
3625 return Ok(StrykeValue::integer(1));
3626 }
3627 self.invoke_require_hook("require__before", &key, line)?;
3628 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3629 StrykeError::runtime(
3630 format!("Can't open {} for reading: {}", canon.display(), e),
3631 line,
3632 )
3633 })?;
3634 let code = crate::data_section::strip_perl_end_marker(&code);
3635 self.scope
3636 .set_hash_element("INC", &key, StrykeValue::string(key.clone()))?;
3637 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3638 let r = crate::parse_and_run_module_in_file(code, self, &key);
3639 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3640 r?;
3641 self.invoke_require_hook("require__after", &key, line)?;
3642 Ok(StrykeValue::integer(1))
3643 }
3644
3645 fn require_relative_path(&mut self, path: &Path, line: usize) -> StrykeResult<StrykeValue> {
3646 if !path.exists() {
3647 return Err(StrykeError::runtime(
3648 format!(
3649 "Can't locate {} (relative path does not exist)",
3650 path.display()
3651 ),
3652 line,
3653 ));
3654 }
3655 self.require_absolute_path(path, line)
3656 }
3657
3658 fn require_from_inc(&mut self, relpath: &str, line: usize) -> StrykeResult<StrykeValue> {
3659 if self.scope.exists_hash_element("INC", relpath) {
3660 return Ok(StrykeValue::integer(1));
3661 }
3662 self.invoke_require_hook("require__before", relpath, line)?;
3663
3664 if let Some(found) = Self::try_resolve_via_lockfile(relpath) {
3670 let code = read_file_text_perl_compat(&found).map_err(|e| {
3671 StrykeError::runtime(
3672 format!("Can't open {} for reading: {}", found.display(), e),
3673 line,
3674 )
3675 })?;
3676 let code = crate::data_section::strip_perl_end_marker(&code);
3677 let abs = found.canonicalize().unwrap_or(found);
3678 let abs_s = abs.to_string_lossy().into_owned();
3679 self.scope
3680 .set_hash_element("INC", relpath, StrykeValue::string(abs_s.clone()))?;
3681 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3682 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3683 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3684 r?;
3685 self.invoke_require_hook("require__after", relpath, line)?;
3686 return Ok(StrykeValue::integer(1));
3687 }
3688
3689 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3691 let code = crate::data_section::strip_perl_end_marker(&code);
3692 self.scope.set_hash_element(
3693 "INC",
3694 relpath,
3695 StrykeValue::string(format!("(virtual)/{}", relpath)),
3696 )?;
3697 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3698 let r = crate::parse_and_run_module_in_file(code, self, relpath);
3699 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3700 r?;
3701 self.invoke_require_hook("require__after", relpath, line)?;
3702 return Ok(StrykeValue::integer(1));
3703 }
3704
3705 for dir in self.inc_directories() {
3706 let full = Path::new(&dir).join(relpath);
3707 if full.is_file() {
3708 let code = read_file_text_perl_compat(&full).map_err(|e| {
3709 StrykeError::runtime(
3710 format!("Can't open {} for reading: {}", full.display(), e),
3711 line,
3712 )
3713 })?;
3714 let code = crate::data_section::strip_perl_end_marker(&code);
3715 let abs = full.canonicalize().unwrap_or(full);
3716 let abs_s = abs.to_string_lossy().into_owned();
3717 self.scope
3718 .set_hash_element("INC", relpath, StrykeValue::string(abs_s.clone()))?;
3719 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3720 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3721 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3722 r?;
3723 self.invoke_require_hook("require__after", relpath, line)?;
3724 return Ok(StrykeValue::integer(1));
3725 }
3726 }
3727 Err(StrykeError::runtime(
3728 format!(
3729 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3730 relpath
3731 ),
3732 line,
3733 ))
3734 }
3735
3736 pub fn register_virtual_module(&mut self, path: String, source: String) {
3738 self.virtual_modules.insert(path, source);
3739 }
3740
3741 pub(crate) fn exec_use_stmt(
3743 &mut self,
3744 module: &str,
3745 imports: &[Expr],
3746 line: usize,
3747 ) -> StrykeResult<()> {
3748 match module {
3749 "strict" => self.apply_use_strict(imports, line),
3750 "utf8" => {
3751 if !imports.is_empty() {
3752 return Err(StrykeError::runtime("use utf8 takes no arguments", line));
3753 }
3754 self.utf8_pragma = true;
3755 Ok(())
3756 }
3757 "feature" => self.apply_use_feature(imports, line),
3758 "v5" => Ok(()),
3759 "warnings" => {
3760 self.warnings = true;
3761 Ok(())
3762 }
3763 "English" => {
3764 self.english_enabled = true;
3765 let args = Self::pragma_import_strings(imports, line)?;
3766 let no_match = args.iter().any(|a| a == "-no_match_vars");
3767 if !no_match {
3771 self.english_match_vars_ever_enabled = true;
3772 }
3773 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3774 Ok(())
3775 }
3776 "Env" => self.apply_use_env(imports, line),
3777 "open" => self.apply_use_open(imports, line),
3778 "constant" => self.apply_use_constant(imports, line),
3779 "bigint" | "bignum" | "bigrat" => {
3780 crate::set_bigint_pragma(true);
3787 Ok(())
3788 }
3789 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3790 _ => {
3791 self.require_execute(module, line)?;
3792 let imports = Self::imports_after_leading_use_version(imports);
3793 self.apply_module_import(module, imports, line)?;
3794 Ok(())
3795 }
3796 }
3797 }
3798
3799 pub(crate) fn exec_no_stmt(
3801 &mut self,
3802 module: &str,
3803 imports: &[Expr],
3804 line: usize,
3805 ) -> StrykeResult<()> {
3806 match module {
3807 "strict" => self.apply_no_strict(imports, line),
3808 "utf8" => {
3809 if !imports.is_empty() {
3810 return Err(StrykeError::runtime("no utf8 takes no arguments", line));
3811 }
3812 self.utf8_pragma = false;
3813 Ok(())
3814 }
3815 "feature" => self.apply_no_feature(imports, line),
3816 "v5" => Ok(()),
3817 "warnings" => {
3818 self.warnings = false;
3819 Ok(())
3820 }
3821 "English" => {
3822 self.english_enabled = false;
3823 if !self.english_match_vars_ever_enabled {
3826 self.english_no_match_vars = false;
3827 }
3828 Ok(())
3829 }
3830 "open" => {
3831 self.open_pragma_utf8 = false;
3832 Ok(())
3833 }
3834 "bigint" | "bignum" | "bigrat" => {
3835 crate::set_bigint_pragma(false);
3836 Ok(())
3837 }
3838 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3839 _ => Ok(()),
3840 }
3841 }
3842
3843 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3845 let names = Self::pragma_import_strings(imports, line)?;
3846 for n in names {
3847 let key = n.trim_start_matches('@');
3848 if key.eq_ignore_ascii_case("PATH") {
3849 let path_env = std::env::var("PATH").unwrap_or_default();
3850 let path_vec: Vec<StrykeValue> = std::env::split_paths(&path_env)
3851 .map(|p| StrykeValue::string(p.to_string_lossy().into_owned()))
3852 .collect();
3853 let aname = self.stash_array_name_for_package("PATH");
3854 self.scope.declare_array(&aname, path_vec);
3855 }
3856 }
3857 Ok(())
3858 }
3859
3860 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3862 let items = Self::pragma_import_strings(imports, line)?;
3863 for item in items {
3864 let s = item.trim();
3865 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3866 self.open_pragma_utf8 = true;
3867 continue;
3868 }
3869 if let Some(rest) = s.strip_prefix(":encoding(") {
3870 if let Some(inner) = rest.strip_suffix(')') {
3871 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3872 self.open_pragma_utf8 = true;
3873 }
3874 }
3875 }
3876 }
3877 Ok(())
3878 }
3879
3880 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3882 if imports.is_empty() {
3883 return Ok(());
3884 }
3885 if imports.len() == 1 {
3887 match &imports[0].kind {
3888 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
3889 _ => {}
3890 }
3891 }
3892 for imp in imports {
3893 match &imp.kind {
3894 ExprKind::List(items) => {
3895 if items.len() % 2 != 0 {
3896 return Err(StrykeError::runtime(
3897 format!(
3898 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
3899 items.len()
3900 ),
3901 line,
3902 ));
3903 }
3904 let mut i = 0;
3905 while i < items.len() {
3906 let name = match &items[i].kind {
3907 ExprKind::String(s) => s.clone(),
3908 _ => {
3909 return Err(StrykeError::runtime(
3910 "use constant: constant name must be a string literal",
3911 line,
3912 ));
3913 }
3914 };
3915 let val = match self.eval_expr(&items[i + 1]) {
3916 Ok(v) => v,
3917 Err(FlowOrError::Error(e)) => return Err(e),
3918 Err(FlowOrError::Flow(_)) => {
3919 return Err(StrykeError::runtime(
3920 "use constant: unexpected control flow in initializer",
3921 line,
3922 ));
3923 }
3924 };
3925 self.install_constant_sub(&name, &val, line)?;
3926 i += 2;
3927 }
3928 }
3929 _ => {
3930 return Err(StrykeError::runtime(
3931 "use constant: expected list of NAME => VALUE pairs",
3932 line,
3933 ));
3934 }
3935 }
3936 }
3937 Ok(())
3938 }
3939
3940 fn install_constant_sub(
3941 &mut self,
3942 name: &str,
3943 val: &StrykeValue,
3944 line: usize,
3945 ) -> StrykeResult<()> {
3946 let key = self.qualify_sub_key(name);
3947 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
3948 let body = vec![Statement {
3949 label: None,
3950 kind: StmtKind::Return(Some(ret_expr)),
3951 line,
3952 }];
3953 self.subs.insert(
3954 key.clone(),
3955 Arc::new(StrykeSub {
3956 name: key,
3957 params: vec![],
3958 body,
3959 prototype: None,
3960 closure_env: None,
3961 fib_like: None,
3962 }),
3963 );
3964 Ok(())
3965 }
3966
3967 fn perl_value_to_const_literal_expr(&self, v: &StrykeValue, line: usize) -> StrykeResult<Expr> {
3969 if v.is_undef() {
3970 return Ok(Expr {
3971 kind: ExprKind::Undef,
3972 line,
3973 });
3974 }
3975 if let Some(n) = v.as_integer() {
3976 return Ok(Expr {
3977 kind: ExprKind::Integer(n),
3978 line,
3979 });
3980 }
3981 if let Some(f) = v.as_float() {
3982 return Ok(Expr {
3983 kind: ExprKind::Float(f),
3984 line,
3985 });
3986 }
3987 if let Some(s) = v.as_str() {
3988 return Ok(Expr {
3989 kind: ExprKind::String(s),
3990 line,
3991 });
3992 }
3993 if let Some(arr) = v.as_array_vec() {
3994 let mut elems = Vec::with_capacity(arr.len());
3995 for e in &arr {
3996 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
3997 }
3998 return Ok(Expr {
3999 kind: ExprKind::ArrayRef(elems),
4000 line,
4001 });
4002 }
4003 if let Some(h) = v.as_hash_map() {
4004 let mut pairs = Vec::with_capacity(h.len());
4005 for (k, vv) in h.iter() {
4006 pairs.push((
4007 Expr {
4008 kind: ExprKind::String(k.clone()),
4009 line,
4010 },
4011 self.perl_value_to_const_literal_expr(vv, line)?,
4012 ));
4013 }
4014 return Ok(Expr {
4015 kind: ExprKind::HashRef(pairs),
4016 line,
4017 });
4018 }
4019 if let Some(aref) = v.as_array_ref() {
4020 let arr = aref.read();
4021 let mut elems = Vec::with_capacity(arr.len());
4022 for e in arr.iter() {
4023 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
4024 }
4025 return Ok(Expr {
4026 kind: ExprKind::ArrayRef(elems),
4027 line,
4028 });
4029 }
4030 if let Some(href) = v.as_hash_ref() {
4031 let h = href.read();
4032 let mut pairs = Vec::with_capacity(h.len());
4033 for (k, vv) in h.iter() {
4034 pairs.push((
4035 Expr {
4036 kind: ExprKind::String(k.clone()),
4037 line,
4038 },
4039 self.perl_value_to_const_literal_expr(vv, line)?,
4040 ));
4041 }
4042 return Ok(Expr {
4043 kind: ExprKind::HashRef(pairs),
4044 line,
4045 });
4046 }
4047 Err(StrykeError::runtime(
4048 format!("use constant: unsupported value type ({v:?})"),
4049 line,
4050 ))
4051 }
4052
4053 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> StrykeResult<()> {
4055 self.utf8_pragma = false;
4061 for stmt in &program.statements {
4062 match &stmt.kind {
4063 StmtKind::Package { name } => {
4064 let _ = self
4065 .scope
4066 .set_scalar("__PACKAGE__", StrykeValue::string(name.clone()));
4067 }
4068 StmtKind::SubDecl {
4069 name,
4070 params,
4071 body,
4072 prototype,
4073 } => {
4074 let key = self.qualify_sub_key(name);
4075 let mut sub = StrykeSub {
4076 name: name.clone(),
4077 params: params.clone(),
4078 body: body.clone(),
4079 closure_env: None,
4080 prototype: prototype.clone(),
4081 fib_like: None,
4082 };
4083 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
4084 self.subs.insert(key, Arc::new(sub));
4085 }
4086 StmtKind::UsePerlVersion { .. } => {}
4087 StmtKind::Use { module, imports } => {
4088 self.exec_use_stmt(module, imports, stmt.line)?;
4089 }
4090 StmtKind::UseOverload { pairs } => {
4091 self.install_use_overload_pairs(pairs);
4092 }
4093 StmtKind::FormatDecl { name, lines } => {
4094 self.install_format_decl(name, lines, stmt.line)?;
4095 }
4096 StmtKind::No { module, imports } => {
4097 self.exec_no_stmt(module, imports, stmt.line)?;
4098 }
4099 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
4100 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
4101 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
4102 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
4103 StmtKind::End(block) => self.end_blocks.push(block.clone()),
4104 _ => {}
4105 }
4106 }
4107 Ok(())
4108 }
4109
4110 pub fn install_data_handle(&mut self, data: Vec<u8>) {
4112 self.input_handles.insert(
4113 "DATA".to_string(),
4114 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
4115 );
4116 }
4117
4118 #[inline]
4120 pub(crate) fn resolve_stryke_path(&self, path: &str) -> PathBuf {
4121 if path.is_empty() {
4122 return self.stryke_pwd.clone();
4123 }
4124 let p = Path::new(path);
4125 if p.is_absolute() {
4126 PathBuf::from(path)
4127 } else {
4128 self.stryke_pwd.join(path)
4129 }
4130 }
4131
4132 pub(crate) fn resolve_stryke_path_string(&self, path: &str) -> String {
4133 self.resolve_stryke_path(path)
4134 .to_string_lossy()
4135 .into_owned()
4136 }
4137
4138 pub(crate) fn builtin_cd_execute(
4142 &mut self,
4143 args: &[StrykeValue],
4144 _line: usize,
4145 ) -> StrykeResult<StrykeValue> {
4146 let dest: PathBuf = if args.is_empty() {
4147 let home = std::env::var_os("HOME")
4148 .or_else(|| std::env::var_os("USERPROFILE"))
4149 .map(PathBuf::from);
4150 let Some(h) = home else {
4151 return Ok(StrykeValue::integer(0));
4152 };
4153 h
4154 } else {
4155 let raw = args[0].to_string();
4156 if raw.is_empty() {
4157 return Ok(StrykeValue::integer(0));
4158 }
4159 self.resolve_stryke_path(&raw)
4160 };
4161 match std::fs::metadata(&dest) {
4162 Ok(m) if m.is_dir() => {
4163 self.stryke_pwd = std::fs::canonicalize(&dest).unwrap_or(dest);
4164 Ok(StrykeValue::integer(1))
4165 }
4166 Ok(_) => Ok(StrykeValue::integer(0)),
4167 Err(e) => {
4168 self.apply_io_error_to_errno(&e);
4169 Ok(StrykeValue::integer(0))
4170 }
4171 }
4172 }
4173
4174 pub(crate) fn open_builtin_execute(
4180 &mut self,
4181 handle_name: String,
4182 mode_s: String,
4183 file_opt: Option<String>,
4184 line: usize,
4185 ) -> StrykeResult<StrykeValue> {
4186 let (actual_mode, path) = if let Some(f) = file_opt {
4191 (mode_s, f)
4192 } else {
4193 let trimmed = mode_s.trim();
4194 if let Some(rest) = trimmed.strip_prefix('|') {
4195 ("|-".to_string(), rest.trim_start().to_string())
4196 } else if trimmed.ends_with('|') {
4197 let mut cmd = trimmed.to_string();
4198 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
4200 } else if let Some(rest) = trimmed.strip_prefix(">>") {
4201 (">>".to_string(), rest.trim().to_string())
4202 } else if let Some(rest) = trimmed.strip_prefix('>') {
4203 (">".to_string(), rest.trim().to_string())
4204 } else if let Some(rest) = trimmed.strip_prefix('<') {
4205 ("<".to_string(), rest.trim().to_string())
4206 } else {
4207 ("<".to_string(), trimmed.to_string())
4208 }
4209 };
4210 let handle_return = handle_name.clone();
4211 let file_path = match actual_mode.as_str() {
4212 "<" | ">" | ">>" => self.resolve_stryke_path_string(&path),
4213 _ => path.clone(),
4214 };
4215 match actual_mode.as_str() {
4216 "-|" => {
4217 let mut cmd = piped_shell_command(&path);
4218 cmd.stdout(Stdio::piped());
4219 let mut child = cmd.spawn().map_err(|e| {
4220 self.apply_io_error_to_errno(&e);
4221 StrykeError::runtime(format!("Can't open pipe from command: {}", e), line)
4222 })?;
4223 let stdout = child
4224 .stdout
4225 .take()
4226 .ok_or_else(|| StrykeError::runtime("pipe: child has no stdout", line))?;
4227 self.input_handles
4228 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
4229 self.pipe_children.insert(handle_name, child);
4230 }
4231 "|-" => {
4232 let mut cmd = piped_shell_command(&path);
4233 cmd.stdin(Stdio::piped());
4234 let mut child = cmd.spawn().map_err(|e| {
4235 self.apply_io_error_to_errno(&e);
4236 StrykeError::runtime(format!("Can't open pipe to command: {}", e), line)
4237 })?;
4238 let stdin = child
4239 .stdin
4240 .take()
4241 .ok_or_else(|| StrykeError::runtime("pipe: child has no stdin", line))?;
4242 self.output_handles
4243 .insert(handle_name.clone(), Box::new(stdin));
4244 self.pipe_children.insert(handle_name, child);
4245 }
4246 "<" => {
4247 let file = match std::fs::File::open(&file_path) {
4248 Ok(f) => f,
4249 Err(e) => {
4250 self.apply_io_error_to_errno(&e);
4251 return Ok(StrykeValue::integer(0));
4252 }
4253 };
4254 let shared = Arc::new(Mutex::new(file));
4255 self.io_file_slots
4256 .insert(handle_name.clone(), Arc::clone(&shared));
4257 self.input_handles.insert(
4258 handle_name.clone(),
4259 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
4260 );
4261 }
4262 ">" => {
4263 let file = match std::fs::File::create(&file_path) {
4264 Ok(f) => f,
4265 Err(e) => {
4266 self.apply_io_error_to_errno(&e);
4267 return Ok(StrykeValue::integer(0));
4268 }
4269 };
4270 let shared = Arc::new(Mutex::new(file));
4271 self.io_file_slots
4272 .insert(handle_name.clone(), Arc::clone(&shared));
4273 self.output_handles.insert(
4274 handle_name.clone(),
4275 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4276 );
4277 }
4278 ">>" => {
4279 let file = match std::fs::OpenOptions::new()
4280 .append(true)
4281 .create(true)
4282 .open(&file_path)
4283 {
4284 Ok(f) => f,
4285 Err(e) => {
4286 self.apply_io_error_to_errno(&e);
4287 return Ok(StrykeValue::integer(0));
4288 }
4289 };
4290 let shared = Arc::new(Mutex::new(file));
4291 self.io_file_slots
4292 .insert(handle_name.clone(), Arc::clone(&shared));
4293 self.output_handles.insert(
4294 handle_name.clone(),
4295 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4296 );
4297 }
4298 _ => {
4299 return Err(StrykeError::runtime(
4300 format!("Unknown open mode '{}'", actual_mode),
4301 line,
4302 ));
4303 }
4304 }
4305 Ok(StrykeValue::io_handle(handle_return))
4306 }
4307
4308 pub(crate) fn eval_chunk_by_builtin(
4312 &mut self,
4313 key_spec: &Expr,
4314 list_expr: &Expr,
4315 ctx: WantarrayCtx,
4316 line: usize,
4317 ) -> ExecResult {
4318 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
4319 let chunks = match &key_spec.kind {
4320 ExprKind::CodeRef { .. } => {
4321 let cr = self.eval_expr(key_spec)?;
4322 let Some(sub) = cr.as_code_ref() else {
4323 return Err(StrykeError::runtime(
4324 "group_by/chunk_by: first argument must be { BLOCK }",
4325 line,
4326 )
4327 .into());
4328 };
4329 let sub = sub.clone();
4330 let mut chunks: Vec<StrykeValue> = Vec::new();
4331 let mut run: Vec<StrykeValue> = Vec::new();
4332 let mut prev_key: Option<StrykeValue> = None;
4333 for item in list {
4334 self.scope.set_topic(item.clone());
4335 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
4336 Ok(k) => k,
4337 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
4338 Err(FlowOrError::Flow(Flow::Return(v))) => v,
4339 Err(_) => StrykeValue::UNDEF,
4340 };
4341 match &prev_key {
4342 None => {
4343 run.push(item);
4344 prev_key = Some(key);
4345 }
4346 Some(pk) => {
4347 if key.str_eq(pk) {
4348 run.push(item);
4349 } else {
4350 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(
4351 std::mem::take(&mut run),
4352 ))));
4353 run.push(item);
4354 prev_key = Some(key);
4355 }
4356 }
4357 }
4358 }
4359 if !run.is_empty() {
4360 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(run))));
4361 }
4362 chunks
4363 }
4364 _ => {
4365 let mut chunks: Vec<StrykeValue> = Vec::new();
4366 let mut run: Vec<StrykeValue> = Vec::new();
4367 let mut prev_key: Option<StrykeValue> = None;
4368 for item in list {
4369 self.scope.set_topic(item.clone());
4370 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
4371 match &prev_key {
4372 None => {
4373 run.push(item);
4374 prev_key = Some(key);
4375 }
4376 Some(pk) => {
4377 if key.str_eq(pk) {
4378 run.push(item);
4379 } else {
4380 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(
4381 std::mem::take(&mut run),
4382 ))));
4383 run.push(item);
4384 prev_key = Some(key);
4385 }
4386 }
4387 }
4388 }
4389 if !run.is_empty() {
4390 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(run))));
4391 }
4392 chunks
4393 }
4394 };
4395 Ok(match ctx {
4396 WantarrayCtx::List => StrykeValue::array(chunks),
4397 WantarrayCtx::Scalar => StrykeValue::integer(chunks.len() as i64),
4398 WantarrayCtx::Void => StrykeValue::UNDEF,
4399 })
4400 }
4401
4402 pub(crate) fn list_higher_order_block_builtin(
4404 &mut self,
4405 name: &str,
4406 args: &[StrykeValue],
4407 line: usize,
4408 ) -> StrykeResult<StrykeValue> {
4409 match self.list_higher_order_block_builtin_exec(name, args, line) {
4410 Ok(v) => Ok(v),
4411 Err(FlowOrError::Error(e)) => Err(e),
4412 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
4413 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
4414 format!("{name}: unsupported control flow in block"),
4415 line,
4416 )),
4417 }
4418 }
4419
4420 fn list_higher_order_block_builtin_exec(
4421 &mut self,
4422 name: &str,
4423 args: &[StrykeValue],
4424 line: usize,
4425 ) -> ExecResult {
4426 if args.is_empty() {
4427 return Err(
4428 StrykeError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
4429 );
4430 }
4431 let Some(sub) = args[0].as_code_ref() else {
4432 return Err(StrykeError::runtime(
4433 format!("{name}: first argument must be {{ BLOCK }}"),
4434 line,
4435 )
4436 .into());
4437 };
4438 let sub = sub.clone();
4439 let items: Vec<StrykeValue> = args[1..].to_vec();
4440 if matches!(name, "tap" | "peek") && items.len() == 1 {
4441 if let Some(p) = items[0].as_pipeline() {
4442 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
4443 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
4444 }
4445 let v = &items[0];
4446 if v.is_iterator() || v.as_array_vec().is_some() {
4447 let source = crate::map_stream::into_pull_iter(v.clone());
4448 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
4449 return Ok(StrykeValue::iterator(Arc::new(
4450 crate::map_stream::TapIterator::new(
4451 source,
4452 sub,
4453 self.subs.clone(),
4454 capture,
4455 atomic_arrays,
4456 atomic_hashes,
4457 ),
4458 )));
4459 }
4460 }
4461 let wa = self.wantarray_kind;
4466 match name {
4467 "take_while" => {
4468 let mut out = Vec::new();
4469 for item in items {
4470 self.scope.set_topic(item.clone());
4475 let pred =
4476 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4477 if !pred.is_true() {
4478 break;
4479 }
4480 out.push(item);
4481 }
4482 Ok(match wa {
4483 WantarrayCtx::List => StrykeValue::array(out),
4484 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4485 WantarrayCtx::Void => StrykeValue::UNDEF,
4486 })
4487 }
4488 "drop_while" | "skip_while" => {
4489 let mut i = 0usize;
4490 while i < items.len() {
4491 let it = items[i].clone();
4492 self.scope.set_topic(it.clone());
4493 let pred = self.call_sub(&sub, vec![it], WantarrayCtx::Scalar, line)?;
4494 if !pred.is_true() {
4495 break;
4496 }
4497 i += 1;
4498 }
4499 let rest = items[i..].to_vec();
4500 Ok(match wa {
4501 WantarrayCtx::List => StrykeValue::array(rest),
4502 WantarrayCtx::Scalar => StrykeValue::integer(rest.len() as i64),
4503 WantarrayCtx::Void => StrykeValue::UNDEF,
4504 })
4505 }
4506 "reject" | "grepv" => {
4507 let mut out = Vec::new();
4508 for item in items {
4509 self.scope.set_topic(item.clone());
4510 let pred =
4511 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4512 if !pred.is_true() {
4513 out.push(item);
4514 }
4515 }
4516 Ok(match wa {
4517 WantarrayCtx::List => StrykeValue::array(out),
4518 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4519 WantarrayCtx::Void => StrykeValue::UNDEF,
4520 })
4521 }
4522 "tap" | "peek" => {
4523 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
4524 Ok(match wa {
4525 WantarrayCtx::List => StrykeValue::array(items),
4526 WantarrayCtx::Scalar => StrykeValue::integer(items.len() as i64),
4527 WantarrayCtx::Void => StrykeValue::UNDEF,
4528 })
4529 }
4530 "partition" => {
4531 let mut yes = Vec::new();
4532 let mut no = Vec::new();
4533 for item in items {
4534 self.scope.set_topic(item.clone());
4535 let pred =
4536 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4537 if pred.is_true() {
4538 yes.push(item);
4539 } else {
4540 no.push(item);
4541 }
4542 }
4543 let yes_ref = StrykeValue::array_ref(Arc::new(RwLock::new(yes)));
4544 let no_ref = StrykeValue::array_ref(Arc::new(RwLock::new(no)));
4545 Ok(match wa {
4546 WantarrayCtx::List => StrykeValue::array(vec![yes_ref, no_ref]),
4547 WantarrayCtx::Scalar => StrykeValue::integer(2),
4548 WantarrayCtx::Void => StrykeValue::UNDEF,
4549 })
4550 }
4551 "min_by" => {
4552 let mut best: Option<(StrykeValue, StrykeValue)> = None;
4553 for item in items {
4554 self.scope.set_topic(item.clone());
4555 let key =
4556 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4557 best = Some(match best {
4558 None => (item, key),
4559 Some((bv, bk)) => {
4560 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
4561 (item, key)
4562 } else {
4563 (bv, bk)
4564 }
4565 }
4566 });
4567 }
4568 Ok(best.map(|(v, _)| v).unwrap_or(StrykeValue::UNDEF))
4569 }
4570 "max_by" => {
4571 let mut best: Option<(StrykeValue, StrykeValue)> = None;
4572 for item in items {
4573 self.scope.set_topic(item.clone());
4574 let key =
4575 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4576 best = Some(match best {
4577 None => (item, key),
4578 Some((bv, bk)) => {
4579 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
4580 (item, key)
4581 } else {
4582 (bv, bk)
4583 }
4584 }
4585 });
4586 }
4587 Ok(best.map(|(v, _)| v).unwrap_or(StrykeValue::UNDEF))
4588 }
4589 "zip_with" => {
4590 let flat: Vec<StrykeValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
4593 let refs: Vec<Vec<StrykeValue>> = flat
4594 .iter()
4595 .map(|el| {
4596 if let Some(ar) = el.as_array_ref() {
4597 ar.read().clone()
4598 } else if let Some(name) = el.as_array_binding_name() {
4599 self.scope.get_array(&name)
4600 } else {
4601 vec![el.clone()]
4602 }
4603 })
4604 .collect();
4605 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
4606 let mut out = Vec::with_capacity(max_len);
4607 for i in 0..max_len {
4608 let pair: Vec<StrykeValue> = refs
4609 .iter()
4610 .map(|l| l.get(i).cloned().unwrap_or(StrykeValue::UNDEF))
4611 .collect();
4612 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
4613 out.push(result);
4614 }
4615 Ok(match wa {
4616 WantarrayCtx::List => StrykeValue::array(out),
4617 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4618 WantarrayCtx::Void => StrykeValue::UNDEF,
4619 })
4620 }
4621 "count_by" => {
4622 let mut counts = indexmap::IndexMap::new();
4623 for item in items {
4624 self.scope.set_topic(item.clone());
4625 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4626 let k = key.to_string();
4627 let entry = counts.entry(k).or_insert(StrykeValue::integer(0));
4628 *entry = StrykeValue::integer(entry.to_int() + 1);
4629 }
4630 Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(counts))))
4631 }
4632 _ => Err(StrykeError::runtime(
4633 format!("internal: unknown list block builtin `{name}`"),
4634 line,
4635 )
4636 .into()),
4637 }
4638 }
4639
4640 pub(crate) fn builtin_rmdir_execute(
4642 &mut self,
4643 args: &[StrykeValue],
4644 _line: usize,
4645 ) -> StrykeResult<StrykeValue> {
4646 let mut count = 0i64;
4647 for a in args {
4648 let p = a.to_string();
4649 if p.is_empty() {
4650 continue;
4651 }
4652 let p = self.resolve_stryke_path_string(&p);
4653 if std::fs::remove_dir(&p).is_ok() {
4654 count += 1;
4655 }
4656 }
4657 Ok(StrykeValue::integer(count))
4658 }
4659
4660 pub(crate) fn builtin_touch_execute(
4662 &mut self,
4663 args: &[StrykeValue],
4664 _line: usize,
4665 ) -> StrykeResult<StrykeValue> {
4666 let paths: Vec<String> = args
4667 .iter()
4668 .map(|v| self.resolve_stryke_path_string(&v.to_string()))
4669 .collect();
4670 Ok(StrykeValue::integer(crate::perl_fs::touch_paths(&paths)))
4671 }
4672
4673 pub(crate) fn builtin_utime_execute(
4675 &mut self,
4676 args: &[StrykeValue],
4677 line: usize,
4678 ) -> StrykeResult<StrykeValue> {
4679 if args.len() < 3 {
4680 return Err(StrykeError::runtime(
4681 "utime requires at least three arguments (atime, mtime, files...)",
4682 line,
4683 ));
4684 }
4685 let at = args[0].to_int();
4686 let mt = args[1].to_int();
4687 let paths: Vec<String> = args
4688 .iter()
4689 .skip(2)
4690 .map(|v| self.resolve_stryke_path_string(&v.to_string()))
4691 .collect();
4692 let n = crate::perl_fs::utime_paths(at, mt, &paths);
4693 #[cfg(not(unix))]
4694 if !paths.is_empty() && n == 0 {
4695 return Err(StrykeError::runtime(
4696 "utime is not supported on this platform",
4697 line,
4698 ));
4699 }
4700 Ok(StrykeValue::integer(n))
4701 }
4702
4703 pub(crate) fn builtin_umask_execute(
4705 &mut self,
4706 args: &[StrykeValue],
4707 line: usize,
4708 ) -> StrykeResult<StrykeValue> {
4709 #[cfg(unix)]
4710 {
4711 let _ = line;
4712 if args.is_empty() {
4713 let cur = unsafe { libc::umask(0) };
4714 unsafe { libc::umask(cur) };
4715 return Ok(StrykeValue::integer(cur as i64));
4716 }
4717 let new_m = args[0].to_int() as libc::mode_t;
4718 let old = unsafe { libc::umask(new_m) };
4719 Ok(StrykeValue::integer(old as i64))
4720 }
4721 #[cfg(not(unix))]
4722 {
4723 let _ = args;
4724 Err(StrykeError::runtime(
4725 "umask is not supported on this platform",
4726 line,
4727 ))
4728 }
4729 }
4730
4731 pub(crate) fn builtin_getcwd_execute(
4733 &mut self,
4734 args: &[StrykeValue],
4735 line: usize,
4736 ) -> StrykeResult<StrykeValue> {
4737 if !args.is_empty() {
4738 return Err(StrykeError::runtime("getcwd takes no arguments", line));
4739 }
4740 match std::env::current_dir() {
4741 Ok(p) => Ok(StrykeValue::string(p.to_string_lossy().into_owned())),
4742 Err(e) => {
4743 self.apply_io_error_to_errno(&e);
4744 Ok(StrykeValue::UNDEF)
4745 }
4746 }
4747 }
4748
4749 pub(crate) fn builtin_realpath_execute(
4751 &mut self,
4752 args: &[StrykeValue],
4753 line: usize,
4754 ) -> StrykeResult<StrykeValue> {
4755 let path = args
4756 .first()
4757 .ok_or_else(|| StrykeError::runtime("realpath: need path", line))?
4758 .to_string();
4759 if path.is_empty() {
4760 return Err(StrykeError::runtime("realpath: need path", line));
4761 }
4762 let path = self.resolve_stryke_path_string(&path);
4763 match crate::perl_fs::realpath_resolved(&path) {
4764 Ok(s) => Ok(StrykeValue::string(s)),
4765 Err(e) => {
4766 self.apply_io_error_to_errno(&e);
4767 Ok(StrykeValue::UNDEF)
4768 }
4769 }
4770 }
4771
4772 pub(crate) fn builtin_pipe_execute(
4774 &mut self,
4775 args: &[StrykeValue],
4776 line: usize,
4777 ) -> StrykeResult<StrykeValue> {
4778 if args.len() != 2 {
4779 return Err(StrykeError::runtime(
4780 "pipe requires exactly two arguments",
4781 line,
4782 ));
4783 }
4784 #[cfg(unix)]
4785 {
4786 use std::fs::File;
4787 use std::os::unix::io::FromRawFd;
4788
4789 let read_name = args[0].to_string();
4790 let write_name = args[1].to_string();
4791 if read_name.is_empty() || write_name.is_empty() {
4792 return Err(StrykeError::runtime("pipe: invalid handle name", line));
4793 }
4794 let mut fds = [0i32; 2];
4795 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
4796 let e = std::io::Error::last_os_error();
4797 self.apply_io_error_to_errno(&e);
4798 return Ok(StrykeValue::integer(0));
4799 }
4800 let read_file = unsafe { File::from_raw_fd(fds[0]) };
4801 let write_file = unsafe { File::from_raw_fd(fds[1]) };
4802
4803 let read_shared = Arc::new(Mutex::new(read_file));
4804 let write_shared = Arc::new(Mutex::new(write_file));
4805
4806 self.close_builtin_execute(read_name.clone()).ok();
4807 self.close_builtin_execute(write_name.clone()).ok();
4808
4809 self.io_file_slots
4810 .insert(read_name.clone(), Arc::clone(&read_shared));
4811 self.input_handles.insert(
4812 read_name,
4813 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
4814 );
4815
4816 self.io_file_slots
4817 .insert(write_name.clone(), Arc::clone(&write_shared));
4818 self.output_handles
4819 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
4820
4821 Ok(StrykeValue::integer(1))
4822 }
4823 #[cfg(not(unix))]
4824 {
4825 let _ = args;
4826 Err(StrykeError::runtime(
4827 "pipe is not supported on this platform",
4828 line,
4829 ))
4830 }
4831 }
4832
4833 pub(crate) fn close_builtin_execute(&mut self, name: String) -> StrykeResult<StrykeValue> {
4834 self.output_handles.remove(&name);
4835 self.input_handles.remove(&name);
4836 self.io_file_slots.remove(&name);
4837 if let Some(mut child) = self.pipe_children.remove(&name) {
4838 if let Ok(st) = child.wait() {
4839 self.record_child_exit_status(st);
4840 }
4841 }
4842 Ok(StrykeValue::integer(1))
4843 }
4844
4845 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
4846 self.input_handles.contains_key(name)
4847 }
4848
4849 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
4853 self.line_mode_eof_pending
4854 }
4855
4856 pub(crate) fn eof_builtin_execute(
4860 &self,
4861 args: &[StrykeValue],
4862 line: usize,
4863 ) -> StrykeResult<StrykeValue> {
4864 match args.len() {
4865 0 => Ok(StrykeValue::integer(if self.eof_without_arg_is_true() {
4866 1
4867 } else {
4868 0
4869 })),
4870 1 => {
4871 let name = args[0].to_string();
4872 let at_eof = !self.has_input_handle(&name);
4873 Ok(StrykeValue::integer(if at_eof { 1 } else { 0 }))
4874 }
4875 _ => Err(StrykeError::runtime("eof: too many arguments", line)),
4876 }
4877 }
4878
4879 pub(crate) fn study_return_value(s: &str) -> StrykeValue {
4882 if s.is_empty() {
4883 StrykeValue::string(String::new())
4884 } else {
4885 StrykeValue::integer(1)
4886 }
4887 }
4888
4889 pub(crate) fn readline_builtin_execute(
4890 &mut self,
4891 handle: Option<&str>,
4892 ) -> StrykeResult<StrykeValue> {
4893 if handle.is_none() {
4895 let argv = self.scope.get_array("ARGV");
4896 if !argv.is_empty() {
4897 loop {
4898 if self.diamond_reader.is_none() {
4899 while self.diamond_next_idx < argv.len() {
4900 let path = self.resolve_stryke_path_string(
4901 &argv[self.diamond_next_idx].to_string(),
4902 );
4903 self.diamond_next_idx += 1;
4904 match File::open(&path) {
4905 Ok(f) => {
4906 self.argv_current_file = path;
4907 self.diamond_reader = Some(BufReader::new(f));
4908 break;
4909 }
4910 Err(e) => {
4911 self.apply_io_error_to_errno(&e);
4912 }
4913 }
4914 }
4915 if self.diamond_reader.is_none() {
4916 return Ok(StrykeValue::UNDEF);
4917 }
4918 }
4919 let mut line_str = String::new();
4920 let read_result: Result<usize, io::Error> =
4921 if let Some(reader) = self.diamond_reader.as_mut() {
4922 if self.open_pragma_utf8 {
4923 let mut buf = Vec::new();
4924 reader.read_until(b'\n', &mut buf).inspect(|n| {
4925 if *n > 0 {
4926 line_str = String::from_utf8_lossy(&buf).into_owned();
4927 }
4928 })
4929 } else {
4930 let mut buf = Vec::new();
4931 match reader.read_until(b'\n', &mut buf) {
4932 Ok(n) => {
4933 if n > 0 {
4934 line_str =
4935 crate::perl_decode::decode_utf8_or_latin1_read_until(
4936 &buf,
4937 );
4938 }
4939 Ok(n)
4940 }
4941 Err(e) => Err(e),
4942 }
4943 }
4944 } else {
4945 unreachable!()
4946 };
4947 match read_result {
4948 Ok(0) => {
4949 self.diamond_reader = None;
4950 continue;
4951 }
4952 Ok(_) => {
4953 self.bump_line_for_handle(&self.argv_current_file.clone());
4954 return Ok(StrykeValue::string(line_str));
4955 }
4956 Err(e) => {
4957 self.apply_io_error_to_errno(&e);
4958 self.diamond_reader = None;
4959 continue;
4960 }
4961 }
4962 }
4963 } else {
4964 self.argv_current_file.clear();
4965 }
4966 }
4967
4968 let handle_name = handle.unwrap_or("STDIN");
4969 let mut line_str = String::new();
4970 if handle_name == "STDIN" {
4971 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
4972 self.last_stdin_die_bracket = if handle.is_none() {
4973 "<>".to_string()
4974 } else {
4975 "<STDIN>".to_string()
4976 };
4977 self.bump_line_for_handle("STDIN");
4978 return Ok(StrykeValue::string(queued));
4979 }
4980 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
4981 let mut buf = Vec::new();
4982 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
4983 if *n > 0 {
4984 line_str = String::from_utf8_lossy(&buf).into_owned();
4985 }
4986 })
4987 } else {
4988 let mut buf = Vec::new();
4989 let mut lock = io::stdin().lock();
4990 match lock.read_until(b'\n', &mut buf) {
4991 Ok(n) => {
4992 if n > 0 {
4993 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
4994 }
4995 Ok(n)
4996 }
4997 Err(e) => Err(e),
4998 }
4999 };
5000 match r {
5001 Ok(0) => Ok(StrykeValue::UNDEF),
5002 Ok(_) => {
5003 self.last_stdin_die_bracket = if handle.is_none() {
5004 "<>".to_string()
5005 } else {
5006 "<STDIN>".to_string()
5007 };
5008 self.bump_line_for_handle("STDIN");
5009 Ok(StrykeValue::string(line_str))
5010 }
5011 Err(e) => {
5012 self.apply_io_error_to_errno(&e);
5013 Ok(StrykeValue::UNDEF)
5014 }
5015 }
5016 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
5017 let slurp_mode = self.irs.is_none();
5019 let r: Result<usize, io::Error> = if slurp_mode {
5020 let mut buf = Vec::new();
5022 match reader.read_to_end(&mut buf) {
5023 Ok(n) => {
5024 if n > 0 {
5025 line_str = if self.open_pragma_utf8 {
5026 String::from_utf8_lossy(&buf).into_owned()
5027 } else {
5028 crate::perl_decode::decode_utf8_or_latin1_read_until(&buf)
5029 };
5030 }
5031 Ok(n)
5032 }
5033 Err(e) => Err(e),
5034 }
5035 } else if self.open_pragma_utf8 {
5036 let mut buf = Vec::new();
5037 reader.read_until(b'\n', &mut buf).inspect(|n| {
5038 if *n > 0 {
5039 line_str = String::from_utf8_lossy(&buf).into_owned();
5040 }
5041 })
5042 } else {
5043 let mut buf = Vec::new();
5044 match reader.read_until(b'\n', &mut buf) {
5045 Ok(n) => {
5046 if n > 0 {
5047 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
5048 }
5049 Ok(n)
5050 }
5051 Err(e) => Err(e),
5052 }
5053 };
5054 match r {
5055 Ok(0) => Ok(StrykeValue::UNDEF),
5056 Ok(_) => {
5057 self.bump_line_for_handle(handle_name);
5058 Ok(StrykeValue::string(line_str))
5059 }
5060 Err(e) => {
5061 self.apply_io_error_to_errno(&e);
5062 Ok(StrykeValue::UNDEF)
5063 }
5064 }
5065 } else {
5066 Ok(StrykeValue::UNDEF)
5067 }
5068 }
5069
5070 pub(crate) fn readline_builtin_execute_list(
5072 &mut self,
5073 handle: Option<&str>,
5074 ) -> StrykeResult<StrykeValue> {
5075 let mut lines = Vec::new();
5076 loop {
5077 let v = self.readline_builtin_execute(handle)?;
5078 if v.is_undef() {
5079 break;
5080 }
5081 lines.push(v);
5082 }
5083 Ok(StrykeValue::array(lines))
5084 }
5085
5086 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> StrykeValue {
5087 let path = self.resolve_stryke_path_string(path);
5088 match std::fs::read_dir(&path) {
5089 Ok(rd) => {
5090 let entries: Vec<String> = rd
5091 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
5092 .collect();
5093 self.dir_handles
5094 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
5095 StrykeValue::integer(1)
5096 }
5097 Err(e) => {
5098 self.apply_io_error_to_errno(&e);
5099 StrykeValue::integer(0)
5100 }
5101 }
5102 }
5103
5104 pub(crate) fn readdir_handle(&mut self, handle: &str) -> StrykeValue {
5105 if let Some(dh) = self.dir_handles.get_mut(handle) {
5106 if dh.pos < dh.entries.len() {
5107 let s = dh.entries[dh.pos].clone();
5108 dh.pos += 1;
5109 StrykeValue::string(s)
5110 } else {
5111 StrykeValue::UNDEF
5112 }
5113 } else {
5114 StrykeValue::UNDEF
5115 }
5116 }
5117
5118 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> StrykeValue {
5120 if let Some(dh) = self.dir_handles.get_mut(handle) {
5121 let rest: Vec<StrykeValue> = dh.entries[dh.pos..]
5122 .iter()
5123 .cloned()
5124 .map(StrykeValue::string)
5125 .collect();
5126 dh.pos = dh.entries.len();
5127 StrykeValue::array(rest)
5128 } else {
5129 StrykeValue::array(Vec::new())
5130 }
5131 }
5132
5133 pub(crate) fn closedir_handle(&mut self, handle: &str) -> StrykeValue {
5134 StrykeValue::integer(if self.dir_handles.remove(handle).is_some() {
5135 1
5136 } else {
5137 0
5138 })
5139 }
5140
5141 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> StrykeValue {
5142 if let Some(dh) = self.dir_handles.get_mut(handle) {
5143 dh.pos = 0;
5144 StrykeValue::integer(1)
5145 } else {
5146 StrykeValue::integer(0)
5147 }
5148 }
5149
5150 pub(crate) fn telldir_handle(&mut self, handle: &str) -> StrykeValue {
5151 self.dir_handles
5152 .get(handle)
5153 .map(|dh| StrykeValue::integer(dh.pos as i64))
5154 .unwrap_or(StrykeValue::UNDEF)
5155 }
5156
5157 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> StrykeValue {
5158 if let Some(dh) = self.dir_handles.get_mut(handle) {
5159 dh.pos = pos.min(dh.entries.len());
5160 StrykeValue::integer(1)
5161 } else {
5162 StrykeValue::integer(0)
5163 }
5164 }
5165
5166 #[inline]
5171 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
5172 crate::special_vars::is_regex_match_scalar_name(name)
5173 }
5174
5175 #[inline]
5179 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
5180 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
5181 self.regex_capture_scope_fresh = false;
5182 }
5183 }
5184
5185 pub(crate) fn apply_regex_captures(
5186 &mut self,
5187 haystack: &str,
5188 offset: usize,
5189 re: &PerlCompiledRegex,
5190 caps: &PerlCaptures<'_>,
5191 capture_all: CaptureAllMode,
5192 ) -> Result<(), FlowOrError> {
5193 let m0 = caps.get(0).expect("regex capture 0");
5194 let s0 = offset + m0.start;
5195 let e0 = offset + m0.end;
5196 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
5197 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
5198 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
5199 let mut last_paren = String::new();
5200 for i in 1..caps.len() {
5201 if let Some(m) = caps.get(i) {
5202 last_paren = m.text.to_string();
5203 }
5204 }
5205 self.last_paren_match = last_paren;
5206 self.last_subpattern_name = String::new();
5207 for n in re.capture_names().flatten() {
5208 if caps.name(n).is_some() {
5209 self.last_subpattern_name = n.to_string();
5210 }
5211 }
5212 self.scope
5213 .set_scalar("&", StrykeValue::string(self.last_match.clone()))?;
5214 self.scope
5215 .set_scalar("`", StrykeValue::string(self.prematch.clone()))?;
5216 self.scope
5217 .set_scalar("'", StrykeValue::string(self.postmatch.clone()))?;
5218 self.scope
5219 .set_scalar("+", StrykeValue::string(self.last_paren_match.clone()))?;
5220 for i in 1..caps.len() {
5221 if let Some(m) = caps.get(i) {
5222 self.scope
5223 .set_scalar(&i.to_string(), StrykeValue::string(m.text.to_string()))?;
5224 }
5225 }
5226 let mut start_arr = vec![StrykeValue::integer(s0 as i64)];
5227 let mut end_arr = vec![StrykeValue::integer(e0 as i64)];
5228 for i in 1..caps.len() {
5229 if let Some(m) = caps.get(i) {
5230 start_arr.push(StrykeValue::integer((offset + m.start) as i64));
5231 end_arr.push(StrykeValue::integer((offset + m.end) as i64));
5232 } else {
5233 start_arr.push(StrykeValue::integer(-1));
5234 end_arr.push(StrykeValue::integer(-1));
5235 }
5236 }
5237 self.scope.set_array("-", start_arr)?;
5238 self.scope.set_array("+", end_arr)?;
5239 let mut named = IndexMap::new();
5240 for name in re.capture_names().flatten() {
5241 if let Some(m) = caps.name(name) {
5242 named.insert(name.to_string(), StrykeValue::string(m.text.to_string()));
5243 }
5244 }
5245 self.scope.set_hash("+", named.clone())?;
5246 let mut named_minus = IndexMap::new();
5248 for (name, val) in &named {
5249 named_minus.insert(
5250 name.clone(),
5251 StrykeValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
5252 );
5253 }
5254 self.scope.set_hash("-", named_minus)?;
5255 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
5256 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
5257 match capture_all {
5258 CaptureAllMode::Empty => {
5259 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5260 }
5261 CaptureAllMode::Append => {
5262 let mut rows = self.scope.get_array("^CAPTURE_ALL");
5263 rows.push(StrykeValue::array(cap_flat));
5264 self.scope.set_array("^CAPTURE_ALL", rows)?;
5265 }
5266 CaptureAllMode::Skip => {}
5267 }
5268 Ok(())
5269 }
5270
5271 pub(crate) fn clear_flip_flop_state(&mut self) {
5272 self.flip_flop_active.clear();
5273 self.flip_flop_exclusive_left_line.clear();
5274 self.flip_flop_sequence.clear();
5275 self.flip_flop_last_dot.clear();
5276 self.flip_flop_tree.clear();
5277 }
5278
5279 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
5280 self.flip_flop_active.resize(slots as usize, false);
5281 self.flip_flop_active.fill(false);
5282 self.flip_flop_exclusive_left_line
5283 .resize(slots as usize, None);
5284 self.flip_flop_exclusive_left_line.fill(None);
5285 self.flip_flop_sequence.resize(slots as usize, 0);
5286 self.flip_flop_sequence.fill(0);
5287 self.flip_flop_last_dot.resize(slots as usize, None);
5288 self.flip_flop_last_dot.fill(None);
5289 }
5290
5291 #[inline]
5295 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
5296 if self.last_readline_handle.is_empty() {
5297 self.line_number
5298 } else {
5299 *self
5300 .handle_line_numbers
5301 .get(&self.last_readline_handle)
5302 .unwrap_or(&0)
5303 }
5304 }
5305
5306 pub(crate) fn scalar_flip_flop_eval(
5315 &mut self,
5316 left: i64,
5317 right: i64,
5318 slot: usize,
5319 exclusive: bool,
5320 ) -> StrykeResult<StrykeValue> {
5321 if self.flip_flop_active.len() <= slot {
5322 self.flip_flop_active.resize(slot + 1, false);
5323 }
5324 if self.flip_flop_exclusive_left_line.len() <= slot {
5325 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5326 }
5327 if self.flip_flop_sequence.len() <= slot {
5328 self.flip_flop_sequence.resize(slot + 1, 0);
5329 }
5330 if self.flip_flop_last_dot.len() <= slot {
5331 self.flip_flop_last_dot.resize(slot + 1, None);
5332 }
5333 let dot = self.scalar_flipflop_dot_line();
5334 let active = &mut self.flip_flop_active[slot];
5335 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5336 let seq = &mut self.flip_flop_sequence[slot];
5337 let last_dot = &mut self.flip_flop_last_dot[slot];
5338 if !*active {
5339 if dot == left {
5340 *active = true;
5341 *seq = 1;
5342 *last_dot = Some(dot);
5343 if exclusive {
5344 *excl_left = Some(dot);
5345 } else {
5346 *excl_left = None;
5347 if dot == right {
5348 *active = false;
5349 return Ok(StrykeValue::string(format!("{}E0", *seq)));
5350 }
5351 }
5352 return Ok(StrykeValue::string(seq.to_string()));
5353 }
5354 *last_dot = Some(dot);
5355 return Ok(StrykeValue::string(String::new()));
5356 }
5357 if *last_dot != Some(dot) {
5360 *seq += 1;
5361 *last_dot = Some(dot);
5362 }
5363 let cur_seq = *seq;
5364 if let Some(ll) = *excl_left {
5365 if dot == right && dot > ll {
5366 *active = false;
5367 *excl_left = None;
5368 *seq = 0;
5369 return Ok(StrykeValue::string(format!("{}E0", cur_seq)));
5370 }
5371 } else if dot == right {
5372 *active = false;
5373 *seq = 0;
5374 return Ok(StrykeValue::string(format!("{}E0", cur_seq)));
5375 }
5376 Ok(StrykeValue::string(cur_seq.to_string()))
5377 }
5378
5379 fn regex_flip_flop_transition(
5380 active: &mut bool,
5381 excl_left: &mut Option<i64>,
5382 exclusive: bool,
5383 dot: i64,
5384 left_m: bool,
5385 right_m: bool,
5386 ) -> i64 {
5387 if !*active {
5388 if left_m {
5389 *active = true;
5390 if exclusive {
5391 *excl_left = Some(dot);
5392 } else {
5393 *excl_left = None;
5394 if right_m {
5395 *active = false;
5396 }
5397 }
5398 return 1;
5399 }
5400 return 0;
5401 }
5402 if let Some(ll) = *excl_left {
5403 if right_m && dot > ll {
5404 *active = false;
5405 *excl_left = None;
5406 }
5407 } else if right_m {
5408 *active = false;
5409 }
5410 1
5411 }
5412
5413 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
5418 &mut self,
5419 left_pat: &str,
5420 left_flags: &str,
5421 right_pat: &str,
5422 right_flags: &str,
5423 slot: usize,
5424 exclusive: bool,
5425 line: usize,
5426 ) -> StrykeResult<StrykeValue> {
5427 let dot = self.scalar_flipflop_dot_line();
5428 let subject = self.scope.get_scalar("_").to_string();
5429 let left_re = self
5430 .compile_regex(left_pat, left_flags, line)
5431 .map_err(|e| match e {
5432 FlowOrError::Error(err) => err,
5433 FlowOrError::Flow(_) => {
5434 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5435 }
5436 })?;
5437 let right_re = self
5438 .compile_regex(right_pat, right_flags, line)
5439 .map_err(|e| match e {
5440 FlowOrError::Error(err) => err,
5441 FlowOrError::Flow(_) => {
5442 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5443 }
5444 })?;
5445 let left_m = left_re.is_match(&subject);
5446 let right_m = right_re.is_match(&subject);
5447 if self.flip_flop_active.len() <= slot {
5448 self.flip_flop_active.resize(slot + 1, false);
5449 }
5450 if self.flip_flop_exclusive_left_line.len() <= slot {
5451 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5452 }
5453 let active = &mut self.flip_flop_active[slot];
5454 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5455 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5456 active, excl_left, exclusive, dot, left_m, right_m,
5457 )))
5458 }
5459
5460 pub(crate) fn regex_flip_flop_eval_dynamic_right(
5462 &mut self,
5463 left_pat: &str,
5464 left_flags: &str,
5465 slot: usize,
5466 exclusive: bool,
5467 line: usize,
5468 right_m: bool,
5469 ) -> StrykeResult<StrykeValue> {
5470 let dot = self.scalar_flipflop_dot_line();
5471 let subject = self.scope.get_scalar("_").to_string();
5472 let left_re = self
5473 .compile_regex(left_pat, left_flags, line)
5474 .map_err(|e| match e {
5475 FlowOrError::Error(err) => err,
5476 FlowOrError::Flow(_) => {
5477 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5478 }
5479 })?;
5480 let left_m = left_re.is_match(&subject);
5481 if self.flip_flop_active.len() <= slot {
5482 self.flip_flop_active.resize(slot + 1, false);
5483 }
5484 if self.flip_flop_exclusive_left_line.len() <= slot {
5485 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5486 }
5487 let active = &mut self.flip_flop_active[slot];
5488 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5489 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5490 active, excl_left, exclusive, dot, left_m, right_m,
5491 )))
5492 }
5493
5494 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
5496 &mut self,
5497 left_pat: &str,
5498 left_flags: &str,
5499 slot: usize,
5500 exclusive: bool,
5501 line: usize,
5502 rhs_line: i64,
5503 ) -> StrykeResult<StrykeValue> {
5504 let dot = self.scalar_flipflop_dot_line();
5505 let subject = self.scope.get_scalar("_").to_string();
5506 let left_re = self
5507 .compile_regex(left_pat, left_flags, line)
5508 .map_err(|e| match e {
5509 FlowOrError::Error(err) => err,
5510 FlowOrError::Flow(_) => {
5511 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5512 }
5513 })?;
5514 let left_m = left_re.is_match(&subject);
5515 let right_m = dot == rhs_line;
5516 if self.flip_flop_active.len() <= slot {
5517 self.flip_flop_active.resize(slot + 1, false);
5518 }
5519 if self.flip_flop_exclusive_left_line.len() <= slot {
5520 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5521 }
5522 let active = &mut self.flip_flop_active[slot];
5523 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5524 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5525 active, excl_left, exclusive, dot, left_m, right_m,
5526 )))
5527 }
5528
5529 pub(crate) fn regex_eof_flip_flop_eval(
5534 &mut self,
5535 left_pat: &str,
5536 left_flags: &str,
5537 slot: usize,
5538 exclusive: bool,
5539 line: usize,
5540 ) -> StrykeResult<StrykeValue> {
5541 let dot = self.scalar_flipflop_dot_line();
5542 let subject = self.scope.get_scalar("_").to_string();
5543 let left_re = self
5544 .compile_regex(left_pat, left_flags, line)
5545 .map_err(|e| match e {
5546 FlowOrError::Error(err) => err,
5547 FlowOrError::Flow(_) => {
5548 StrykeError::runtime("unexpected flow in regex/eof flip-flop", line)
5549 }
5550 })?;
5551 let left_m = left_re.is_match(&subject);
5552 let right_m = self.eof_without_arg_is_true();
5553 if self.flip_flop_active.len() <= slot {
5554 self.flip_flop_active.resize(slot + 1, false);
5555 }
5556 if self.flip_flop_exclusive_left_line.len() <= slot {
5557 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5558 }
5559 let active = &mut self.flip_flop_active[slot];
5560 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5561 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5562 active, excl_left, exclusive, dot, left_m, right_m,
5563 )))
5564 }
5565
5566 pub(crate) fn builtin_read_into(
5570 &mut self,
5571 fh_val: StrykeValue,
5572 var_name: &str,
5573 length: usize,
5574 line: usize,
5575 ) -> ExecResult {
5576 use std::io::Read;
5577 let fh = fh_val
5578 .as_io_handle_name()
5579 .unwrap_or_else(|| fh_val.to_string());
5580 let mut buf = vec![0u8; length];
5581 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
5582 slot.lock().read(&mut buf).unwrap_or(0)
5583 } else if fh == "STDIN" {
5584 std::io::stdin().read(&mut buf).unwrap_or(0)
5585 } else {
5586 return Err(StrykeError::runtime(format!("read: unopened handle {}", fh), line).into());
5587 };
5588 buf.truncate(n);
5589 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
5590 let _ = self
5591 .scope
5592 .set_scalar(var_name, StrykeValue::string(read_str));
5593 Ok(StrykeValue::integer(n as i64))
5594 }
5595
5596 pub(crate) fn chomp_inplace_execute(&mut self, val: StrykeValue, target: &Expr) -> ExecResult {
5597 match &target.kind {
5603 ExprKind::ArrayVar(name) => {
5604 let arr = self.scope.get_array(name);
5605 let mut total = 0i64;
5606 let mut new_arr = Vec::with_capacity(arr.len());
5607 for v in arr {
5608 let mut s = v.to_string();
5609 if s.ends_with('\n') {
5610 s.pop();
5611 total += 1;
5612 }
5613 new_arr.push(StrykeValue::string(s));
5614 }
5615 self.scope
5616 .set_array(name, new_arr)
5617 .map_err(FlowOrError::Error)?;
5618 return Ok(StrykeValue::integer(total));
5619 }
5620 ExprKind::HashVar(name) => {
5621 let h = self.scope.get_hash(name);
5622 let mut total = 0i64;
5623 let mut new_h: indexmap::IndexMap<String, StrykeValue> =
5624 indexmap::IndexMap::with_capacity(h.len());
5625 for (k, v) in h {
5626 let mut s = v.to_string();
5627 if s.ends_with('\n') {
5628 s.pop();
5629 total += 1;
5630 }
5631 new_h.insert(k, StrykeValue::string(s));
5632 }
5633 self.scope
5634 .set_hash(name, new_h)
5635 .map_err(FlowOrError::Error)?;
5636 return Ok(StrykeValue::integer(total));
5637 }
5638 _ => {}
5639 }
5640 let mut s = val.to_string();
5641 let removed = if s.ends_with('\n') {
5642 s.pop();
5643 1i64
5644 } else {
5645 0i64
5646 };
5647 self.assign_value(target, StrykeValue::string(s))?;
5648 Ok(StrykeValue::integer(removed))
5649 }
5650
5651 pub(crate) fn chop_inplace_execute(&mut self, val: StrykeValue, target: &Expr) -> ExecResult {
5653 match &target.kind {
5659 ExprKind::ArrayVar(name) => {
5660 let arr = self.scope.get_array(name);
5661 let mut last = StrykeValue::UNDEF;
5662 let mut new_arr = Vec::with_capacity(arr.len());
5663 for v in arr {
5664 let mut s = v.to_string();
5665 if let Some(c) = s.pop() {
5666 last = StrykeValue::string(c.to_string());
5667 }
5668 new_arr.push(StrykeValue::string(s));
5669 }
5670 self.scope
5671 .set_array(name, new_arr)
5672 .map_err(FlowOrError::Error)?;
5673 return Ok(last);
5674 }
5675 ExprKind::HashVar(name) => {
5676 let h = self.scope.get_hash(name);
5677 let mut last = StrykeValue::UNDEF;
5678 let mut new_h: indexmap::IndexMap<String, StrykeValue> =
5679 indexmap::IndexMap::with_capacity(h.len());
5680 for (k, v) in h {
5681 let mut s = v.to_string();
5682 if let Some(c) = s.pop() {
5683 last = StrykeValue::string(c.to_string());
5684 }
5685 new_h.insert(k, StrykeValue::string(s));
5686 }
5687 self.scope
5688 .set_hash(name, new_h)
5689 .map_err(FlowOrError::Error)?;
5690 return Ok(last);
5691 }
5692 _ => {}
5693 }
5694 let mut s = val.to_string();
5695 let chopped = s
5696 .pop()
5697 .map(|c| StrykeValue::string(c.to_string()))
5698 .unwrap_or(StrykeValue::UNDEF);
5699 self.assign_value(target, StrykeValue::string(s))?;
5700 Ok(chopped)
5701 }
5702
5703 pub(crate) fn regex_match_execute(
5705 &mut self,
5706 s: String,
5707 pattern: &str,
5708 flags: &str,
5709 scalar_g: bool,
5710 pos_key: &str,
5711 line: usize,
5712 ) -> ExecResult {
5713 if !flags.contains('g') && !scalar_g {
5721 let memo_hit = {
5722 if let Some(ref mem) = self.regex_match_memo {
5723 mem.pattern == pattern
5724 && mem.flags == flags
5725 && mem.multiline == self.multiline_match
5726 && mem.haystack == s
5727 } else {
5728 false
5729 }
5730 };
5731 if memo_hit {
5732 if self.regex_capture_scope_fresh {
5733 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
5734 }
5735 let (memo_s, memo_result) = {
5738 let mem = self.regex_match_memo.as_ref().expect("memo");
5739 (mem.haystack.clone(), mem.result.clone())
5740 };
5741 let re = self.compile_regex(pattern, flags, line)?;
5742 if let Some(caps) = re.captures(&memo_s) {
5743 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
5744 }
5745 self.regex_capture_scope_fresh = true;
5746 return Ok(memo_result);
5747 }
5748 }
5749 let re = self.compile_regex(pattern, flags, line)?;
5750 if flags.contains('g') && scalar_g {
5751 let key = pos_key.to_string();
5752 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
5753 if start == 0 {
5754 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5755 }
5756 if start > s.len() {
5757 self.regex_pos.insert(key, None);
5758 return Ok(StrykeValue::integer(0));
5759 }
5760 let sub = s.get(start..).unwrap_or("");
5761 if let Some(caps) = re.captures(sub) {
5762 let overall = caps.get(0).expect("capture 0");
5763 let abs_end = start + overall.end;
5764 self.regex_pos.insert(key, Some(abs_end));
5765 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
5766 Ok(StrykeValue::integer(1))
5767 } else {
5768 self.regex_pos.insert(key, None);
5769 Ok(StrykeValue::integer(0))
5770 }
5771 } else if flags.contains('g') {
5772 let mut rows = Vec::new();
5773 let mut last_caps: Option<PerlCaptures<'_>> = None;
5774 for caps in re.captures_iter(&s) {
5775 rows.push(StrykeValue::array(
5776 crate::perl_regex::numbered_capture_flat(&caps),
5777 ));
5778 last_caps = Some(caps);
5779 }
5780 self.scope.set_array("^CAPTURE_ALL", rows)?;
5781 let matches: Vec<StrykeValue> = match &*re {
5782 PerlCompiledRegex::Rust(r) => r
5783 .find_iter(&s)
5784 .map(|m| StrykeValue::string(m.as_str().to_string()))
5785 .collect(),
5786 PerlCompiledRegex::Fancy(r) => r
5787 .find_iter(&s)
5788 .filter_map(|m| m.ok())
5789 .map(|m| StrykeValue::string(m.as_str().to_string()))
5790 .collect(),
5791 PerlCompiledRegex::Pcre2(r) => r
5792 .find_iter(s.as_bytes())
5793 .filter_map(|m| m.ok())
5794 .map(|m| {
5795 let t = s.get(m.start()..m.end()).unwrap_or("");
5796 StrykeValue::string(t.to_string())
5797 })
5798 .collect(),
5799 };
5800 if matches.is_empty() {
5801 Ok(StrykeValue::integer(0))
5802 } else {
5803 if let Some(caps) = last_caps {
5804 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
5805 }
5806 Ok(StrykeValue::array(matches))
5807 }
5808 } else if let Some(caps) = re.captures(&s) {
5809 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
5810 let result = StrykeValue::integer(1);
5811 self.regex_match_memo = Some(RegexMatchMemo {
5812 pattern: pattern.to_string(),
5813 flags: flags.to_string(),
5814 multiline: self.multiline_match,
5815 haystack: s,
5816 result: result.clone(),
5817 });
5818 self.regex_capture_scope_fresh = true;
5819 Ok(result)
5820 } else {
5821 let result = StrykeValue::integer(0);
5822 self.regex_match_memo = Some(RegexMatchMemo {
5824 pattern: pattern.to_string(),
5825 flags: flags.to_string(),
5826 multiline: self.multiline_match,
5827 haystack: s,
5828 result: result.clone(),
5829 });
5830 Ok(result)
5833 }
5834 }
5835
5836 pub(crate) fn expand_env_braces_in_subst(
5840 &mut self,
5841 raw: &str,
5842 line: usize,
5843 ) -> StrykeResult<String> {
5844 self.materialize_env_if_needed();
5845 let mut out = String::new();
5846 let mut rest = raw;
5847 while let Some(idx) = rest.find("$ENV{") {
5848 out.push_str(&rest[..idx]);
5849 let after = &rest[idx + 5..];
5850 let end = after
5851 .find('}')
5852 .ok_or_else(|| StrykeError::runtime("Unclosed $ENV{...} in s///", line))?;
5853 let key = &after[..end];
5854 let val = self.scope.get_hash_element("ENV", key);
5855 out.push_str(&val.to_string());
5856 rest = &after[end + 1..];
5857 }
5858 out.push_str(rest);
5859 Ok(out)
5860 }
5861
5862 pub(crate) fn regex_subst_execute(
5868 &mut self,
5869 s: String,
5870 pattern: &str,
5871 replacement: &str,
5872 flags: &str,
5873 target: &Expr,
5874 line: usize,
5875 ) -> ExecResult {
5876 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
5877 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
5878 let re = self.compile_regex(&pattern, &re_flags, line)?;
5879 if flags.contains('e') {
5880 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
5881 }
5882 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
5883 let replacement = self.interpolate_replacement_string(&replacement);
5884 let replacement = normalize_replacement_backrefs(&replacement);
5885 let last_caps = if flags.contains('g') {
5886 let mut rows = Vec::new();
5887 let mut last = None;
5888 for caps in re.captures_iter(&s) {
5889 rows.push(StrykeValue::array(
5890 crate::perl_regex::numbered_capture_flat(&caps),
5891 ));
5892 last = Some(caps);
5893 }
5894 self.scope.set_array("^CAPTURE_ALL", rows)?;
5895 last
5896 } else {
5897 re.captures(&s)
5898 };
5899 if let Some(caps) = last_caps {
5900 let mode = if flags.contains('g') {
5901 CaptureAllMode::Skip
5902 } else {
5903 CaptureAllMode::Empty
5904 };
5905 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
5906 }
5907 let (new_s, count) = if flags.contains('g') {
5908 let count = re.find_iter_count(&s);
5909 (re.replace_all(&s, replacement.as_str()), count)
5910 } else {
5911 let count = if re.is_match(&s) { 1 } else { 0 };
5912 (re.replace(&s, replacement.as_str()), count)
5913 };
5914 if flags.contains('r') {
5915 Ok(StrykeValue::string(new_s))
5917 } else {
5918 self.assign_value(target, StrykeValue::string(new_s))?;
5919 Ok(StrykeValue::integer(count as i64))
5920 }
5921 }
5922
5923 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
5926 let prep_source = |raw: &str| -> String {
5927 let mut code = raw.trim().to_string();
5928 if !code.ends_with(';') {
5929 code.push(';');
5930 }
5931 code
5932 };
5933 let mut cur = prep_source(replacement);
5934 let mut last = StrykeValue::UNDEF;
5935 for round in 0..e_count {
5936 last = crate::parse_and_run_string(&cur, self)?;
5937 if round + 1 < e_count {
5938 cur = prep_source(&last.to_string());
5939 }
5940 }
5941 Ok(last)
5942 }
5943
5944 fn regex_subst_execute_eval(
5945 &mut self,
5946 s: String,
5947 re: &PerlCompiledRegex,
5948 replacement: &str,
5949 flags: &str,
5950 target: &Expr,
5951 line: usize,
5952 ) -> ExecResult {
5953 let e_count = flags.chars().filter(|c| *c == 'e').count();
5954 if e_count == 0 {
5955 return Err(StrykeError::runtime("s///e: internal error (no e flag)", line).into());
5956 }
5957
5958 if flags.contains('g') {
5959 let mut rows = Vec::new();
5960 let mut out = String::new();
5961 let mut last = 0usize;
5962 let mut count = 0usize;
5963 for caps in re.captures_iter(&s) {
5964 let m0 = caps.get(0).expect("regex capture 0");
5965 out.push_str(&s[last..m0.start]);
5966 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5967 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5968 out.push_str(&repl_val.to_string());
5969 last = m0.end;
5970 count += 1;
5971 rows.push(StrykeValue::array(
5972 crate::perl_regex::numbered_capture_flat(&caps),
5973 ));
5974 }
5975 self.scope.set_array("^CAPTURE_ALL", rows)?;
5976 out.push_str(&s[last..]);
5977 if flags.contains('r') {
5978 return Ok(StrykeValue::string(out));
5979 }
5980 self.assign_value(target, StrykeValue::string(out))?;
5981 return Ok(StrykeValue::integer(count as i64));
5982 }
5983 if let Some(caps) = re.captures(&s) {
5984 let m0 = caps.get(0).expect("regex capture 0");
5985 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
5986 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
5987 let mut out = String::new();
5988 out.push_str(&s[..m0.start]);
5989 out.push_str(&repl_val.to_string());
5990 out.push_str(&s[m0.end..]);
5991 if flags.contains('r') {
5992 return Ok(StrykeValue::string(out));
5993 }
5994 self.assign_value(target, StrykeValue::string(out))?;
5995 return Ok(StrykeValue::integer(1));
5996 }
5997 if flags.contains('r') {
5998 return Ok(StrykeValue::string(s));
5999 }
6000 self.assign_value(target, StrykeValue::string(s))?;
6001 Ok(StrykeValue::integer(0))
6002 }
6003
6004 pub(crate) fn regex_transliterate_execute(
6006 &mut self,
6007 s: String,
6008 from: &str,
6009 to: &str,
6010 flags: &str,
6011 target: &Expr,
6012 line: usize,
6013 ) -> ExecResult {
6014 let _ = line;
6015 let from_chars = Self::tr_expand_ranges(from);
6016 let to_chars = Self::tr_expand_ranges(to);
6017 let delete_mode = flags.contains('d');
6018 let mut count = 0i64;
6019 let new_s: String = s
6020 .chars()
6021 .filter_map(|c| {
6022 if let Some(pos) = from_chars.iter().position(|&fc| fc == c) {
6023 count += 1;
6024 if delete_mode {
6025 if pos < to_chars.len() {
6027 Some(to_chars[pos])
6028 } else {
6029 None }
6031 } else {
6032 Some(to_chars.get(pos).or(to_chars.last()).copied().unwrap_or(c))
6034 }
6035 } else {
6036 Some(c)
6037 }
6038 })
6039 .collect();
6040 if flags.contains('r') {
6041 Ok(StrykeValue::string(new_s))
6043 } else {
6044 self.assign_value(target, StrykeValue::string(new_s))?;
6045 Ok(StrykeValue::integer(count))
6046 }
6047 }
6048
6049 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
6052 let raw: Vec<char> = spec.chars().collect();
6053 let mut out = Vec::with_capacity(raw.len());
6054 let mut i = 0;
6055 while i < raw.len() {
6056 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
6057 let start = raw[i] as u32;
6058 let end = raw[i + 2] as u32;
6059 for code in start..=end {
6060 if let Some(c) = char::from_u32(code) {
6061 out.push(c);
6062 }
6063 }
6064 i += 3;
6065 } else {
6066 out.push(raw[i]);
6067 i += 1;
6068 }
6069 }
6070 out
6071 }
6072
6073 pub(crate) fn splice_builtin_execute(
6075 &mut self,
6076 args: &[StrykeValue],
6077 line: usize,
6078 ) -> StrykeResult<StrykeValue> {
6079 if args.is_empty() {
6080 return Err(StrykeError::runtime("splice: missing array", line));
6081 }
6082 let arr_name = args[0].to_string();
6083 let arr_len = self.scope.array_len(&arr_name);
6084 let offset_val = args
6085 .get(1)
6086 .cloned()
6087 .unwrap_or_else(|| StrykeValue::integer(0));
6088 let length_val = match args.get(2) {
6089 None => StrykeValue::UNDEF,
6090 Some(v) => v.clone(),
6091 };
6092 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
6093 let rep_vals: Vec<StrykeValue> = args.iter().skip(3).cloned().collect();
6094 let removed = self.scope.splice_in_place(&arr_name, off, end, rep_vals)?;
6095 Ok(match self.wantarray_kind {
6096 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
6097 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
6098 })
6099 }
6100
6101 pub(crate) fn unshift_builtin_execute(
6103 &mut self,
6104 args: &[StrykeValue],
6105 line: usize,
6106 ) -> StrykeResult<StrykeValue> {
6107 if args.is_empty() {
6108 return Err(StrykeError::runtime("unshift: missing array", line));
6109 }
6110 let arr_name = args[0].to_string();
6111 let mut flat_vals: Vec<StrykeValue> = Vec::new();
6112 for a in args.iter().skip(1) {
6113 if let Some(items) = a.as_array_vec() {
6114 flat_vals.extend(items);
6115 } else {
6116 flat_vals.push(a.clone());
6117 }
6118 }
6119 let arr = self.scope.get_array_mut(&arr_name)?;
6120 for (i, v) in flat_vals.into_iter().enumerate() {
6121 arr.insert(i, v);
6122 }
6123 Ok(StrykeValue::integer(arr.len() as i64))
6124 }
6125
6126 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
6129 if upper == 0.0 {
6130 self.rand_rng.gen_range(0.0..1.0)
6131 } else if upper > 0.0 {
6132 self.rand_rng.gen_range(0.0..upper)
6133 } else {
6134 self.rand_rng.gen_range(upper..0.0)
6135 }
6136 }
6137
6138 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
6140 let n = if let Some(s) = seed {
6141 s as i64
6142 } else {
6143 std::time::SystemTime::now()
6144 .duration_since(std::time::UNIX_EPOCH)
6145 .map(|d| d.as_secs() as i64)
6146 .unwrap_or(1)
6147 };
6148 let mag = n.unsigned_abs();
6149 self.rand_rng = StdRng::seed_from_u64(mag);
6150 n.abs()
6151 }
6152
6153 pub fn set_file(&mut self, file: &str) {
6154 self.file = file.to_string();
6155 }
6156
6157 pub fn repl_completion_names(&self) -> Vec<String> {
6159 let mut v = self.scope.repl_binding_names();
6160 v.extend(self.subs.keys().cloned());
6161 v.sort();
6162 v.dedup();
6163 v
6164 }
6165
6166 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
6168 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
6169 subs.sort();
6170 let mut classes: HashSet<String> = HashSet::new();
6171 for k in &subs {
6172 if let Some((pkg, rest)) = k.split_once("::") {
6173 if !rest.contains("::") {
6174 classes.insert(pkg.to_string());
6175 }
6176 }
6177 }
6178 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
6179 for bn in self.scope.repl_binding_names() {
6180 if let Some(r) = bn.strip_prefix('$') {
6181 let v = self.scope.get_scalar(r);
6182 if let Some(b) = v.as_blessed_ref() {
6183 blessed_scalars.insert(r.to_string(), b.class.clone());
6184 classes.insert(b.class.clone());
6185 }
6186 }
6187 }
6188 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
6189 for c in classes {
6190 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
6191 }
6192 ReplCompletionSnapshot {
6193 subs,
6194 blessed_scalars,
6195 isa_for_class,
6196 }
6197 }
6198
6199 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
6200 if n == 0 {
6201 return Err(FlowOrError::Error(StrykeError::runtime(
6202 "bench: iteration count must be positive",
6203 line,
6204 )));
6205 }
6206 let mut samples = Vec::with_capacity(n);
6207 for _ in 0..n {
6208 let start = std::time::Instant::now();
6209 self.exec_block(body)?;
6210 samples.push(start.elapsed().as_secs_f64() * 1000.0);
6211 }
6212 let mut sorted = samples.clone();
6213 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6214 let min_ms = sorted[0];
6215 let mean = samples.iter().sum::<f64>() / n as f64;
6216 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
6217 .saturating_sub(1)
6218 .min(n - 1);
6219 let p99_ms = sorted[p99_idx];
6220 Ok(StrykeValue::string(format!(
6221 "bench: n={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
6222 n, min_ms, mean, p99_ms
6223 )))
6224 }
6225
6226 pub fn execute(&mut self, program: &Program) -> StrykeResult<StrykeValue> {
6227 crate::serialize_normalize::install_class_defs(self.class_defs.clone());
6233 if self.line_mode_skip_main {
6235 crate::compile_and_run_prelude(program, self)?;
6236 return Ok(StrykeValue::UNDEF);
6237 }
6238 crate::try_vm_execute(program, self)
6239 .expect("VM compilation must succeed — all execution is VM-only")
6240 }
6241
6242 pub fn run_end_blocks(&mut self) -> StrykeResult<()> {
6244 self.global_phase = "END".to_string();
6245 let ends = std::mem::take(&mut self.end_blocks);
6246 for block in &ends {
6247 self.exec_block(block).map_err(|e| match e {
6248 FlowOrError::Error(e) => e,
6249 FlowOrError::Flow(_) => StrykeError::runtime("Unexpected flow control in END", 0),
6250 })?;
6251 }
6252 Ok(())
6253 }
6254
6255 pub fn run_global_teardown(&mut self) -> StrykeResult<()> {
6258 self.global_phase = "DESTRUCT".to_string();
6259 self.drain_pending_destroys(0)
6260 }
6261
6262 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> StrykeResult<()> {
6264 loop {
6265 let batch = crate::pending_destroy::take_queue();
6266 if batch.is_empty() {
6267 break;
6268 }
6269 for (class, payload) in batch {
6270 let fq = format!("{}::DESTROY", class);
6271 let Some(sub) = self.subs.get(&fq).cloned() else {
6272 continue;
6273 };
6274 let inv = StrykeValue::blessed(Arc::new(
6275 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
6276 ));
6277 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
6278 Ok(_) => {}
6279 Err(FlowOrError::Error(e)) => return Err(e),
6280 Err(FlowOrError::Flow(Flow::Return(_))) => {}
6281 Err(FlowOrError::Flow(other)) => {
6282 return Err(StrykeError::runtime(
6283 format!("DESTROY: unexpected control flow ({other:?})"),
6284 line,
6285 ));
6286 }
6287 }
6288 }
6289 }
6290 Ok(())
6291 }
6292
6293 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
6294 self.exec_block_with_tail(block, WantarrayCtx::Void)
6295 }
6296
6297 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6300 let uses_goto = block
6301 .iter()
6302 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
6303 if uses_goto {
6304 self.scope_push_hook();
6305 let r = self.exec_block_with_goto_tail(block, tail);
6306 self.scope_pop_hook();
6307 r
6308 } else {
6309 self.scope_push_hook();
6310 let result = self.exec_block_no_scope_with_tail(block, tail);
6311 self.scope_pop_hook();
6312 result
6313 }
6314 }
6315
6316 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6317 let mut map: HashMap<String, usize> = HashMap::new();
6318 for (i, s) in block.iter().enumerate() {
6319 if let Some(l) = &s.label {
6320 map.insert(l.clone(), i);
6321 }
6322 }
6323 let mut pc = 0usize;
6324 let mut last = StrykeValue::UNDEF;
6325 let last_idx = block.len().saturating_sub(1);
6326 while pc < block.len() {
6327 if let StmtKind::Goto { target } = &block[pc].kind {
6328 let line = block[pc].line;
6329 let name = self.eval_expr(target)?.to_string();
6330 pc = *map.get(&name).ok_or_else(|| {
6331 FlowOrError::Error(StrykeError::runtime(
6332 format!("goto: unknown label {}", name),
6333 line,
6334 ))
6335 })?;
6336 continue;
6337 }
6338 let v = if pc == last_idx {
6339 match &block[pc].kind {
6340 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
6341 _ => self.exec_statement(&block[pc])?,
6342 }
6343 } else {
6344 self.exec_statement(&block[pc])?
6345 };
6346 last = v;
6347 pc += 1;
6348 }
6349 Ok(last)
6350 }
6351
6352 #[inline]
6355 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
6356 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
6357 }
6358
6359 pub(crate) fn exec_block_no_scope_with_tail(
6360 &mut self,
6361 block: &Block,
6362 tail: WantarrayCtx,
6363 ) -> ExecResult {
6364 if block.is_empty() {
6365 return Ok(StrykeValue::UNDEF);
6366 }
6367 let last_i = block.len() - 1;
6368 for (i, stmt) in block.iter().enumerate() {
6369 if i < last_i {
6370 self.exec_statement(stmt)?;
6371 } else {
6372 return match &stmt.kind {
6373 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
6374 _ => self.exec_statement(stmt),
6375 };
6376 }
6377 }
6378 Ok(StrykeValue::UNDEF)
6379 }
6380
6381 pub(crate) fn spawn_async_block(&self, block: &Block) -> StrykeValue {
6383 use parking_lot::Mutex as ParkMutex;
6384
6385 let block = block.clone();
6386 let subs = self.subs.clone();
6387 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6388 let result = Arc::new(ParkMutex::new(None));
6389 let join = Arc::new(ParkMutex::new(None));
6390 let result2 = result.clone();
6391 let h = std::thread::spawn(move || {
6392 let mut interp = VMHelper::new();
6393 interp.subs = subs;
6394 interp.scope.restore_capture(&scalars);
6395 interp.scope.restore_atomics(&aar, &ahash);
6396 interp.enable_parallel_guard();
6397 let r = match interp.exec_block(&block) {
6398 Ok(v) => Ok(v),
6399 Err(FlowOrError::Error(e)) => Err(e),
6400 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6401 Err(StrykeError::runtime("yield inside async/spawn block", 0))
6402 }
6403 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
6404 };
6405 *result2.lock() = Some(r);
6406 });
6407 *join.lock() = Some(h);
6408 StrykeValue::async_task(Arc::new(StrykeAsyncTask { result, join }))
6409 }
6410
6411 pub(crate) fn eval_timeout_block(
6413 &mut self,
6414 body: &Block,
6415 secs: f64,
6416 line: usize,
6417 ) -> ExecResult {
6418 use std::sync::mpsc::channel;
6419 use std::time::Duration;
6420
6421 let block = body.clone();
6422 let subs = self.subs.clone();
6423 let struct_defs = self.struct_defs.clone();
6424 let enum_defs = self.enum_defs.clone();
6425 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6426 self.materialize_env_if_needed();
6427 let env = self.env.clone();
6428 let argv = self.argv.clone();
6429 let inc = self.scope.get_array("INC");
6430 let (tx, rx) = channel::<StrykeResult<StrykeValue>>();
6431 let _handle = std::thread::spawn(move || {
6432 let mut interp = VMHelper::new();
6433 interp.subs = subs;
6434 interp.struct_defs = struct_defs;
6435 interp.enum_defs = enum_defs;
6436 interp.env = env.clone();
6437 interp.argv = argv.clone();
6438 interp.scope.declare_array(
6439 "ARGV",
6440 argv.iter()
6441 .map(|s| StrykeValue::string(s.clone()))
6442 .collect(),
6443 );
6444 for (k, v) in env {
6445 interp
6446 .scope
6447 .set_hash_element("ENV", &k, v)
6448 .expect("set ENV in timeout thread");
6449 }
6450 interp.scope.declare_array("INC", inc);
6451 interp.scope.restore_capture(&scalars);
6452 interp.scope.restore_atomics(&aar, &ahash);
6453 interp.enable_parallel_guard();
6454 let out: StrykeResult<StrykeValue> = match interp.exec_block(&block) {
6455 Ok(v) => Ok(v),
6456 Err(FlowOrError::Error(e)) => Err(e),
6457 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6458 Err(StrykeError::runtime("yield inside eval_timeout block", 0))
6459 }
6460 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
6461 };
6462 let _ = tx.send(out);
6463 });
6464 let dur = Duration::from_secs_f64(secs.max(0.0));
6465 match rx.recv_timeout(dur) {
6466 Ok(Ok(v)) => Ok(v),
6467 Ok(Err(e)) => Err(FlowOrError::Error(e)),
6468 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(StrykeError::runtime(
6469 format!(
6470 "eval_timeout: exceeded {} second(s) (worker continues in background)",
6471 secs
6472 ),
6473 line,
6474 )
6475 .into()),
6476 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(StrykeError::runtime(
6477 "eval_timeout: worker thread panicked or disconnected",
6478 line,
6479 )
6480 .into()),
6481 }
6482 }
6483
6484 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
6485 let mut last = StrykeValue::UNDEF;
6486 for stmt in body {
6487 match &stmt.kind {
6488 StmtKind::When { cond, body: wb } => {
6489 if self.when_matches(cond)? {
6490 return self.exec_block_smart(wb);
6491 }
6492 }
6493 StmtKind::DefaultCase { body: db } => {
6494 return self.exec_block_smart(db);
6495 }
6496 _ => {
6497 last = self.exec_statement(stmt)?;
6498 }
6499 }
6500 }
6501 Ok(last)
6502 }
6503
6504 pub(crate) fn exec_given_with_topic_value(
6506 &mut self,
6507 topic: StrykeValue,
6508 body: &Block,
6509 ) -> ExecResult {
6510 self.scope_push_hook();
6511 self.scope.declare_scalar("_", topic);
6512 self.english_note_lexical_scalar("_");
6513 let r = self.exec_given_body(body);
6514 self.scope_pop_hook();
6515 r
6516 }
6517
6518 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
6519 let t = self.eval_expr(topic)?;
6520 self.exec_given_with_topic_value(t, body)
6521 }
6522
6523 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6525 let topic = self.scope.get_scalar("_");
6526 let line = cond.line;
6527 match &cond.kind {
6528 ExprKind::Regex(pattern, flags) => {
6529 let re = self.compile_regex(pattern, flags, line)?;
6530 let s = topic.to_string();
6531 Ok(re.is_match(&s))
6532 }
6533 ExprKind::String(s) => Ok(topic.to_string() == *s),
6534 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
6535 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
6536 _ => {
6537 let c = self.eval_expr(cond)?;
6538 Ok(self.smartmatch_when(&topic, &c))
6539 }
6540 }
6541 }
6542
6543 fn smartmatch_when(&self, topic: &StrykeValue, c: &StrykeValue) -> bool {
6544 if let Some(re) = c.as_regex() {
6545 return re.is_match(&topic.to_string());
6546 }
6547 if let Some(arr) = c.as_array_ref() {
6554 let arr = arr.read();
6555 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
6556 }
6557 if let Some(arr) = c.as_array_vec() {
6558 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
6559 }
6560 if let Some(href) = c.as_hash_ref() {
6562 return href.read().contains_key(&topic.to_string());
6563 }
6564 if let Some(h) = c.as_hash_map() {
6565 return h.contains_key(&topic.to_string());
6566 }
6567 if let Some(sub) = c.as_code_ref() {
6569 let _ = sub;
6573 }
6574 if let (Some(a), Some(b)) = (topic.as_integer(), c.as_integer()) {
6576 return a == b;
6577 }
6578 topic.to_string() == c.to_string()
6579 }
6580
6581 pub(crate) fn eval_boolean_rvalue_condition(
6583 &mut self,
6584 cond: &Expr,
6585 ) -> Result<bool, FlowOrError> {
6586 match &cond.kind {
6587 ExprKind::Regex(pattern, flags) => {
6588 let topic = self.scope.get_scalar("_");
6589 let line = cond.line;
6590 let s = topic.to_string();
6591 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
6592 Ok(v.is_true())
6593 }
6594 ExprKind::ReadLine(_) => {
6596 let v = self.eval_expr(cond)?;
6597 self.scope.set_topic(v.clone());
6598 Ok(!v.is_undef())
6599 }
6600 _ => {
6601 let v = self.eval_expr(cond)?;
6602 Ok(v.is_true())
6603 }
6604 }
6605 }
6606
6607 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6609 self.eval_boolean_rvalue_condition(cond)
6610 }
6611
6612 pub(crate) fn eval_algebraic_match(
6613 &mut self,
6614 subject: &Expr,
6615 arms: &[MatchArm],
6616 line: usize,
6617 ) -> ExecResult {
6618 let val = self.eval_algebraic_match_subject(subject, line)?;
6619 self.eval_algebraic_match_with_subject_value(val, arms, line)
6620 }
6621
6622 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
6624 match &subject.kind {
6625 ExprKind::ArrayVar(name) => {
6626 self.check_strict_array_var(name, line)?;
6627 let aname = self.stash_array_name_for_package(name);
6628 Ok(StrykeValue::array_binding_ref(aname))
6629 }
6630 ExprKind::HashVar(name) => {
6631 self.check_strict_hash_var(name, line)?;
6632 self.touch_env_hash(name);
6633 Ok(StrykeValue::hash_binding_ref(name.clone()))
6634 }
6635 _ => self.eval_expr(subject),
6636 }
6637 }
6638
6639 pub(crate) fn eval_algebraic_match_with_subject_value(
6641 &mut self,
6642 val: StrykeValue,
6643 arms: &[MatchArm],
6644 line: usize,
6645 ) -> ExecResult {
6646 if let Some(e) = val.as_enum_inst() {
6648 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
6649 if !has_catchall {
6650 let covered: Vec<String> = arms
6651 .iter()
6652 .filter_map(|a| {
6653 if let MatchPattern::Value(expr) = &a.pattern {
6654 if let ExprKind::FuncCall { name, .. } = &expr.kind {
6655 return name.rsplit_once("::").map(|(_, v)| v.to_string());
6656 }
6657 }
6658 None
6659 })
6660 .collect();
6661 let missing: Vec<&str> = e
6662 .def
6663 .variants
6664 .iter()
6665 .filter(|v| !covered.contains(&v.name))
6666 .map(|v| v.name.as_str())
6667 .collect();
6668 if !missing.is_empty() {
6669 return Err(StrykeError::runtime(
6670 format!(
6671 "non-exhaustive match on enum `{}`: missing variant(s) {}",
6672 e.def.name,
6673 missing.join(", ")
6674 ),
6675 line,
6676 )
6677 .into());
6678 }
6679 }
6680 }
6681 for arm in arms {
6682 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
6683 let re = self.compile_regex(pattern, flags, line)?;
6684 let s = val.to_string();
6685 if let Some(caps) = re.captures(&s) {
6686 self.scope_push_hook();
6687 self.scope.declare_scalar("_", val.clone());
6688 self.english_note_lexical_scalar("_");
6689 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
6690 let guard_ok = if let Some(g) = &arm.guard {
6691 self.eval_expr(g)?.is_true()
6692 } else {
6693 true
6694 };
6695 if !guard_ok {
6696 self.scope_pop_hook();
6697 continue;
6698 }
6699 let out = self.eval_expr(&arm.body);
6700 self.scope_pop_hook();
6701 return out;
6702 }
6703 continue;
6704 }
6705 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
6706 self.scope_push_hook();
6707 self.scope.declare_scalar("_", val.clone());
6708 self.english_note_lexical_scalar("_");
6709 for b in bindings {
6710 match b {
6711 PatternBinding::Scalar(name, v) => {
6712 self.scope.declare_scalar(&name, v);
6713 self.english_note_lexical_scalar(&name);
6714 }
6715 PatternBinding::Array(name, elems) => {
6716 self.scope.declare_array(&name, elems);
6717 }
6718 }
6719 }
6720 let guard_ok = if let Some(g) = &arm.guard {
6721 self.eval_expr(g)?.is_true()
6722 } else {
6723 true
6724 };
6725 if !guard_ok {
6726 self.scope_pop_hook();
6727 continue;
6728 }
6729 let out = self.eval_expr(&arm.body);
6730 self.scope_pop_hook();
6731 return out;
6732 }
6733 }
6734 Err(StrykeError::runtime(
6735 "match: no arm matched the value (add a `_` catch-all)",
6736 line,
6737 )
6738 .into())
6739 }
6740
6741 fn parse_duration_seconds(pv: &StrykeValue) -> Option<f64> {
6742 let s = pv.to_string();
6743 let s = s.trim();
6744 if let Some(rest) = s.strip_suffix("ms") {
6745 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
6746 }
6747 if let Some(rest) = s.strip_suffix('s') {
6748 return rest.trim().parse::<f64>().ok();
6749 }
6750 if let Some(rest) = s.strip_suffix('m') {
6751 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
6752 }
6753 s.parse::<f64>().ok()
6754 }
6755
6756 fn eval_retry_block(
6757 &mut self,
6758 body: &Block,
6759 times: &Expr,
6760 backoff: RetryBackoff,
6761 _line: usize,
6762 ) -> ExecResult {
6763 let max = self.eval_expr(times)?.to_int().max(1) as usize;
6764 let base_ms: u64 = 10;
6765 let mut attempt = 0usize;
6766 loop {
6767 attempt += 1;
6768 match self.exec_block(body) {
6769 Ok(v) => return Ok(v),
6770 Err(FlowOrError::Error(e)) => {
6771 if attempt >= max {
6772 return Err(FlowOrError::Error(e));
6773 }
6774 let delay_ms = match backoff {
6775 RetryBackoff::None => 0,
6776 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
6777 RetryBackoff::Exponential => {
6778 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
6779 }
6780 };
6781 if delay_ms > 0 {
6782 std::thread::sleep(Duration::from_millis(delay_ms));
6783 }
6784 }
6785 Err(e) => return Err(e),
6786 }
6787 }
6788 }
6789
6790 fn eval_rate_limit_block(
6791 &mut self,
6792 slot: u32,
6793 max: &Expr,
6794 window: &Expr,
6795 body: &Block,
6796 _line: usize,
6797 ) -> ExecResult {
6798 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
6799 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
6800 .filter(|s| *s > 0.0)
6801 .unwrap_or(1.0);
6802 let window_d = Duration::from_secs_f64(window_sec);
6803 let slot = slot as usize;
6804 while self.rate_limit_slots.len() <= slot {
6805 self.rate_limit_slots.push(VecDeque::new());
6806 }
6807 {
6808 let dq = &mut self.rate_limit_slots[slot];
6809 loop {
6810 let now = Instant::now();
6811 while let Some(t0) = dq.front().copied() {
6812 if now.duration_since(t0) >= window_d {
6813 dq.pop_front();
6814 } else {
6815 break;
6816 }
6817 }
6818 if dq.len() < max_n || max_n == 0 {
6819 break;
6820 }
6821 let t0 = dq.front().copied().unwrap();
6822 let wait = window_d.saturating_sub(now.duration_since(t0));
6823 if wait.is_zero() {
6824 dq.pop_front();
6825 continue;
6826 }
6827 std::thread::sleep(wait);
6828 }
6829 dq.push_back(Instant::now());
6830 }
6831 self.exec_block(body)
6832 }
6833
6834 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
6835 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
6836 .filter(|s| *s > 0.0)
6837 .unwrap_or(1.0);
6838 loop {
6839 match self.exec_block(body) {
6840 Ok(_) => {}
6841 Err(e) => return Err(e),
6842 }
6843 std::thread::sleep(Duration::from_secs_f64(sec));
6844 }
6845 }
6846
6847 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> StrykeResult<StrykeValue> {
6849 let pair = |value: StrykeValue, more: i64| {
6850 StrykeValue::array_ref(Arc::new(RwLock::new(vec![
6851 value,
6852 StrykeValue::integer(more),
6853 ])))
6854 };
6855 let mut exhausted = gen.exhausted.lock();
6856 if *exhausted {
6857 return Ok(pair(StrykeValue::UNDEF, 0));
6858 }
6859 let mut pc = gen.pc.lock();
6860 let mut scope_started = gen.scope_started.lock();
6861 if *pc >= gen.block.len() {
6862 if *scope_started {
6863 self.scope_pop_hook();
6864 *scope_started = false;
6865 }
6866 *exhausted = true;
6867 return Ok(pair(StrykeValue::UNDEF, 0));
6868 }
6869 if !*scope_started {
6870 self.scope_push_hook();
6871 *scope_started = true;
6872 }
6873 self.in_generator = true;
6874 while *pc < gen.block.len() {
6875 let stmt = &gen.block[*pc];
6876 match self.exec_statement(stmt) {
6877 Ok(_) => {
6878 *pc += 1;
6879 }
6880 Err(FlowOrError::Flow(Flow::Yield(v))) => {
6881 *pc += 1;
6882 self.in_generator = false;
6883 if *scope_started {
6886 self.scope_pop_hook();
6887 *scope_started = false;
6888 }
6889 return Ok(pair(v, 1));
6890 }
6891 Err(e) => {
6892 self.in_generator = false;
6893 if *scope_started {
6894 self.scope_pop_hook();
6895 *scope_started = false;
6896 }
6897 return Err(match e {
6898 FlowOrError::Error(ee) => ee,
6899 FlowOrError::Flow(Flow::Yield(_)) => {
6900 unreachable!("yield handled above")
6901 }
6902 FlowOrError::Flow(flow) => StrykeError::runtime(
6903 format!("unexpected control flow in generator: {:?}", flow),
6904 0,
6905 ),
6906 });
6907 }
6908 }
6909 }
6910 self.in_generator = false;
6911 if *scope_started {
6912 self.scope_pop_hook();
6913 *scope_started = false;
6914 }
6915 *exhausted = true;
6916 Ok(pair(StrykeValue::UNDEF, 0))
6917 }
6918
6919 fn match_pattern_try(
6920 &mut self,
6921 subject: &StrykeValue,
6922 pattern: &MatchPattern,
6923 line: usize,
6924 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
6925 match pattern {
6926 MatchPattern::Any => Ok(Some(vec![])),
6927 MatchPattern::Regex { .. } => {
6928 unreachable!("regex arms are handled in eval_algebraic_match")
6929 }
6930 MatchPattern::Value(expr) => {
6931 if self.match_pattern_value_alternation(subject, expr, line)? {
6932 Ok(Some(vec![]))
6933 } else {
6934 Ok(None)
6935 }
6936 }
6937 MatchPattern::Array(elems) => {
6938 let Some(arr) = self.match_subject_as_array(subject) else {
6939 return Ok(None);
6940 };
6941 self.match_array_pattern_elems(&arr, elems, line)
6942 }
6943 MatchPattern::Hash(pairs) => {
6944 let Some(h) = self.match_subject_as_hash(subject) else {
6945 return Ok(None);
6946 };
6947 self.match_hash_pattern_pairs(&h, pairs, line)
6948 }
6949 MatchPattern::OptionSome(name) => {
6950 let Some(arr) = self.match_subject_as_array(subject) else {
6951 return Ok(None);
6952 };
6953 if arr.len() < 2 {
6954 return Ok(None);
6955 }
6956 if !arr[1].is_true() {
6957 return Ok(None);
6958 }
6959 Ok(Some(vec![PatternBinding::Scalar(
6960 name.clone(),
6961 arr[0].clone(),
6962 )]))
6963 }
6964 }
6965 }
6966
6967 fn match_pattern_value_alternation(
6970 &mut self,
6971 subject: &StrykeValue,
6972 expr: &Expr,
6973 _line: usize,
6974 ) -> Result<bool, FlowOrError> {
6975 if let ExprKind::BinOp {
6976 left,
6977 op: BinOp::BitOr,
6978 right,
6979 } = &expr.kind
6980 {
6981 if self.match_pattern_value_alternation(subject, left, _line)? {
6982 return Ok(true);
6983 }
6984 return self.match_pattern_value_alternation(subject, right, _line);
6985 }
6986 let pv = self.eval_expr(expr)?;
6987 Ok(self.smartmatch_when(subject, &pv))
6988 }
6989
6990 fn match_subject_as_array(&self, v: &StrykeValue) -> Option<Vec<StrykeValue>> {
6992 if let Some(a) = v.as_array_vec() {
6993 return Some(a);
6994 }
6995 if let Some(r) = v.as_array_ref() {
6996 return Some(r.read().clone());
6997 }
6998 if let Some(name) = v.as_array_binding_name() {
6999 return Some(self.scope.get_array(&name));
7000 }
7001 None
7002 }
7003
7004 fn match_subject_as_hash(&mut self, v: &StrykeValue) -> Option<IndexMap<String, StrykeValue>> {
7005 if let Some(h) = v.as_hash_map() {
7006 return Some(h);
7007 }
7008 if let Some(r) = v.as_hash_ref() {
7009 return Some(r.read().clone());
7010 }
7011 if let Some(name) = v.as_hash_binding_name() {
7012 self.touch_env_hash(&name);
7013 return Some(self.scope.get_hash(&name));
7014 }
7015 None
7016 }
7017
7018 pub(crate) fn hash_slice_deref_values(
7021 &mut self,
7022 container: &StrykeValue,
7023 key_values: &[StrykeValue],
7024 line: usize,
7025 ) -> Result<StrykeValue, FlowOrError> {
7026 let h = if let Some(m) = self.match_subject_as_hash(container) {
7027 m
7028 } else {
7029 return Err(StrykeError::runtime(
7030 "Hash slice dereference needs a hash or hash reference value",
7031 line,
7032 )
7033 .into());
7034 };
7035 let mut result = Vec::new();
7036 for kv in key_values {
7037 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7038 vv.iter().map(|x| x.to_string()).collect()
7039 } else {
7040 vec![kv.to_string()]
7041 };
7042 for k in key_strings {
7043 result.push(h.get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
7044 }
7045 }
7046 Ok(StrykeValue::array(result))
7047 }
7048
7049 pub(crate) fn assign_hash_slice_one_key(
7052 &mut self,
7053 container: StrykeValue,
7054 key: &str,
7055 val: StrykeValue,
7056 line: usize,
7057 ) -> Result<StrykeValue, FlowOrError> {
7058 if let Some(r) = container.as_hash_ref() {
7059 r.write().insert(key.to_string(), val);
7060 return Ok(StrykeValue::UNDEF);
7061 }
7062 if let Some(name) = container.as_hash_binding_name() {
7063 self.touch_env_hash(&name);
7064 self.scope
7065 .set_hash_element(&name, key, val)
7066 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7067 return Ok(StrykeValue::UNDEF);
7068 }
7069 if let Some(s) = container.as_str() {
7070 self.touch_env_hash(&s);
7071 if self.strict_refs {
7072 return Err(StrykeError::runtime(
7073 format!(
7074 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7075 s
7076 ),
7077 line,
7078 )
7079 .into());
7080 }
7081 self.scope
7082 .set_hash_element(&s, key, val)
7083 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7084 return Ok(StrykeValue::UNDEF);
7085 }
7086 Err(StrykeError::runtime(
7087 "Hash slice assignment needs a hash or hash reference value",
7088 line,
7089 )
7090 .into())
7091 }
7092
7093 pub(crate) fn assign_named_hash_slice(
7096 &mut self,
7097 hash: &str,
7098 key_values: Vec<StrykeValue>,
7099 val: StrykeValue,
7100 line: usize,
7101 ) -> Result<StrykeValue, FlowOrError> {
7102 self.touch_env_hash(hash);
7103 let mut ks: Vec<String> = Vec::new();
7104 for kv in key_values {
7105 if let Some(vv) = kv.as_array_vec() {
7106 ks.extend(vv.iter().map(|x| x.to_string()));
7107 } else {
7108 ks.push(kv.to_string());
7109 }
7110 }
7111 if ks.is_empty() {
7112 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7113 }
7114 let items = val.to_list();
7115 for (i, k) in ks.iter().enumerate() {
7116 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7117 self.scope
7118 .set_hash_element(hash, k, v)
7119 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7120 }
7121 Ok(StrykeValue::UNDEF)
7122 }
7123
7124 pub(crate) fn assign_hash_slice_deref(
7126 &mut self,
7127 container: StrykeValue,
7128 key_values: Vec<StrykeValue>,
7129 val: StrykeValue,
7130 line: usize,
7131 ) -> Result<StrykeValue, FlowOrError> {
7132 let mut ks: Vec<String> = Vec::new();
7133 for kv in key_values {
7134 if let Some(vv) = kv.as_array_vec() {
7135 ks.extend(vv.iter().map(|x| x.to_string()));
7136 } else {
7137 ks.push(kv.to_string());
7138 }
7139 }
7140 if ks.is_empty() {
7141 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7142 }
7143 let items = val.to_list();
7144 if let Some(r) = container.as_hash_ref() {
7145 let mut h = r.write();
7146 for (i, k) in ks.iter().enumerate() {
7147 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7148 h.insert(k.clone(), v);
7149 }
7150 return Ok(StrykeValue::UNDEF);
7151 }
7152 if let Some(name) = container.as_hash_binding_name() {
7153 self.touch_env_hash(&name);
7154 for (i, k) in ks.iter().enumerate() {
7155 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7156 self.scope
7157 .set_hash_element(&name, k, v)
7158 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7159 }
7160 return Ok(StrykeValue::UNDEF);
7161 }
7162 if let Some(s) = container.as_str() {
7163 if self.strict_refs {
7164 return Err(StrykeError::runtime(
7165 format!(
7166 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7167 s
7168 ),
7169 line,
7170 )
7171 .into());
7172 }
7173 self.touch_env_hash(&s);
7174 for (i, k) in ks.iter().enumerate() {
7175 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7176 self.scope
7177 .set_hash_element(&s, k, v)
7178 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7179 }
7180 return Ok(StrykeValue::UNDEF);
7181 }
7182 Err(StrykeError::runtime(
7183 "Hash slice assignment needs a hash or hash reference value",
7184 line,
7185 )
7186 .into())
7187 }
7188
7189 pub(crate) fn compound_assign_hash_slice_deref(
7192 &mut self,
7193 container: StrykeValue,
7194 key_values: Vec<StrykeValue>,
7195 op: BinOp,
7196 rhs: StrykeValue,
7197 line: usize,
7198 ) -> Result<StrykeValue, FlowOrError> {
7199 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7200 let last_old = old_list
7201 .to_list()
7202 .last()
7203 .cloned()
7204 .unwrap_or(StrykeValue::UNDEF);
7205 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7206 let mut ks: Vec<String> = Vec::new();
7207 for kv in &key_values {
7208 if let Some(vv) = kv.as_array_vec() {
7209 ks.extend(vv.iter().map(|x| x.to_string()));
7210 } else {
7211 ks.push(kv.to_string());
7212 }
7213 }
7214 if ks.is_empty() {
7215 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7216 }
7217 let last_key = ks.last().expect("non-empty ks");
7218 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7219 Ok(new_val)
7220 }
7221
7222 pub(crate) fn hash_slice_deref_inc_dec(
7228 &mut self,
7229 container: StrykeValue,
7230 key_values: Vec<StrykeValue>,
7231 kind: u8,
7232 line: usize,
7233 ) -> Result<StrykeValue, FlowOrError> {
7234 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7235 let last_old = old_list
7236 .to_list()
7237 .last()
7238 .cloned()
7239 .unwrap_or(StrykeValue::UNDEF);
7240 let new_val = if kind & 1 == 0 {
7241 StrykeValue::integer(last_old.to_int() + 1)
7242 } else {
7243 StrykeValue::integer(last_old.to_int() - 1)
7244 };
7245 let mut ks: Vec<String> = Vec::new();
7246 for kv in &key_values {
7247 if let Some(vv) = kv.as_array_vec() {
7248 ks.extend(vv.iter().map(|x| x.to_string()));
7249 } else {
7250 ks.push(kv.to_string());
7251 }
7252 }
7253 let last_key = ks.last().ok_or_else(|| {
7254 StrykeError::runtime("Hash slice increment needs at least one key", line)
7255 })?;
7256 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7257 Ok(if kind < 2 { new_val } else { last_old })
7258 }
7259
7260 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[StrykeValue]) -> StrykeValue {
7261 self.touch_env_hash(hash);
7262 let h = self.scope.get_hash(hash);
7263 let mut result = Vec::new();
7264 for kv in key_values {
7265 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7266 vv.iter().map(|x| x.to_string()).collect()
7267 } else {
7268 vec![kv.to_string()]
7269 };
7270 for k in key_strings {
7271 result.push(h.get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
7272 }
7273 }
7274 StrykeValue::array(result)
7275 }
7276
7277 pub(crate) fn compound_assign_named_hash_slice(
7279 &mut self,
7280 hash: &str,
7281 key_values: Vec<StrykeValue>,
7282 op: BinOp,
7283 rhs: StrykeValue,
7284 line: usize,
7285 ) -> Result<StrykeValue, FlowOrError> {
7286 let old_list = self.hash_slice_named_values(hash, &key_values);
7287 let last_old = old_list
7288 .to_list()
7289 .last()
7290 .cloned()
7291 .unwrap_or(StrykeValue::UNDEF);
7292 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7293 let mut ks: Vec<String> = Vec::new();
7294 for kv in &key_values {
7295 if let Some(vv) = kv.as_array_vec() {
7296 ks.extend(vv.iter().map(|x| x.to_string()));
7297 } else {
7298 ks.push(kv.to_string());
7299 }
7300 }
7301 if ks.is_empty() {
7302 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7303 }
7304 let last_key = ks.last().expect("non-empty ks");
7305 let container = StrykeValue::string(hash.to_string());
7306 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7307 Ok(new_val)
7308 }
7309
7310 pub(crate) fn named_hash_slice_inc_dec(
7312 &mut self,
7313 hash: &str,
7314 key_values: Vec<StrykeValue>,
7315 kind: u8,
7316 line: usize,
7317 ) -> Result<StrykeValue, FlowOrError> {
7318 let old_list = self.hash_slice_named_values(hash, &key_values);
7319 let last_old = old_list
7320 .to_list()
7321 .last()
7322 .cloned()
7323 .unwrap_or(StrykeValue::UNDEF);
7324 let new_val = if kind & 1 == 0 {
7325 StrykeValue::integer(last_old.to_int() + 1)
7326 } else {
7327 StrykeValue::integer(last_old.to_int() - 1)
7328 };
7329 let mut ks: Vec<String> = Vec::new();
7330 for kv in &key_values {
7331 if let Some(vv) = kv.as_array_vec() {
7332 ks.extend(vv.iter().map(|x| x.to_string()));
7333 } else {
7334 ks.push(kv.to_string());
7335 }
7336 }
7337 let last_key = ks.last().ok_or_else(|| {
7338 StrykeError::runtime("Hash slice increment needs at least one key", line)
7339 })?;
7340 let container = StrykeValue::string(hash.to_string());
7341 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7342 Ok(if kind < 2 { new_val } else { last_old })
7343 }
7344
7345 fn match_array_pattern_elems(
7346 &mut self,
7347 arr: &[StrykeValue],
7348 elems: &[MatchArrayElem],
7349 line: usize,
7350 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7351 let has_rest = elems
7352 .iter()
7353 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
7354 let mut binds: Vec<PatternBinding> = Vec::new();
7355 let mut idx = 0usize;
7356 for (i, elem) in elems.iter().enumerate() {
7357 match elem {
7358 MatchArrayElem::Rest => {
7359 if i != elems.len() - 1 {
7360 return Err(StrykeError::runtime(
7361 "internal: `*` must be last in array match pattern",
7362 line,
7363 )
7364 .into());
7365 }
7366 return Ok(Some(binds));
7367 }
7368 MatchArrayElem::RestBind(name) => {
7369 if i != elems.len() - 1 {
7370 return Err(StrykeError::runtime(
7371 "internal: `@name` rest bind must be last in array match pattern",
7372 line,
7373 )
7374 .into());
7375 }
7376 let tail = arr[idx..].to_vec();
7377 binds.push(PatternBinding::Array(name.clone(), tail));
7378 return Ok(Some(binds));
7379 }
7380 MatchArrayElem::CaptureScalar(name) => {
7381 if idx >= arr.len() {
7382 return Ok(None);
7383 }
7384 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
7385 idx += 1;
7386 }
7387 MatchArrayElem::Expr(e) => {
7388 if idx >= arr.len() {
7389 return Ok(None);
7390 }
7391 let expected = self.eval_expr(e)?;
7392 if !self.smartmatch_when(&arr[idx], &expected) {
7393 return Ok(None);
7394 }
7395 idx += 1;
7396 }
7397 }
7398 }
7399 if !has_rest && idx != arr.len() {
7400 return Ok(None);
7401 }
7402 Ok(Some(binds))
7403 }
7404
7405 fn match_hash_pattern_pairs(
7406 &mut self,
7407 h: &IndexMap<String, StrykeValue>,
7408 pairs: &[MatchHashPair],
7409 _line: usize,
7410 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7411 let mut binds = Vec::new();
7412 for pair in pairs {
7413 match pair {
7414 MatchHashPair::KeyOnly { key } => {
7415 let ks = self.eval_expr(key)?.to_string();
7416 if !h.contains_key(&ks) {
7417 return Ok(None);
7418 }
7419 }
7420 MatchHashPair::Capture { key, name } => {
7421 let ks = self.eval_expr(key)?.to_string();
7422 let Some(v) = h.get(&ks) else {
7423 return Ok(None);
7424 };
7425 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
7426 }
7427 }
7428 }
7429 Ok(Some(binds))
7430 }
7431
7432 #[inline]
7434 fn block_needs_scope(block: &Block) -> bool {
7435 block.iter().any(|s| match &s.kind {
7436 StmtKind::My(_)
7437 | StmtKind::Our(_)
7438 | StmtKind::Local(_)
7439 | StmtKind::State(_)
7440 | StmtKind::LocalExpr { .. } => true,
7441 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
7442 _ => false,
7443 })
7444 }
7445
7446 #[inline]
7448 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
7449 if Self::block_needs_scope(block) {
7450 self.exec_block(block)
7451 } else {
7452 self.exec_block_no_scope(block)
7453 }
7454 }
7455
7456 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
7457 let t0 = self.profiler.is_some().then(std::time::Instant::now);
7458 let r = self.exec_statement_inner(stmt);
7459 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
7460 prof.on_line(&self.file, stmt.line, t0.elapsed());
7461 }
7462 r
7463 }
7464
7465 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
7466 if let Err(e) = crate::perl_signal::poll(self) {
7467 return Err(FlowOrError::Error(e));
7468 }
7469 if let Err(e) = self.drain_pending_destroys(stmt.line) {
7470 return Err(FlowOrError::Error(e));
7471 }
7472 match &stmt.kind {
7473 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
7474 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
7475 StmtKind::If {
7476 condition,
7477 body,
7478 elsifs,
7479 else_block,
7480 } => {
7481 if self.eval_boolean_rvalue_condition(condition)? {
7482 return self.exec_block(body);
7483 }
7484 for (c, b) in elsifs {
7485 if self.eval_boolean_rvalue_condition(c)? {
7486 return self.exec_block(b);
7487 }
7488 }
7489 if let Some(eb) = else_block {
7490 return self.exec_block(eb);
7491 }
7492 Ok(StrykeValue::UNDEF)
7493 }
7494 StmtKind::Unless {
7495 condition,
7496 body,
7497 else_block,
7498 } => {
7499 if !self.eval_boolean_rvalue_condition(condition)? {
7500 return self.exec_block(body);
7501 }
7502 if let Some(eb) = else_block {
7503 return self.exec_block(eb);
7504 }
7505 Ok(StrykeValue::UNDEF)
7506 }
7507 StmtKind::While {
7508 condition,
7509 body,
7510 label,
7511 continue_block,
7512 } => {
7513 'outer: loop {
7514 if !self.eval_boolean_rvalue_condition(condition)? {
7515 break;
7516 }
7517 'inner: loop {
7518 match self.exec_block_smart(body) {
7519 Ok(_) => break 'inner,
7520 Err(FlowOrError::Flow(Flow::Last(ref l)))
7521 if l == label || l.is_none() =>
7522 {
7523 break 'outer;
7524 }
7525 Err(FlowOrError::Flow(Flow::Next(ref l)))
7526 if l == label || l.is_none() =>
7527 {
7528 if let Some(cb) = continue_block {
7529 let _ = self.exec_block_smart(cb);
7530 }
7531 continue 'outer;
7532 }
7533 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7534 if l == label || l.is_none() =>
7535 {
7536 continue 'inner;
7537 }
7538 Err(e) => return Err(e),
7539 }
7540 }
7541 if let Some(cb) = continue_block {
7542 let _ = self.exec_block_smart(cb);
7543 }
7544 }
7545 Ok(StrykeValue::UNDEF)
7546 }
7547 StmtKind::Until {
7548 condition,
7549 body,
7550 label,
7551 continue_block,
7552 } => {
7553 'outer: loop {
7554 if self.eval_boolean_rvalue_condition(condition)? {
7555 break;
7556 }
7557 'inner: loop {
7558 match self.exec_block(body) {
7559 Ok(_) => break 'inner,
7560 Err(FlowOrError::Flow(Flow::Last(ref l)))
7561 if l == label || l.is_none() =>
7562 {
7563 break 'outer;
7564 }
7565 Err(FlowOrError::Flow(Flow::Next(ref l)))
7566 if l == label || l.is_none() =>
7567 {
7568 if let Some(cb) = continue_block {
7569 let _ = self.exec_block_smart(cb);
7570 }
7571 continue 'outer;
7572 }
7573 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7574 if l == label || l.is_none() =>
7575 {
7576 continue 'inner;
7577 }
7578 Err(e) => return Err(e),
7579 }
7580 }
7581 if let Some(cb) = continue_block {
7582 let _ = self.exec_block_smart(cb);
7583 }
7584 }
7585 Ok(StrykeValue::UNDEF)
7586 }
7587 StmtKind::DoWhile { body, condition } => {
7588 loop {
7589 self.exec_block(body)?;
7590 if !self.eval_boolean_rvalue_condition(condition)? {
7591 break;
7592 }
7593 }
7594 Ok(StrykeValue::UNDEF)
7595 }
7596 StmtKind::For {
7597 init,
7598 condition,
7599 step,
7600 body,
7601 label,
7602 continue_block,
7603 } => {
7604 self.scope_push_hook();
7605 if let Some(init) = init {
7606 self.exec_statement(init)?;
7607 }
7608 'outer: loop {
7609 if let Some(cond) = condition {
7610 if !self.eval_boolean_rvalue_condition(cond)? {
7611 break;
7612 }
7613 }
7614 'inner: loop {
7615 match self.exec_block_smart(body) {
7616 Ok(_) => break 'inner,
7617 Err(FlowOrError::Flow(Flow::Last(ref l)))
7618 if l == label || l.is_none() =>
7619 {
7620 break 'outer;
7621 }
7622 Err(FlowOrError::Flow(Flow::Next(ref l)))
7623 if l == label || l.is_none() =>
7624 {
7625 if let Some(cb) = continue_block {
7626 let _ = self.exec_block_smart(cb);
7627 }
7628 if let Some(step) = step {
7629 self.eval_expr(step)?;
7630 }
7631 continue 'outer;
7632 }
7633 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7634 if l == label || l.is_none() =>
7635 {
7636 continue 'inner;
7637 }
7638 Err(e) => {
7639 self.scope_pop_hook();
7640 return Err(e);
7641 }
7642 }
7643 }
7644 if let Some(cb) = continue_block {
7645 let _ = self.exec_block_smart(cb);
7646 }
7647 if let Some(step) = step {
7648 self.eval_expr(step)?;
7649 }
7650 }
7651 self.scope_pop_hook();
7652 Ok(StrykeValue::UNDEF)
7653 }
7654 StmtKind::Foreach {
7655 var,
7656 list,
7657 body,
7658 label,
7659 continue_block,
7660 } => {
7661 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
7662 let items = list_val.to_list();
7663 self.scope_push_hook();
7664 self.scope.declare_scalar(var, StrykeValue::UNDEF);
7665 self.english_note_lexical_scalar(var);
7666 let mut i = 0usize;
7667 'outer: while i < items.len() {
7668 if var == "_" {
7675 self.scope.set_topic(items[i].clone());
7676 } else {
7677 self.scope
7678 .set_scalar(var, items[i].clone())
7679 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
7680 }
7681 'inner: loop {
7682 match self.exec_block_smart(body) {
7683 Ok(_) => break 'inner,
7684 Err(FlowOrError::Flow(Flow::Last(ref l)))
7685 if l == label || l.is_none() =>
7686 {
7687 break 'outer;
7688 }
7689 Err(FlowOrError::Flow(Flow::Next(ref l)))
7690 if l == label || l.is_none() =>
7691 {
7692 if let Some(cb) = continue_block {
7693 let _ = self.exec_block_smart(cb);
7694 }
7695 i += 1;
7696 continue 'outer;
7697 }
7698 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7699 if l == label || l.is_none() =>
7700 {
7701 continue 'inner;
7702 }
7703 Err(e) => {
7704 self.scope_pop_hook();
7705 return Err(e);
7706 }
7707 }
7708 }
7709 if let Some(cb) = continue_block {
7710 let _ = self.exec_block_smart(cb);
7711 }
7712 i += 1;
7713 }
7714 self.scope_pop_hook();
7715 Ok(StrykeValue::UNDEF)
7716 }
7717 StmtKind::SubDecl {
7718 name,
7719 params,
7720 body,
7721 prototype,
7722 } => {
7723 let key = self.qualify_sub_key(name);
7724 let captured = self.scope.capture();
7725 let closure_env = if captured.is_empty() {
7726 None
7727 } else {
7728 Some(captured)
7729 };
7730 let mut sub = StrykeSub {
7731 name: name.clone(),
7732 params: params.clone(),
7733 body: body.clone(),
7734 closure_env,
7735 prototype: prototype.clone(),
7736 fib_like: None,
7737 };
7738 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
7739 self.subs.insert(key, Arc::new(sub));
7740 Ok(StrykeValue::UNDEF)
7741 }
7742 StmtKind::StructDecl { def } => {
7743 if self.struct_defs.contains_key(&def.name) {
7744 return Err(StrykeError::runtime(
7745 format!("duplicate struct `{}`", def.name),
7746 stmt.line,
7747 )
7748 .into());
7749 }
7750 self.struct_defs
7751 .insert(def.name.clone(), Arc::new(def.clone()));
7752 Ok(StrykeValue::UNDEF)
7753 }
7754 StmtKind::EnumDecl { def } => {
7755 if self.enum_defs.contains_key(&def.name) {
7756 return Err(StrykeError::runtime(
7757 format!("duplicate enum `{}`", def.name),
7758 stmt.line,
7759 )
7760 .into());
7761 }
7762 self.enum_defs
7763 .insert(def.name.clone(), Arc::new(def.clone()));
7764 Ok(StrykeValue::UNDEF)
7765 }
7766 StmtKind::ClassDecl { def } => {
7767 if self.class_defs.contains_key(&def.name) {
7768 return Err(StrykeError::runtime(
7769 format!("duplicate class `{}`", def.name),
7770 stmt.line,
7771 )
7772 .into());
7773 }
7774 for parent_name in &def.extends {
7776 if let Some(parent_def) = self.class_defs.get(parent_name) {
7777 if parent_def.is_final {
7778 return Err(StrykeError::runtime(
7779 format!("cannot extend final class `{}`", parent_name),
7780 stmt.line,
7781 )
7782 .into());
7783 }
7784 for m in &def.methods {
7786 if let Some(parent_method) = parent_def.method(&m.name) {
7787 if parent_method.is_final {
7788 return Err(StrykeError::runtime(
7789 format!(
7790 "cannot override final method `{}` from class `{}`",
7791 m.name, parent_name
7792 ),
7793 stmt.line,
7794 )
7795 .into());
7796 }
7797 }
7798 }
7799 }
7800 }
7801 let mut def = def.clone();
7803 for trait_name in &def.implements.clone() {
7804 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
7805 for required in trait_def.required_methods() {
7806 let has_method = def.methods.iter().any(|m| m.name == required.name);
7807 if !has_method {
7808 return Err(StrykeError::runtime(
7809 format!(
7810 "class `{}` implements trait `{}` but does not define required method `{}`",
7811 def.name, trait_name, required.name
7812 ),
7813 stmt.line,
7814 )
7815 .into());
7816 }
7817 }
7818 for tm in &trait_def.methods {
7820 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
7821 def.methods.push(tm.clone());
7822 }
7823 }
7824 }
7825 }
7826 if !def.is_abstract {
7829 for parent_name in &def.extends.clone() {
7830 if let Some(parent_def) = self.class_defs.get(parent_name) {
7831 if parent_def.is_abstract {
7832 for m in &parent_def.methods {
7833 if m.body.is_none()
7834 && !def.methods.iter().any(|dm| dm.name == m.name)
7835 {
7836 return Err(StrykeError::runtime(
7837 format!(
7838 "class `{}` must implement abstract method `{}` from `{}`",
7839 def.name, m.name, parent_name
7840 ),
7841 stmt.line,
7842 )
7843 .into());
7844 }
7845 }
7846 }
7847 }
7848 }
7849 }
7850 for sf in &def.static_fields {
7852 let val = if let Some(ref expr) = sf.default {
7853 self.eval_expr(expr)?
7854 } else {
7855 StrykeValue::UNDEF
7856 };
7857 let key = format!("{}::{}", def.name, sf.name);
7858 self.scope.declare_scalar(&key, val);
7859 }
7860 for m in &def.methods {
7862 if let Some(ref body) = m.body {
7863 let fq = format!("{}::{}", def.name, m.name);
7864 let sub = Arc::new(StrykeSub {
7865 name: fq.clone(),
7866 params: m.params.clone(),
7867 body: body.clone(),
7868 closure_env: None,
7869 prototype: None,
7870 fib_like: None,
7871 });
7872 self.subs.insert(fq, sub);
7873 }
7874 }
7875 if !def.extends.is_empty() {
7877 let isa_key = format!("{}::ISA", def.name);
7878 let parents: Vec<StrykeValue> = def
7879 .extends
7880 .iter()
7881 .map(|p| StrykeValue::string(p.clone()))
7882 .collect();
7883 self.scope.declare_array(&isa_key, parents);
7884 }
7885 let arc_def = Arc::new(def);
7886 self.class_defs
7887 .insert(arc_def.name.clone(), Arc::clone(&arc_def));
7888 crate::serialize_normalize::register_class_def(arc_def);
7892 Ok(StrykeValue::UNDEF)
7893 }
7894 StmtKind::TraitDecl { def } => {
7895 if self.trait_defs.contains_key(&def.name) {
7896 return Err(StrykeError::runtime(
7897 format!("duplicate trait `{}`", def.name),
7898 stmt.line,
7899 )
7900 .into());
7901 }
7902 self.trait_defs
7903 .insert(def.name.clone(), Arc::new(def.clone()));
7904 Ok(StrykeValue::UNDEF)
7905 }
7906 StmtKind::My(decls) | StmtKind::Our(decls) => {
7907 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
7908 if decls.len() > 1 && decls[0].initializer.is_some() {
7911 let val = self.eval_expr_ctx(
7912 decls[0].initializer.as_ref().unwrap(),
7913 WantarrayCtx::List,
7914 )?;
7915 let items = val.to_list();
7916 let mut idx = 0;
7917 for decl in decls {
7918 match decl.sigil {
7919 Sigil::Scalar => {
7920 let v = items.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
7921 let skey = if is_our {
7922 self.stash_scalar_name_for_package(&decl.name)
7923 } else {
7924 decl.name.clone()
7925 };
7926 self.scope.declare_scalar_frozen(
7927 &skey,
7928 v,
7929 decl.frozen,
7930 decl.type_annotation.clone(),
7931 )?;
7932 self.english_note_lexical_scalar(&decl.name);
7933 if is_our {
7934 self.note_our_scalar(&decl.name);
7935 }
7936 idx += 1;
7937 }
7938 Sigil::Array => {
7939 let rest: Vec<StrykeValue> = items[idx..].to_vec();
7941 idx = items.len();
7942 if is_our {
7943 self.record_exporter_our_array_name(&decl.name, &rest);
7944 }
7945 let aname = self.stash_array_name_for_package(&decl.name);
7946 self.scope.declare_array(&aname, rest);
7947 }
7948 Sigil::Hash => {
7949 let rest: Vec<StrykeValue> = items[idx..].to_vec();
7950 idx = items.len();
7951 let mut map = IndexMap::new();
7952 let mut i = 0;
7953 while i + 1 < rest.len() {
7954 map.insert(rest[i].to_string(), rest[i + 1].clone());
7955 i += 2;
7956 }
7957 self.scope.declare_hash(&decl.name, map);
7958 }
7959 Sigil::Typeglob => {
7960 return Err(StrykeError::runtime(
7961 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
7962 stmt.line,
7963 )
7964 .into());
7965 }
7966 }
7967 }
7968 } else {
7969 for decl in decls {
7971 let compound_init = decl
7975 .initializer
7976 .as_ref()
7977 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
7978
7979 if compound_init {
7980 match decl.sigil {
7981 Sigil::Typeglob => {
7982 return Err(StrykeError::runtime(
7983 "compound assignment on typeglob declaration is not supported",
7984 stmt.line,
7985 )
7986 .into());
7987 }
7988 Sigil::Scalar => {
7989 let skey = if is_our {
7990 self.stash_scalar_name_for_package(&decl.name)
7991 } else {
7992 decl.name.clone()
7993 };
7994 self.scope.declare_scalar_frozen(
7995 &skey,
7996 StrykeValue::UNDEF,
7997 decl.frozen,
7998 decl.type_annotation.clone(),
7999 )?;
8000 self.english_note_lexical_scalar(&decl.name);
8001 if is_our {
8002 self.note_our_scalar(&decl.name);
8003 }
8004 let init = decl.initializer.as_ref().unwrap();
8005 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8006 }
8007 Sigil::Array => {
8008 let aname = self.stash_array_name_for_package(&decl.name);
8009 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
8010 let init = decl.initializer.as_ref().unwrap();
8011 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8012 if is_our {
8013 let items = self.scope.get_array(&aname);
8014 self.record_exporter_our_array_name(&decl.name, &items);
8015 }
8016 }
8017 Sigil::Hash => {
8018 self.scope.declare_hash_frozen(
8019 &decl.name,
8020 IndexMap::new(),
8021 decl.frozen,
8022 );
8023 let init = decl.initializer.as_ref().unwrap();
8024 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8025 }
8026 }
8027 continue;
8028 }
8029
8030 let val = if let Some(init) = &decl.initializer {
8031 let ctx = match decl.sigil {
8032 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8033 Sigil::Scalar if decl.list_context => WantarrayCtx::List,
8034 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8035 };
8036 let v = self.eval_expr_ctx(init, ctx)?;
8037 if decl.sigil == Sigil::Scalar && decl.list_context {
8039 v.to_list().first().cloned().unwrap_or(StrykeValue::UNDEF)
8040 } else {
8041 v
8042 }
8043 } else {
8044 StrykeValue::UNDEF
8045 };
8046 match decl.sigil {
8047 Sigil::Typeglob => {
8048 return Err(StrykeError::runtime(
8049 "`my *FH` / typeglob declaration is not supported",
8050 stmt.line,
8051 )
8052 .into());
8053 }
8054 Sigil::Scalar => {
8055 let skey = if is_our {
8056 self.stash_scalar_name_for_package(&decl.name)
8057 } else {
8058 decl.name.clone()
8059 };
8060 self.scope.declare_scalar_frozen(
8061 &skey,
8062 val,
8063 decl.frozen,
8064 decl.type_annotation.clone(),
8065 )?;
8066 self.english_note_lexical_scalar(&decl.name);
8067 if is_our {
8068 self.note_our_scalar(&decl.name);
8069 }
8070 }
8071 Sigil::Array => {
8072 let items = val.to_list();
8073 if is_our {
8074 self.record_exporter_our_array_name(&decl.name, &items);
8075 }
8076 let aname = self.stash_array_name_for_package(&decl.name);
8077 self.scope.declare_array_frozen(&aname, items, decl.frozen);
8078 }
8079 Sigil::Hash => {
8080 let items = val.to_list();
8081 let mut map = IndexMap::new();
8082 let mut i = 0;
8083 while i + 1 < items.len() {
8084 let k = items[i].to_string();
8085 let v = items[i + 1].clone();
8086 map.insert(k, v);
8087 i += 2;
8088 }
8089 self.scope.declare_hash_frozen(&decl.name, map, decl.frozen);
8090 }
8091 }
8092 }
8093 }
8094 Ok(StrykeValue::UNDEF)
8095 }
8096 StmtKind::State(decls) => {
8097 for decl in decls {
8100 let state_key = format!("{}:{}", stmt.line, decl.name);
8101 match decl.sigil {
8102 Sigil::Scalar => {
8103 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
8104 self.scope.declare_scalar(&decl.name, prev);
8106 } else {
8107 let val = if let Some(init) = &decl.initializer {
8109 self.eval_expr(init)?
8110 } else {
8111 StrykeValue::UNDEF
8112 };
8113 self.state_vars.insert(state_key.clone(), val.clone());
8114 self.scope.declare_scalar(&decl.name, val);
8115 }
8116 if let Some(frame) = self.state_bindings_stack.last_mut() {
8118 frame.push((decl.name.clone(), state_key));
8119 }
8120 }
8121 _ => {
8122 let val = if let Some(init) = &decl.initializer {
8124 self.eval_expr(init)?
8125 } else {
8126 StrykeValue::UNDEF
8127 };
8128 match decl.sigil {
8129 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
8130 Sigil::Hash => {
8131 let items = val.to_list();
8132 let mut map = IndexMap::new();
8133 let mut i = 0;
8134 while i + 1 < items.len() {
8135 map.insert(items[i].to_string(), items[i + 1].clone());
8136 i += 2;
8137 }
8138 self.scope.declare_hash(&decl.name, map);
8139 }
8140 _ => {}
8141 }
8142 }
8143 }
8144 }
8145 Ok(StrykeValue::UNDEF)
8146 }
8147 StmtKind::Local(decls) => {
8148 if decls.len() > 1 && decls[0].initializer.is_some() {
8149 let val = self.eval_expr_ctx(
8150 decls[0].initializer.as_ref().unwrap(),
8151 WantarrayCtx::List,
8152 )?;
8153 let items = val.to_list();
8154 let mut idx = 0;
8155 for decl in decls {
8156 match decl.sigil {
8157 Sigil::Scalar => {
8158 let v = items.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
8159 idx += 1;
8160 self.scope.local_set_scalar(&decl.name, v)?;
8161 }
8162 Sigil::Array => {
8163 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8164 idx = items.len();
8165 self.scope.local_set_array(&decl.name, rest)?;
8166 }
8167 Sigil::Hash => {
8168 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8169 idx = items.len();
8170 if decl.name == "ENV" {
8171 self.materialize_env_if_needed();
8172 }
8173 let mut map = IndexMap::new();
8174 let mut i = 0;
8175 while i + 1 < rest.len() {
8176 map.insert(rest[i].to_string(), rest[i + 1].clone());
8177 i += 2;
8178 }
8179 self.scope.local_set_hash(&decl.name, map)?;
8180 }
8181 Sigil::Typeglob => {
8182 return Err(StrykeError::runtime(
8183 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
8184 stmt.line,
8185 )
8186 .into());
8187 }
8188 }
8189 }
8190 Ok(val)
8191 } else {
8192 let mut last_val = StrykeValue::UNDEF;
8193 for decl in decls {
8194 let val = if let Some(init) = &decl.initializer {
8195 let ctx = match decl.sigil {
8196 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8197 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8198 };
8199 self.eval_expr_ctx(init, ctx)?
8200 } else {
8201 StrykeValue::UNDEF
8202 };
8203 last_val = val.clone();
8204 match decl.sigil {
8205 Sigil::Typeglob => {
8206 let old = self.glob_handle_alias.remove(&decl.name);
8207 if let Some(frame) = self.glob_restore_frames.last_mut() {
8208 frame.push((decl.name.clone(), old));
8209 }
8210 if let Some(init) = &decl.initializer {
8211 if let ExprKind::Typeglob(rhs) = &init.kind {
8212 self.glob_handle_alias
8213 .insert(decl.name.clone(), rhs.clone());
8214 } else {
8215 return Err(StrykeError::runtime(
8216 "local *GLOB = *OTHER — right side must be a typeglob",
8217 stmt.line,
8218 )
8219 .into());
8220 }
8221 }
8222 }
8223 Sigil::Scalar => {
8224 if Self::is_special_scalar_name_for_set(&decl.name) {
8230 let old = self.get_special_var(&decl.name);
8231 if let Some(frame) = self.special_var_restore_frames.last_mut()
8232 {
8233 frame.push((decl.name.clone(), old));
8234 }
8235 self.set_special_var(&decl.name, &val)
8236 .map_err(|e| e.at_line(stmt.line))?;
8237 }
8238 self.scope.local_set_scalar(&decl.name, val)?;
8239 }
8240 Sigil::Array => {
8241 self.scope.local_set_array(&decl.name, val.to_list())?;
8242 }
8243 Sigil::Hash => {
8244 if decl.name == "ENV" {
8245 self.materialize_env_if_needed();
8246 }
8247 let items = val.to_list();
8248 let mut map = IndexMap::new();
8249 let mut i = 0;
8250 while i + 1 < items.len() {
8251 let k = items[i].to_string();
8252 let v = items[i + 1].clone();
8253 map.insert(k, v);
8254 i += 2;
8255 }
8256 self.scope.local_set_hash(&decl.name, map)?;
8257 }
8258 }
8259 }
8260 Ok(last_val)
8261 }
8262 }
8263 StmtKind::LocalExpr {
8264 target,
8265 initializer,
8266 } => {
8267 let rhs_name = |init: &Expr| -> StrykeResult<Option<String>> {
8268 match &init.kind {
8269 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
8270 _ => Err(StrykeError::runtime(
8271 "local *GLOB = *OTHER — right side must be a typeglob",
8272 stmt.line,
8273 )),
8274 }
8275 };
8276 match &target.kind {
8277 ExprKind::Typeglob(name) => {
8278 let rhs = if let Some(init) = initializer {
8279 rhs_name(init)?
8280 } else {
8281 None
8282 };
8283 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
8284 return Ok(StrykeValue::UNDEF);
8285 }
8286 ExprKind::Deref {
8287 expr,
8288 kind: Sigil::Typeglob,
8289 } => {
8290 let lhs = self.eval_expr(expr)?.to_string();
8291 let rhs = if let Some(init) = initializer {
8292 rhs_name(init)?
8293 } else {
8294 None
8295 };
8296 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8297 return Ok(StrykeValue::UNDEF);
8298 }
8299 ExprKind::TypeglobExpr(e) => {
8300 let lhs = self.eval_expr(e)?.to_string();
8301 let rhs = if let Some(init) = initializer {
8302 rhs_name(init)?
8303 } else {
8304 None
8305 };
8306 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8307 return Ok(StrykeValue::UNDEF);
8308 }
8309 _ => {}
8310 }
8311 let val = if let Some(init) = initializer {
8312 let ctx = match &target.kind {
8313 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
8314 _ => WantarrayCtx::Scalar,
8315 };
8316 self.eval_expr_ctx(init, ctx)?
8317 } else {
8318 StrykeValue::UNDEF
8319 };
8320 match &target.kind {
8321 ExprKind::ScalarVar(name) => {
8322 if Self::is_special_scalar_name_for_set(name) {
8325 let old = self.get_special_var(name);
8326 if let Some(frame) = self.special_var_restore_frames.last_mut() {
8327 frame.push((name.clone(), old));
8328 }
8329 self.set_special_var(name, &val)
8330 .map_err(|e| e.at_line(stmt.line))?;
8331 }
8332 self.scope.local_set_scalar(name, val.clone())?;
8333 }
8334 ExprKind::ArrayVar(name) => {
8335 self.scope.local_set_array(name, val.to_list())?;
8336 }
8337 ExprKind::HashVar(name) => {
8338 if name == "ENV" {
8339 self.materialize_env_if_needed();
8340 }
8341 let items = val.to_list();
8342 let mut map = IndexMap::new();
8343 let mut i = 0;
8344 while i + 1 < items.len() {
8345 map.insert(items[i].to_string(), items[i + 1].clone());
8346 i += 2;
8347 }
8348 self.scope.local_set_hash(name, map)?;
8349 }
8350 ExprKind::HashElement { hash, key } => {
8351 let ks = self.eval_expr(key)?.to_string();
8352 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
8353 }
8354 ExprKind::ArrayElement { array, index } => {
8355 self.check_strict_array_var(array, stmt.line)?;
8356 let aname = self.stash_array_name_for_package(array);
8357 let idx = self.eval_expr(index)?.to_int();
8358 self.scope
8359 .local_set_array_element(&aname, idx, val.clone())?;
8360 }
8361 _ => {
8362 return Err(StrykeError::runtime(
8363 format!(
8364 "local on this lvalue is not supported yet ({:?})",
8365 target.kind
8366 ),
8367 stmt.line,
8368 )
8369 .into());
8370 }
8371 }
8372 Ok(val)
8373 }
8374 StmtKind::MySync(decls) => {
8375 for decl in decls {
8376 let val = if let Some(init) = &decl.initializer {
8377 self.eval_expr(init)?
8378 } else {
8379 StrykeValue::UNDEF
8380 };
8381 match decl.sigil {
8382 Sigil::Typeglob => {
8383 return Err(StrykeError::runtime(
8384 "`mysync` does not support typeglob variables",
8385 stmt.line,
8386 )
8387 .into());
8388 }
8389 Sigil::Scalar => {
8390 let stored = if val.is_mysync_deque_or_heap() {
8393 val
8394 } else {
8395 StrykeValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(
8396 val,
8397 )))
8398 };
8399 self.scope.declare_scalar(&decl.name, stored);
8400 }
8401 Sigil::Array => {
8402 self.scope.declare_atomic_array(&decl.name, val.to_list());
8403 }
8404 Sigil::Hash => {
8405 let items = val.to_list();
8406 let mut map = IndexMap::new();
8407 let mut i = 0;
8408 while i + 1 < items.len() {
8409 map.insert(items[i].to_string(), items[i + 1].clone());
8410 i += 2;
8411 }
8412 self.scope.declare_atomic_hash(&decl.name, map);
8413 }
8414 }
8415 }
8416 Ok(StrykeValue::UNDEF)
8417 }
8418 StmtKind::OurSync(decls) => {
8419 for decl in decls {
8426 let val = if let Some(init) = &decl.initializer {
8427 self.eval_expr(init)?
8428 } else {
8429 StrykeValue::UNDEF
8430 };
8431 match decl.sigil {
8432 Sigil::Typeglob => {
8433 return Err(StrykeError::runtime(
8434 "`oursync` does not support typeglob variables",
8435 stmt.line,
8436 )
8437 .into());
8438 }
8439 Sigil::Scalar => {
8440 let stash = self.stash_scalar_name_for_package(&decl.name);
8441 let stored = if val.is_mysync_deque_or_heap() {
8442 val
8443 } else {
8444 StrykeValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(
8445 val,
8446 )))
8447 };
8448 self.scope.declare_scalar(&stash, stored);
8449 self.english_note_lexical_scalar(&decl.name);
8450 self.note_our_scalar(&decl.name);
8451 }
8452 Sigil::Array => {
8453 let stash = self.stash_array_name_for_package(&decl.name);
8454 self.scope.declare_atomic_array(&stash, val.to_list());
8455 self.english_note_lexical_scalar(&decl.name);
8456 self.note_our_scalar(&decl.name);
8457 }
8458 Sigil::Hash => {
8459 let items = val.to_list();
8460 let mut map = IndexMap::new();
8461 let mut i = 0;
8462 while i + 1 < items.len() {
8463 map.insert(items[i].to_string(), items[i + 1].clone());
8464 i += 2;
8465 }
8466 self.scope.declare_atomic_hash(&decl.name, map);
8469 self.english_note_lexical_scalar(&decl.name);
8470 self.note_our_scalar(&decl.name);
8471 }
8472 }
8473 }
8474 Ok(StrykeValue::UNDEF)
8475 }
8476 StmtKind::Package { name } => {
8477 let _ = self
8479 .scope
8480 .set_scalar("__PACKAGE__", StrykeValue::string(name.clone()));
8481 Ok(StrykeValue::UNDEF)
8482 }
8483 StmtKind::UsePerlVersion { .. } => Ok(StrykeValue::UNDEF),
8484 StmtKind::Use { .. } => {
8485 Ok(StrykeValue::UNDEF)
8487 }
8488 StmtKind::UseOverload { pairs } => {
8489 self.install_use_overload_pairs(pairs);
8490 Ok(StrykeValue::UNDEF)
8491 }
8492 StmtKind::No { .. } => {
8493 Ok(StrykeValue::UNDEF)
8495 }
8496 StmtKind::Return(val) => {
8497 let v = if let Some(e) = val {
8498 self.eval_expr_ctx(e, self.wantarray_kind)?
8502 } else {
8503 StrykeValue::UNDEF
8504 };
8505 Err(Flow::Return(v).into())
8506 }
8507 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
8508 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
8509 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
8510 StmtKind::Block(block) => self.exec_block(block),
8511 StmtKind::Begin(_)
8512 | StmtKind::UnitCheck(_)
8513 | StmtKind::Check(_)
8514 | StmtKind::Init(_)
8515 | StmtKind::End(_) => Ok(StrykeValue::UNDEF),
8516 StmtKind::Empty => Ok(StrykeValue::UNDEF),
8517 StmtKind::Goto { target } => {
8518 if let ExprKind::SubroutineRef(name) = &target.kind {
8520 return Err(Flow::GotoSub(name.clone()).into());
8521 }
8522 Err(StrykeError::runtime("goto reached outside goto-aware block", stmt.line).into())
8523 }
8524 StmtKind::EvalTimeout { timeout, body } => {
8525 let secs = self.eval_expr(timeout)?.to_number();
8526 self.eval_timeout_block(body, secs, stmt.line)
8527 }
8528 StmtKind::Tie {
8529 target,
8530 class,
8531 args,
8532 } => {
8533 let kind = match &target {
8534 TieTarget::Scalar(_) => 0u8,
8535 TieTarget::Array(_) => 1u8,
8536 TieTarget::Hash(_) => 2u8,
8537 };
8538 let name = match &target {
8539 TieTarget::Scalar(s) => s.as_str(),
8540 TieTarget::Array(a) => a.as_str(),
8541 TieTarget::Hash(h) => h.as_str(),
8542 };
8543 let mut vals = vec![self.eval_expr(class)?];
8544 for a in args {
8545 vals.push(self.eval_expr(a)?);
8546 }
8547 self.tie_execute(kind, name, vals, stmt.line)
8548 .map_err(Into::into)
8549 }
8550 StmtKind::TryCatch {
8551 try_block,
8552 catch_var,
8553 catch_block,
8554 finally_block,
8555 } => match self.exec_block(try_block) {
8556 Ok(v) => {
8557 if let Some(fb) = finally_block {
8558 self.exec_block(fb)?;
8559 }
8560 Ok(v)
8561 }
8562 Err(FlowOrError::Error(e)) => {
8563 if matches!(e.kind, ErrorKind::Exit(_)) {
8564 return Err(FlowOrError::Error(e));
8565 }
8566 self.scope_push_hook();
8567 self.scope
8568 .declare_scalar(catch_var, StrykeValue::string(e.to_string()));
8569 self.english_note_lexical_scalar(catch_var);
8570 let r = self.exec_block(catch_block);
8571 self.scope_pop_hook();
8572 if let Some(fb) = finally_block {
8573 self.exec_block(fb)?;
8574 }
8575 r
8576 }
8577 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
8578 },
8579 StmtKind::Given { topic, body } => self.exec_given(topic, body),
8580 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(StrykeError::runtime(
8581 "when/default may only appear inside a given block",
8582 stmt.line,
8583 )
8584 .into()),
8585 StmtKind::FormatDecl { .. } => {
8586 Ok(StrykeValue::UNDEF)
8588 }
8589 StmtKind::AdviceDecl {
8590 kind,
8591 pattern,
8592 body,
8593 } => {
8594 let id = self.next_intercept_id;
8600 self.next_intercept_id = id.saturating_add(1);
8601 self.intercepts.push(crate::aop::Intercept {
8602 id,
8603 kind: *kind,
8604 pattern: pattern.clone(),
8605 body: body.clone(),
8606 body_block_idx: u16::MAX,
8607 });
8608 Ok(StrykeValue::UNDEF)
8609 }
8610 StmtKind::Continue(block) => self.exec_block_smart(block),
8611 }
8612 }
8613
8614 #[inline]
8615 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
8616 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
8617 }
8618
8619 pub(crate) fn scalar_compound_assign_scalar_target(
8623 &mut self,
8624 name: &str,
8625 op: BinOp,
8626 rhs: StrykeValue,
8627 ) -> Result<StrykeValue, StrykeError> {
8628 if op == BinOp::Concat {
8629 return self.scope.scalar_concat_inplace(name, &rhs);
8630 }
8631 self.scope
8632 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs))
8633 }
8634
8635 fn compound_scalar_binop(old: &StrykeValue, op: BinOp, rhs: &StrykeValue) -> StrykeValue {
8636 match op {
8637 BinOp::Add => {
8638 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8639 StrykeValue::integer(a.wrapping_add(b))
8640 } else {
8641 StrykeValue::float(old.to_number() + rhs.to_number())
8642 }
8643 }
8644 BinOp::Sub => {
8645 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8646 StrykeValue::integer(a.wrapping_sub(b))
8647 } else {
8648 StrykeValue::float(old.to_number() - rhs.to_number())
8649 }
8650 }
8651 BinOp::Mul => {
8652 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8653 StrykeValue::integer(a.wrapping_mul(b))
8654 } else {
8655 StrykeValue::float(old.to_number() * rhs.to_number())
8656 }
8657 }
8658 BinOp::BitAnd => {
8659 if let Some(s) = crate::value::set_intersection(old, rhs) {
8660 s
8661 } else {
8662 StrykeValue::integer(old.to_int() & rhs.to_int())
8663 }
8664 }
8665 BinOp::BitOr => {
8666 if let Some(s) = crate::value::set_union(old, rhs) {
8667 s
8668 } else {
8669 StrykeValue::integer(old.to_int() | rhs.to_int())
8670 }
8671 }
8672 BinOp::BitXor => StrykeValue::integer(old.to_int() ^ rhs.to_int()),
8673 BinOp::ShiftLeft => StrykeValue::integer(old.to_int() << rhs.to_int()),
8674 BinOp::ShiftRight => StrykeValue::integer(old.to_int() >> rhs.to_int()),
8675 BinOp::Div => StrykeValue::float(old.to_number() / rhs.to_number()),
8676 BinOp::Mod => {
8677 let b = rhs.to_int();
8682 if b == 0 {
8683 StrykeValue::integer(0)
8684 } else {
8685 StrykeValue::integer(crate::value::perl_mod_i64(old.to_int(), b))
8686 }
8687 }
8688 BinOp::Pow => StrykeValue::float(old.to_number().powf(rhs.to_number())),
8689 BinOp::LogOr => {
8690 if old.is_true() {
8691 old.clone()
8692 } else {
8693 rhs.clone()
8694 }
8695 }
8696 BinOp::DefinedOr => {
8697 if !old.is_undef() {
8698 old.clone()
8699 } else {
8700 rhs.clone()
8701 }
8702 }
8703 BinOp::LogAnd => {
8704 if old.is_true() {
8705 rhs.clone()
8706 } else {
8707 old.clone()
8708 }
8709 }
8710 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
8711 }
8712 }
8713
8714 fn eval_hash_slice_key_components(
8718 &mut self,
8719 key_expr: &Expr,
8720 ) -> Result<Vec<String>, FlowOrError> {
8721 let v = if matches!(
8722 key_expr.kind,
8723 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
8724 ) {
8725 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
8726 } else {
8727 self.eval_expr(key_expr)?
8728 };
8729 if let Some(vv) = v.as_array_vec() {
8730 Ok(vv.iter().map(|x| x.to_string()).collect())
8731 } else {
8732 Ok(vec![v.to_string()])
8733 }
8734 }
8735
8736 pub(crate) fn symbolic_deref(
8738 &mut self,
8739 val: StrykeValue,
8740 kind: Sigil,
8741 line: usize,
8742 ) -> ExecResult {
8743 match kind {
8744 Sigil::Scalar => {
8745 if let Some(name) = val.as_scalar_binding_name() {
8746 return Ok(self.get_special_var(&name));
8747 }
8748 if let Some(r) = val.as_scalar_ref() {
8749 return Ok(r.read().clone());
8750 }
8751 if let Some(r) = val.as_array_ref() {
8753 return Ok(StrykeValue::array(r.read().clone()));
8754 }
8755 if let Some(name) = val.as_array_binding_name() {
8756 return Ok(StrykeValue::array(self.scope.get_array(&name)));
8757 }
8758 if let Some(r) = val.as_hash_ref() {
8759 return Ok(StrykeValue::hash(r.read().clone()));
8760 }
8761 if let Some(name) = val.as_hash_binding_name() {
8762 self.touch_env_hash(&name);
8763 return Ok(StrykeValue::hash(self.scope.get_hash(&name)));
8764 }
8765 if let Some(s) = val.as_str() {
8766 if self.strict_refs {
8767 return Err(StrykeError::runtime(
8768 format!(
8769 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
8770 s
8771 ),
8772 line,
8773 )
8774 .into());
8775 }
8776 return Ok(self.get_special_var(&s));
8777 }
8778 Err(StrykeError::runtime("Can't dereference non-reference as scalar", line).into())
8779 }
8780 Sigil::Array => {
8781 if let Some(r) = val.as_array_ref() {
8782 return Ok(StrykeValue::array(r.read().clone()));
8783 }
8784 if let Some(name) = val.as_array_binding_name() {
8785 return Ok(StrykeValue::array(self.scope.get_array(&name)));
8786 }
8787 if val.is_undef() {
8788 if self.strict_refs {
8789 return Err(StrykeError::runtime(
8790 "Can't use an undefined value as an ARRAY reference",
8791 line,
8792 )
8793 .into());
8794 }
8795 return Ok(StrykeValue::array(vec![]));
8796 }
8797 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
8803 let s = val.to_string();
8804 if self.strict_refs {
8805 return Err(StrykeError::runtime(
8806 format!(
8807 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8808 s
8809 ),
8810 line,
8811 )
8812 .into());
8813 }
8814 return Ok(StrykeValue::array(self.scope.get_array(&s)));
8815 }
8816 Err(StrykeError::runtime("Can't dereference non-reference as array", line).into())
8817 }
8818 Sigil::Hash => {
8819 if let Some(r) = val.as_hash_ref() {
8820 return Ok(StrykeValue::hash(r.read().clone()));
8821 }
8822 if let Some(name) = val.as_hash_binding_name() {
8823 self.touch_env_hash(&name);
8824 return Ok(StrykeValue::hash(self.scope.get_hash(&name)));
8825 }
8826 if let Some(c) = val.as_class_inst() {
8834 let all_fields = self.collect_class_fields_full(&c.def);
8835 let values = c.get_values();
8836 let mut map = IndexMap::new();
8837 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
8838 if let Some(v) = values.get(i) {
8839 map.insert(name.clone(), v.clone());
8840 }
8841 }
8842 return Ok(StrykeValue::hash(map));
8843 }
8844 if let Some(s) = val.as_struct_inst() {
8847 let values = s.get_values();
8848 let mut map = IndexMap::new();
8849 for (i, field) in s.def.fields.iter().enumerate() {
8850 if let Some(v) = values.get(i) {
8851 map.insert(field.name.clone(), v.clone());
8852 }
8853 }
8854 return Ok(StrykeValue::hash(map));
8855 }
8856 if let Some(b) = val.as_blessed_ref() {
8861 let inner = b.data.read().clone();
8862 if let Some(r) = inner.as_hash_ref() {
8863 return Ok(StrykeValue::hash(r.read().clone()));
8864 }
8865 if let Some(h) = inner.as_hash_map() {
8866 return Ok(StrykeValue::hash(h));
8867 }
8868 }
8869 if val.is_undef() {
8870 if self.strict_refs {
8871 return Err(StrykeError::runtime(
8872 "Can't use an undefined value as a HASH reference",
8873 line,
8874 )
8875 .into());
8876 }
8877 return Ok(StrykeValue::hash(IndexMap::new()));
8878 }
8879 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
8880 let s = val.to_string();
8881 if self.strict_refs {
8882 return Err(StrykeError::runtime(
8883 format!(
8884 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8885 s
8886 ),
8887 line,
8888 )
8889 .into());
8890 }
8891 self.touch_env_hash(&s);
8892 return Ok(StrykeValue::hash(self.scope.get_hash(&s)));
8893 }
8894 Err(StrykeError::runtime("Can't dereference non-reference as hash", line).into())
8895 }
8896 Sigil::Typeglob => {
8897 if let Some(s) = val.as_str() {
8898 return Ok(StrykeValue::string(self.resolve_io_handle_name(&s)));
8899 }
8900 Err(
8901 StrykeError::runtime("Can't dereference non-reference as typeglob", line)
8902 .into(),
8903 )
8904 }
8905 }
8906 }
8907
8908 #[inline]
8911 pub(crate) fn peel_array_ref_for_list_join(&self, v: StrykeValue) -> StrykeValue {
8912 if let Some(r) = v.as_array_ref() {
8913 return StrykeValue::array(r.read().clone());
8914 }
8915 v
8916 }
8917
8918 pub(crate) fn make_array_ref_alias(&self, val: StrykeValue, line: usize) -> ExecResult {
8920 if let Some(a) = val.as_array_ref() {
8921 return Ok(StrykeValue::array_ref(Arc::clone(&a)));
8922 }
8923 if let Some(name) = val.as_array_binding_name() {
8924 return Ok(StrykeValue::array_binding_ref(name));
8925 }
8926 if let Some(s) = val.as_str() {
8927 if self.strict_refs {
8928 return Err(StrykeError::runtime(
8929 format!(
8930 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
8931 s
8932 ),
8933 line,
8934 )
8935 .into());
8936 }
8937 return Ok(StrykeValue::array_binding_ref(s.to_string()));
8938 }
8939 if let Some(r) = val.as_scalar_ref() {
8940 let inner = r.read().clone();
8941 return self.make_array_ref_alias(inner, line);
8942 }
8943 Err(StrykeError::runtime("Can't make array reference from value", line).into())
8944 }
8945
8946 pub(crate) fn make_hash_ref_alias(&self, val: StrykeValue, line: usize) -> ExecResult {
8948 if let Some(h) = val.as_hash_ref() {
8949 return Ok(StrykeValue::hash_ref(Arc::clone(&h)));
8950 }
8951 if let Some(name) = val.as_hash_binding_name() {
8952 return Ok(StrykeValue::hash_binding_ref(name));
8953 }
8954 if let Some(s) = val.as_str() {
8955 if self.strict_refs {
8956 return Err(StrykeError::runtime(
8957 format!(
8958 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
8959 s
8960 ),
8961 line,
8962 )
8963 .into());
8964 }
8965 return Ok(StrykeValue::hash_binding_ref(s.to_string()));
8966 }
8967 if let Some(r) = val.as_scalar_ref() {
8968 let inner = r.read().clone();
8969 return self.make_hash_ref_alias(inner, line);
8970 }
8971 Err(StrykeError::runtime("Can't make hash reference from value", line).into())
8972 }
8973
8974 pub(crate) fn process_case_escapes(s: &str) -> String {
8977 if !s.contains('\\') {
8979 return s.to_string();
8980 }
8981 let mut result = String::with_capacity(s.len());
8982 let mut chars = s.chars().peekable();
8983 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
8987 if c == '\\' {
8988 match chars.peek() {
8989 Some(&'U') => {
8990 chars.next();
8991 mode = Some('U');
8992 continue;
8993 }
8994 Some(&'L') => {
8995 chars.next();
8996 mode = Some('L');
8997 continue;
8998 }
8999 Some(&'Q') => {
9000 chars.next();
9001 mode = Some('Q');
9002 continue;
9003 }
9004 Some(&'E') => {
9005 chars.next();
9006 mode = None;
9007 next_char_mod = None;
9008 continue;
9009 }
9010 Some(&'u') => {
9011 chars.next();
9012 next_char_mod = Some('u');
9013 continue;
9014 }
9015 Some(&'l') => {
9016 chars.next();
9017 next_char_mod = Some('l');
9018 continue;
9019 }
9020 _ => {}
9021 }
9022 }
9023
9024 let ch = c;
9025
9026 if let Some(m) = next_char_mod.take() {
9028 let transformed = match m {
9029 'u' => ch.to_uppercase().next().unwrap_or(ch),
9030 'l' => ch.to_lowercase().next().unwrap_or(ch),
9031 _ => ch,
9032 };
9033 result.push(transformed);
9034 } else {
9035 match mode {
9037 Some('U') => {
9038 for uc in ch.to_uppercase() {
9039 result.push(uc);
9040 }
9041 }
9042 Some('L') => {
9043 for lc in ch.to_lowercase() {
9044 result.push(lc);
9045 }
9046 }
9047 Some('Q') => {
9048 if !ch.is_ascii_alphanumeric() && ch != '_' {
9049 result.push('\\');
9050 }
9051 result.push(ch);
9052 }
9053 None | Some(_) => {
9054 result.push(ch);
9055 }
9056 }
9057 }
9058 }
9059 result
9060 }
9061
9062 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
9063 let line = expr.line;
9064 match &expr.kind {
9065 ExprKind::Integer(n) => Ok(StrykeValue::integer(*n)),
9066 ExprKind::Float(f) => Ok(StrykeValue::float(*f)),
9067 ExprKind::String(s) => {
9068 let processed = Self::process_case_escapes(s);
9069 Ok(StrykeValue::string(processed))
9070 }
9071 ExprKind::Bareword(s) => {
9072 if s == "__PACKAGE__" {
9073 return Ok(StrykeValue::string(self.current_package()));
9074 }
9075 if let Some(sub) = self.resolve_sub_by_name(s) {
9076 return self.call_sub(&sub, vec![], ctx, line);
9077 }
9078 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
9080 return r.map_err(Into::into);
9081 }
9082 Ok(StrykeValue::string(s.clone()))
9083 }
9084 ExprKind::Undef => Ok(StrykeValue::UNDEF),
9085 ExprKind::MagicConst(MagicConstKind::File) => {
9086 Ok(StrykeValue::string(self.file.clone()))
9087 }
9088 ExprKind::MagicConst(MagicConstKind::Line) => {
9089 Ok(StrykeValue::integer(expr.line as i64))
9090 }
9091 ExprKind::MagicConst(MagicConstKind::Sub) => {
9092 if let Some(sub) = self.current_sub_stack.last().cloned() {
9093 Ok(StrykeValue::code_ref(sub))
9094 } else {
9095 Ok(StrykeValue::UNDEF)
9096 }
9097 }
9098 ExprKind::Regex(pattern, flags) => {
9099 if ctx == WantarrayCtx::Void {
9100 let topic = self.scope.get_scalar("_");
9102 let s = topic.to_string();
9103 self.regex_match_execute(s, pattern, flags, false, "_", line)
9104 } else {
9105 let re = self.compile_regex(pattern, flags, line)?;
9106 Ok(StrykeValue::regex(re, pattern.clone(), flags.clone()))
9107 }
9108 }
9109 ExprKind::QW(words) => Ok(StrykeValue::array(
9110 words
9111 .iter()
9112 .map(|w| StrykeValue::string(w.clone()))
9113 .collect(),
9114 )),
9115
9116 ExprKind::InterpolatedString(parts) => {
9118 let mut raw_result = String::new();
9119 for part in parts {
9120 match part {
9121 StringPart::Literal(s) => raw_result.push_str(s),
9122 StringPart::ScalarVar(name) => {
9123 self.check_strict_scalar_var(name, line)?;
9124 let val = self.get_special_var(name);
9125 let s = self.stringify_value(val, line)?;
9126 raw_result.push_str(&s);
9127 }
9128 StringPart::ArrayVar(name) => {
9129 self.check_strict_array_var(name, line)?;
9130 let aname = self.stash_array_name_for_package(name);
9131 let arr = self.scope.get_array(&aname);
9132 let mut parts = Vec::with_capacity(arr.len());
9133 for v in &arr {
9134 parts.push(self.stringify_value(v.clone(), line)?);
9135 }
9136 let sep = self.list_separator.clone();
9137 raw_result.push_str(&parts.join(&sep));
9138 }
9139 StringPart::Expr(e) => {
9140 if let ExprKind::ArraySlice { array, .. } = &e.kind {
9141 self.check_strict_array_var(array, line)?;
9142 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9143 let val = self.peel_array_ref_for_list_join(val);
9144 let list = val.to_list();
9145 let sep = self.list_separator.clone();
9146 let mut parts = Vec::with_capacity(list.len());
9147 for v in list {
9148 parts.push(self.stringify_value(v, line)?);
9149 }
9150 raw_result.push_str(&parts.join(&sep));
9151 } else if let ExprKind::Deref {
9152 kind: Sigil::Array, ..
9153 } = &e.kind
9154 {
9155 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9156 let val = self.peel_array_ref_for_list_join(val);
9157 let list = val.to_list();
9158 let sep = self.list_separator.clone();
9159 let mut parts = Vec::with_capacity(list.len());
9160 for v in list {
9161 parts.push(self.stringify_value(v, line)?);
9162 }
9163 raw_result.push_str(&parts.join(&sep));
9164 } else {
9165 let val = self.eval_expr(e)?;
9166 let s = self.stringify_value(val, line)?;
9167 raw_result.push_str(&s);
9168 }
9169 }
9170 }
9171 }
9172 let result = Self::process_case_escapes(&raw_result);
9173 Ok(StrykeValue::string(result))
9174 }
9175
9176 ExprKind::ScalarVar(name) => {
9178 self.check_strict_scalar_var(name, line)?;
9179 let stor = self.tree_scalar_storage_name(name);
9180 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
9181 let class = obj
9182 .as_blessed_ref()
9183 .map(|b| b.class.clone())
9184 .unwrap_or_default();
9185 let full = format!("{}::FETCH", class);
9186 if let Some(sub) = self.subs.get(&full).cloned() {
9187 return self.call_sub(&sub, vec![obj], ctx, line);
9188 }
9189 }
9190 Ok(self.get_special_var(&stor))
9191 }
9192 ExprKind::ArrayVar(name) => {
9193 self.check_strict_array_var(name, line)?;
9194 let aname = self.stash_array_name_for_package(name);
9195 let arr = self.scope.get_array(&aname);
9196 if ctx == WantarrayCtx::List {
9197 Ok(StrykeValue::array(arr))
9198 } else {
9199 Ok(StrykeValue::integer(arr.len() as i64))
9200 }
9201 }
9202 ExprKind::HashVar(name) => {
9203 self.check_strict_hash_var(name, line)?;
9204 self.touch_env_hash(name);
9205 let h = self.scope.get_hash(name);
9206 let pv = StrykeValue::hash(h);
9207 if ctx == WantarrayCtx::List {
9208 Ok(pv)
9209 } else {
9210 Ok(pv.scalar_context())
9211 }
9212 }
9213 ExprKind::Typeglob(name) => {
9214 let n = self.resolve_io_handle_name(name);
9215 Ok(StrykeValue::string(n))
9216 }
9217 ExprKind::TypeglobExpr(e) => {
9218 let name = self.eval_expr(e)?.to_string();
9219 let n = self.resolve_io_handle_name(&name);
9220 Ok(StrykeValue::string(n))
9221 }
9222 ExprKind::ArrayElement { array, index } => {
9223 if let Some(real) = array.strip_prefix("__topicstr__") {
9228 let s = self.scope.get_scalar(real).to_string();
9229 if let ExprKind::Range {
9230 from,
9231 to,
9232 exclusive,
9233 step,
9234 } = &index.kind
9235 {
9236 let n = s.chars().count() as i64;
9237 let mut from_i = self.eval_expr(from)?.to_int();
9238 let mut to_i = self.eval_expr(to)?.to_int();
9239 let step_i = match step {
9240 Some(e) => self.eval_expr(e)?.to_int(),
9241 None => 1,
9242 };
9243 if from_i < 0 {
9244 from_i += n
9245 }
9246 if to_i < 0 {
9247 to_i += n
9248 }
9249 if *exclusive {
9250 to_i -= 1
9251 }
9252 let chars: Vec<char> = s.chars().collect();
9253 let mut out = String::new();
9254 if step_i > 0 {
9255 let mut i = from_i;
9256 while i <= to_i && i < n {
9257 if i >= 0 {
9258 out.push(chars[i as usize]);
9259 }
9260 i += step_i;
9261 }
9262 } else if step_i < 0 {
9263 let mut i = from_i;
9264 while i >= to_i && i >= 0 {
9265 if i < n {
9266 out.push(chars[i as usize]);
9267 }
9268 i += step_i;
9269 }
9270 }
9271 return Ok(StrykeValue::string(out));
9272 }
9273 let idx = self.eval_expr(index)?.to_int();
9274 let n = s.chars().count() as i64;
9275 let i = if idx < 0 { idx + n } else { idx };
9276 return Ok(if i >= 0 && i < n {
9277 s.chars()
9278 .nth(i as usize)
9279 .map(|c| StrykeValue::string(c.to_string()))
9280 .unwrap_or(StrykeValue::UNDEF)
9281 } else {
9282 StrykeValue::UNDEF
9283 });
9284 }
9285 self.check_strict_array_var(array, line)?;
9286 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9291 if let ExprKind::Range {
9292 from,
9293 to,
9294 exclusive,
9295 step,
9296 } = &index.kind
9297 {
9298 let aname_check = self.stash_array_name_for_package(array);
9299 let prefer_scalar =
9300 array == "_" || self.scope.get_array(&aname_check).is_empty();
9301 if prefer_scalar {
9302 let s = self.scope.get_scalar(array).to_string();
9303 if !s.is_empty() {
9304 let n = s.chars().count() as i64;
9305 let mut from_i = self.eval_expr(from)?.to_int();
9306 let mut to_i = self.eval_expr(to)?.to_int();
9307 let step_i = match step {
9308 Some(e) => self.eval_expr(e)?.to_int(),
9309 None => 1,
9310 };
9311 if from_i < 0 {
9312 from_i += n
9313 }
9314 if to_i < 0 {
9315 to_i += n
9316 }
9317 if *exclusive {
9318 to_i -= 1
9319 }
9320 let chars: Vec<char> = s.chars().collect();
9321 let mut out = String::new();
9322 if step_i > 0 {
9323 let mut i = from_i;
9324 while i <= to_i && i < n {
9325 if i >= 0 {
9326 out.push(chars[i as usize]);
9327 }
9328 i += step_i;
9329 }
9330 } else if step_i < 0 {
9331 let mut i = from_i;
9332 while i >= to_i && i >= 0 {
9333 if i < n {
9334 out.push(chars[i as usize]);
9335 }
9336 i += step_i;
9337 }
9338 }
9339 return Ok(StrykeValue::string(out));
9340 }
9341 }
9342 }
9343 }
9344 let idx = self.eval_expr(index)?.to_int();
9345 let aname = self.stash_array_name_for_package(array);
9346 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
9347 let class = obj
9348 .as_blessed_ref()
9349 .map(|b| b.class.clone())
9350 .unwrap_or_default();
9351 let full = format!("{}::FETCH", class);
9352 if let Some(sub) = self.subs.get(&full).cloned() {
9353 let arg_vals = vec![obj, StrykeValue::integer(idx)];
9354 return self.call_sub(&sub, arg_vals, ctx, line);
9355 }
9356 }
9357 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9366 let prefer_scalar = self.scope.get_array(&aname).is_empty();
9367 if prefer_scalar {
9368 let s = self.scope.get_scalar(array).to_string();
9369 if !s.is_empty() {
9370 let n = s.chars().count() as i64;
9371 let i = if idx < 0 { idx + n } else { idx };
9372 if i >= 0 && i < n {
9373 if let Some(c) = s.chars().nth(i as usize) {
9374 return Ok(StrykeValue::string(c.to_string()));
9375 }
9376 }
9377 return Ok(StrykeValue::UNDEF);
9378 }
9379 }
9380 }
9381 Ok(self.scope.get_array_element(&aname, idx))
9382 }
9383 ExprKind::HashElement { hash, key } => {
9384 self.check_strict_hash_var(hash, line)?;
9385 let k = self.eval_expr(key)?.to_string();
9386 self.touch_env_hash(hash);
9387 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
9388 let class = obj
9389 .as_blessed_ref()
9390 .map(|b| b.class.clone())
9391 .unwrap_or_default();
9392 let full = format!("{}::FETCH", class);
9393 if let Some(sub) = self.subs.get(&full).cloned() {
9394 let arg_vals = vec![obj, StrykeValue::string(k)];
9395 return self.call_sub(&sub, arg_vals, ctx, line);
9396 }
9397 }
9398 Ok(self.scope.get_hash_element(hash, &k))
9399 }
9400 ExprKind::ArraySlice { array, indices } => {
9401 self.check_strict_array_var(array, line)?;
9402 let aname = self.stash_array_name_for_package(array);
9403 let flat = self.flatten_array_slice_index_specs(indices)?;
9404 let mut result = Vec::with_capacity(flat.len());
9405 for idx in flat {
9406 result.push(self.scope.get_array_element(&aname, idx));
9407 }
9408 Ok(StrykeValue::array(result))
9409 }
9410 ExprKind::HashSlice { hash, keys } => {
9411 self.check_strict_hash_var(hash, line)?;
9412 self.touch_env_hash(hash);
9413 let mut result = Vec::new();
9414 for key_expr in keys {
9415 for k in self.eval_hash_slice_key_components(key_expr)? {
9416 result.push(self.scope.get_hash_element(hash, &k));
9417 }
9418 }
9419 Ok(StrykeValue::array(result))
9420 }
9421 ExprKind::HashKvSlice { hash, keys } => {
9422 self.check_strict_hash_var(hash, line)?;
9425 self.touch_env_hash(hash);
9426 let mut result = Vec::new();
9427 for key_expr in keys {
9428 for k in self.eval_hash_slice_key_components(key_expr)? {
9429 let v = self.scope.get_hash_element(hash, &k);
9430 result.push(StrykeValue::string(k));
9431 result.push(v);
9432 }
9433 }
9434 Ok(StrykeValue::array(result))
9435 }
9436 ExprKind::HashSliceDeref { container, keys } => {
9437 let hv = self.eval_expr(container)?;
9438 let mut key_vals = Vec::with_capacity(keys.len());
9439 for key_expr in keys {
9440 let v = if matches!(
9441 key_expr.kind,
9442 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
9443 ) {
9444 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
9445 } else {
9446 self.eval_expr(key_expr)?
9447 };
9448 key_vals.push(v);
9449 }
9450 self.hash_slice_deref_values(&hv, &key_vals, line)
9451 }
9452 ExprKind::AnonymousListSlice { source, indices } => {
9453 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
9454 let items = list_val.to_list();
9455 let flat = self.flatten_array_slice_index_specs(indices)?;
9456 let mut out = Vec::with_capacity(flat.len());
9457 for idx in flat {
9458 let i = if idx < 0 {
9459 (items.len() as i64 + idx) as usize
9460 } else {
9461 idx as usize
9462 };
9463 out.push(items.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
9464 }
9465 let arr = StrykeValue::array(out);
9466 if ctx != WantarrayCtx::List {
9467 let v = arr.to_list();
9468 Ok(v.last().cloned().unwrap_or(StrykeValue::UNDEF))
9469 } else {
9470 Ok(arr)
9471 }
9472 }
9473
9474 ExprKind::ScalarRef(inner) => match &inner.kind {
9476 ExprKind::ScalarVar(name) => Ok(StrykeValue::scalar_binding_ref(name.clone())),
9477 ExprKind::ArrayVar(name) => {
9478 self.check_strict_array_var(name, line)?;
9479 let aname = self.stash_array_name_for_package(name);
9480 let arc = self.scope.promote_array_to_shared(&aname);
9483 Ok(StrykeValue::array_ref(arc))
9484 }
9485 ExprKind::HashVar(name) => {
9486 self.check_strict_hash_var(name, line)?;
9487 let arc = self.scope.promote_hash_to_shared(name);
9488 Ok(StrykeValue::hash_ref(arc))
9489 }
9490 ExprKind::Deref {
9491 expr: e,
9492 kind: Sigil::Array,
9493 } => {
9494 let v = self.eval_expr(e)?;
9495 self.make_array_ref_alias(v, line)
9496 }
9497 ExprKind::Deref {
9498 expr: e,
9499 kind: Sigil::Hash,
9500 } => {
9501 let v = self.eval_expr(e)?;
9502 self.make_hash_ref_alias(v, line)
9503 }
9504 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
9505 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
9506 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(
9507 list.to_list(),
9508 ))))
9509 }
9510 ExprKind::HashSliceDeref { .. } => {
9511 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
9512 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(
9513 list.to_list(),
9514 ))))
9515 }
9516 _ => {
9517 let val = self.eval_expr(inner)?;
9518 Ok(StrykeValue::scalar_ref(Arc::new(RwLock::new(val))))
9519 }
9520 },
9521 ExprKind::ArrayRef(elems) => {
9522 let mut arr = Vec::with_capacity(elems.len());
9526 for e in elems {
9527 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9528 let v = self.scope.resolve_container_binding_ref(v);
9529 if let Some(vec) = v.as_array_vec() {
9530 arr.extend(vec);
9531 } else {
9532 arr.push(v);
9533 }
9534 }
9535 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(arr))))
9536 }
9537 ExprKind::HashRef(pairs) => {
9538 let mut map = IndexMap::new();
9541 for (k, v) in pairs {
9542 let key_str = self.eval_expr(k)?.to_string();
9543 if key_str == "__HASH_SPREAD__" {
9544 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
9546 let items = spread.to_list();
9547 let mut i = 0;
9548 while i + 1 < items.len() {
9549 map.insert(items[i].to_string(), items[i + 1].clone());
9550 i += 2;
9551 }
9552 } else {
9553 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
9554 map.insert(key_str, val);
9555 }
9556 }
9557 Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map))))
9558 }
9559 ExprKind::CodeRef { params, body } => {
9560 let captured = self.scope.capture();
9561 Ok(StrykeValue::code_ref(Arc::new(StrykeSub {
9562 name: "__ANON__".to_string(),
9563 params: params.clone(),
9564 body: body.clone(),
9565 closure_env: Some(captured),
9566 prototype: None,
9567 fib_like: None,
9568 })))
9569 }
9570 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
9571 ExprKind::SubroutineCodeRef(name) => {
9572 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
9573 StrykeError::runtime(self.undefined_subroutine_resolve_message(name), line)
9574 })?;
9575 Ok(StrykeValue::code_ref(sub))
9576 }
9577 ExprKind::DynamicSubCodeRef(expr) => {
9578 let name = self.eval_expr(expr)?.to_string();
9579 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
9580 StrykeError::runtime(self.undefined_subroutine_resolve_message(&name), line)
9581 })?;
9582 Ok(StrykeValue::code_ref(sub))
9583 }
9584 ExprKind::Deref { expr, kind } => {
9585 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
9586 let val = self.eval_expr(expr)?;
9587 let n = self.array_deref_len(val, line)?;
9588 return Ok(StrykeValue::integer(n));
9589 }
9590 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
9591 let val = self.eval_expr(expr)?;
9592 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
9593 return Ok(h.scalar_context());
9594 }
9595 let val = self.eval_expr(expr)?;
9596 self.symbolic_deref(val, *kind, line)
9597 }
9598 ExprKind::ArrowDeref { expr, index, kind } => {
9599 match kind {
9600 DerefKind::Array => {
9601 let container = self.eval_arrow_array_base(expr, line)?;
9602 if let ExprKind::List(indices) = &index.kind {
9603 let mut out = Vec::with_capacity(indices.len());
9604 for ix in indices {
9605 let idx = self.eval_expr(ix)?.to_int();
9606 out.push(self.read_arrow_array_element(
9607 container.clone(),
9608 idx,
9609 line,
9610 )?);
9611 }
9612 let arr = StrykeValue::array(out);
9613 if ctx != WantarrayCtx::List {
9614 let v = arr.to_list();
9615 return Ok(v.last().cloned().unwrap_or(StrykeValue::UNDEF));
9616 }
9617 return Ok(arr);
9618 }
9619 let idx = self.eval_expr(index)?.to_int();
9620 self.read_arrow_array_element(container, idx, line)
9621 }
9622 DerefKind::Hash => {
9623 let val = self.eval_arrow_hash_base(expr, line)?;
9624 let key = self.eval_expr(index)?.to_string();
9625 self.read_arrow_hash_element(val, key.as_str(), line)
9626 }
9627 DerefKind::Call => {
9628 let val = self.eval_expr(expr)?;
9637 if let ExprKind::List(ref arg_exprs) = index.kind {
9638 let mut args = Vec::with_capacity(arg_exprs.len());
9639 for a in arg_exprs {
9640 if matches!(a.kind, ExprKind::ArrayVar(_) | ExprKind::HashVar(_)) {
9641 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
9642 if let Some(items) = v.as_array_vec() {
9643 args.extend(items);
9644 } else {
9645 args.push(v);
9646 }
9647 } else {
9648 args.push(self.eval_expr(a)?);
9649 }
9650 }
9651 let callable = if let Some(inner) = val.as_scalar_ref() {
9653 inner.read().clone()
9654 } else {
9655 val
9656 };
9657 if let Some(sub) = callable.as_code_ref() {
9658 return self.call_sub(&sub, args, ctx, line);
9659 }
9660 Err(StrykeError::runtime("Not a code reference", line).into())
9661 } else {
9662 Err(StrykeError::runtime("Invalid call deref", line).into())
9663 }
9664 }
9665 }
9666 }
9667
9668 ExprKind::BinOp { left, op, right } => {
9670 match op {
9672 BinOp::BindMatch => {
9673 let lv = self.eval_expr(left)?;
9674 let rv = self.eval_expr(right)?;
9675 let s = lv.to_string();
9676 let pat = rv.to_string();
9677 return self.regex_match_execute(s, &pat, "", false, "_", line);
9678 }
9679 BinOp::BindNotMatch => {
9680 let lv = self.eval_expr(left)?;
9681 let rv = self.eval_expr(right)?;
9682 let s = lv.to_string();
9683 let pat = rv.to_string();
9684 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
9685 return Ok(StrykeValue::integer(if m.is_true() { 0 } else { 1 }));
9686 }
9687 BinOp::LogAnd | BinOp::LogAndWord => {
9688 match &left.kind {
9689 ExprKind::Regex(_, _) => {
9690 if !self.eval_boolean_rvalue_condition(left)? {
9691 return Ok(StrykeValue::string(String::new()));
9692 }
9693 }
9694 _ => {
9695 let lv = self.eval_expr(left)?;
9696 if !lv.is_true() {
9697 return Ok(lv);
9698 }
9699 }
9700 }
9701 return match &right.kind {
9702 ExprKind::Regex(_, _) => Ok(StrykeValue::integer(
9703 if self.eval_boolean_rvalue_condition(right)? {
9704 1
9705 } else {
9706 0
9707 },
9708 )),
9709 _ => self.eval_expr(right),
9710 };
9711 }
9712 BinOp::LogOr | BinOp::LogOrWord => {
9713 match &left.kind {
9714 ExprKind::Regex(_, _) => {
9715 if self.eval_boolean_rvalue_condition(left)? {
9716 return Ok(StrykeValue::integer(1));
9717 }
9718 }
9719 _ => {
9720 let lv = self.eval_expr(left)?;
9721 if lv.is_true() {
9722 return Ok(lv);
9723 }
9724 }
9725 }
9726 return match &right.kind {
9727 ExprKind::Regex(_, _) => Ok(StrykeValue::integer(
9728 if self.eval_boolean_rvalue_condition(right)? {
9729 1
9730 } else {
9731 0
9732 },
9733 )),
9734 _ => self.eval_expr(right),
9735 };
9736 }
9737 BinOp::DefinedOr => {
9738 let lv = self.eval_expr(left)?;
9739 if !lv.is_undef() {
9740 return Ok(lv);
9741 }
9742 return self.eval_expr(right);
9743 }
9744 _ => {}
9745 }
9746 let lv = self.eval_expr(left)?;
9747 let rv = self.eval_expr(right)?;
9748 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
9749 return r;
9750 }
9751 self.eval_binop(*op, &lv, &rv, line)
9752 }
9753
9754 ExprKind::UnaryOp { op, expr } => match op {
9756 UnaryOp::PreIncrement => {
9757 if let ExprKind::ScalarVar(name) = &expr.kind {
9758 self.check_strict_scalar_var(name, line)?;
9759 let n = self.resolved_scalar_storage_name(name);
9760 return Ok(self
9761 .scope
9762 .atomic_mutate(&n, perl_inc)
9763 .map_err(|e| e.at_line(line))?);
9764 }
9765 if let ExprKind::Deref { kind, .. } = &expr.kind {
9766 if matches!(kind, Sigil::Array | Sigil::Hash) {
9767 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
9768 *kind, true, true, line,
9769 ));
9770 }
9771 }
9772 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
9773 let href = self.eval_expr(container)?;
9774 let mut key_vals = Vec::with_capacity(keys.len());
9775 for key_expr in keys {
9776 key_vals.push(self.eval_expr(key_expr)?);
9777 }
9778 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
9779 }
9780 if let ExprKind::ArrowDeref {
9781 expr: arr_expr,
9782 index,
9783 kind: DerefKind::Array,
9784 } = &expr.kind
9785 {
9786 if let ExprKind::List(indices) = &index.kind {
9787 let container = self.eval_arrow_array_base(arr_expr, line)?;
9788 let mut idxs = Vec::with_capacity(indices.len());
9789 for ix in indices {
9790 idxs.push(self.eval_expr(ix)?.to_int());
9791 }
9792 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
9793 }
9794 }
9795 let val = self.eval_expr(expr)?;
9796 let new_val = perl_inc(&val);
9797 self.assign_value(expr, new_val.clone())?;
9798 Ok(new_val)
9799 }
9800 UnaryOp::PreDecrement => {
9801 if let ExprKind::ScalarVar(name) = &expr.kind {
9802 self.check_strict_scalar_var(name, line)?;
9803 let n = self.resolved_scalar_storage_name(name);
9804 return Ok(self
9805 .scope
9806 .atomic_mutate(&n, |v| StrykeValue::integer(v.to_int() - 1))
9807 .map_err(|e| e.at_line(line))?);
9808 }
9809 if let ExprKind::Deref { kind, .. } = &expr.kind {
9810 if matches!(kind, Sigil::Array | Sigil::Hash) {
9811 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
9812 *kind, true, false, line,
9813 ));
9814 }
9815 }
9816 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
9817 let href = self.eval_expr(container)?;
9818 let mut key_vals = Vec::with_capacity(keys.len());
9819 for key_expr in keys {
9820 key_vals.push(self.eval_expr(key_expr)?);
9821 }
9822 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
9823 }
9824 if let ExprKind::ArrowDeref {
9825 expr: arr_expr,
9826 index,
9827 kind: DerefKind::Array,
9828 } = &expr.kind
9829 {
9830 if let ExprKind::List(indices) = &index.kind {
9831 let container = self.eval_arrow_array_base(arr_expr, line)?;
9832 let mut idxs = Vec::with_capacity(indices.len());
9833 for ix in indices {
9834 idxs.push(self.eval_expr(ix)?.to_int());
9835 }
9836 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
9837 }
9838 }
9839 let val = self.eval_expr(expr)?;
9840 let new_val = StrykeValue::integer(val.to_int() - 1);
9841 self.assign_value(expr, new_val.clone())?;
9842 Ok(new_val)
9843 }
9844 _ => {
9845 match op {
9846 UnaryOp::LogNot | UnaryOp::LogNotWord => {
9847 if let ExprKind::Regex(pattern, flags) = &expr.kind {
9848 let topic = self.scope.get_scalar("_");
9849 let rl = expr.line;
9850 let s = topic.to_string();
9851 let v =
9852 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
9853 return Ok(StrykeValue::integer(if v.is_true() { 0 } else { 1 }));
9854 }
9855 }
9856 _ => {}
9857 }
9858 let val = self.eval_expr(expr)?;
9859 match op {
9860 UnaryOp::Negate => {
9861 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
9862 return r;
9863 }
9864 if let Some(n) = val.as_integer() {
9865 Ok(StrykeValue::integer(-n))
9866 } else {
9867 Ok(StrykeValue::float(-val.to_number()))
9868 }
9869 }
9870 UnaryOp::LogNot => {
9871 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
9872 let pv = r?;
9873 return Ok(StrykeValue::integer(if pv.is_true() { 0 } else { 1 }));
9874 }
9875 Ok(StrykeValue::integer(if val.is_true() { 0 } else { 1 }))
9876 }
9877 UnaryOp::BitNot => Ok(StrykeValue::integer(!val.to_int())),
9878 UnaryOp::LogNotWord => {
9879 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
9880 let pv = r?;
9881 return Ok(StrykeValue::integer(if pv.is_true() { 0 } else { 1 }));
9882 }
9883 Ok(StrykeValue::integer(if val.is_true() { 0 } else { 1 }))
9884 }
9885 UnaryOp::Ref => {
9886 if let ExprKind::ScalarVar(name) = &expr.kind {
9887 return Ok(StrykeValue::scalar_binding_ref(name.clone()));
9888 }
9889 Ok(StrykeValue::scalar_ref(Arc::new(RwLock::new(val))))
9890 }
9891 _ => unreachable!(),
9892 }
9893 }
9894 },
9895
9896 ExprKind::PostfixOp { expr, op } => {
9897 if let ExprKind::ScalarVar(name) = &expr.kind {
9900 self.check_strict_scalar_var(name, line)?;
9901 let n = self.resolved_scalar_storage_name(name);
9902 let f: fn(&StrykeValue) -> StrykeValue = match op {
9903 PostfixOp::Increment => |v| perl_inc(v),
9904 PostfixOp::Decrement => |v| StrykeValue::integer(v.to_int() - 1),
9905 };
9906 return Ok(self
9907 .scope
9908 .atomic_mutate_post(&n, f)
9909 .map_err(|e| e.at_line(line))?);
9910 }
9911 if let ExprKind::Deref { kind, .. } = &expr.kind {
9912 if matches!(kind, Sigil::Array | Sigil::Hash) {
9913 let is_inc = matches!(op, PostfixOp::Increment);
9914 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
9915 *kind, false, is_inc, line,
9916 ));
9917 }
9918 }
9919 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
9920 let href = self.eval_expr(container)?;
9921 let mut key_vals = Vec::with_capacity(keys.len());
9922 for key_expr in keys {
9923 key_vals.push(self.eval_expr(key_expr)?);
9924 }
9925 let kind_byte = match op {
9926 PostfixOp::Increment => 2u8,
9927 PostfixOp::Decrement => 3u8,
9928 };
9929 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
9930 }
9931 if let ExprKind::ArrowDeref {
9932 expr: arr_expr,
9933 index,
9934 kind: DerefKind::Array,
9935 } = &expr.kind
9936 {
9937 if let ExprKind::List(indices) = &index.kind {
9938 let container = self.eval_arrow_array_base(arr_expr, line)?;
9939 let mut idxs = Vec::with_capacity(indices.len());
9940 for ix in indices {
9941 idxs.push(self.eval_expr(ix)?.to_int());
9942 }
9943 let kind_byte = match op {
9944 PostfixOp::Increment => 2u8,
9945 PostfixOp::Decrement => 3u8,
9946 };
9947 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
9948 }
9949 }
9950 let val = self.eval_expr(expr)?;
9951 let old = val.clone();
9952 let new_val = match op {
9953 PostfixOp::Increment => perl_inc(&val),
9954 PostfixOp::Decrement => StrykeValue::integer(val.to_int() - 1),
9955 };
9956 self.assign_value(expr, new_val)?;
9957 Ok(old)
9958 }
9959
9960 ExprKind::Assign { target, value } => {
9962 if let ExprKind::Typeglob(lhs) = &target.kind {
9963 if let ExprKind::Typeglob(rhs) = &value.kind {
9964 self.copy_typeglob_slots(lhs, rhs, line)?;
9965 return self.eval_expr(value);
9966 }
9967 }
9968 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
9969 self.assign_value(target, val.clone())?;
9970 Ok(val)
9971 }
9972 ExprKind::CompoundAssign { target, op, value } => {
9973 if let ExprKind::ScalarVar(name) = &target.kind {
9976 self.check_strict_scalar_var(name, line)?;
9977 let n = self.resolved_scalar_storage_name(name);
9978 let op = *op;
9979 let rhs = match op {
9980 BinOp::LogOr => {
9981 let old = self.scope.get_scalar(&n);
9982 if old.is_true() {
9983 return Ok(old);
9984 }
9985 self.eval_expr(value)?
9986 }
9987 BinOp::DefinedOr => {
9988 let old = self.scope.get_scalar(&n);
9989 if !old.is_undef() {
9990 return Ok(old);
9991 }
9992 self.eval_expr(value)?
9993 }
9994 BinOp::LogAnd => {
9995 let old = self.scope.get_scalar(&n);
9996 if !old.is_true() {
9997 return Ok(old);
9998 }
9999 self.eval_expr(value)?
10000 }
10001 _ => self.eval_expr(value)?,
10002 };
10003 return Ok(self.scalar_compound_assign_scalar_target(&n, op, rhs)?);
10004 }
10005 let rhs = self.eval_expr(value)?;
10006 if let ExprKind::HashElement { hash, key } = &target.kind {
10008 self.check_strict_hash_var(hash, line)?;
10009 let k = self.eval_expr(key)?.to_string();
10010 let op = *op;
10011 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
10012 BinOp::Add => {
10013 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10014 StrykeValue::integer(a.wrapping_add(b))
10015 } else {
10016 StrykeValue::float(old.to_number() + rhs.to_number())
10017 }
10018 }
10019 BinOp::Sub => {
10020 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10021 StrykeValue::integer(a.wrapping_sub(b))
10022 } else {
10023 StrykeValue::float(old.to_number() - rhs.to_number())
10024 }
10025 }
10026 BinOp::Concat => {
10027 let mut s = old.to_string();
10028 rhs.append_to(&mut s);
10029 StrykeValue::string(s)
10030 }
10031 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
10032 })?);
10033 }
10034 if let ExprKind::ArrayElement { array, index } = &target.kind {
10036 self.check_strict_array_var(array, line)?;
10037 let idx = self.eval_expr(index)?.to_int();
10038 let op = *op;
10039 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
10040 BinOp::Add => {
10041 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10042 StrykeValue::integer(a.wrapping_add(b))
10043 } else {
10044 StrykeValue::float(old.to_number() + rhs.to_number())
10045 }
10046 }
10047 BinOp::Sub => {
10048 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10049 StrykeValue::integer(a.wrapping_sub(b))
10050 } else {
10051 StrykeValue::float(old.to_number() - rhs.to_number())
10052 }
10053 }
10054 BinOp::Mul => {
10055 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10056 StrykeValue::integer(a.wrapping_mul(b))
10057 } else {
10058 StrykeValue::float(old.to_number() * rhs.to_number())
10059 }
10060 }
10061 BinOp::Div => StrykeValue::float(old.to_number() / rhs.to_number()),
10062 BinOp::Mod => {
10063 let a = old.to_int();
10068 let b = rhs.to_int();
10069 if b == 0 {
10070 StrykeValue::integer(0)
10071 } else {
10072 StrykeValue::integer(crate::value::perl_mod_i64(a, b))
10073 }
10074 }
10075 BinOp::Concat => {
10076 let mut s = old.to_string();
10077 rhs.append_to(&mut s);
10078 StrykeValue::string(s)
10079 }
10080 BinOp::Pow => StrykeValue::float(old.to_number().powf(rhs.to_number())),
10081 BinOp::BitAnd => StrykeValue::integer(old.to_int() & rhs.to_int()),
10082 BinOp::BitOr => StrykeValue::integer(old.to_int() | rhs.to_int()),
10083 BinOp::BitXor => StrykeValue::integer(old.to_int() ^ rhs.to_int()),
10084 BinOp::ShiftLeft => StrykeValue::integer(old.to_int() << rhs.to_int()),
10085 BinOp::ShiftRight => StrykeValue::integer(old.to_int() >> rhs.to_int()),
10086 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
10087 })?);
10088 }
10089 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
10090 let href = self.eval_expr(container)?;
10091 let mut key_vals = Vec::with_capacity(keys.len());
10092 for key_expr in keys {
10093 key_vals.push(self.eval_expr(key_expr)?);
10094 }
10095 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
10096 }
10097 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
10098 if let ExprKind::Deref {
10099 expr: inner,
10100 kind: Sigil::Array,
10101 } = &source.kind
10102 {
10103 let container = self.eval_arrow_array_base(inner, line)?;
10104 let idxs = self.flatten_array_slice_index_specs(indices)?;
10105 return self
10106 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
10107 }
10108 }
10109 if let ExprKind::ArrowDeref {
10110 expr: arr_expr,
10111 index,
10112 kind: DerefKind::Array,
10113 } = &target.kind
10114 {
10115 if let ExprKind::List(indices) = &index.kind {
10116 let container = self.eval_arrow_array_base(arr_expr, line)?;
10117 let mut idxs = Vec::with_capacity(indices.len());
10118 for ix in indices {
10119 idxs.push(self.eval_expr(ix)?.to_int());
10120 }
10121 return self
10122 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
10123 }
10124 }
10125 let old = self.eval_expr(target)?;
10126 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
10127 self.assign_value(target, new_val.clone())?;
10128 Ok(new_val)
10129 }
10130
10131 ExprKind::Ternary {
10135 condition,
10136 then_expr,
10137 else_expr,
10138 } => {
10139 if self.eval_boolean_rvalue_condition(condition)? {
10140 self.eval_expr_ctx(then_expr, ctx)
10141 } else {
10142 self.eval_expr_ctx(else_expr, ctx)
10143 }
10144 }
10145
10146 ExprKind::Range {
10148 from,
10149 to,
10150 exclusive,
10151 step,
10152 } => {
10153 if ctx == WantarrayCtx::List {
10154 let f = self.eval_expr(from)?;
10155 let t = self.eval_expr(to)?;
10156 if let Some(s) = step {
10157 let step_val = self.eval_expr(s)?.to_int();
10158 let from_i = f.to_int();
10159 let to_i = t.to_int();
10160 let list = if step_val == 0 {
10161 vec![]
10162 } else if step_val > 0 {
10163 (from_i..=to_i)
10164 .step_by(step_val as usize)
10165 .map(StrykeValue::integer)
10166 .collect()
10167 } else {
10168 std::iter::successors(Some(from_i), |&x| {
10169 let next = x - step_val.abs();
10170 if next >= to_i {
10171 Some(next)
10172 } else {
10173 None
10174 }
10175 })
10176 .map(StrykeValue::integer)
10177 .collect()
10178 };
10179 Ok(StrykeValue::array(list))
10180 } else {
10181 let list = perl_list_range_expand(f, t);
10182 Ok(StrykeValue::array(list))
10183 }
10184 } else {
10185 let key = std::ptr::from_ref(expr) as usize;
10186 match (&from.kind, &to.kind) {
10187 (
10188 ExprKind::Regex(left_pat, left_flags),
10189 ExprKind::Regex(right_pat, right_flags),
10190 ) => {
10191 let dot = self.scalar_flipflop_dot_line();
10192 let subject = self.scope.get_scalar("_").to_string();
10193 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10194 |e| match e {
10195 FlowOrError::Error(err) => err,
10196 FlowOrError::Flow(_) => StrykeError::runtime(
10197 "unexpected flow in regex flip-flop",
10198 line,
10199 ),
10200 },
10201 )?;
10202 let right_re = self
10203 .compile_regex(right_pat, right_flags, line)
10204 .map_err(|e| match e {
10205 FlowOrError::Error(err) => err,
10206 FlowOrError::Flow(_) => StrykeError::runtime(
10207 "unexpected flow in regex flip-flop",
10208 line,
10209 ),
10210 })?;
10211 let left_m = left_re.is_match(&subject);
10212 let right_m = right_re.is_match(&subject);
10213 let st = self.flip_flop_tree.entry(key).or_default();
10214 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10215 &mut st.active,
10216 &mut st.exclusive_left_line,
10217 *exclusive,
10218 dot,
10219 left_m,
10220 right_m,
10221 )))
10222 }
10223 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
10224 let dot = self.scalar_flipflop_dot_line();
10225 let subject = self.scope.get_scalar("_").to_string();
10226 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10227 |e| match e {
10228 FlowOrError::Error(err) => err,
10229 FlowOrError::Flow(_) => StrykeError::runtime(
10230 "unexpected flow in regex/eof flip-flop",
10231 line,
10232 ),
10233 },
10234 )?;
10235 let left_m = left_re.is_match(&subject);
10236 let right_m = self.eof_without_arg_is_true();
10237 let st = self.flip_flop_tree.entry(key).or_default();
10238 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10239 &mut st.active,
10240 &mut st.exclusive_left_line,
10241 *exclusive,
10242 dot,
10243 left_m,
10244 right_m,
10245 )))
10246 }
10247 (
10248 ExprKind::Regex(left_pat, left_flags),
10249 ExprKind::Integer(_) | ExprKind::Float(_),
10250 ) => {
10251 let dot = self.scalar_flipflop_dot_line();
10252 let right = self.eval_expr(to)?.to_int();
10253 let subject = self.scope.get_scalar("_").to_string();
10254 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10255 |e| match e {
10256 FlowOrError::Error(err) => err,
10257 FlowOrError::Flow(_) => StrykeError::runtime(
10258 "unexpected flow in regex flip-flop",
10259 line,
10260 ),
10261 },
10262 )?;
10263 let left_m = left_re.is_match(&subject);
10264 let right_m = dot == right;
10265 let st = self.flip_flop_tree.entry(key).or_default();
10266 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10267 &mut st.active,
10268 &mut st.exclusive_left_line,
10269 *exclusive,
10270 dot,
10271 left_m,
10272 right_m,
10273 )))
10274 }
10275 (ExprKind::Regex(left_pat, left_flags), _) => {
10276 if let ExprKind::Eof(Some(_)) = &to.kind {
10277 return Err(FlowOrError::Error(StrykeError::runtime(
10278 "regex flip-flop with eof(HANDLE) is not supported",
10279 line,
10280 )));
10281 }
10282 let dot = self.scalar_flipflop_dot_line();
10283 let subject = self.scope.get_scalar("_").to_string();
10284 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10285 |e| match e {
10286 FlowOrError::Error(err) => err,
10287 FlowOrError::Flow(_) => StrykeError::runtime(
10288 "unexpected flow in regex flip-flop",
10289 line,
10290 ),
10291 },
10292 )?;
10293 let left_m = left_re.is_match(&subject);
10294 let right_m = self.eval_boolean_rvalue_condition(to)?;
10295 let st = self.flip_flop_tree.entry(key).or_default();
10296 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10297 &mut st.active,
10298 &mut st.exclusive_left_line,
10299 *exclusive,
10300 dot,
10301 left_m,
10302 right_m,
10303 )))
10304 }
10305 _ => {
10306 let left = self.eval_expr(from)?.to_int();
10307 let right = self.eval_expr(to)?.to_int();
10308 let dot = self.scalar_flipflop_dot_line();
10309 let st = self.flip_flop_tree.entry(key).or_default();
10310 if !st.active {
10311 if dot == left {
10312 st.active = true;
10313 if *exclusive {
10314 st.exclusive_left_line = Some(dot);
10315 } else {
10316 st.exclusive_left_line = None;
10317 if dot == right {
10318 st.active = false;
10319 }
10320 }
10321 return Ok(StrykeValue::integer(1));
10322 }
10323 return Ok(StrykeValue::integer(0));
10324 }
10325 if let Some(ll) = st.exclusive_left_line {
10326 if dot == right && dot > ll {
10327 st.active = false;
10328 st.exclusive_left_line = None;
10329 }
10330 } else if dot == right {
10331 st.active = false;
10332 }
10333 Ok(StrykeValue::integer(1))
10334 }
10335 }
10336 }
10337 }
10338
10339 ExprKind::SliceRange { from, to, step } => {
10345 let f = match from {
10346 Some(e) => self.eval_expr(e)?,
10347 None => {
10348 return Err(StrykeError::runtime(
10349 "open-ended slice range cannot be evaluated outside slice subscript",
10350 line,
10351 )
10352 .into());
10353 }
10354 };
10355 let t = match to {
10356 Some(e) => self.eval_expr(e)?,
10357 None => {
10358 return Err(StrykeError::runtime(
10359 "open-ended slice range cannot be evaluated outside slice subscript",
10360 line,
10361 )
10362 .into());
10363 }
10364 };
10365 let list = if let Some(s) = step {
10366 let sv = self.eval_expr(s)?;
10367 crate::value::perl_list_range_expand_stepped(f, t, sv)
10368 } else {
10369 perl_list_range_expand(f, t)
10370 };
10371 Ok(StrykeValue::array(list))
10372 }
10373
10374 ExprKind::Repeat {
10376 expr,
10377 count,
10378 list_repeat,
10379 } => {
10380 let n = self.eval_expr(count)?.to_int().max(0) as usize;
10381 if *list_repeat {
10382 let saved = self.wantarray_kind;
10384 self.wantarray_kind = WantarrayCtx::List;
10385 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10386 self.wantarray_kind = saved;
10387 let items: Vec<StrykeValue> = val.as_array_vec().unwrap_or_else(|| vec![val]);
10388 let mut result = Vec::with_capacity(items.len() * n);
10389 for _ in 0..n {
10390 result.extend(items.iter().cloned());
10391 }
10392 Ok(StrykeValue::array(result))
10393 } else {
10394 let val = self.eval_expr(expr)?;
10396 Ok(StrykeValue::string(val.to_string().repeat(n)))
10397 }
10398 }
10399
10400 ExprKind::MyExpr { keyword, decls } => {
10405 let stmt_kind = match keyword.as_str() {
10408 "my" => StmtKind::My(decls.clone()),
10409 "our" => StmtKind::Our(decls.clone()),
10410 "state" => StmtKind::State(decls.clone()),
10411 "local" => StmtKind::Local(decls.clone()),
10412 _ => StmtKind::My(decls.clone()),
10413 };
10414 let stmt = Statement {
10415 label: None,
10416 kind: stmt_kind,
10417 line,
10418 };
10419 self.exec_statement(&stmt)?;
10420 let first = decls.first().ok_or_else(|| {
10424 FlowOrError::Error(StrykeError::runtime("MyExpr: empty decl list", line))
10425 })?;
10426 Ok(match first.sigil {
10427 Sigil::Scalar => self.scope.get_scalar(&first.name),
10428 Sigil::Array => StrykeValue::array(self.scope.get_array(&first.name)),
10429 Sigil::Hash => {
10430 let h = self.scope.get_hash(&first.name);
10431 let mut flat: Vec<StrykeValue> = Vec::with_capacity(h.len() * 2);
10432 for (k, v) in h {
10433 flat.push(StrykeValue::string(k));
10434 flat.push(v);
10435 }
10436 StrykeValue::array(flat)
10437 }
10438 Sigil::Typeglob => StrykeValue::UNDEF,
10439 })
10440 }
10441
10442 ExprKind::FuncCall { name, args } => {
10444 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
10447 if matches!(dispatch_name, "read") && args.len() >= 3 {
10449 let fh_val = self.eval_expr(&args[0])?;
10450 let fh = fh_val
10451 .as_io_handle_name()
10452 .unwrap_or_else(|| fh_val.to_string());
10453 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
10454 let offset = if args.len() > 3 {
10455 self.eval_expr(&args[3])?.to_int().max(0) as usize
10456 } else {
10457 0
10458 };
10459 let var_name = match &args[1].kind {
10461 ExprKind::ScalarVar(n) => n.clone(),
10462 _ => self.eval_expr(&args[1])?.to_string(),
10463 };
10464 let mut buf = vec![0u8; len];
10465 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
10466 slot.lock().read(&mut buf).unwrap_or(0)
10467 } else if fh == "STDIN" {
10468 std::io::stdin().read(&mut buf).unwrap_or(0)
10469 } else {
10470 return Err(StrykeError::runtime(
10471 format!("read: unopened handle {}", fh),
10472 line,
10473 )
10474 .into());
10475 };
10476 buf.truncate(n);
10477 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
10478 if offset > 0 {
10479 let mut existing = self.scope.get_scalar(&var_name).to_string();
10480 while existing.len() < offset {
10481 existing.push('\0');
10482 }
10483 existing.push_str(&read_str);
10484 let _ = self
10485 .scope
10486 .set_scalar(&var_name, StrykeValue::string(existing));
10487 } else {
10488 let _ = self
10489 .scope
10490 .set_scalar(&var_name, StrykeValue::string(read_str));
10491 }
10492 return Ok(StrykeValue::integer(n as i64));
10493 }
10494 if matches!(dispatch_name, "group_by" | "chunk_by") {
10495 if args.len() != 2 {
10496 return Err(StrykeError::runtime(
10497 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
10498 line,
10499 )
10500 .into());
10501 }
10502 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
10503 }
10504 if matches!(dispatch_name, "puniq" | "pfirst" | "pany") {
10505 let mut arg_vals = Vec::with_capacity(args.len());
10506 for a in args {
10507 arg_vals.push(self.eval_expr(a)?);
10508 }
10509 let saved_wa = self.wantarray_kind;
10510 self.wantarray_kind = ctx;
10511 let r = self.eval_par_list_call(dispatch_name, &arg_vals, ctx, line);
10512 self.wantarray_kind = saved_wa;
10513 return r.map_err(Into::into);
10514 }
10515 let arg_vals = if matches!(dispatch_name, "any" | "all" | "none" | "first")
10516 || matches!(
10517 dispatch_name,
10518 "take_while"
10519 | "drop_while"
10520 | "skip_while"
10521 | "reject"
10522 | "grepv"
10523 | "tap"
10524 | "peek"
10525 )
10526 || matches!(
10527 dispatch_name,
10528 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
10529 ) {
10530 if args.len() != 2 {
10531 return Err(StrykeError::runtime(
10532 format!("{}: expected BLOCK, LIST", name),
10533 line,
10534 )
10535 .into());
10536 }
10537 let cr = self.eval_expr(&args[0])?;
10538 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
10539 let mut v = vec![cr];
10540 v.extend(list_src.to_list());
10541 v
10542 } else if matches!(
10543 dispatch_name,
10544 "zip"
10545 | "zip_longest"
10546 | "zip_shortest"
10547 | "mesh"
10548 | "mesh_longest"
10549 | "mesh_shortest"
10550 ) {
10551 let mut v = Vec::with_capacity(args.len());
10552 for a in args {
10553 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
10554 }
10555 v
10556 } else if matches!(
10557 dispatch_name,
10558 "count" | "size" | "cnt" | "len" | "list_count" | "list_size"
10559 ) {
10560 let mut list_out = Vec::new();
10567 if args.len() == 1 {
10568 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10569 } else {
10570 for a in args {
10571 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
10572 }
10573 }
10574 list_out
10575 } else if matches!(
10576 dispatch_name,
10577 "uniq"
10578 | "distinct"
10579 | "uniqstr"
10580 | "uniqint"
10581 | "uniqnum"
10582 | "flatten"
10583 | "set"
10584 | "with_index"
10585 | "shuffle"
10586 | "sum"
10587 | "sum0"
10588 | "product"
10589 | "min"
10590 | "max"
10591 | "minstr"
10592 | "maxstr"
10593 | "mean"
10594 | "median"
10595 | "mode"
10596 | "stddev"
10597 | "variance"
10598 | "pairs"
10599 | "unpairs"
10600 | "pairkeys"
10601 | "pairvalues"
10602 ) {
10603 let mut list_out = Vec::new();
10607 if args.len() == 1 {
10608 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
10609 } else {
10610 for a in args {
10611 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
10612 }
10613 }
10614 list_out
10615 } else if matches!(dispatch_name, "take" | "head" | "tail" | "drop") {
10616 if args.is_empty() {
10617 return Err(StrykeError::runtime(
10618 "take/head/tail/drop: need LIST..., N or unary N",
10619 line,
10620 )
10621 .into());
10622 }
10623 let mut arg_vals = Vec::with_capacity(args.len());
10624 if args.len() == 1 {
10625 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10627 } else {
10628 for a in &args[..args.len() - 1] {
10629 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
10630 }
10631 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
10632 }
10633 arg_vals
10634 } else if matches!(dispatch_name, "chunked" | "windowed") {
10635 let mut list_out = Vec::new();
10636 match args.len() {
10637 0 => {
10638 return Err(StrykeError::runtime(
10639 format!("{name}: expected (LIST, N) or unary N after |>"),
10640 line,
10641 )
10642 .into());
10643 }
10644 1 => {
10645 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10647 }
10648 2 => {
10649 list_out.extend(
10650 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
10651 );
10652 list_out.push(self.eval_expr(&args[1])?);
10653 }
10654 _ => {
10655 return Err(StrykeError::runtime(
10656 format!(
10657 "{name}: expected exactly (LIST, N); use one list expression then size"
10658 ),
10659 line,
10660 )
10661 .into());
10662 }
10663 }
10664 list_out
10665 } else {
10666 let mut arg_vals = Vec::with_capacity(args.len());
10669 for a in args {
10670 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
10671 if let Some(items) = v.as_array_vec() {
10672 arg_vals.extend(items);
10673 } else {
10674 arg_vals.push(v);
10675 }
10676 }
10677 arg_vals
10678 };
10679 let saved_wa = self.wantarray_kind;
10681 self.wantarray_kind = ctx;
10682 if !crate::compat_mode() {
10685 if matches!(
10686 dispatch_name,
10687 "take_while"
10688 | "drop_while"
10689 | "skip_while"
10690 | "reject"
10691 | "grepv"
10692 | "tap"
10693 | "peek"
10694 ) {
10695 let r =
10696 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
10697 self.wantarray_kind = saved_wa;
10698 return r.map_err(Into::into);
10699 }
10700 if let Some(r) =
10701 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
10702 {
10703 self.wantarray_kind = saved_wa;
10704 return r.map_err(Into::into);
10705 }
10706 }
10707 if let Some(sub) = self.resolve_sub_by_name(name) {
10708 self.wantarray_kind = saved_wa;
10709 let args = self.with_topic_default_args(arg_vals);
10710 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
10711 return self.call_sub_with_package(&sub, args, ctx, line, pkg);
10712 }
10713 if crate::compat_mode() {
10715 if matches!(
10716 dispatch_name,
10717 "take_while"
10718 | "drop_while"
10719 | "skip_while"
10720 | "reject"
10721 | "grepv"
10722 | "tap"
10723 | "peek"
10724 ) {
10725 let r =
10726 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
10727 self.wantarray_kind = saved_wa;
10728 return r.map_err(Into::into);
10729 }
10730 if let Some(r) =
10731 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
10732 {
10733 self.wantarray_kind = saved_wa;
10734 return r.map_err(Into::into);
10735 }
10736 }
10737 self.wantarray_kind = saved_wa;
10738 self.call_named_sub(name, arg_vals, line, ctx)
10739 }
10740 ExprKind::IndirectCall {
10741 target,
10742 args,
10743 ampersand: _,
10744 pass_caller_arglist,
10745 } => {
10746 let tval = self.eval_expr(target)?;
10747 let arg_vals = if *pass_caller_arglist {
10748 self.scope.get_array("_")
10749 } else {
10750 let mut v = Vec::with_capacity(args.len());
10758 for a in args {
10759 if matches!(a.kind, ExprKind::ArrayVar(_) | ExprKind::HashVar(_)) {
10760 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
10761 if let Some(items) = val.as_array_vec() {
10762 v.extend(items);
10763 } else {
10764 v.push(val);
10765 }
10766 } else {
10767 v.push(self.eval_expr(a)?);
10768 }
10769 }
10770 v
10771 };
10772 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
10773 }
10774 ExprKind::MethodCall {
10775 object,
10776 method,
10777 args,
10778 super_call,
10779 } => {
10780 let obj = self.eval_expr(object)?;
10781 let mut arg_vals = vec![obj.clone()];
10782 for a in args {
10783 arg_vals.push(self.eval_expr(a)?);
10784 }
10785 if let Some(r) =
10786 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
10787 {
10788 return r.map_err(Into::into);
10789 }
10790 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
10791 return r.map_err(Into::into);
10792 }
10793 let class = if let Some(b) = obj.as_blessed_ref() {
10795 b.class.clone()
10796 } else if let Some(s) = obj.as_str() {
10797 s } else {
10799 return Err(
10800 StrykeError::runtime("Can't call method on non-object", line).into(),
10801 );
10802 };
10803 if method == "VERSION" && !*super_call {
10804 if let Some(ver) = self.package_version_scalar(class.as_str())? {
10805 return Ok(ver);
10806 }
10807 }
10808 if !*super_call {
10810 match method.as_str() {
10811 "isa" => {
10812 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
10813 let mro = self.mro_linearize(&class);
10814 let result = mro.iter().any(|c| c == &target);
10815 return Ok(StrykeValue::integer(if result { 1 } else { 0 }));
10816 }
10817 "can" => {
10818 let target_method =
10819 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
10820 let found = self
10821 .resolve_method_full_name(&class, &target_method, false)
10822 .and_then(|fq| self.subs.get(&fq))
10823 .is_some();
10824 if found {
10825 return Ok(StrykeValue::code_ref(Arc::new(StrykeSub {
10826 name: target_method,
10827 params: vec![],
10828 body: vec![],
10829 closure_env: None,
10830 prototype: None,
10831 fib_like: None,
10832 })));
10833 } else {
10834 return Ok(StrykeValue::UNDEF);
10835 }
10836 }
10837 "DOES" => {
10838 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
10839 let mro = self.mro_linearize(&class);
10840 let result = mro.iter().any(|c| c == &target);
10841 return Ok(StrykeValue::integer(if result { 1 } else { 0 }));
10842 }
10843 _ => {}
10844 }
10845 }
10846 let full_name = self
10847 .resolve_method_full_name(&class, method, *super_call)
10848 .ok_or_else(|| {
10849 StrykeError::runtime(
10850 format!(
10851 "Can't locate method \"{}\" for invocant \"{}\"",
10852 method, class
10853 ),
10854 line,
10855 )
10856 })?;
10857 if let Some(sub) = self.subs.get(&full_name).cloned() {
10858 self.call_sub(&sub, arg_vals, ctx, line)
10859 } else if method == "new" && !*super_call {
10860 self.builtin_new(&class, arg_vals, line)
10862 } else if let Some(r) =
10863 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
10864 {
10865 r
10866 } else {
10867 Err(StrykeError::runtime(
10868 format!(
10869 "Can't locate method \"{}\" in package \"{}\"",
10870 method, class
10871 ),
10872 line,
10873 )
10874 .into())
10875 }
10876 }
10877
10878 ExprKind::Print { handle, args } => {
10880 self.exec_print(handle.as_deref(), args, false, line)
10881 }
10882 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
10883 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
10884 ExprKind::Die(args) => {
10885 if args.is_empty() {
10886 let current = self.scope.get_scalar("@");
10888 let msg = if current.is_undef() || current.to_string().is_empty() {
10889 let mut m = "Died".to_string();
10890 m.push_str(&self.die_warn_at_suffix(line));
10891 m.push('\n');
10892 m
10893 } else {
10894 current.to_string()
10895 };
10896 self.fire_pseudosig_die(&msg, line)?;
10897 return Err(StrykeError::die(msg, line).into());
10898 }
10899 if args.len() == 1 {
10901 let v = self.eval_expr(&args[0])?;
10902 if v.as_hash_ref().is_some()
10903 || v.as_blessed_ref().is_some()
10904 || v.as_array_ref().is_some()
10905 || v.as_code_ref().is_some()
10906 {
10907 let msg = v.to_string();
10908 self.fire_pseudosig_die(&msg, line)?;
10909 return Err(StrykeError::die_with_value(v, msg, line).into());
10910 }
10911 }
10912 let mut msg = String::new();
10913 for a in args {
10914 let v = self.eval_expr(a)?;
10915 msg.push_str(&v.to_string());
10916 }
10917 if msg.is_empty() {
10918 msg = "Died".to_string();
10919 }
10920 if !msg.ends_with('\n') {
10921 msg.push_str(&self.die_warn_at_suffix(line));
10922 msg.push('\n');
10923 }
10924 self.fire_pseudosig_die(&msg, line)?;
10925 Err(StrykeError::die(msg, line).into())
10926 }
10927 ExprKind::Warn(args) => {
10928 let mut msg = String::new();
10929 for a in args {
10930 let v = self.eval_expr(a)?;
10931 msg.push_str(&v.to_string());
10932 }
10933 if msg.is_empty() {
10934 msg = "Warning: something's wrong".to_string();
10935 }
10936 if !msg.ends_with('\n') {
10937 msg.push_str(&self.die_warn_at_suffix(line));
10938 msg.push('\n');
10939 }
10940 self.fire_pseudosig_warn(&msg, line)?;
10941 Ok(StrykeValue::integer(1))
10942 }
10943
10944 ExprKind::Match {
10946 expr,
10947 pattern,
10948 flags,
10949 scalar_g,
10950 delim: _,
10951 } => {
10952 let val = self.eval_expr(expr)?;
10953 if val.is_iterator() {
10954 let source = crate::map_stream::into_pull_iter(val);
10955 let re = self.compile_regex(pattern, flags, line)?;
10956 let global = flags.contains('g');
10957 if global {
10958 return Ok(StrykeValue::iterator(std::sync::Arc::new(
10959 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
10960 )));
10961 } else {
10962 return Ok(StrykeValue::iterator(std::sync::Arc::new(
10963 crate::map_stream::MatchStreamIterator::new(source, re),
10964 )));
10965 }
10966 }
10967 let s = val.to_string();
10968 let pos_key = match &expr.kind {
10969 ExprKind::ScalarVar(n) => n.as_str(),
10970 _ => "_",
10971 };
10972 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
10973 }
10974 ExprKind::Substitution {
10975 expr,
10976 pattern,
10977 replacement,
10978 flags,
10979 delim: _,
10980 } => {
10981 let val = self.eval_expr(expr)?;
10982 if val.is_iterator() {
10983 let source = crate::map_stream::into_pull_iter(val);
10984 let re = self.compile_regex(pattern, flags, line)?;
10985 let global = flags.contains('g');
10986 return Ok(StrykeValue::iterator(std::sync::Arc::new(
10987 crate::map_stream::SubstStreamIterator::new(
10988 source,
10989 re,
10990 normalize_replacement_backrefs(replacement),
10991 global,
10992 ),
10993 )));
10994 }
10995 let s = val.to_string();
10996 self.regex_subst_execute(
10997 s,
10998 pattern,
10999 replacement.as_str(),
11000 flags.as_str(),
11001 expr,
11002 line,
11003 )
11004 }
11005 ExprKind::Transliterate {
11006 expr,
11007 from,
11008 to,
11009 flags,
11010 delim: _,
11011 } => {
11012 let val = self.eval_expr(expr)?;
11013 if val.is_iterator() {
11014 let source = crate::map_stream::into_pull_iter(val);
11015 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11016 crate::map_stream::TransliterateStreamIterator::new(
11017 source, from, to, flags,
11018 ),
11019 )));
11020 }
11021 let s = val.to_string();
11022 self.regex_transliterate_execute(
11023 s,
11024 from.as_str(),
11025 to.as_str(),
11026 flags.as_str(),
11027 expr,
11028 line,
11029 )
11030 }
11031
11032 ExprKind::MapExpr {
11034 block,
11035 list,
11036 flatten_array_refs,
11037 stream,
11038 } => {
11039 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11040 if *stream {
11041 let out =
11042 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
11043 if ctx == WantarrayCtx::List {
11044 return Ok(out);
11045 }
11046 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11047 }
11048 let items = list_val.to_list();
11049 if items.len() == 1 {
11050 if let Some(p) = items[0].as_pipeline() {
11051 if *flatten_array_refs {
11052 return Err(StrykeError::runtime(
11053 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
11054 line,
11055 )
11056 .into());
11057 }
11058 let sub = self.anon_coderef_from_block(block);
11059 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
11060 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
11061 }
11062 }
11063 let mut result = Vec::new();
11068 for item in items {
11069 self.scope.set_topic(item);
11070 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
11071 result.extend(val.map_flatten_outputs(*flatten_array_refs));
11072 }
11073 if ctx == WantarrayCtx::List {
11074 Ok(StrykeValue::array(result))
11075 } else {
11076 Ok(StrykeValue::integer(result.len() as i64))
11077 }
11078 }
11079 ExprKind::ForEachExpr { block, list } => {
11080 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11081 if list_val.is_iterator() {
11083 let iter = list_val.into_iterator();
11084 let mut count = 0i64;
11085 while let Some(item) = iter.next_item() {
11086 count += 1;
11087 self.scope.set_topic(item);
11088 self.exec_block(block)?;
11089 }
11090 return Ok(StrykeValue::integer(count));
11091 }
11092 let items = list_val.to_list();
11093 let count = items.len();
11094 for item in items {
11095 self.scope.set_topic(item);
11096 self.exec_block(block)?;
11097 }
11098 Ok(StrykeValue::integer(count as i64))
11099 }
11100 ExprKind::MapExprComma {
11101 expr,
11102 list,
11103 flatten_array_refs,
11104 stream,
11105 } => {
11106 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11107 if *stream {
11108 let out =
11109 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
11110 if ctx == WantarrayCtx::List {
11111 return Ok(out);
11112 }
11113 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11114 }
11115 let items = list_val.to_list();
11116 let mut result = Vec::new();
11117 for item in items {
11118 self.scope.set_topic_local(item.clone());
11124 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11125 let val = if !crate::compat_mode() {
11129 if let Some(sub) = val.as_code_ref() {
11130 let sub = sub.clone();
11131 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::List, line)?
11132 } else {
11133 val
11134 }
11135 } else {
11136 val
11137 };
11138 result.extend(val.map_flatten_outputs(*flatten_array_refs));
11139 }
11140 if ctx == WantarrayCtx::List {
11141 Ok(StrykeValue::array(result))
11142 } else {
11143 Ok(StrykeValue::integer(result.len() as i64))
11144 }
11145 }
11146 ExprKind::GrepExpr {
11147 block,
11148 list,
11149 keyword,
11150 } => {
11151 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11152 if keyword.is_stream() {
11153 let out = self.filter_stream_block_output(list_val, block, line)?;
11154 if ctx == WantarrayCtx::List {
11155 return Ok(out);
11156 }
11157 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11158 }
11159 let items = list_val.to_list();
11160 if items.len() == 1 {
11161 if let Some(p) = items[0].as_pipeline() {
11162 let sub = self.anon_coderef_from_block(block);
11163 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
11164 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
11165 }
11166 }
11167 let mut result = Vec::new();
11168 for item in items {
11169 self.scope.set_topic(item.clone());
11170 let val = self.exec_block(block)?;
11171 let keep = if let Some(re) = val.as_regex() {
11174 re.is_match(&item.to_string())
11175 } else {
11176 val.is_true()
11177 };
11178 if keep {
11179 result.push(item);
11180 }
11181 }
11182 if ctx == WantarrayCtx::List {
11183 Ok(StrykeValue::array(result))
11184 } else {
11185 Ok(StrykeValue::integer(result.len() as i64))
11186 }
11187 }
11188 ExprKind::GrepExprComma {
11189 expr,
11190 list,
11191 keyword,
11192 } => {
11193 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11194 if keyword.is_stream() {
11195 let out = self.filter_stream_expr_output(list_val, expr, line)?;
11196 if ctx == WantarrayCtx::List {
11197 return Ok(out);
11198 }
11199 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11200 }
11201 let items = list_val.to_list();
11202 let mut result = Vec::new();
11203 for item in items {
11204 self.scope.set_topic_local(item.clone());
11208 let val = self.eval_expr(expr)?;
11209 let val = if !crate::compat_mode() {
11213 if let Some(sub) = val.as_code_ref() {
11214 let sub = sub.clone();
11215 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?
11216 } else {
11217 val
11218 }
11219 } else {
11220 val
11221 };
11222 let keep = if let Some(re) = val.as_regex() {
11223 re.is_match(&item.to_string())
11224 } else {
11225 val.is_true()
11226 };
11227 if keep {
11228 result.push(item);
11229 }
11230 }
11231 if ctx == WantarrayCtx::List {
11232 Ok(StrykeValue::array(result))
11233 } else {
11234 Ok(StrykeValue::integer(result.len() as i64))
11235 }
11236 }
11237 ExprKind::SortExpr { cmp, list } => {
11238 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11239 let mut items = list_val.to_list();
11240 match cmp {
11241 Some(SortComparator::Code(code_expr)) => {
11242 let sub = self.eval_expr(code_expr)?;
11243 let Some(sub) = sub.as_code_ref() else {
11244 return Err(StrykeError::runtime(
11245 "sort: comparator must be a code reference",
11246 line,
11247 )
11248 .into());
11249 };
11250 let saved_topic = self.scope.save_topic_chain();
11253 let sub = sub.clone();
11254 items.sort_by(|a, b| {
11255 self.scope.set_sort_pair(a.clone(), b.clone());
11260 match self.call_sub(&sub, vec![a.clone(), b.clone()], ctx, line) {
11261 Ok(v) => {
11262 let n = v.to_int();
11263 if n < 0 {
11264 Ordering::Less
11265 } else if n > 0 {
11266 Ordering::Greater
11267 } else {
11268 Ordering::Equal
11269 }
11270 }
11271 Err(_) => Ordering::Equal,
11272 }
11273 });
11274 self.scope.restore_topic_chain(saved_topic);
11275 }
11276 Some(SortComparator::Block(cmp_block)) => {
11277 if let Some(mode) = detect_sort_block_fast(cmp_block) {
11278 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
11279 } else {
11280 let saved_topic = self.scope.save_topic_chain();
11283 let cmp_block = cmp_block.clone();
11284 items.sort_by(|a, b| {
11285 self.scope.set_sort_pair(a.clone(), b.clone());
11286 match self.exec_block(&cmp_block) {
11287 Ok(v) => {
11288 let n = v.to_int();
11289 if n < 0 {
11290 Ordering::Less
11291 } else if n > 0 {
11292 Ordering::Greater
11293 } else {
11294 Ordering::Equal
11295 }
11296 }
11297 Err(_) => Ordering::Equal,
11298 }
11299 });
11300 self.scope.restore_topic_chain(saved_topic);
11301 }
11302 }
11303 None => {
11304 items.sort_by_key(|a| a.to_string());
11305 }
11306 }
11307 Ok(StrykeValue::array(items))
11308 }
11309 ExprKind::Rev(expr) => {
11310 let val = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11312 if val.is_iterator() {
11313 return Ok(StrykeValue::iterator(Arc::new(
11314 crate::value::RevIterator::new(val.into_iterator()),
11315 )));
11316 }
11317 if let Some(s) = crate::value::set_payload(&val) {
11318 let mut out = crate::value::PerlSet::new();
11319 for (k, v) in s.iter().rev() {
11320 out.insert(k.clone(), v.clone());
11321 }
11322 return Ok(StrykeValue::set(Arc::new(out)));
11323 }
11324 if let Some(ar) = val.as_array_ref() {
11325 let items: Vec<_> = ar.read().iter().rev().cloned().collect();
11326 return Ok(StrykeValue::array_ref(Arc::new(parking_lot::RwLock::new(
11327 items,
11328 ))));
11329 }
11330 if let Some(hr) = val.as_hash_ref() {
11331 let mut out: indexmap::IndexMap<String, StrykeValue> =
11332 indexmap::IndexMap::new();
11333 for (k, v) in hr.read().iter() {
11334 out.insert(v.to_string(), StrykeValue::string(k.clone()));
11335 }
11336 return Ok(StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(
11337 out,
11338 ))));
11339 }
11340 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11342 if let Some(hm) = val.as_hash_map() {
11343 let mut out: indexmap::IndexMap<String, StrykeValue> =
11344 indexmap::IndexMap::new();
11345 for (k, v) in hm.iter() {
11346 out.insert(v.to_string(), StrykeValue::string(k.clone()));
11347 }
11348 return Ok(StrykeValue::hash(out));
11349 }
11350 if val.as_array_vec().is_some() {
11351 let mut items = val.to_list();
11352 items.reverse();
11353 Ok(StrykeValue::array(items))
11354 } else {
11355 let items = val.to_list();
11356 if items.len() > 1 {
11357 let mut items = items;
11358 items.reverse();
11359 Ok(StrykeValue::array(items))
11360 } else {
11361 let s = val.to_string();
11362 Ok(StrykeValue::string(s.chars().rev().collect()))
11363 }
11364 }
11365 }
11366 ExprKind::ReverseExpr(list) => {
11367 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11368 match ctx {
11369 WantarrayCtx::List => {
11370 let mut items = val.to_list();
11371 items.reverse();
11372 Ok(StrykeValue::array(items))
11373 }
11374 _ => {
11375 let items = val.to_list();
11376 let s: String = items.iter().map(|v| v.to_string()).collect();
11377 Ok(StrykeValue::string(s.chars().rev().collect()))
11378 }
11379 }
11380 }
11381
11382 ExprKind::ParLinesExpr {
11384 path,
11385 callback,
11386 progress,
11387 } => self.eval_par_lines_expr(
11388 path.as_ref(),
11389 callback.as_ref(),
11390 progress.as_deref(),
11391 line,
11392 ),
11393 ExprKind::ParWalkExpr {
11394 path,
11395 callback,
11396 progress,
11397 } => {
11398 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
11399 }
11400 ExprKind::PwatchExpr { path, callback } => {
11401 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
11402 }
11403 ExprKind::PMapExpr {
11404 block,
11405 list,
11406 progress,
11407 flat_outputs,
11408 on_cluster,
11409 stream,
11410 } => {
11411 let show_progress = progress
11412 .as_ref()
11413 .map(|p| self.eval_expr(p))
11414 .transpose()?
11415 .map(|v| v.is_true())
11416 .unwrap_or(false);
11417 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11424 if let Some(cluster_e) = on_cluster {
11425 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
11426 return self.eval_pmap_remote(
11427 cluster_val,
11428 list_val,
11429 show_progress,
11430 block,
11431 *flat_outputs,
11432 line,
11433 );
11434 }
11435 if *stream {
11436 let source = crate::map_stream::into_pull_iter(list_val);
11437 let sub = self.anon_coderef_from_block(block);
11438 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
11439 return Ok(StrykeValue::iterator(Arc::new(
11440 crate::map_stream::PMapStreamIterator::new(
11441 source,
11442 sub,
11443 self.subs.clone(),
11444 capture,
11445 atomic_arrays,
11446 atomic_hashes,
11447 *flat_outputs,
11448 ),
11449 )));
11450 }
11451 let items = list_val.to_list();
11452 let block = block.clone();
11453 let subs = self.subs.clone();
11454 let (scope_capture, atomic_arrays, atomic_hashes) =
11455 self.scope.capture_with_atomics();
11456 let pmap_progress = PmapProgress::new(show_progress, items.len());
11457
11458 if *flat_outputs {
11459 let mut indexed: Vec<(usize, Vec<StrykeValue>)> = items
11460 .into_par_iter()
11461 .enumerate()
11462 .map(|(i, item)| {
11463 let mut local_interp = VMHelper::new();
11464 local_interp.subs = subs.clone();
11465 local_interp.scope.restore_capture(&scope_capture);
11466 local_interp
11467 .scope
11468 .restore_atomics(&atomic_arrays, &atomic_hashes);
11469 local_interp.enable_parallel_guard();
11470 local_interp.scope.set_topic(item);
11471 let val = match local_interp.exec_block(&block) {
11472 Ok(val) => val,
11473 Err(_) => StrykeValue::UNDEF,
11474 };
11475 let chunk = val.map_flatten_outputs(true);
11476 pmap_progress.tick();
11477 (i, chunk)
11478 })
11479 .collect();
11480 pmap_progress.finish();
11481 indexed.sort_by_key(|(i, _)| *i);
11482 let results: Vec<StrykeValue> =
11483 indexed.into_iter().flat_map(|(_, v)| v).collect();
11484 Ok(StrykeValue::array(results))
11485 } else {
11486 let results: Vec<StrykeValue> = items
11487 .into_par_iter()
11488 .map(|item| {
11489 let mut local_interp = VMHelper::new();
11490 local_interp.subs = subs.clone();
11491 local_interp.scope.restore_capture(&scope_capture);
11492 local_interp
11493 .scope
11494 .restore_atomics(&atomic_arrays, &atomic_hashes);
11495 local_interp.enable_parallel_guard();
11496 local_interp.scope.set_topic(item);
11497 let val = match local_interp.exec_block(&block) {
11498 Ok(val) => val,
11499 Err(_) => StrykeValue::UNDEF,
11500 };
11501 pmap_progress.tick();
11502 val
11503 })
11504 .collect();
11505 pmap_progress.finish();
11506 Ok(StrykeValue::array(results))
11507 }
11508 }
11509 ExprKind::PMapChunkedExpr {
11510 chunk_size,
11511 block,
11512 list,
11513 progress,
11514 } => {
11515 let show_progress = progress
11516 .as_ref()
11517 .map(|p| self.eval_expr(p))
11518 .transpose()?
11519 .map(|v| v.is_true())
11520 .unwrap_or(false);
11521 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
11522 let list_val = self.eval_expr(list)?;
11523 let items = list_val.to_list();
11524 let block = block.clone();
11525 let subs = self.subs.clone();
11526 let (scope_capture, atomic_arrays, atomic_hashes) =
11527 self.scope.capture_with_atomics();
11528
11529 let indexed_chunks: Vec<(usize, Vec<StrykeValue>)> = items
11530 .chunks(chunk_n)
11531 .enumerate()
11532 .map(|(i, c)| (i, c.to_vec()))
11533 .collect();
11534
11535 let n_chunks = indexed_chunks.len();
11536 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
11537
11538 let mut chunk_results: Vec<(usize, Vec<StrykeValue>)> = indexed_chunks
11539 .into_par_iter()
11540 .map(|(chunk_idx, chunk)| {
11541 let mut local_interp = VMHelper::new();
11542 local_interp.subs = subs.clone();
11543 local_interp.scope.restore_capture(&scope_capture);
11544 local_interp
11545 .scope
11546 .restore_atomics(&atomic_arrays, &atomic_hashes);
11547 local_interp.enable_parallel_guard();
11548 let mut out = Vec::with_capacity(chunk.len());
11549 for item in chunk {
11550 local_interp.scope.set_topic(item);
11551 match local_interp.exec_block(&block) {
11552 Ok(val) => out.push(val),
11553 Err(_) => out.push(StrykeValue::UNDEF),
11554 }
11555 }
11556 pmap_progress.tick();
11557 (chunk_idx, out)
11558 })
11559 .collect();
11560
11561 pmap_progress.finish();
11562 chunk_results.sort_by_key(|(i, _)| *i);
11563 let results: Vec<StrykeValue> =
11564 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
11565 Ok(StrykeValue::array(results))
11566 }
11567 ExprKind::PGrepExpr {
11568 block,
11569 list,
11570 progress,
11571 stream,
11572 } => {
11573 let show_progress = progress
11574 .as_ref()
11575 .map(|p| self.eval_expr(p))
11576 .transpose()?
11577 .map(|v| v.is_true())
11578 .unwrap_or(false);
11579 let list_val = self.eval_expr(list)?;
11580 if *stream {
11581 let source = crate::map_stream::into_pull_iter(list_val);
11582 let sub = self.anon_coderef_from_block(block);
11583 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
11584 return Ok(StrykeValue::iterator(Arc::new(
11585 crate::map_stream::PGrepStreamIterator::new(
11586 source,
11587 sub,
11588 self.subs.clone(),
11589 capture,
11590 atomic_arrays,
11591 atomic_hashes,
11592 ),
11593 )));
11594 }
11595 let items = list_val.to_list();
11596 let block = block.clone();
11597 let subs = self.subs.clone();
11598 let (scope_capture, atomic_arrays, atomic_hashes) =
11599 self.scope.capture_with_atomics();
11600 let pmap_progress = PmapProgress::new(show_progress, items.len());
11601
11602 let results: Vec<StrykeValue> = items
11603 .into_par_iter()
11604 .filter_map(|item| {
11605 let mut local_interp = VMHelper::new();
11606 local_interp.subs = subs.clone();
11607 local_interp.scope.restore_capture(&scope_capture);
11608 local_interp
11609 .scope
11610 .restore_atomics(&atomic_arrays, &atomic_hashes);
11611 local_interp.enable_parallel_guard();
11612 local_interp.scope.set_topic(item.clone());
11613 let keep = match local_interp.exec_block(&block) {
11614 Ok(val) => val.is_true(),
11615 Err(_) => false,
11616 };
11617 pmap_progress.tick();
11618 if keep {
11619 Some(item)
11620 } else {
11621 None
11622 }
11623 })
11624 .collect();
11625 pmap_progress.finish();
11626 Ok(StrykeValue::array(results))
11627 }
11628 ExprKind::ParExpr { block, list } => {
11629 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11644 let n_threads = rayon::current_num_threads().clamp(1, 8);
11645 let chunks = par_chunk_value(&list_val, n_threads);
11646 if chunks.len() < 2 {
11647 self.scope.set_topic(list_val);
11650 let v = self.exec_block(block)?;
11651 return Ok(v);
11652 }
11653 let block_clone = block.clone();
11654 let subs = self.subs.clone();
11655 let (scope_capture, atomic_arrays, atomic_hashes) =
11656 self.scope.capture_with_atomics();
11657 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
11658 let err_w = Arc::clone(&first_err);
11659 let per_chunk: Vec<Vec<StrykeValue>> = chunks
11660 .into_par_iter()
11661 .map(|chunk| {
11662 if err_w.lock().is_some() {
11663 return Vec::new();
11664 }
11665 let mut local_interp = VMHelper::new();
11666 local_interp.subs = subs.clone();
11667 local_interp.scope.restore_capture(&scope_capture);
11668 local_interp
11669 .scope
11670 .restore_atomics(&atomic_arrays, &atomic_hashes);
11671 local_interp.enable_parallel_guard();
11672 local_interp.scope.set_topic(chunk);
11673 match local_interp.exec_block(&block_clone) {
11674 Ok(v) => v.map_flatten_outputs(true),
11675 Err(e) => {
11676 let mut g = err_w.lock();
11677 if g.is_none() {
11678 *g = Some(format!("par: {:?}", e));
11679 }
11680 Vec::new()
11681 }
11682 }
11683 })
11684 .collect();
11685 if let Some(msg) = first_err.lock().take() {
11686 return Err(FlowOrError::Error(StrykeError::runtime(msg, line)));
11687 }
11688 let total: usize = per_chunk.iter().map(|v| v.len()).sum();
11689 let mut out = Vec::with_capacity(total);
11690 for v in per_chunk {
11691 out.extend(v);
11692 }
11693 Ok(StrykeValue::array(out))
11694 }
11695 ExprKind::ParReduceExpr {
11696 extract_block,
11697 reduce_block,
11698 list,
11699 } => {
11700 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11711 let n_threads = rayon::current_num_threads().clamp(1, 8);
11712 let chunks = par_chunk_value(&list_val, n_threads);
11713 if chunks.len() < 2 {
11714 let chunk_arr = match list_val.as_array_vec() {
11719 Some(arr) => arr,
11720 None => vec![list_val.clone()],
11721 };
11722 let first = chunk_arr.first().cloned().unwrap_or(StrykeValue::UNDEF);
11723 self.scope.declare_array("_", chunk_arr);
11724 self.scope.set_topic(first);
11725 return self.exec_block(extract_block);
11726 }
11727 let extract = extract_block.clone();
11728 let subs = self.subs.clone();
11729 let (scope_capture, atomic_arrays, atomic_hashes) =
11730 self.scope.capture_with_atomics();
11731 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
11732 let err_w = Arc::clone(&first_err);
11733 let per_chunk: Vec<StrykeValue> = chunks
11734 .into_par_iter()
11735 .map(|chunk| {
11736 if err_w.lock().is_some() {
11737 return StrykeValue::UNDEF;
11738 }
11739 let mut local = VMHelper::new();
11740 local.subs = subs.clone();
11741 local.scope.restore_capture(&scope_capture);
11742 local.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
11743 local.enable_parallel_guard();
11744 let chunk_arr = match chunk.as_array_vec() {
11748 Some(arr) => arr,
11749 None => vec![chunk.clone()],
11750 };
11751 let first = chunk_arr.first().cloned().unwrap_or(StrykeValue::UNDEF);
11752 local.scope.declare_array("_", chunk_arr);
11753 local.scope.set_topic(first);
11754 match local.exec_block(&extract) {
11755 Ok(v) => v,
11756 Err(e) => {
11757 let mut g = err_w.lock();
11758 if g.is_none() {
11759 *g = Some(format!("par_reduce: {:?}", e));
11760 }
11761 StrykeValue::UNDEF
11762 }
11763 }
11764 })
11765 .collect();
11766 if let Some(msg) = first_err.lock().take() {
11767 return Err(FlowOrError::Error(StrykeError::runtime(msg, line)));
11768 }
11769 if per_chunk.is_empty() {
11770 return Ok(StrykeValue::UNDEF);
11771 }
11772 if let Some(rb) = reduce_block {
11774 let mut acc = per_chunk[0].clone();
11775 for v in per_chunk.into_iter().skip(1) {
11776 self.scope.declare_scalar("a", acc.clone());
11777 self.scope.declare_scalar("b", v);
11778 acc = self.exec_block(rb)?;
11779 }
11780 return Ok(acc);
11781 }
11782 Ok(par_reduce_auto_merge(per_chunk))
11784 }
11785 ExprKind::DistReduceExpr {
11786 cluster,
11787 extract_block,
11788 list,
11789 } => {
11790 let cluster_pv = self.eval_expr(cluster)?;
11802 let Some(remote_cluster) = cluster_pv.as_remote_cluster() else {
11803 return Err(StrykeError::runtime(
11804 "~d>: expected cluster(...) value after `on`",
11805 line,
11806 )
11807 .into());
11808 };
11809 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11810 let items_flat = list_val.to_list();
11811 if items_flat.is_empty() {
11812 return Ok(StrykeValue::array(vec![]));
11813 }
11814 let (scope_capture, atomic_arrays, atomic_hashes) =
11815 self.scope.capture_with_atomics();
11816 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
11817 return Err(StrykeError::runtime(
11818 "~d>: mysync/atomic capture is not supported for remote workers",
11819 line,
11820 )
11821 .into());
11822 }
11823 let n_slots = remote_cluster.slots.len().max(1);
11828 let target_chunks = (n_slots * 4).min(items_flat.len()).max(1);
11829 let chunk_size = items_flat.len().div_ceil(target_chunks);
11830 let mut chunk_items: Vec<serde_json::Value> = Vec::new();
11831 let mut chunk_jsons_buf: Vec<StrykeValue> = Vec::new();
11832 for item in items_flat.into_iter() {
11833 chunk_jsons_buf.push(item);
11834 if chunk_jsons_buf.len() >= chunk_size {
11835 let drained: Vec<StrykeValue> = std::mem::take(&mut chunk_jsons_buf);
11836 let as_array = StrykeValue::array(drained);
11837 chunk_items.push(
11838 crate::remote_wire::perl_to_json_value(&as_array)
11839 .map_err(|e| StrykeError::runtime(e, line))?,
11840 );
11841 }
11842 }
11843 if !chunk_jsons_buf.is_empty() {
11844 let as_array = StrykeValue::array(chunk_jsons_buf);
11845 chunk_items.push(
11846 crate::remote_wire::perl_to_json_value(&as_array)
11847 .map_err(|e| StrykeError::runtime(e, line))?,
11848 );
11849 }
11850 let cap_json: Vec<(String, serde_json::Value)> = scope_capture
11858 .iter()
11859 .filter_map(|(k, v)| {
11860 crate::remote_wire::perl_to_json_value(v)
11861 .ok()
11862 .map(|j| (k.clone(), j))
11863 })
11864 .collect();
11865 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
11866 let user_block_src = crate::fmt::format_block(extract_block);
11878 let block_src = format!("@_ = $_;\n[ {user_block_src} ]");
11879 let result_values = crate::cluster::run_cluster(
11880 &remote_cluster,
11881 subs_prelude,
11882 block_src,
11883 cap_json,
11884 chunk_items,
11885 )
11886 .map_err(|e| StrykeError::runtime(format!("~d> remote: {e}"), line))?;
11887 let mut merged: Vec<StrykeValue> = Vec::new();
11891 for v in result_values {
11892 if let Some(items) = v.as_array_vec() {
11893 merged.extend(items);
11894 } else if let Some(ar) = v.as_array_ref() {
11895 merged.extend(ar.read().iter().cloned());
11896 } else {
11897 merged.push(v);
11898 }
11899 }
11900 Ok(StrykeValue::array(merged))
11901 }
11902 ExprKind::PForExpr {
11903 block,
11904 list,
11905 progress,
11906 } => {
11907 let show_progress = progress
11908 .as_ref()
11909 .map(|p| self.eval_expr(p))
11910 .transpose()?
11911 .map(|v| v.is_true())
11912 .unwrap_or(false);
11913 let list_val = self.eval_expr(list)?;
11914 let items = list_val.to_list();
11915 let block = block.clone();
11916 let subs = self.subs.clone();
11917 let (scope_capture, atomic_arrays, atomic_hashes) =
11918 self.scope.capture_with_atomics();
11919
11920 let pmap_progress = PmapProgress::new(show_progress, items.len());
11921 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
11922 items.into_par_iter().for_each(|item| {
11923 if first_err.lock().is_some() {
11924 return;
11925 }
11926 let mut local_interp = VMHelper::new();
11927 local_interp.subs = subs.clone();
11928 local_interp.scope.restore_capture(&scope_capture);
11929 local_interp
11930 .scope
11931 .restore_atomics(&atomic_arrays, &atomic_hashes);
11932 local_interp.enable_parallel_guard();
11933 local_interp.scope.set_topic(item);
11934 match local_interp.exec_block(&block) {
11935 Ok(_) => {}
11936 Err(e) => {
11937 let stryke = match e {
11938 FlowOrError::Error(stryke) => stryke,
11939 FlowOrError::Flow(_) => StrykeError::runtime(
11940 "return/last/next/redo not supported inside pfor block",
11941 line,
11942 ),
11943 };
11944 let mut g = first_err.lock();
11945 if g.is_none() {
11946 *g = Some(stryke);
11947 }
11948 }
11949 }
11950 pmap_progress.tick();
11951 });
11952 pmap_progress.finish();
11953 if let Some(e) = first_err.lock().take() {
11954 return Err(FlowOrError::Error(e));
11955 }
11956 Ok(StrykeValue::UNDEF)
11957 }
11958 ExprKind::FanExpr {
11959 count,
11960 block,
11961 progress,
11962 capture,
11963 } => {
11964 let show_progress = progress
11965 .as_ref()
11966 .map(|p| self.eval_expr(p))
11967 .transpose()?
11968 .map(|v| v.is_true())
11969 .unwrap_or(false);
11970 let n = match count {
11971 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
11972 None => self.parallel_thread_count(),
11973 };
11974 let block = block.clone();
11975 let subs = self.subs.clone();
11976 let (scope_capture, atomic_arrays, atomic_hashes) =
11977 self.scope.capture_with_atomics();
11978
11979 let fan_progress = FanProgress::new(show_progress, n);
11980 if *capture {
11981 if n == 0 {
11982 return Ok(StrykeValue::array(Vec::new()));
11983 }
11984 let pairs: Vec<(usize, ExecResult)> = (0..n)
11985 .into_par_iter()
11986 .map(|i| {
11987 fan_progress.start_worker(i);
11988 let mut local_interp = VMHelper::new();
11989 local_interp.subs = subs.clone();
11990 local_interp.suppress_stdout = show_progress;
11991 local_interp.scope.restore_capture(&scope_capture);
11992 local_interp
11993 .scope
11994 .restore_atomics(&atomic_arrays, &atomic_hashes);
11995 local_interp.enable_parallel_guard();
11996 local_interp.scope.set_topic(StrykeValue::integer(i as i64));
11997 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
11998 let res = local_interp.exec_block(&block);
11999 crate::parallel_trace::fan_worker_set_index(None);
12000 fan_progress.finish_worker(i);
12001 (i, res)
12002 })
12003 .collect();
12004 fan_progress.finish();
12005 let mut pairs = pairs;
12006 pairs.sort_by_key(|(i, _)| *i);
12007 let mut out = Vec::with_capacity(n);
12008 for (_, r) in pairs {
12009 match r {
12010 Ok(v) => out.push(v),
12011 Err(e) => return Err(e),
12012 }
12013 }
12014 return Ok(StrykeValue::array(out));
12015 }
12016 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
12017 (0..n).into_par_iter().for_each(|i| {
12018 if first_err.lock().is_some() {
12019 return;
12020 }
12021 fan_progress.start_worker(i);
12022 let mut local_interp = VMHelper::new();
12023 local_interp.subs = subs.clone();
12024 local_interp.suppress_stdout = show_progress;
12025 local_interp.scope.restore_capture(&scope_capture);
12026 local_interp
12027 .scope
12028 .restore_atomics(&atomic_arrays, &atomic_hashes);
12029 local_interp.enable_parallel_guard();
12030 local_interp.scope.set_topic(StrykeValue::integer(i as i64));
12031 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
12032 match local_interp.exec_block(&block) {
12033 Ok(_) => {}
12034 Err(e) => {
12035 let stryke = match e {
12036 FlowOrError::Error(stryke) => stryke,
12037 FlowOrError::Flow(_) => StrykeError::runtime(
12038 "return/last/next/redo not supported inside fan block",
12039 line,
12040 ),
12041 };
12042 let mut g = first_err.lock();
12043 if g.is_none() {
12044 *g = Some(stryke);
12045 }
12046 }
12047 }
12048 crate::parallel_trace::fan_worker_set_index(None);
12049 fan_progress.finish_worker(i);
12050 });
12051 fan_progress.finish();
12052 if let Some(e) = first_err.lock().take() {
12053 return Err(FlowOrError::Error(e));
12054 }
12055 Ok(StrykeValue::UNDEF)
12056 }
12057 ExprKind::RetryBlock {
12058 body,
12059 times,
12060 backoff,
12061 } => self.eval_retry_block(body, times, *backoff, line),
12062 ExprKind::RateLimitBlock {
12063 slot,
12064 max,
12065 window,
12066 body,
12067 } => self.eval_rate_limit_block(*slot, max, window, body, line),
12068 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
12069 ExprKind::GenBlock { body } => {
12070 let g = Arc::new(PerlGenerator {
12071 block: body.clone(),
12072 pc: Mutex::new(0),
12073 scope_started: Mutex::new(false),
12074 exhausted: Mutex::new(false),
12075 });
12076 Ok(StrykeValue::generator(g))
12077 }
12078 ExprKind::Yield(e) => {
12079 if !self.in_generator {
12080 return Err(StrykeError::runtime("yield outside gen block", line).into());
12081 }
12082 let v = self.eval_expr(e)?;
12083 Err(FlowOrError::Flow(Flow::Yield(v)))
12084 }
12085 ExprKind::AlgebraicMatch { subject, arms } => {
12086 self.eval_algebraic_match(subject, arms, line)
12087 }
12088 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
12089 Ok(self.spawn_async_block(body))
12090 }
12091 ExprKind::Trace { body } => {
12092 crate::parallel_trace::trace_enter();
12093 let out = self.exec_block(body);
12094 crate::parallel_trace::trace_leave();
12095 out
12096 }
12097 ExprKind::Spinner { message, body } => {
12098 use std::io::Write as _;
12099 let msg = self.eval_expr(message)?.to_string();
12100 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
12101 let done2 = done.clone();
12102 let handle = std::thread::spawn(move || {
12103 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12104 let mut i = 0;
12105 let stderr = std::io::stderr();
12106 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
12107 {
12108 let stdout = std::io::stdout();
12109 let _stdout_lock = stdout.lock();
12110 let mut err = stderr.lock();
12111 let _ = write!(
12112 err,
12113 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
12114 frames[i % frames.len()],
12115 msg
12116 );
12117 let _ = err.flush();
12118 }
12119 std::thread::sleep(std::time::Duration::from_millis(80));
12120 i += 1;
12121 }
12122 let mut err = stderr.lock();
12123 let _ = write!(err, "\r\x1b[2K");
12124 let _ = err.flush();
12125 });
12126 let result = self.exec_block(body);
12127 done.store(true, std::sync::atomic::Ordering::Relaxed);
12128 let _ = handle.join();
12129 result
12130 }
12131 ExprKind::Timer { body } => {
12132 let start = std::time::Instant::now();
12133 self.exec_block(body)?;
12134 let ms = start.elapsed().as_secs_f64() * 1000.0;
12135 Ok(StrykeValue::float(ms))
12136 }
12137 ExprKind::Bench { body, times } => {
12138 let n = self.eval_expr(times)?.to_int();
12139 if n < 0 {
12140 return Err(StrykeError::runtime(
12141 "bench: iteration count must be non-negative",
12142 line,
12143 )
12144 .into());
12145 }
12146 self.run_bench_block(body, n as usize, line)
12147 }
12148 ExprKind::Await(expr) => {
12149 let v = self.eval_expr(expr)?;
12150 if let Some(t) = v.as_async_task() {
12151 t.await_result().map_err(FlowOrError::from)
12152 } else {
12153 Ok(v)
12154 }
12155 }
12156 ExprKind::Slurp(e) => {
12157 let path = self.eval_expr(e)?.to_string();
12158 let path = self.resolve_stryke_path_string(&path);
12159 crate::perl_fs::read_file_text_or_glob(&path)
12160 .map(StrykeValue::string)
12161 .map_err(|e| {
12162 FlowOrError::Error(StrykeError::runtime(format!("slurp: {}", e), line))
12163 })
12164 }
12165 ExprKind::Capture(e) => {
12166 let cmd = self.eval_expr(e)?.to_string();
12167 let output = Command::new("sh")
12168 .arg("-c")
12169 .arg(&cmd)
12170 .output()
12171 .map_err(|e| {
12172 FlowOrError::Error(StrykeError::runtime(format!("capture: {}", e), line))
12173 })?;
12174 self.record_child_exit_status(output.status);
12175 let exitcode = output.status.code().unwrap_or(-1) as i64;
12176 let stdout = decode_utf8_or_latin1(&output.stdout);
12177 let stderr = decode_utf8_or_latin1(&output.stderr);
12178 Ok(StrykeValue::capture(Arc::new(CaptureResult {
12179 stdout,
12180 stderr,
12181 exitcode,
12182 })))
12183 }
12184 ExprKind::Qx(e) => {
12185 let cmd = self.eval_expr(e)?.to_string();
12186 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)
12187 }
12188 ExprKind::FetchUrl(e) => {
12189 let url = self.eval_expr(e)?.to_string();
12190 ureq::get(&url)
12191 .call()
12192 .map_err(|e| {
12193 FlowOrError::Error(StrykeError::runtime(format!("fetch_url: {}", e), line))
12194 })
12195 .and_then(|r| {
12196 r.into_string().map(StrykeValue::string).map_err(|e| {
12197 FlowOrError::Error(StrykeError::runtime(
12198 format!("fetch_url: {}", e),
12199 line,
12200 ))
12201 })
12202 })
12203 }
12204 ExprKind::Pchannel { capacity } => {
12205 if let Some(c) = capacity {
12206 let n = self.eval_expr(c)?.to_int().max(1) as usize;
12207 Ok(crate::pchannel::create_bounded_pair(n))
12208 } else {
12209 Ok(crate::pchannel::create_pair())
12210 }
12211 }
12212 ExprKind::PSortExpr {
12213 cmp,
12214 list,
12215 progress,
12216 } => {
12217 let show_progress = progress
12218 .as_ref()
12219 .map(|p| self.eval_expr(p))
12220 .transpose()?
12221 .map(|v| v.is_true())
12222 .unwrap_or(false);
12223 let list_val = self.eval_expr(list)?;
12224 let mut items = list_val.to_list();
12225 let pmap_progress = PmapProgress::new(show_progress, 2);
12226 pmap_progress.tick();
12227 if let Some(cmp_block) = cmp {
12228 if let Some(mode) = detect_sort_block_fast(cmp_block) {
12229 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
12230 } else {
12231 let cmp_block = cmp_block.clone();
12232 let subs = self.subs.clone();
12233 let scope_capture = self.scope.capture();
12234 items.par_sort_by(|a, b| {
12235 let mut local_interp = VMHelper::new();
12236 local_interp.subs = subs.clone();
12237 local_interp.scope.restore_capture(&scope_capture);
12238 local_interp.scope.set_sort_pair(a.clone(), b.clone());
12239 match local_interp.exec_block(&cmp_block) {
12240 Ok(v) => {
12241 let n = v.to_int();
12242 if n < 0 {
12243 std::cmp::Ordering::Less
12244 } else if n > 0 {
12245 std::cmp::Ordering::Greater
12246 } else {
12247 std::cmp::Ordering::Equal
12248 }
12249 }
12250 Err(_) => std::cmp::Ordering::Equal,
12251 }
12252 });
12253 }
12254 } else {
12255 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
12256 }
12257 pmap_progress.tick();
12258 pmap_progress.finish();
12259 Ok(StrykeValue::array(items))
12260 }
12261
12262 ExprKind::ReduceExpr { block, list } => {
12263 let list_val = self.eval_expr(list)?;
12264 let items = list_val.to_list();
12265 if items.is_empty() {
12266 return Ok(StrykeValue::UNDEF);
12267 }
12268 if items.len() == 1 {
12269 return Ok(items.into_iter().next().unwrap());
12270 }
12271 let block = block.clone();
12272 let subs = self.subs.clone();
12273 let scope_capture = self.scope.capture();
12274 let mut acc = items[0].clone();
12275 for b in items.into_iter().skip(1) {
12276 let mut local_interp = VMHelper::new();
12277 local_interp.subs = subs.clone();
12278 local_interp.scope.restore_capture(&scope_capture);
12279 local_interp.scope.set_sort_pair(acc, b);
12280 acc = match local_interp.exec_block(&block) {
12281 Ok(val) => val,
12282 Err(_) => StrykeValue::UNDEF,
12283 };
12284 }
12285 Ok(acc)
12286 }
12287
12288 ExprKind::PReduceExpr {
12289 block,
12290 list,
12291 progress,
12292 } => {
12293 let show_progress = progress
12294 .as_ref()
12295 .map(|p| self.eval_expr(p))
12296 .transpose()?
12297 .map(|v| v.is_true())
12298 .unwrap_or(false);
12299 let list_val = self.eval_expr(list)?;
12300 let items = list_val.to_list();
12301 if items.is_empty() {
12302 return Ok(StrykeValue::UNDEF);
12303 }
12304 if items.len() == 1 {
12305 return Ok(items.into_iter().next().unwrap());
12306 }
12307 let block = block.clone();
12308 let subs = self.subs.clone();
12309 let scope_capture = self.scope.capture();
12310 let pmap_progress = PmapProgress::new(show_progress, items.len());
12311
12312 let result = items
12313 .into_par_iter()
12314 .map(|x| {
12315 pmap_progress.tick();
12316 x
12317 })
12318 .reduce_with(|a, b| {
12319 let mut local_interp = VMHelper::new();
12320 local_interp.subs = subs.clone();
12321 local_interp.scope.restore_capture(&scope_capture);
12322 local_interp.scope.set_sort_pair(a, b);
12323 match local_interp.exec_block(&block) {
12324 Ok(val) => val,
12325 Err(_) => StrykeValue::UNDEF,
12326 }
12327 });
12328 pmap_progress.finish();
12329 Ok(result.unwrap_or(StrykeValue::UNDEF))
12330 }
12331
12332 ExprKind::PReduceInitExpr {
12333 init,
12334 block,
12335 list,
12336 progress,
12337 } => {
12338 let show_progress = progress
12339 .as_ref()
12340 .map(|p| self.eval_expr(p))
12341 .transpose()?
12342 .map(|v| v.is_true())
12343 .unwrap_or(false);
12344 let init_val = self.eval_expr(init)?;
12345 let list_val = self.eval_expr(list)?;
12346 let items = list_val.to_list();
12347 if items.is_empty() {
12348 return Ok(init_val);
12349 }
12350 let block = block.clone();
12351 let subs = self.subs.clone();
12352 let scope_capture = self.scope.capture();
12353 let cap: &[(String, StrykeValue)] = scope_capture.as_slice();
12354 if items.len() == 1 {
12355 return Ok(fold_preduce_init_step(
12356 &subs,
12357 cap,
12358 &block,
12359 preduce_init_fold_identity(&init_val),
12360 items.into_iter().next().unwrap(),
12361 ));
12362 }
12363 let pmap_progress = PmapProgress::new(show_progress, items.len());
12364 let result = items
12365 .into_par_iter()
12366 .fold(
12367 || preduce_init_fold_identity(&init_val),
12368 |acc, item| {
12369 pmap_progress.tick();
12370 fold_preduce_init_step(&subs, cap, &block, acc, item)
12371 },
12372 )
12373 .reduce(
12374 || preduce_init_fold_identity(&init_val),
12375 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
12376 );
12377 pmap_progress.finish();
12378 Ok(result)
12379 }
12380
12381 ExprKind::PMapReduceExpr {
12382 map_block,
12383 reduce_block,
12384 list,
12385 progress,
12386 } => {
12387 let show_progress = progress
12388 .as_ref()
12389 .map(|p| self.eval_expr(p))
12390 .transpose()?
12391 .map(|v| v.is_true())
12392 .unwrap_or(false);
12393 let list_val = self.eval_expr(list)?;
12394 let items = list_val.to_list();
12395 if items.is_empty() {
12396 return Ok(StrykeValue::UNDEF);
12397 }
12398 let map_block = map_block.clone();
12399 let reduce_block = reduce_block.clone();
12400 let subs = self.subs.clone();
12401 let scope_capture = self.scope.capture();
12402 if items.len() == 1 {
12403 let mut local_interp = VMHelper::new();
12404 local_interp.subs = subs.clone();
12405 local_interp.scope.restore_capture(&scope_capture);
12406 local_interp.scope.set_topic(items[0].clone());
12407 return match local_interp.exec_block_no_scope(&map_block) {
12408 Ok(v) => Ok(v),
12409 Err(_) => Ok(StrykeValue::UNDEF),
12410 };
12411 }
12412 let pmap_progress = PmapProgress::new(show_progress, items.len());
12413 let result = items
12414 .into_par_iter()
12415 .map(|item| {
12416 let mut local_interp = VMHelper::new();
12417 local_interp.subs = subs.clone();
12418 local_interp.scope.restore_capture(&scope_capture);
12419 local_interp.scope.set_topic(item);
12420 let val = match local_interp.exec_block_no_scope(&map_block) {
12421 Ok(val) => val,
12422 Err(_) => StrykeValue::UNDEF,
12423 };
12424 pmap_progress.tick();
12425 val
12426 })
12427 .reduce_with(|a, b| {
12428 let mut local_interp = VMHelper::new();
12429 local_interp.subs = subs.clone();
12430 local_interp.scope.restore_capture(&scope_capture);
12431 local_interp.scope.set_sort_pair(a, b);
12432 match local_interp.exec_block_no_scope(&reduce_block) {
12433 Ok(val) => val,
12434 Err(_) => StrykeValue::UNDEF,
12435 }
12436 });
12437 pmap_progress.finish();
12438 Ok(result.unwrap_or(StrykeValue::UNDEF))
12439 }
12440
12441 ExprKind::PcacheExpr {
12442 block,
12443 list,
12444 progress,
12445 } => {
12446 let show_progress = progress
12447 .as_ref()
12448 .map(|p| self.eval_expr(p))
12449 .transpose()?
12450 .map(|v| v.is_true())
12451 .unwrap_or(false);
12452 let list_val = self.eval_expr(list)?;
12453 let items = list_val.to_list();
12454 let block = block.clone();
12455 let subs = self.subs.clone();
12456 let scope_capture = self.scope.capture();
12457 let cache = &*crate::pcache::GLOBAL_PCACHE;
12458 let pmap_progress = PmapProgress::new(show_progress, items.len());
12459 let results: Vec<StrykeValue> = items
12460 .into_par_iter()
12461 .map(|item| {
12462 let k = crate::pcache::cache_key(&item);
12463 if let Some(v) = cache.get(&k) {
12464 pmap_progress.tick();
12465 return v.clone();
12466 }
12467 let mut local_interp = VMHelper::new();
12468 local_interp.subs = subs.clone();
12469 local_interp.scope.restore_capture(&scope_capture);
12470 local_interp.scope.set_topic(item.clone());
12471 let val = match local_interp.exec_block_no_scope(&block) {
12472 Ok(v) => v,
12473 Err(_) => StrykeValue::UNDEF,
12474 };
12475 cache.insert(k, val.clone());
12476 pmap_progress.tick();
12477 val
12478 })
12479 .collect();
12480 pmap_progress.finish();
12481 Ok(StrykeValue::array(results))
12482 }
12483
12484 ExprKind::PselectExpr { receivers, timeout } => {
12485 let mut rx_vals = Vec::with_capacity(receivers.len());
12486 for r in receivers {
12487 rx_vals.push(self.eval_expr(r)?);
12488 }
12489 let dur = if let Some(t) = timeout.as_ref() {
12490 Some(std::time::Duration::from_secs_f64(
12491 self.eval_expr(t)?.to_number().max(0.0),
12492 ))
12493 } else {
12494 None
12495 };
12496 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
12497 &rx_vals, dur, line,
12498 )?)
12499 }
12500
12501 ExprKind::Push { array, values } => {
12503 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
12504 }
12505 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
12506 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
12507 ExprKind::Unshift { array, values } => {
12508 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
12509 }
12510 ExprKind::Splice {
12511 array,
12512 offset,
12513 length,
12514 replacement,
12515 } => self.eval_splice_expr(
12516 array.as_ref(),
12517 offset.as_deref(),
12518 length.as_deref(),
12519 replacement.as_slice(),
12520 ctx,
12521 line,
12522 ),
12523 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
12524 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
12525 ExprKind::Keys(expr) => {
12526 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
12527 let keys = Self::keys_from_value(val, line)?;
12528 if ctx == WantarrayCtx::List {
12529 Ok(keys)
12530 } else {
12531 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
12532 Ok(StrykeValue::integer(n as i64))
12533 }
12534 }
12535 ExprKind::Values(expr) => {
12536 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
12537 let vals = Self::values_from_value(val, line)?;
12538 if ctx == WantarrayCtx::List {
12539 Ok(vals)
12540 } else {
12541 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
12542 Ok(StrykeValue::integer(n as i64))
12543 }
12544 }
12545 ExprKind::Each(_) => {
12546 Ok(StrykeValue::array(vec![]))
12548 }
12549
12550 ExprKind::Chomp(expr) => {
12552 let val = self.eval_expr(expr)?;
12553 self.chomp_inplace_execute(val, expr)
12554 }
12555 ExprKind::Chop(expr) => {
12556 let val = self.eval_expr(expr)?;
12557 self.chop_inplace_execute(val, expr)
12558 }
12559 ExprKind::Length(expr) => {
12560 let val = self.eval_expr(expr)?;
12561 Ok(if let Some(a) = val.as_array_vec() {
12562 StrykeValue::integer(a.len() as i64)
12563 } else if let Some(h) = val.as_hash_map() {
12564 StrykeValue::integer(h.len() as i64)
12565 } else if let Some(b) = val.as_bytes_arc() {
12566 StrykeValue::integer(b.len() as i64)
12568 } else {
12569 let s = val.to_string();
12570 let n = if self.utf8_pragma {
12571 s.chars().count()
12572 } else {
12573 s.len()
12574 };
12575 StrykeValue::integer(n as i64)
12576 })
12577 }
12578 ExprKind::Substr {
12579 string,
12580 offset,
12581 length,
12582 replacement,
12583 } => self.eval_substr_expr(
12584 string.as_ref(),
12585 offset.as_ref(),
12586 length.as_deref(),
12587 replacement.as_deref(),
12588 line,
12589 ),
12590 ExprKind::Index {
12591 string,
12592 substr,
12593 position,
12594 } => {
12595 let s = self.eval_expr(string)?.to_string();
12596 let sub = self.eval_expr(substr)?.to_string();
12597 let pos = if let Some(p) = position {
12598 self.eval_expr(p)?.to_int() as usize
12599 } else {
12600 0
12601 };
12602 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
12603 Ok(StrykeValue::integer(result))
12604 }
12605 ExprKind::Rindex {
12606 string,
12607 substr,
12608 position,
12609 } => {
12610 let s = self.eval_expr(string)?.to_string();
12611 let sub = self.eval_expr(substr)?.to_string();
12612 let end = if let Some(p) = position {
12613 self.eval_expr(p)?.to_int() as usize + sub.len()
12614 } else {
12615 s.len()
12616 };
12617 let search = &s[..end.min(s.len())];
12618 let result = search.rfind(&sub).map(|i| i as i64).unwrap_or(-1);
12619 Ok(StrykeValue::integer(result))
12620 }
12621 ExprKind::Sprintf { format, args } => {
12622 let fmt = self.eval_expr(format)?.to_string();
12623 let mut arg_vals = Vec::new();
12626 for a in args {
12627 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
12628 if let Some(items) = v.as_array_vec() {
12629 arg_vals.extend(items);
12630 } else {
12631 arg_vals.push(v);
12632 }
12633 }
12634 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
12635 Ok(StrykeValue::string(s))
12636 }
12637 ExprKind::JoinExpr { separator, list } => {
12638 let sep = self.eval_expr(separator)?.to_string();
12639 let items = if let ExprKind::List(exprs) = &list.kind {
12643 let saved = self.wantarray_kind;
12644 self.wantarray_kind = WantarrayCtx::List;
12645 let mut vals = Vec::new();
12646 for e in exprs {
12647 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
12648 if let Some(items) = v.as_array_vec() {
12649 vals.extend(items);
12650 } else if v.is_iterator() {
12651 vals.extend(v.into_iterator().collect_all());
12655 } else {
12656 vals.push(v);
12657 }
12658 }
12659 self.wantarray_kind = saved;
12660 vals
12661 } else {
12662 let saved = self.wantarray_kind;
12663 self.wantarray_kind = WantarrayCtx::List;
12664 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12665 self.wantarray_kind = saved;
12666 if let Some(items) = v.as_array_vec() {
12667 items
12668 } else if v.is_iterator() {
12669 v.into_iterator().collect_all()
12673 } else {
12674 vec![v]
12675 }
12676 };
12677 let mut strs = Vec::with_capacity(items.len());
12678 for v in &items {
12679 strs.push(self.stringify_value(v.clone(), line)?);
12680 }
12681 Ok(StrykeValue::string(strs.join(&sep)))
12682 }
12683 ExprKind::SplitExpr {
12684 pattern,
12685 string,
12686 limit,
12687 } => {
12688 let pat_val = self.eval_expr(pattern)?;
12689 let pat = pat_val
12693 .regex_src_and_flags()
12694 .map(|(s, _)| s)
12695 .unwrap_or_else(|| pat_val.to_string());
12696 let s = self.eval_expr(string)?.to_string();
12697 if s.is_empty() {
12698 return Ok(StrykeValue::array(vec![]));
12699 }
12700 let lim_opt: Option<i64> = limit
12709 .as_ref()
12710 .map(|l| self.eval_expr(l).map(|v| v.to_int()))
12711 .transpose()?;
12712 let re = self.compile_regex(&pat, "", line)?;
12713 let mut parts: Vec<String> = match lim_opt {
12714 Some(l) if l > 0 => re.splitn_strings(&s, l as usize),
12715 _ => re.split_strings(&s),
12716 };
12717
12718 if pat.is_empty() && parts.first().is_some_and(|p| p.is_empty()) {
12724 parts.remove(0);
12725 }
12726 let strip_trailing = matches!(lim_opt, None | Some(0));
12730 if strip_trailing {
12731 while parts.last().is_some_and(|p| p.is_empty()) {
12732 parts.pop();
12733 }
12734 }
12735
12736 Ok(StrykeValue::array(
12737 parts.into_iter().map(StrykeValue::string).collect(),
12738 ))
12739 }
12740
12741 ExprKind::Abs(expr) => {
12743 let val = self.eval_expr(expr)?;
12744 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
12745 return r;
12746 }
12747 Ok(StrykeValue::float(val.to_number().abs()))
12748 }
12749 ExprKind::Int(expr) => {
12750 let val = self.eval_expr(expr)?;
12751 Ok(StrykeValue::integer(val.to_number() as i64))
12752 }
12753 ExprKind::Sqrt(expr) => {
12754 let val = self.eval_expr(expr)?;
12755 Ok(StrykeValue::float(val.to_number().sqrt()))
12756 }
12757 ExprKind::Sin(expr) => {
12758 let val = self.eval_expr(expr)?;
12759 Ok(StrykeValue::float(val.to_number().sin()))
12760 }
12761 ExprKind::Cos(expr) => {
12762 let val = self.eval_expr(expr)?;
12763 Ok(StrykeValue::float(val.to_number().cos()))
12764 }
12765 ExprKind::Atan2 { y, x } => {
12766 let yv = self.eval_expr(y)?.to_number();
12767 let xv = self.eval_expr(x)?.to_number();
12768 Ok(StrykeValue::float(yv.atan2(xv)))
12769 }
12770 ExprKind::Exp(expr) => {
12771 let val = self.eval_expr(expr)?;
12772 Ok(StrykeValue::float(val.to_number().exp()))
12773 }
12774 ExprKind::Log(expr) => {
12775 let val = self.eval_expr(expr)?;
12776 Ok(StrykeValue::float(val.to_number().ln()))
12777 }
12778 ExprKind::Rand(upper) => {
12779 let u = match upper {
12780 Some(e) => self.eval_expr(e)?.to_number(),
12781 None => 1.0,
12782 };
12783 Ok(StrykeValue::float(self.perl_rand(u)))
12784 }
12785 ExprKind::Srand(seed) => {
12786 let s = match seed {
12787 Some(e) => Some(self.eval_expr(e)?.to_number()),
12788 None => None,
12789 };
12790 Ok(StrykeValue::integer(self.perl_srand(s)))
12791 }
12792 ExprKind::Hex(expr) => {
12793 let val = self.eval_expr(expr)?.to_string();
12794 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
12795 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
12796 Ok(StrykeValue::integer(n))
12797 }
12798 ExprKind::Oct(expr) => {
12799 let val = self.eval_expr(expr)?.to_string();
12800 let s = val.trim();
12801 let n = if s.starts_with("0x") || s.starts_with("0X") {
12802 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
12803 } else if s.starts_with("0b") || s.starts_with("0B") {
12804 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
12805 } else if s.starts_with("0o") || s.starts_with("0O") {
12806 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
12807 } else {
12808 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
12809 };
12810 Ok(StrykeValue::integer(n))
12811 }
12812
12813 ExprKind::Lc(expr) => Ok(StrykeValue::string(
12815 self.eval_expr(expr)?.to_string().to_lowercase(),
12816 )),
12817 ExprKind::Uc(expr) => Ok(StrykeValue::string(
12818 self.eval_expr(expr)?.to_string().to_uppercase(),
12819 )),
12820 ExprKind::Lcfirst(expr) => {
12821 let s = self.eval_expr(expr)?.to_string();
12822 let mut chars = s.chars();
12823 let result = match chars.next() {
12824 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
12825 None => String::new(),
12826 };
12827 Ok(StrykeValue::string(result))
12828 }
12829 ExprKind::Ucfirst(expr) => {
12830 let s = self.eval_expr(expr)?.to_string();
12831 let mut chars = s.chars();
12832 let result = match chars.next() {
12833 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
12834 None => String::new(),
12835 };
12836 Ok(StrykeValue::string(result))
12837 }
12838 ExprKind::Fc(expr) => Ok(StrykeValue::string(default_case_fold_str(
12839 &self.eval_expr(expr)?.to_string(),
12840 ))),
12841 ExprKind::Crypt { plaintext, salt } => {
12842 let p = self.eval_expr(plaintext)?.to_string();
12843 let sl = self.eval_expr(salt)?.to_string();
12844 Ok(StrykeValue::string(perl_crypt(&p, &sl)))
12845 }
12846 ExprKind::Pos(e) => {
12847 let key = match e {
12848 None => "_".to_string(),
12849 Some(expr) => match &expr.kind {
12850 ExprKind::ScalarVar(n) => n.clone(),
12851 _ => self.eval_expr(expr)?.to_string(),
12852 },
12853 };
12854 Ok(self
12855 .regex_pos
12856 .get(&key)
12857 .copied()
12858 .flatten()
12859 .map(|p| StrykeValue::integer(p as i64))
12860 .unwrap_or(StrykeValue::UNDEF))
12861 }
12862 ExprKind::Study(expr) => {
12863 let s = self.eval_expr(expr)?.to_string();
12864 Ok(Self::study_return_value(&s))
12865 }
12866
12867 ExprKind::Defined(expr) => {
12869 if let ExprKind::SubroutineRef(name) = &expr.kind {
12871 let exists = self.resolve_sub_by_name(name).is_some();
12872 return Ok(StrykeValue::integer(if exists { 1 } else { 0 }));
12873 }
12874 let val = self.eval_expr(expr)?;
12875 Ok(StrykeValue::integer(if val.is_undef() { 0 } else { 1 }))
12876 }
12877 ExprKind::Ref(expr) => {
12878 let val = self.eval_expr(expr)?;
12879 Ok(val.ref_type())
12880 }
12881 ExprKind::ScalarContext(expr) => {
12882 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
12883 Ok(v.scalar_context())
12884 }
12885
12886 ExprKind::Chr(expr) => {
12888 let n = self.eval_expr(expr)?.to_int() as u32;
12889 Ok(StrykeValue::string(
12890 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
12891 ))
12892 }
12893 ExprKind::Ord(expr) => {
12894 let s = self.eval_expr(expr)?.to_string();
12895 Ok(StrykeValue::integer(
12896 s.chars().next().map(|c| c as i64).unwrap_or(0),
12897 ))
12898 }
12899
12900 ExprKind::OpenMyHandle { .. } => Err(StrykeError::runtime(
12902 "internal: `open my $fh` handle used outside open()",
12903 line,
12904 )
12905 .into()),
12906 ExprKind::Open { handle, mode, file } => {
12907 if let ExprKind::OpenMyHandle { name } = &handle.kind {
12908 self.scope
12909 .declare_scalar_frozen(name, StrykeValue::UNDEF, false, None)?;
12910 self.english_note_lexical_scalar(name);
12911 let mode_s = self.eval_expr(mode)?.to_string();
12912 let file_opt = if let Some(f) = file {
12913 Some(self.eval_expr(f)?.to_string())
12914 } else {
12915 None
12916 };
12917 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
12918 self.scope.set_scalar(name, ret.clone())?;
12919 return Ok(ret);
12920 }
12921 let handle_s = self.eval_expr(handle)?.to_string();
12922 let handle_name = self.resolve_io_handle_name(&handle_s);
12923 let mode_s = self.eval_expr(mode)?.to_string();
12924 let file_opt = if let Some(f) = file {
12925 Some(self.eval_expr(f)?.to_string())
12926 } else {
12927 None
12928 };
12929 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
12930 .map_err(Into::into)
12931 }
12932 ExprKind::Close(expr) => {
12933 let s = self.eval_expr(expr)?.to_string();
12934 let name = self.resolve_io_handle_name(&s);
12935 self.close_builtin_execute(name).map_err(Into::into)
12936 }
12937 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
12938 self.readline_builtin_execute_list(handle.as_deref())
12939 } else {
12940 self.readline_builtin_execute(handle.as_deref())
12941 }
12942 .map_err(Into::into),
12943 ExprKind::Eof(expr) => match expr {
12944 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
12945 Some(e) => {
12946 let name = self.eval_expr(e)?;
12947 self.eof_builtin_execute(&[name], line).map_err(Into::into)
12948 }
12949 },
12950
12951 ExprKind::Opendir { handle, path } => {
12952 let h = self.eval_expr(handle)?.to_string();
12953 let p = self.eval_expr(path)?.to_string();
12954 Ok(self.opendir_handle(&h, &p))
12955 }
12956 ExprKind::Readdir(e) => {
12957 let h = self.eval_expr(e)?.to_string();
12958 Ok(if ctx == WantarrayCtx::List {
12959 self.readdir_handle_list(&h)
12960 } else {
12961 self.readdir_handle(&h)
12962 })
12963 }
12964 ExprKind::Closedir(e) => {
12965 let h = self.eval_expr(e)?.to_string();
12966 Ok(self.closedir_handle(&h))
12967 }
12968 ExprKind::Rewinddir(e) => {
12969 let h = self.eval_expr(e)?.to_string();
12970 Ok(self.rewinddir_handle(&h))
12971 }
12972 ExprKind::Telldir(e) => {
12973 let h = self.eval_expr(e)?.to_string();
12974 Ok(self.telldir_handle(&h))
12975 }
12976 ExprKind::Seekdir { handle, position } => {
12977 let h = self.eval_expr(handle)?.to_string();
12978 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
12979 Ok(self.seekdir_handle(&h, pos))
12980 }
12981
12982 ExprKind::FileTest { op, expr } => {
12984 let raw = self.eval_expr(expr)?.to_string();
12985 let path = self.resolve_stryke_path_string(&raw);
12986 if matches!(op, 'M' | 'A' | 'C') {
12988 #[cfg(unix)]
12989 {
12990 return match crate::perl_fs::filetest_age_days(&path, *op) {
12991 Some(days) => Ok(StrykeValue::float(days)),
12992 None => Ok(StrykeValue::UNDEF),
12993 };
12994 }
12995 #[cfg(not(unix))]
12996 return Ok(StrykeValue::UNDEF);
12997 }
12998 if *op == 's' {
13000 return match std::fs::metadata(&path) {
13001 Ok(m) => Ok(StrykeValue::integer(m.len() as i64)),
13002 Err(_) => Ok(StrykeValue::UNDEF),
13003 };
13004 }
13005 let result = match op {
13006 'e' => std::path::Path::new(&path).exists(),
13007 'f' => std::path::Path::new(&path).is_file(),
13008 'd' => std::path::Path::new(&path).is_dir(),
13009 'l' => std::path::Path::new(&path).is_symlink(),
13010 #[cfg(unix)]
13011 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
13012 #[cfg(not(unix))]
13013 'r' => std::fs::metadata(&path).is_ok(),
13014 #[cfg(unix)]
13015 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
13016 #[cfg(not(unix))]
13017 'w' => std::fs::metadata(&path).is_ok(),
13018 #[cfg(unix)]
13019 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
13020 #[cfg(not(unix))]
13021 'x' => false,
13022 #[cfg(unix)]
13023 'o' => crate::perl_fs::filetest_owned_effective(&path),
13024 #[cfg(not(unix))]
13025 'o' => false,
13026 #[cfg(unix)]
13027 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
13028 #[cfg(not(unix))]
13029 'R' => false,
13030 #[cfg(unix)]
13031 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
13032 #[cfg(not(unix))]
13033 'W' => false,
13034 #[cfg(unix)]
13035 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
13036 #[cfg(not(unix))]
13037 'X' => false,
13038 #[cfg(unix)]
13039 'O' => crate::perl_fs::filetest_owned_real(&path),
13040 #[cfg(not(unix))]
13041 'O' => false,
13042 'z' => std::fs::metadata(&path)
13043 .map(|m| m.len() == 0)
13044 .unwrap_or(true),
13045 't' => crate::perl_fs::filetest_is_tty(&path),
13046 #[cfg(unix)]
13047 'p' => crate::perl_fs::filetest_is_pipe(&path),
13048 #[cfg(not(unix))]
13049 'p' => false,
13050 #[cfg(unix)]
13051 'S' => crate::perl_fs::filetest_is_socket(&path),
13052 #[cfg(not(unix))]
13053 'S' => false,
13054 #[cfg(unix)]
13055 'b' => crate::perl_fs::filetest_is_block_device(&path),
13056 #[cfg(not(unix))]
13057 'b' => false,
13058 #[cfg(unix)]
13059 'c' => crate::perl_fs::filetest_is_char_device(&path),
13060 #[cfg(not(unix))]
13061 'c' => false,
13062 #[cfg(unix)]
13063 'u' => crate::perl_fs::filetest_is_setuid(&path),
13064 #[cfg(not(unix))]
13065 'u' => false,
13066 #[cfg(unix)]
13067 'g' => crate::perl_fs::filetest_is_setgid(&path),
13068 #[cfg(not(unix))]
13069 'g' => false,
13070 #[cfg(unix)]
13071 'k' => crate::perl_fs::filetest_is_sticky(&path),
13072 #[cfg(not(unix))]
13073 'k' => false,
13074 'T' => crate::perl_fs::filetest_is_text(&path),
13075 'B' => crate::perl_fs::filetest_is_binary(&path),
13076 _ => false,
13077 };
13078 Ok(StrykeValue::integer(if result { 1 } else { 0 }))
13079 }
13080
13081 ExprKind::System(args) => {
13083 let mut cmd_args = Vec::new();
13084 for a in args {
13085 cmd_args.push(self.eval_expr(a)?.to_string());
13086 }
13087 if cmd_args.is_empty() {
13088 return Ok(StrykeValue::integer(-1));
13089 }
13090 let status = Command::new("sh")
13091 .arg("-c")
13092 .arg(cmd_args.join(" "))
13093 .status();
13094 match status {
13095 Ok(s) => {
13096 self.record_child_exit_status(s);
13097 Ok(StrykeValue::integer(s.code().unwrap_or(-1) as i64))
13098 }
13099 Err(e) => {
13100 self.apply_io_error_to_errno(&e);
13101 Ok(StrykeValue::integer(-1))
13102 }
13103 }
13104 }
13105 ExprKind::Exec(args) => {
13106 let mut cmd_args = Vec::new();
13107 for a in args {
13108 cmd_args.push(self.eval_expr(a)?.to_string());
13109 }
13110 if cmd_args.is_empty() {
13111 return Ok(StrykeValue::integer(-1));
13112 }
13113 let status = Command::new("sh")
13114 .arg("-c")
13115 .arg(cmd_args.join(" "))
13116 .status();
13117 match status {
13118 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
13119 Err(e) => {
13120 self.apply_io_error_to_errno(&e);
13121 Ok(StrykeValue::integer(-1))
13122 }
13123 }
13124 }
13125 ExprKind::Eval(expr) => {
13126 self.eval_nesting += 1;
13127 let out = match &expr.kind {
13128 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
13129 Ok(v) => {
13130 self.clear_eval_error();
13131 Ok(v)
13132 }
13133 Err(FlowOrError::Error(e)) => {
13134 self.set_eval_error_from_perl_error(&e);
13135 Ok(StrykeValue::UNDEF)
13136 }
13137 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
13138 },
13139 _ => {
13140 let code = self.eval_expr(expr)?.to_string();
13141 match crate::parse_and_run_string(&code, self) {
13143 Ok(v) => {
13144 self.clear_eval_error();
13145 Ok(v)
13146 }
13147 Err(e) => {
13148 self.set_eval_error(e.to_string());
13149 Ok(StrykeValue::UNDEF)
13150 }
13151 }
13152 }
13153 };
13154 self.eval_nesting -= 1;
13155 out
13156 }
13157 ExprKind::Do(expr) => match &expr.kind {
13158 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
13159 _ => {
13160 let val = self.eval_expr(expr)?;
13161 let filename = val.to_string();
13162 match read_file_text_perl_compat(&filename) {
13163 Ok(code) => {
13164 let code = crate::data_section::strip_perl_end_marker(&code);
13165 match crate::parse_and_run_string_in_file(code, self, &filename) {
13166 Ok(v) => Ok(v),
13167 Err(e) => {
13168 self.set_eval_error(e.to_string());
13169 Ok(StrykeValue::UNDEF)
13170 }
13171 }
13172 }
13173 Err(e) => {
13174 self.apply_io_error_to_errno(&e);
13175 Ok(StrykeValue::UNDEF)
13176 }
13177 }
13178 }
13179 },
13180 ExprKind::Require(expr) => {
13181 let spec = self.eval_expr(expr)?.to_string();
13182 self.require_execute(&spec, line)
13183 .map_err(FlowOrError::Error)
13184 }
13185 ExprKind::Exit(code) => {
13186 let c = if let Some(e) = code {
13187 self.eval_expr(e)?.to_int() as i32
13188 } else {
13189 0
13190 };
13191 Err(StrykeError::new(ErrorKind::Exit(c), "", line, &self.file).into())
13192 }
13193 ExprKind::Chdir(expr) => {
13194 let path = self.eval_expr(expr)?.to_string();
13195 match std::env::set_current_dir(&path) {
13196 Ok(_) => {
13197 if let Ok(c) = std::env::current_dir() {
13198 self.stryke_pwd = std::fs::canonicalize(&c).unwrap_or(c);
13199 }
13200 Ok(StrykeValue::integer(1))
13201 }
13202 Err(e) => {
13203 self.apply_io_error_to_errno(&e);
13204 Ok(StrykeValue::integer(0))
13205 }
13206 }
13207 }
13208 ExprKind::Mkdir { path, mode: _ } => {
13209 let raw = self.eval_expr(path)?.to_string();
13210 let p = self.resolve_stryke_path_string(&raw);
13211 match std::fs::create_dir(&p) {
13212 Ok(_) => Ok(StrykeValue::integer(1)),
13213 Err(e) => {
13214 self.apply_io_error_to_errno(&e);
13215 Ok(StrykeValue::integer(0))
13216 }
13217 }
13218 }
13219 ExprKind::Unlink(args) => {
13220 let mut count = 0i64;
13221 for a in args {
13222 let raw = self.eval_expr(a)?.to_string();
13223 let path = self.resolve_stryke_path_string(&raw);
13224 if std::fs::remove_file(&path).is_ok() {
13225 count += 1;
13226 }
13227 }
13228 Ok(StrykeValue::integer(count))
13229 }
13230 ExprKind::Rename { old, new } => {
13231 let o_raw = self.eval_expr(old)?.to_string();
13232 let n_raw = self.eval_expr(new)?.to_string();
13233 let o = self.resolve_stryke_path_string(&o_raw);
13234 let n = self.resolve_stryke_path_string(&n_raw);
13235 Ok(crate::perl_fs::rename_paths(&o, &n))
13236 }
13237 ExprKind::Chmod(args) => {
13238 let mode = self.eval_expr(&args[0])?.to_int();
13239 let mut paths = Vec::new();
13240 for a in &args[1..] {
13241 let raw = self.eval_expr(a)?.to_string();
13242 paths.push(self.resolve_stryke_path_string(&raw));
13243 }
13244 Ok(StrykeValue::integer(crate::perl_fs::chmod_paths(
13245 &paths, mode,
13246 )))
13247 }
13248 ExprKind::Chown(args) => {
13249 let uid = self.eval_expr(&args[0])?.to_int();
13250 let gid = self.eval_expr(&args[1])?.to_int();
13251 let mut paths = Vec::new();
13252 for a in &args[2..] {
13253 let raw = self.eval_expr(a)?.to_string();
13254 paths.push(self.resolve_stryke_path_string(&raw));
13255 }
13256 Ok(StrykeValue::integer(crate::perl_fs::chown_paths(
13257 &paths, uid, gid,
13258 )))
13259 }
13260 ExprKind::Stat(e) => {
13261 let raw = self.eval_expr(e)?.to_string();
13262 let path = self.resolve_stryke_path_string(&raw);
13263 Ok(crate::perl_fs::stat_path(&path, false))
13264 }
13265 ExprKind::Lstat(e) => {
13266 let raw = self.eval_expr(e)?.to_string();
13267 let path = self.resolve_stryke_path_string(&raw);
13268 Ok(crate::perl_fs::stat_path(&path, true))
13269 }
13270 ExprKind::Link { old, new } => {
13271 let o_raw = self.eval_expr(old)?.to_string();
13272 let n_raw = self.eval_expr(new)?.to_string();
13273 let o = self.resolve_stryke_path_string(&o_raw);
13274 let n = self.resolve_stryke_path_string(&n_raw);
13275 Ok(crate::perl_fs::link_hard(&o, &n))
13276 }
13277 ExprKind::Symlink { old, new } => {
13278 let o = self.eval_expr(old)?.to_string();
13279 let n_raw = self.eval_expr(new)?.to_string();
13280 let n = self.resolve_stryke_path_string(&n_raw);
13281 Ok(crate::perl_fs::link_sym(&o, &n))
13282 }
13283 ExprKind::Readlink(e) => {
13284 let raw = self.eval_expr(e)?.to_string();
13285 let path = self.resolve_stryke_path_string(&raw);
13286 Ok(crate::perl_fs::read_link(&path))
13287 }
13288 ExprKind::Files(args) => {
13289 let dir_raw = if args.is_empty() {
13290 ".".to_string()
13291 } else {
13292 self.eval_expr(&args[0])?.to_string()
13293 };
13294 let dir = self.resolve_stryke_path_string(&dir_raw);
13295 Ok(crate::perl_fs::list_files(&dir))
13296 }
13297 ExprKind::Filesf(args) => {
13298 let dir_raw = if args.is_empty() {
13299 ".".to_string()
13300 } else {
13301 self.eval_expr(&args[0])?.to_string()
13302 };
13303 let dir = self.resolve_stryke_path_string(&dir_raw);
13304 Ok(crate::perl_fs::list_filesf(&dir))
13305 }
13306 ExprKind::FilesfRecursive(args) => {
13307 let dir_raw = if args.is_empty() {
13308 ".".to_string()
13309 } else {
13310 self.eval_expr(&args[0])?.to_string()
13311 };
13312 let dir = self.resolve_stryke_path_string(&dir_raw);
13313 Ok(StrykeValue::iterator(Arc::new(
13314 crate::value::FsWalkIterator::new(&dir, true),
13315 )))
13316 }
13317 ExprKind::Dirs(args) => {
13318 let dir_raw = if args.is_empty() {
13319 ".".to_string()
13320 } else {
13321 self.eval_expr(&args[0])?.to_string()
13322 };
13323 let dir = self.resolve_stryke_path_string(&dir_raw);
13324 Ok(crate::perl_fs::list_dirs(&dir))
13325 }
13326 ExprKind::DirsRecursive(args) => {
13327 let dir_raw = if args.is_empty() {
13328 ".".to_string()
13329 } else {
13330 self.eval_expr(&args[0])?.to_string()
13331 };
13332 let dir = self.resolve_stryke_path_string(&dir_raw);
13333 Ok(StrykeValue::iterator(Arc::new(
13334 crate::value::FsWalkIterator::new(&dir, false),
13335 )))
13336 }
13337 ExprKind::SymLinks(args) => {
13338 let dir_raw = if args.is_empty() {
13339 ".".to_string()
13340 } else {
13341 self.eval_expr(&args[0])?.to_string()
13342 };
13343 let dir = self.resolve_stryke_path_string(&dir_raw);
13344 Ok(crate::perl_fs::list_sym_links(&dir))
13345 }
13346 ExprKind::Sockets(args) => {
13347 let dir_raw = if args.is_empty() {
13348 ".".to_string()
13349 } else {
13350 self.eval_expr(&args[0])?.to_string()
13351 };
13352 let dir = self.resolve_stryke_path_string(&dir_raw);
13353 Ok(crate::perl_fs::list_sockets(&dir))
13354 }
13355 ExprKind::Pipes(args) => {
13356 let dir_raw = if args.is_empty() {
13357 ".".to_string()
13358 } else {
13359 self.eval_expr(&args[0])?.to_string()
13360 };
13361 let dir = self.resolve_stryke_path_string(&dir_raw);
13362 Ok(crate::perl_fs::list_pipes(&dir))
13363 }
13364 ExprKind::BlockDevices(args) => {
13365 let dir_raw = if args.is_empty() {
13366 ".".to_string()
13367 } else {
13368 self.eval_expr(&args[0])?.to_string()
13369 };
13370 let dir = self.resolve_stryke_path_string(&dir_raw);
13371 Ok(crate::perl_fs::list_block_devices(&dir))
13372 }
13373 ExprKind::CharDevices(args) => {
13374 let dir_raw = if args.is_empty() {
13375 ".".to_string()
13376 } else {
13377 self.eval_expr(&args[0])?.to_string()
13378 };
13379 let dir = self.resolve_stryke_path_string(&dir_raw);
13380 Ok(crate::perl_fs::list_char_devices(&dir))
13381 }
13382 ExprKind::Executables(args) => {
13383 let dir_raw = if args.is_empty() {
13384 ".".to_string()
13385 } else {
13386 self.eval_expr(&args[0])?.to_string()
13387 };
13388 let dir = self.resolve_stryke_path_string(&dir_raw);
13389 Ok(crate::perl_fs::list_executables(&dir))
13390 }
13391 ExprKind::Glob(args) => {
13392 let mut pats = Vec::new();
13399 for a in args {
13400 pats.push(self.eval_expr(a)?.to_string());
13401 }
13402 Ok(crate::perl_fs::glob_patterns(&pats))
13403 }
13404 ExprKind::GlobPar { args, progress } => {
13405 let mut pats = Vec::new();
13406 for a in args {
13407 pats.push(self.eval_expr(a)?.to_string());
13408 }
13409 let show_progress = progress
13410 .as_ref()
13411 .map(|p| self.eval_expr(p))
13412 .transpose()?
13413 .map(|v| v.is_true())
13414 .unwrap_or(false);
13415 if show_progress {
13416 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
13417 } else {
13418 Ok(crate::perl_fs::glob_par_patterns(&pats))
13419 }
13420 }
13421 ExprKind::ParSed { args, progress } => {
13422 let has_progress = progress.is_some();
13423 let mut vals: Vec<StrykeValue> = Vec::new();
13424 for a in args {
13425 vals.push(self.eval_expr(a)?);
13426 }
13427 if let Some(p) = progress {
13428 vals.push(self.eval_expr(p.as_ref())?);
13429 }
13430 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
13431 }
13432 ExprKind::Bless { ref_expr, class } => {
13433 let val = self.eval_expr(ref_expr)?;
13434 let class_name = if let Some(c) = class {
13435 self.eval_expr(c)?.to_string()
13436 } else {
13437 self.scope.get_scalar("__PACKAGE__").to_string()
13438 };
13439 Ok(StrykeValue::blessed(Arc::new(
13440 crate::value::BlessedRef::new_blessed(class_name, val),
13441 )))
13442 }
13443 ExprKind::Caller(_) => {
13444 Ok(StrykeValue::array(vec![
13446 StrykeValue::string("main".into()),
13447 StrykeValue::string(self.file.clone()),
13448 StrykeValue::integer(line as i64),
13449 ]))
13450 }
13451 ExprKind::Wantarray => Ok(match self.wantarray_kind {
13452 WantarrayCtx::Void => StrykeValue::UNDEF,
13453 WantarrayCtx::Scalar => StrykeValue::integer(0),
13454 WantarrayCtx::List => StrykeValue::integer(1),
13455 }),
13456
13457 ExprKind::List(exprs) => {
13458 if ctx == WantarrayCtx::Scalar {
13460 if let Some(last) = exprs.last() {
13461 for e in &exprs[..exprs.len() - 1] {
13463 self.eval_expr(e)?;
13464 }
13465 return self.eval_expr(last);
13466 } else {
13467 return Ok(StrykeValue::UNDEF);
13468 }
13469 }
13470 let mut vals = Vec::new();
13471 for e in exprs {
13472 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
13473 if let Some(items) = v.as_array_vec() {
13474 vals.extend(items);
13475 } else {
13476 vals.push(v);
13477 }
13478 }
13479 if vals.len() == 1 {
13480 Ok(vals.pop().unwrap())
13481 } else {
13482 Ok(StrykeValue::array(vals))
13483 }
13484 }
13485
13486 ExprKind::PostfixIf { expr, condition } => {
13488 if self.eval_postfix_condition(condition)? {
13489 self.eval_expr(expr)
13490 } else {
13491 Ok(StrykeValue::UNDEF)
13492 }
13493 }
13494 ExprKind::PostfixUnless { expr, condition } => {
13495 if !self.eval_postfix_condition(condition)? {
13496 self.eval_expr(expr)
13497 } else {
13498 Ok(StrykeValue::UNDEF)
13499 }
13500 }
13501 ExprKind::PostfixWhile { expr, condition } => {
13502 let is_do_block = matches!(
13505 &expr.kind,
13506 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
13507 );
13508 let mut last = StrykeValue::UNDEF;
13509 if is_do_block {
13510 loop {
13511 last = self.eval_expr(expr)?;
13512 if !self.eval_postfix_condition(condition)? {
13513 break;
13514 }
13515 }
13516 } else {
13517 loop {
13518 if !self.eval_postfix_condition(condition)? {
13519 break;
13520 }
13521 last = self.eval_expr(expr)?;
13522 }
13523 }
13524 Ok(last)
13525 }
13526 ExprKind::PostfixUntil { expr, condition } => {
13527 let is_do_block = matches!(
13528 &expr.kind,
13529 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
13530 );
13531 let mut last = StrykeValue::UNDEF;
13532 if is_do_block {
13533 loop {
13534 last = self.eval_expr(expr)?;
13535 if self.eval_postfix_condition(condition)? {
13536 break;
13537 }
13538 }
13539 } else {
13540 loop {
13541 if self.eval_postfix_condition(condition)? {
13542 break;
13543 }
13544 last = self.eval_expr(expr)?;
13545 }
13546 }
13547 Ok(last)
13548 }
13549 ExprKind::PostfixForeach { expr, list } => {
13550 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
13551 let mut last = StrykeValue::UNDEF;
13552 for item in items {
13553 self.scope.set_topic(item);
13554 last = self.eval_expr(expr)?;
13555 }
13556 Ok(last)
13557 }
13558 }
13559 }
13560
13561 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
13564 match op {
13565 BinOp::Add => Some("+"),
13566 BinOp::Sub => Some("-"),
13567 BinOp::Mul => Some("*"),
13568 BinOp::Div => Some("/"),
13569 BinOp::Mod => Some("%"),
13570 BinOp::Pow => Some("**"),
13571 BinOp::Concat => Some("."),
13572 BinOp::StrEq => Some("eq"),
13573 BinOp::NumEq => Some("=="),
13574 BinOp::StrNe => Some("ne"),
13575 BinOp::NumNe => Some("!="),
13576 BinOp::StrLt => Some("lt"),
13577 BinOp::StrGt => Some("gt"),
13578 BinOp::StrLe => Some("le"),
13579 BinOp::StrGe => Some("ge"),
13580 BinOp::NumLt => Some("<"),
13581 BinOp::NumGt => Some(">"),
13582 BinOp::NumLe => Some("<="),
13583 BinOp::NumGe => Some(">="),
13584 BinOp::Spaceship => Some("<=>"),
13585 BinOp::StrCmp => Some("cmp"),
13586 _ => None,
13587 }
13588 }
13589
13590 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
13592 map.get("").or_else(|| map.get("\"\""))
13593 }
13594
13595 pub(crate) fn stringify_value(
13597 &mut self,
13598 v: StrykeValue,
13599 line: usize,
13600 ) -> Result<String, FlowOrError> {
13601 if let Some(r) = self.try_overload_stringify(&v, line) {
13602 let pv = r?;
13603 return Ok(pv.to_string());
13604 }
13605 Ok(v.to_string())
13606 }
13607
13608 pub(crate) fn perl_sprintf_stringify(
13610 &mut self,
13611 fmt: &str,
13612 args: &[StrykeValue],
13613 line: usize,
13614 ) -> Result<String, FlowOrError> {
13615 let (out, pending_n) = {
13617 let mut stringify = |v: &StrykeValue| -> Result<String, FlowOrError> {
13618 self.stringify_value(v.clone(), line)
13619 };
13620 perl_sprintf_format_full(fmt, args, &mut stringify)?
13621 };
13622 for (target, count) in pending_n {
13624 self.assign_scalar_ref_deref(target, StrykeValue::integer(count), line)?;
13625 }
13626 Ok(out)
13627 }
13628
13629 pub(crate) fn render_format_template(
13631 &mut self,
13632 tmpl: &crate::format::FormatTemplate,
13633 line: usize,
13634 ) -> Result<String, FlowOrError> {
13635 use crate::format::{FormatRecord, PictureSegment};
13636 let mut buf = String::new();
13637 for rec in &tmpl.records {
13638 match rec {
13639 FormatRecord::Literal(s) => {
13640 buf.push_str(s);
13641 buf.push('\n');
13642 }
13643 FormatRecord::Picture { segments, exprs } => {
13644 let mut vals: Vec<String> = Vec::new();
13645 for e in exprs {
13646 let v = self.eval_expr(e)?;
13647 vals.push(self.stringify_value(v, line)?);
13648 }
13649 let mut vi = 0usize;
13650 let mut line_out = String::new();
13651 for seg in segments {
13652 match seg {
13653 PictureSegment::Literal(t) => line_out.push_str(t),
13654 PictureSegment::Field {
13655 width,
13656 align,
13657 kind: _,
13658 } => {
13659 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
13660 vi += 1;
13661 line_out.push_str(&crate::format::pad_field(s, *width, *align));
13662 }
13663 }
13664 }
13665 buf.push_str(line_out.trim_end());
13666 buf.push('\n');
13667 }
13668 }
13669 }
13670 Ok(buf)
13671 }
13672
13673 pub(crate) fn resolve_write_output_handle(
13675 &self,
13676 v: &StrykeValue,
13677 line: usize,
13678 ) -> StrykeResult<String> {
13679 if let Some(n) = v.as_io_handle_name() {
13680 let n = self.resolve_io_handle_name(&n);
13681 if self.is_bound_handle(&n) {
13682 return Ok(n);
13683 }
13684 }
13685 if let Some(s) = v.as_str() {
13686 if self.is_bound_handle(&s) {
13687 return Ok(self.resolve_io_handle_name(&s));
13688 }
13689 }
13690 let s = v.to_string();
13691 if self.is_bound_handle(&s) {
13692 return Ok(self.resolve_io_handle_name(&s));
13693 }
13694 Err(StrykeError::runtime(
13695 format!("write: invalid or unopened filehandle {}", s),
13696 line,
13697 ))
13698 }
13699
13700 pub(crate) fn write_format_execute(
13704 &mut self,
13705 args: &[StrykeValue],
13706 line: usize,
13707 ) -> StrykeResult<StrykeValue> {
13708 let handle_name = match args.len() {
13709 0 => self.default_print_handle.clone(),
13710 1 => self.resolve_write_output_handle(&args[0], line)?,
13711 _ => {
13712 return Err(StrykeError::runtime("write: too many arguments", line));
13713 }
13714 };
13715 let pkg = self.current_package();
13716 let mut fmt_name = self.scope.get_scalar("~").to_string();
13717 if fmt_name.is_empty() {
13718 fmt_name = "STDOUT".to_string();
13719 }
13720 let key = format!("{}::{}", pkg, fmt_name);
13721 let tmpl = self
13722 .format_templates
13723 .get(&key)
13724 .map(Arc::clone)
13725 .ok_or_else(|| {
13726 StrykeError::runtime(
13727 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
13728 line,
13729 )
13730 })?;
13731 let out = self
13732 .render_format_template(&tmpl, line)
13733 .map_err(|e| match e {
13734 FlowOrError::Error(e) => e,
13735 FlowOrError::Flow(_) => {
13736 StrykeError::runtime("write: unexpected control flow", line)
13737 }
13738 })?;
13739 self.write_formatted_print(handle_name.as_str(), &out, line)?;
13740 Ok(StrykeValue::integer(1))
13741 }
13742
13743 pub(crate) fn try_overload_stringify(
13744 &mut self,
13745 v: &StrykeValue,
13746 line: usize,
13747 ) -> Option<ExecResult> {
13748 if let Some(c) = v.as_class_inst() {
13750 let method_name = c
13751 .def
13752 .method("stringify")
13753 .or_else(|| c.def.method("\"\""))
13754 .filter(|m| m.body.is_some())?;
13755 let body = method_name.body.clone().unwrap();
13756 let params = method_name.params.clone();
13757 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
13758 }
13759 let br = v.as_blessed_ref()?;
13760 let class = br.class.clone();
13761 let map = self.overload_table.get(&class)?;
13762 let sub_short = Self::overload_stringify_method(map)?;
13763 let fq = format!("{}::{}", class, sub_short);
13764 let sub = self.subs.get(&fq)?.clone();
13765 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
13766 }
13767
13768 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
13770 match key {
13771 "+" => Some("op_add"),
13772 "-" => Some("op_sub"),
13773 "*" => Some("op_mul"),
13774 "/" => Some("op_div"),
13775 "%" => Some("op_mod"),
13776 "**" => Some("op_pow"),
13777 "." => Some("op_concat"),
13778 "==" => Some("op_eq"),
13779 "!=" => Some("op_ne"),
13780 "<" => Some("op_lt"),
13781 ">" => Some("op_gt"),
13782 "<=" => Some("op_le"),
13783 ">=" => Some("op_ge"),
13784 "<=>" => Some("op_spaceship"),
13785 "eq" => Some("op_str_eq"),
13786 "ne" => Some("op_str_ne"),
13787 "lt" => Some("op_str_lt"),
13788 "gt" => Some("op_str_gt"),
13789 "le" => Some("op_str_le"),
13790 "ge" => Some("op_str_ge"),
13791 "cmp" => Some("op_cmp"),
13792 _ => None,
13793 }
13794 }
13795
13796 pub(crate) fn try_overload_binop(
13797 &mut self,
13798 op: BinOp,
13799 lv: &StrykeValue,
13800 rv: &StrykeValue,
13801 line: usize,
13802 ) -> Option<ExecResult> {
13803 let key = Self::overload_key_for_binop(op)?;
13804 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
13806 (Some(c.def.clone()), lv.clone(), rv.clone())
13807 } else if let Some(c) = rv.as_class_inst() {
13808 (Some(c.def.clone()), rv.clone(), lv.clone())
13809 } else {
13810 (None, lv.clone(), rv.clone())
13811 };
13812 if let Some(ref def) = ci_def {
13813 if let Some(method_name) = Self::overload_method_name_for_key(key) {
13814 if let Some((m, _)) = self.find_class_method(def, method_name) {
13815 if let Some(ref body) = m.body {
13816 let params = m.params.clone();
13817 return Some(self.call_class_method(
13818 body,
13819 ¶ms,
13820 vec![invocant, other],
13821 line,
13822 ));
13823 }
13824 }
13825 }
13826 }
13827 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
13829 (br.class.clone(), lv.clone(), rv.clone())
13830 } else if let Some(br) = rv.as_blessed_ref() {
13831 (br.class.clone(), rv.clone(), lv.clone())
13832 } else {
13833 return None;
13834 };
13835 let map = self.overload_table.get(&class)?;
13836 let sub_short = if let Some(s) = map.get(key) {
13837 s.clone()
13838 } else if let Some(nm) = map.get("nomethod") {
13839 let fq = format!("{}::{}", class, nm);
13840 let sub = self.subs.get(&fq)?.clone();
13841 return Some(self.call_sub(
13842 &sub,
13843 vec![invocant, other, StrykeValue::string(key.to_string())],
13844 WantarrayCtx::Scalar,
13845 line,
13846 ));
13847 } else {
13848 return None;
13849 };
13850 let fq = format!("{}::{}", class, sub_short);
13851 let sub = self.subs.get(&fq)?.clone();
13852 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
13853 }
13854
13855 pub(crate) fn try_overload_unary_dispatch(
13857 &mut self,
13858 op_key: &str,
13859 val: &StrykeValue,
13860 line: usize,
13861 ) -> Option<ExecResult> {
13862 if let Some(c) = val.as_class_inst() {
13864 let method_name = match op_key {
13865 "neg" => "op_neg",
13866 "bool" => "op_bool",
13867 "abs" => "op_abs",
13868 "0+" => "op_numify",
13869 _ => return None,
13870 };
13871 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
13872 if let Some(ref body) = m.body {
13873 let params = m.params.clone();
13874 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
13875 }
13876 }
13877 return None;
13878 }
13879 let br = val.as_blessed_ref()?;
13881 let class = br.class.clone();
13882 let map = self.overload_table.get(&class)?;
13883 if let Some(s) = map.get(op_key) {
13884 let fq = format!("{}::{}", class, s);
13885 let sub = self.subs.get(&fq)?.clone();
13886 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
13887 }
13888 if let Some(nm) = map.get("nomethod") {
13889 let fq = format!("{}::{}", class, nm);
13890 let sub = self.subs.get(&fq)?.clone();
13891 return Some(self.call_sub(
13892 &sub,
13893 vec![val.clone(), StrykeValue::string(op_key.to_string())],
13894 WantarrayCtx::Scalar,
13895 line,
13896 ));
13897 }
13898 None
13899 }
13900
13901 #[inline]
13902 fn eval_binop(
13903 &mut self,
13904 op: BinOp,
13905 lv: &StrykeValue,
13906 rv: &StrykeValue,
13907 _line: usize,
13908 ) -> ExecResult {
13909 Ok(match op {
13910 BinOp::Add => {
13913 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13914 StrykeValue::integer(a.wrapping_add(b))
13915 } else {
13916 StrykeValue::float(lv.to_number() + rv.to_number())
13917 }
13918 }
13919 BinOp::Sub => {
13920 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13921 StrykeValue::integer(a.wrapping_sub(b))
13922 } else {
13923 StrykeValue::float(lv.to_number() - rv.to_number())
13924 }
13925 }
13926 BinOp::Mul => {
13927 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13928 StrykeValue::integer(a.wrapping_mul(b))
13929 } else {
13930 StrykeValue::float(lv.to_number() * rv.to_number())
13931 }
13932 }
13933 BinOp::Div => {
13934 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13935 if b == 0 {
13936 return Err(StrykeError::division_by_zero(
13937 "Illegal division by zero",
13938 _line,
13939 )
13940 .into());
13941 }
13942 if a % b == 0 {
13943 StrykeValue::integer(a / b)
13944 } else {
13945 StrykeValue::float(a as f64 / b as f64)
13946 }
13947 } else {
13948 let d = rv.to_number();
13949 if d == 0.0 {
13950 return Err(StrykeError::division_by_zero(
13951 "Illegal division by zero",
13952 _line,
13953 )
13954 .into());
13955 }
13956 StrykeValue::float(lv.to_number() / d)
13957 }
13958 }
13959 BinOp::Mod => {
13960 let d = rv.to_int();
13961 if d == 0 {
13962 return Err(StrykeError::division_by_zero("Illegal modulus zero", _line).into());
13963 }
13964 StrykeValue::integer(crate::value::perl_mod_i64(lv.to_int(), d))
13965 }
13966 BinOp::Pow => {
13967 if crate::compat_mode() || crate::bigint_pragma() {
13971 crate::value::compat_pow(lv, rv)
13972 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
13973 let int_pow = (b >= 0)
13974 .then(|| u32::try_from(b).ok())
13975 .flatten()
13976 .and_then(|bu| a.checked_pow(bu))
13977 .map(StrykeValue::integer);
13978 int_pow
13979 .unwrap_or_else(|| StrykeValue::float(lv.to_number().powf(rv.to_number())))
13980 } else {
13981 StrykeValue::float(lv.to_number().powf(rv.to_number()))
13982 }
13983 }
13984 BinOp::Concat => {
13985 let mut s = String::new();
13986 lv.append_to(&mut s);
13987 rv.append_to(&mut s);
13988 StrykeValue::string(s)
13989 }
13990 BinOp::NumEq => {
13991 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
13993 if a.def.name != b.def.name {
13994 StrykeValue::integer(0)
13995 } else {
13996 let av = a.get_values();
13997 let bv = b.get_values();
13998 let eq = av.len() == bv.len()
13999 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
14000 StrykeValue::integer(if eq { 1 } else { 0 })
14001 }
14002 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14003 StrykeValue::integer(if a == b { 1 } else { 0 })
14004 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
14005 StrykeValue::integer(if lv.to_string() == rv.to_string() {
14011 1
14012 } else {
14013 0
14014 })
14015 } else {
14016 StrykeValue::integer(if lv.to_number() == rv.to_number() {
14017 1
14018 } else {
14019 0
14020 })
14021 }
14022 }
14023 BinOp::NumNe => {
14024 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14025 StrykeValue::integer(if a != b { 1 } else { 0 })
14026 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
14027 StrykeValue::integer(if lv.to_string() != rv.to_string() {
14028 1
14029 } else {
14030 0
14031 })
14032 } else {
14033 StrykeValue::integer(if lv.to_number() != rv.to_number() {
14034 1
14035 } else {
14036 0
14037 })
14038 }
14039 }
14040 BinOp::NumLt => {
14041 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14042 StrykeValue::integer(if a < b { 1 } else { 0 })
14043 } else {
14044 StrykeValue::integer(if lv.to_number() < rv.to_number() {
14045 1
14046 } else {
14047 0
14048 })
14049 }
14050 }
14051 BinOp::NumGt => {
14052 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14053 StrykeValue::integer(if a > b { 1 } else { 0 })
14054 } else {
14055 StrykeValue::integer(if lv.to_number() > rv.to_number() {
14056 1
14057 } else {
14058 0
14059 })
14060 }
14061 }
14062 BinOp::NumLe => {
14063 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14064 StrykeValue::integer(if a <= b { 1 } else { 0 })
14065 } else {
14066 StrykeValue::integer(if lv.to_number() <= rv.to_number() {
14067 1
14068 } else {
14069 0
14070 })
14071 }
14072 }
14073 BinOp::NumGe => {
14074 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14075 StrykeValue::integer(if a >= b { 1 } else { 0 })
14076 } else {
14077 StrykeValue::integer(if lv.to_number() >= rv.to_number() {
14078 1
14079 } else {
14080 0
14081 })
14082 }
14083 }
14084 BinOp::Spaceship => {
14085 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14086 StrykeValue::integer(if a < b {
14087 -1
14088 } else if a > b {
14089 1
14090 } else {
14091 0
14092 })
14093 } else {
14094 let a = lv.to_number();
14095 let b = rv.to_number();
14096 StrykeValue::integer(if a < b {
14097 -1
14098 } else if a > b {
14099 1
14100 } else {
14101 0
14102 })
14103 }
14104 }
14105 BinOp::StrEq => StrykeValue::integer(if lv.to_string() == rv.to_string() {
14106 1
14107 } else {
14108 0
14109 }),
14110 BinOp::StrNe => StrykeValue::integer(if lv.to_string() != rv.to_string() {
14111 1
14112 } else {
14113 0
14114 }),
14115 BinOp::StrLt => StrykeValue::integer(if lv.to_string() < rv.to_string() {
14116 1
14117 } else {
14118 0
14119 }),
14120 BinOp::StrGt => StrykeValue::integer(if lv.to_string() > rv.to_string() {
14121 1
14122 } else {
14123 0
14124 }),
14125 BinOp::StrLe => StrykeValue::integer(if lv.to_string() <= rv.to_string() {
14126 1
14127 } else {
14128 0
14129 }),
14130 BinOp::StrGe => StrykeValue::integer(if lv.to_string() >= rv.to_string() {
14131 1
14132 } else {
14133 0
14134 }),
14135 BinOp::StrCmp => {
14136 let cmp = lv.to_string().cmp(&rv.to_string());
14137 StrykeValue::integer(match cmp {
14138 std::cmp::Ordering::Less => -1,
14139 std::cmp::Ordering::Greater => 1,
14140 std::cmp::Ordering::Equal => 0,
14141 })
14142 }
14143 BinOp::BitAnd => {
14144 if let Some(s) = crate::value::set_intersection(lv, rv) {
14145 s
14146 } else {
14147 StrykeValue::integer(lv.to_int() & rv.to_int())
14148 }
14149 }
14150 BinOp::BitOr => {
14151 if let Some(s) = crate::value::set_union(lv, rv) {
14152 s
14153 } else {
14154 StrykeValue::integer(lv.to_int() | rv.to_int())
14155 }
14156 }
14157 BinOp::BitXor => StrykeValue::integer(lv.to_int() ^ rv.to_int()),
14158 BinOp::ShiftLeft => StrykeValue::integer(lv.to_int() << rv.to_int()),
14159 BinOp::ShiftRight => StrykeValue::integer(lv.to_int() >> rv.to_int()),
14160 BinOp::LogAnd
14162 | BinOp::LogOr
14163 | BinOp::DefinedOr
14164 | BinOp::LogAndWord
14165 | BinOp::LogOrWord => unreachable!(),
14166 BinOp::BindMatch | BinOp::BindNotMatch => {
14167 unreachable!("regex bind handled in eval_expr BinOp arm")
14168 }
14169 })
14170 }
14171
14172 fn err_modify_symbolic_aggregate_deref_inc_dec(
14176 kind: Sigil,
14177 is_pre: bool,
14178 is_inc: bool,
14179 line: usize,
14180 ) -> FlowOrError {
14181 let agg = match kind {
14182 Sigil::Array => "array",
14183 Sigil::Hash => "hash",
14184 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
14185 };
14186 let op = match (is_pre, is_inc) {
14187 (true, true) => "preincrement (++)",
14188 (true, false) => "predecrement (--)",
14189 (false, true) => "postincrement (++)",
14190 (false, false) => "postdecrement (--)",
14191 };
14192 FlowOrError::Error(StrykeError::runtime(
14193 format!("Can't modify {agg} dereference in {op}"),
14194 line,
14195 ))
14196 }
14197
14198 pub(crate) fn symbolic_scalar_ref_postfix(
14200 &mut self,
14201 ref_val: StrykeValue,
14202 decrement: bool,
14203 line: usize,
14204 ) -> Result<StrykeValue, FlowOrError> {
14205 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
14206 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14207 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
14208 Ok(old)
14209 }
14210
14211 pub(crate) fn assign_scalar_ref_deref(
14214 &mut self,
14215 ref_val: StrykeValue,
14216 val: StrykeValue,
14217 line: usize,
14218 ) -> ExecResult {
14219 if let Some(name) = ref_val.as_scalar_binding_name() {
14220 self.set_special_var(&name, &val)
14221 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14222 return Ok(StrykeValue::UNDEF);
14223 }
14224 if let Some(r) = ref_val.as_scalar_ref() {
14225 *r.write() = val;
14226 return Ok(StrykeValue::UNDEF);
14227 }
14228 if ref_val.is_integer_like() || ref_val.is_float_like() || ref_val.is_string_like() {
14231 let s = ref_val.to_string();
14232 if self.strict_refs {
14233 return Err(StrykeError::runtime(
14234 format!(
14235 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
14236 s
14237 ),
14238 line,
14239 )
14240 .into());
14241 }
14242 self.set_special_var(&s, &val)
14243 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14244 return Ok(StrykeValue::UNDEF);
14245 }
14246 Err(StrykeError::runtime("Can't assign to non-scalar reference", line).into())
14247 }
14248
14249 pub(crate) fn assign_symbolic_array_ref_deref(
14251 &mut self,
14252 ref_val: StrykeValue,
14253 val: StrykeValue,
14254 line: usize,
14255 ) -> ExecResult {
14256 if let Some(a) = ref_val.as_array_ref() {
14257 *a.write() = val.to_list();
14258 return Ok(StrykeValue::UNDEF);
14259 }
14260 if let Some(name) = ref_val.as_array_binding_name() {
14261 self.scope
14262 .set_array(&name, val.to_list())
14263 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14264 return Ok(StrykeValue::UNDEF);
14265 }
14266 if let Some(s) = ref_val.as_str() {
14267 if self.strict_refs {
14268 return Err(StrykeError::runtime(
14269 format!(
14270 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
14271 s
14272 ),
14273 line,
14274 )
14275 .into());
14276 }
14277 self.scope
14278 .set_array(&s, val.to_list())
14279 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14280 return Ok(StrykeValue::UNDEF);
14281 }
14282 Err(StrykeError::runtime("Can't assign to non-array reference", line).into())
14283 }
14284
14285 pub(crate) fn assign_symbolic_typeglob_ref_deref(
14288 &mut self,
14289 ref_val: StrykeValue,
14290 val: StrykeValue,
14291 line: usize,
14292 ) -> ExecResult {
14293 let lhs_name = if let Some(s) = ref_val.as_str() {
14294 if self.strict_refs {
14295 return Err(StrykeError::runtime(
14296 format!(
14297 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
14298 s
14299 ),
14300 line,
14301 )
14302 .into());
14303 }
14304 s.to_string()
14305 } else {
14306 return Err(
14307 StrykeError::runtime("Can't assign to non-glob symbolic reference", line).into(),
14308 );
14309 };
14310 let is_coderef = val.as_code_ref().is_some()
14311 || val
14312 .as_scalar_ref()
14313 .map(|r| r.read().as_code_ref().is_some())
14314 .unwrap_or(false);
14315 if is_coderef {
14316 return self.assign_typeglob_value(&lhs_name, val, line);
14317 }
14318 let rhs_key = val.to_string();
14319 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
14320 .map_err(FlowOrError::Error)?;
14321 Ok(StrykeValue::UNDEF)
14322 }
14323
14324 pub(crate) fn assign_symbolic_hash_ref_deref(
14326 &mut self,
14327 ref_val: StrykeValue,
14328 val: StrykeValue,
14329 line: usize,
14330 ) -> ExecResult {
14331 let items = val.to_list();
14332 let mut map = IndexMap::new();
14333 let mut i = 0;
14334 while i + 1 < items.len() {
14335 map.insert(items[i].to_string(), items[i + 1].clone());
14336 i += 2;
14337 }
14338 if let Some(h) = ref_val.as_hash_ref() {
14339 *h.write() = map;
14340 return Ok(StrykeValue::UNDEF);
14341 }
14342 if let Some(name) = ref_val.as_hash_binding_name() {
14343 self.touch_env_hash(&name);
14344 self.scope
14345 .set_hash(&name, map)
14346 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14347 return Ok(StrykeValue::UNDEF);
14348 }
14349 if let Some(s) = ref_val.as_str() {
14350 if self.strict_refs {
14351 return Err(StrykeError::runtime(
14352 format!(
14353 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
14354 s
14355 ),
14356 line,
14357 )
14358 .into());
14359 }
14360 self.touch_env_hash(&s);
14361 self.scope
14362 .set_hash(&s, map)
14363 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14364 return Ok(StrykeValue::UNDEF);
14365 }
14366 Err(StrykeError::runtime("Can't assign to non-hash reference", line).into())
14367 }
14368
14369 pub(crate) fn assign_arrow_hash_deref(
14371 &mut self,
14372 container: StrykeValue,
14373 key: String,
14374 val: StrykeValue,
14375 line: usize,
14376 ) -> ExecResult {
14377 if let Some(b) = container.as_blessed_ref() {
14378 let mut data = b.data.write();
14379 if let Some(r) = data.as_hash_ref() {
14380 r.write().insert(key, val);
14381 return Ok(StrykeValue::UNDEF);
14382 }
14383 if let Some(mut map) = data.as_hash_map() {
14384 map.insert(key, val);
14385 *data = StrykeValue::hash(map);
14386 return Ok(StrykeValue::UNDEF);
14387 }
14388 return Err(
14389 StrykeError::runtime("Can't assign into non-hash blessed ref", line).into(),
14390 );
14391 }
14392 if let Some(r) = container.as_hash_ref() {
14393 r.write().insert(key, val);
14394 return Ok(StrykeValue::UNDEF);
14395 }
14396 if let Some(name) = container.as_hash_binding_name() {
14397 self.touch_env_hash(&name);
14398 self.scope
14399 .set_hash_element(&name, &key, val)
14400 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14401 return Ok(StrykeValue::UNDEF);
14402 }
14403 Err(StrykeError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
14404 }
14405
14406 pub(crate) fn eval_arrow_array_base(
14409 &mut self,
14410 expr: &Expr,
14411 _line: usize,
14412 ) -> Result<StrykeValue, FlowOrError> {
14413 match &expr.kind {
14414 ExprKind::Deref {
14415 expr: inner,
14416 kind: Sigil::Array | Sigil::Scalar,
14417 } => self.eval_expr(inner),
14418 _ => self.eval_expr(expr),
14419 }
14420 }
14421
14422 pub(crate) fn eval_arrow_hash_base(
14424 &mut self,
14425 expr: &Expr,
14426 _line: usize,
14427 ) -> Result<StrykeValue, FlowOrError> {
14428 match &expr.kind {
14429 ExprKind::Deref {
14430 expr: inner,
14431 kind: Sigil::Scalar,
14432 } => self.eval_expr(inner),
14433 _ => self.eval_expr(expr),
14434 }
14435 }
14436
14437 pub(crate) fn read_arrow_array_element(
14439 &self,
14440 container: StrykeValue,
14441 idx: i64,
14442 line: usize,
14443 ) -> Result<StrykeValue, FlowOrError> {
14444 if let Some(a) = container.as_array_ref() {
14445 let arr = a.read();
14446 let i = if idx < 0 {
14447 (arr.len() as i64 + idx) as usize
14448 } else {
14449 idx as usize
14450 };
14451 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
14452 }
14453 if let Some(name) = container.as_array_binding_name() {
14454 return Ok(self.scope.get_array_element(&name, idx));
14455 }
14456 if let Some(arr) = container.as_array_vec() {
14457 let i = if idx < 0 {
14458 (arr.len() as i64 + idx) as usize
14459 } else {
14460 idx as usize
14461 };
14462 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
14463 }
14464 if let Some(b) = container.as_blessed_ref() {
14467 let inner = b.data.read().clone();
14468 if let Some(a) = inner.as_array_ref() {
14469 let arr = a.read();
14470 let i = if idx < 0 {
14471 (arr.len() as i64 + idx) as usize
14472 } else {
14473 idx as usize
14474 };
14475 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
14476 }
14477 }
14478 Err(StrykeError::runtime("Can't use arrow deref on non-array-ref", line).into())
14479 }
14480
14481 pub(crate) fn read_arrow_hash_element(
14483 &mut self,
14484 container: StrykeValue,
14485 key: &str,
14486 line: usize,
14487 ) -> Result<StrykeValue, FlowOrError> {
14488 if let Some(r) = container.as_hash_ref() {
14489 let h = r.read();
14490 return Ok(h.get(key).cloned().unwrap_or(StrykeValue::UNDEF));
14491 }
14492 if let Some(name) = container.as_hash_binding_name() {
14493 self.touch_env_hash(&name);
14494 return Ok(self.scope.get_hash_element(&name, key));
14495 }
14496 if let Some(b) = container.as_blessed_ref() {
14497 let data = b.data.read();
14498 if let Some(v) = data.hash_get(key) {
14499 return Ok(v);
14500 }
14501 if let Some(r) = data.as_hash_ref() {
14502 let h = r.read();
14503 return Ok(h.get(key).cloned().unwrap_or(StrykeValue::UNDEF));
14504 }
14505 return Err(StrykeError::runtime(
14506 "Can't access hash field on non-hash blessed ref",
14507 line,
14508 )
14509 .into());
14510 }
14511 if let Some(s) = container.as_struct_inst() {
14513 if let Some(idx) = s.def.field_index(key) {
14514 return Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF));
14515 }
14516 return Err(StrykeError::runtime(
14517 format!("struct {} has no field `{}`", s.def.name, key),
14518 line,
14519 )
14520 .into());
14521 }
14522 if let Some(c) = container.as_class_inst() {
14524 if let Some(idx) = c.def.field_index(key) {
14525 return Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF));
14526 }
14527 return Err(StrykeError::runtime(
14528 format!("class {} has no field `{}`", c.def.name, key),
14529 line,
14530 )
14531 .into());
14532 }
14533 Err(StrykeError::runtime("Can't use arrow deref on non-hash-ref", line).into())
14534 }
14535
14536 pub(crate) fn arrow_array_postfix(
14538 &mut self,
14539 container: StrykeValue,
14540 idx: i64,
14541 decrement: bool,
14542 line: usize,
14543 ) -> Result<StrykeValue, FlowOrError> {
14544 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
14545 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14546 self.assign_arrow_array_deref(container, idx, new_val, line)?;
14547 Ok(old)
14548 }
14549
14550 pub(crate) fn arrow_hash_postfix(
14552 &mut self,
14553 container: StrykeValue,
14554 key: String,
14555 decrement: bool,
14556 line: usize,
14557 ) -> Result<StrykeValue, FlowOrError> {
14558 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
14559 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14560 self.assign_arrow_hash_deref(container, key, new_val, line)?;
14561 Ok(old)
14562 }
14563
14564 pub(crate) fn resolve_bareword_rvalue(
14572 &mut self,
14573 name: &str,
14574 want: WantarrayCtx,
14575 line: usize,
14576 ) -> Result<StrykeValue, FlowOrError> {
14577 if name == "__PACKAGE__" {
14578 return Ok(StrykeValue::string(self.current_package()));
14579 }
14580 if let Some(sub) = self.resolve_sub_by_name(name) {
14581 return self.call_sub(&sub, vec![], want, line);
14582 }
14583 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
14585 return r.map_err(Into::into);
14586 }
14587 Ok(StrykeValue::string(name.to_string()))
14588 }
14589
14590 pub(crate) fn arrow_array_slice_values(
14594 &mut self,
14595 container: StrykeValue,
14596 indices: &[i64],
14597 line: usize,
14598 ) -> Result<StrykeValue, FlowOrError> {
14599 let mut out = Vec::with_capacity(indices.len());
14600 for &idx in indices {
14601 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
14602 out.push(v);
14603 }
14604 Ok(StrykeValue::array(out))
14605 }
14606
14607 pub(crate) fn assign_arrow_array_slice(
14611 &mut self,
14612 container: StrykeValue,
14613 indices: Vec<i64>,
14614 val: StrykeValue,
14615 line: usize,
14616 ) -> Result<StrykeValue, FlowOrError> {
14617 if indices.is_empty() {
14618 return Err(StrykeError::runtime("assign to empty array slice", line).into());
14619 }
14620 let vals = val.to_list();
14621 for (i, idx) in indices.iter().enumerate() {
14622 let v = vals.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
14623 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
14624 }
14625 Ok(StrykeValue::UNDEF)
14626 }
14627
14628 pub(crate) fn flatten_array_slice_index_specs(
14630 &mut self,
14631 indices: &[Expr],
14632 ) -> Result<Vec<i64>, FlowOrError> {
14633 let mut out = Vec::new();
14634 for idx_expr in indices {
14635 let v = if matches!(
14636 idx_expr.kind,
14637 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
14638 ) {
14639 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
14640 } else {
14641 self.eval_expr(idx_expr)?
14642 };
14643 if let Some(list) = v.as_array_vec() {
14644 for idx in list {
14645 out.push(idx.to_int());
14646 }
14647 } else {
14648 out.push(v.to_int());
14649 }
14650 }
14651 Ok(out)
14652 }
14653
14654 pub(crate) fn assign_named_array_slice(
14656 &mut self,
14657 stash_array_name: &str,
14658 indices: Vec<i64>,
14659 val: StrykeValue,
14660 line: usize,
14661 ) -> Result<StrykeValue, FlowOrError> {
14662 if indices.is_empty() {
14663 return Err(StrykeError::runtime("assign to empty array slice", line).into());
14664 }
14665 let vals = val.to_list();
14666 for (i, idx) in indices.iter().enumerate() {
14667 let v = vals.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
14668 self.scope
14669 .set_array_element(stash_array_name, *idx, v)
14670 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14671 }
14672 Ok(StrykeValue::UNDEF)
14673 }
14674
14675 pub(crate) fn compound_assign_arrow_array_slice(
14678 &mut self,
14679 container: StrykeValue,
14680 indices: Vec<i64>,
14681 op: BinOp,
14682 rhs: StrykeValue,
14683 line: usize,
14684 ) -> Result<StrykeValue, FlowOrError> {
14685 if indices.is_empty() {
14686 return Err(StrykeError::runtime("assign to empty array slice", line).into());
14687 }
14688 let last_idx = *indices.last().expect("non-empty indices");
14689 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
14690 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
14691 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
14692 Ok(new_val)
14693 }
14694
14695 pub(crate) fn arrow_array_slice_inc_dec(
14700 &mut self,
14701 container: StrykeValue,
14702 indices: Vec<i64>,
14703 kind: u8,
14704 line: usize,
14705 ) -> Result<StrykeValue, FlowOrError> {
14706 if indices.is_empty() {
14707 return Err(StrykeError::runtime(
14708 "array slice increment needs at least one index",
14709 line,
14710 )
14711 .into());
14712 }
14713 let last_idx = *indices.last().expect("non-empty indices");
14714 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
14715 let new_val = if kind & 1 == 0 {
14716 StrykeValue::integer(last_old.to_int() + 1)
14717 } else {
14718 StrykeValue::integer(last_old.to_int() - 1)
14719 };
14720 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
14721 Ok(if kind < 2 { new_val } else { last_old })
14722 }
14723
14724 pub(crate) fn named_array_slice_inc_dec(
14727 &mut self,
14728 stash_array_name: &str,
14729 indices: Vec<i64>,
14730 kind: u8,
14731 line: usize,
14732 ) -> Result<StrykeValue, FlowOrError> {
14733 let last_idx = *indices.last().ok_or_else(|| {
14734 StrykeError::runtime("array slice increment needs at least one index", line)
14735 })?;
14736 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
14737 let new_val = if kind & 1 == 0 {
14738 StrykeValue::integer(last_old.to_int() + 1)
14739 } else {
14740 StrykeValue::integer(last_old.to_int() - 1)
14741 };
14742 self.scope
14743 .set_array_element(stash_array_name, last_idx, new_val.clone())
14744 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14745 Ok(if kind < 2 { new_val } else { last_old })
14746 }
14747
14748 pub(crate) fn compound_assign_named_array_slice(
14750 &mut self,
14751 stash_array_name: &str,
14752 indices: Vec<i64>,
14753 op: BinOp,
14754 rhs: StrykeValue,
14755 line: usize,
14756 ) -> Result<StrykeValue, FlowOrError> {
14757 if indices.is_empty() {
14758 return Err(StrykeError::runtime("assign to empty array slice", line).into());
14759 }
14760 let last_idx = *indices.last().expect("non-empty indices");
14761 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
14762 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
14763 self.scope
14764 .set_array_element(stash_array_name, last_idx, new_val.clone())
14765 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14766 Ok(new_val)
14767 }
14768
14769 pub(crate) fn assign_arrow_array_deref(
14771 &mut self,
14772 container: StrykeValue,
14773 idx: i64,
14774 val: StrykeValue,
14775 line: usize,
14776 ) -> ExecResult {
14777 if let Some(a) = container.as_array_ref() {
14778 let mut arr = a.write();
14779 let i = if idx < 0 {
14780 (arr.len() as i64 + idx) as usize
14781 } else {
14782 idx as usize
14783 };
14784 if i >= arr.len() {
14785 arr.resize(i + 1, StrykeValue::UNDEF);
14786 }
14787 arr[i] = val;
14788 return Ok(StrykeValue::UNDEF);
14789 }
14790 if let Some(name) = container.as_array_binding_name() {
14791 self.scope
14792 .set_array_element(&name, idx, val)
14793 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14794 return Ok(StrykeValue::UNDEF);
14795 }
14796 Err(StrykeError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
14797 }
14798
14799 pub(crate) fn assign_typeglob_value(
14801 &mut self,
14802 name: &str,
14803 val: StrykeValue,
14804 line: usize,
14805 ) -> ExecResult {
14806 let sub = if let Some(c) = val.as_code_ref() {
14807 Some(c)
14808 } else if let Some(r) = val.as_scalar_ref() {
14809 r.read().as_code_ref().map(|c| Arc::clone(&c))
14810 } else {
14811 None
14812 };
14813 if let Some(sub) = sub {
14814 let lhs_sub = self.qualify_typeglob_sub_key(name);
14815 self.subs.insert(lhs_sub, sub);
14816 return Ok(StrykeValue::UNDEF);
14817 }
14818 Err(StrykeError::runtime(
14819 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
14820 line,
14821 )
14822 .into())
14823 }
14824
14825 fn assign_value(&mut self, target: &Expr, val: StrykeValue) -> ExecResult {
14826 match &target.kind {
14827 ExprKind::Substr {
14833 string,
14834 offset,
14835 length,
14836 replacement: None,
14837 } => {
14838 let s = self.eval_expr(string)?.to_string();
14839 let off = self.eval_expr(offset)?.to_int();
14840 let start = if off < 0 {
14841 (s.len() as i64 + off).max(0) as usize
14842 } else {
14843 (off as usize).min(s.len())
14844 };
14845 let len = if let Some(l) = length {
14846 let lv = self.eval_expr(l)?.to_int();
14847 if lv < 0 {
14848 let remaining = s.len().saturating_sub(start) as i64;
14849 (remaining + lv).max(0) as usize
14850 } else {
14851 lv as usize
14852 }
14853 } else {
14854 s.len().saturating_sub(start)
14855 };
14856 let end = start.saturating_add(len).min(s.len());
14857 let mut new_s = String::with_capacity(s.len());
14858 new_s.push_str(&s[..start]);
14859 new_s.push_str(&val.to_string());
14860 new_s.push_str(&s[end..]);
14861 self.assign_value(string, StrykeValue::string(new_s))?;
14862 Ok(StrykeValue::UNDEF)
14863 }
14864 ExprKind::MyExpr { decls, .. } => {
14871 let first = decls.first().ok_or_else(|| {
14872 FlowOrError::Error(StrykeError::runtime(
14873 "assign_value: empty MyExpr decl list",
14874 target.line,
14875 ))
14876 })?;
14877 match first.sigil {
14878 Sigil::Scalar => {
14879 let stor = self.tree_scalar_storage_name(&first.name);
14880 self.set_special_var(&stor, &val)
14881 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
14882 Ok(StrykeValue::UNDEF)
14883 }
14884 Sigil::Array => {
14885 self.scope.set_array(&first.name, val.to_list())?;
14886 Ok(StrykeValue::UNDEF)
14887 }
14888 Sigil::Hash => {
14889 let items = val.to_list();
14890 let mut map = IndexMap::new();
14891 let mut i = 0;
14892 while i + 1 < items.len() {
14893 map.insert(items[i].to_string(), items[i + 1].clone());
14894 i += 2;
14895 }
14896 self.scope.set_hash(&first.name, map)?;
14897 Ok(StrykeValue::UNDEF)
14898 }
14899 Sigil::Typeglob => Ok(StrykeValue::UNDEF),
14900 }
14901 }
14902 ExprKind::ScalarVar(name) => {
14903 let stor = self.tree_scalar_storage_name(name);
14904 if self.scope.is_scalar_frozen(&stor) {
14905 return Err(FlowOrError::Error(StrykeError::runtime(
14906 format!("Modification of a frozen value: ${}", name),
14907 target.line,
14908 )));
14909 }
14910 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
14911 let class = obj
14912 .as_blessed_ref()
14913 .map(|b| b.class.clone())
14914 .unwrap_or_default();
14915 let full = format!("{}::STORE", class);
14916 if let Some(sub) = self.subs.get(&full).cloned() {
14917 let arg_vals = vec![obj, val];
14918 return match self.call_sub(
14919 &sub,
14920 arg_vals,
14921 WantarrayCtx::Scalar,
14922 target.line,
14923 ) {
14924 Ok(_) => Ok(StrykeValue::UNDEF),
14925 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
14926 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
14927 };
14928 }
14929 }
14930 self.set_special_var(&stor, &val)
14931 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
14932 Ok(StrykeValue::UNDEF)
14933 }
14934 ExprKind::ArrayVar(name) => {
14935 if self.scope.is_array_frozen(name) {
14936 return Err(StrykeError::runtime(
14937 format!("Modification of a frozen value: @{}", name),
14938 target.line,
14939 )
14940 .into());
14941 }
14942 if self.strict_vars
14943 && !name.contains("::")
14944 && !self.scope.array_binding_exists(name)
14945 {
14946 return Err(StrykeError::runtime(
14947 format!(
14948 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
14949 name, name
14950 ),
14951 target.line,
14952 )
14953 .into());
14954 }
14955 self.scope.set_array(name, val.to_list())?;
14956 Ok(StrykeValue::UNDEF)
14957 }
14958 ExprKind::HashVar(name) => {
14959 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
14960 {
14961 return Err(StrykeError::runtime(
14962 format!(
14963 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
14964 name, name
14965 ),
14966 target.line,
14967 )
14968 .into());
14969 }
14970 let items = val.to_list();
14971 let mut map = IndexMap::new();
14972 let mut i = 0;
14973 while i + 1 < items.len() {
14974 map.insert(items[i].to_string(), items[i + 1].clone());
14975 i += 2;
14976 }
14977 self.scope.set_hash(name, map)?;
14978 Ok(StrykeValue::UNDEF)
14979 }
14980 ExprKind::ArrayElement { array, index } => {
14981 if self.strict_vars
14982 && !array.contains("::")
14983 && !self.scope.array_binding_exists(array)
14984 {
14985 return Err(StrykeError::runtime(
14986 format!(
14987 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
14988 array, array
14989 ),
14990 target.line,
14991 )
14992 .into());
14993 }
14994 if self.scope.is_array_frozen(array) {
14995 return Err(StrykeError::runtime(
14996 format!("Modification of a frozen value: @{}", array),
14997 target.line,
14998 )
14999 .into());
15000 }
15001 let idx = self.eval_expr(index)?.to_int();
15002 let aname = self.stash_array_name_for_package(array);
15003 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
15004 let class = obj
15005 .as_blessed_ref()
15006 .map(|b| b.class.clone())
15007 .unwrap_or_default();
15008 let full = format!("{}::STORE", class);
15009 if let Some(sub) = self.subs.get(&full).cloned() {
15010 let arg_vals = vec![obj, StrykeValue::integer(idx), val];
15011 return match self.call_sub(
15012 &sub,
15013 arg_vals,
15014 WantarrayCtx::Scalar,
15015 target.line,
15016 ) {
15017 Ok(_) => Ok(StrykeValue::UNDEF),
15018 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15019 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15020 };
15021 }
15022 }
15023 self.scope.set_array_element(&aname, idx, val)?;
15024 Ok(StrykeValue::UNDEF)
15025 }
15026 ExprKind::ArraySlice { array, indices } => {
15027 if indices.is_empty() {
15028 return Err(
15029 StrykeError::runtime("assign to empty array slice", target.line).into(),
15030 );
15031 }
15032 self.check_strict_array_var(array, target.line)?;
15033 if self.scope.is_array_frozen(array) {
15034 return Err(StrykeError::runtime(
15035 format!("Modification of a frozen value: @{}", array),
15036 target.line,
15037 )
15038 .into());
15039 }
15040 let aname = self.stash_array_name_for_package(array);
15041 let flat = self.flatten_array_slice_index_specs(indices)?;
15042 self.assign_named_array_slice(&aname, flat, val, target.line)
15043 }
15044 ExprKind::HashElement { hash, key } => {
15045 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
15046 {
15047 return Err(StrykeError::runtime(
15048 format!(
15049 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15050 hash, hash
15051 ),
15052 target.line,
15053 )
15054 .into());
15055 }
15056 if self.scope.is_hash_frozen(hash) {
15057 return Err(StrykeError::runtime(
15058 format!("Modification of a frozen value: %%{}", hash),
15059 target.line,
15060 )
15061 .into());
15062 }
15063 let k = self.eval_expr(key)?.to_string();
15064 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
15065 let class = obj
15066 .as_blessed_ref()
15067 .map(|b| b.class.clone())
15068 .unwrap_or_default();
15069 let full = format!("{}::STORE", class);
15070 if let Some(sub) = self.subs.get(&full).cloned() {
15071 let arg_vals = vec![obj, StrykeValue::string(k), val];
15072 return match self.call_sub(
15073 &sub,
15074 arg_vals,
15075 WantarrayCtx::Scalar,
15076 target.line,
15077 ) {
15078 Ok(_) => Ok(StrykeValue::UNDEF),
15079 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15080 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15081 };
15082 }
15083 }
15084 self.scope.set_hash_element(hash, &k, val)?;
15085 Ok(StrykeValue::UNDEF)
15086 }
15087 ExprKind::HashSlice { hash, keys } => {
15088 if keys.is_empty() {
15089 return Err(
15090 StrykeError::runtime("assign to empty hash slice", target.line).into(),
15091 );
15092 }
15093 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
15094 {
15095 return Err(StrykeError::runtime(
15096 format!(
15097 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15098 hash, hash
15099 ),
15100 target.line,
15101 )
15102 .into());
15103 }
15104 if self.scope.is_hash_frozen(hash) {
15105 return Err(StrykeError::runtime(
15106 format!("Modification of a frozen value: %%{}", hash),
15107 target.line,
15108 )
15109 .into());
15110 }
15111 let mut key_vals = Vec::with_capacity(keys.len());
15112 for key_expr in keys {
15113 let v = if matches!(
15114 key_expr.kind,
15115 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
15116 ) {
15117 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
15118 } else {
15119 self.eval_expr(key_expr)?
15120 };
15121 key_vals.push(v);
15122 }
15123 self.assign_named_hash_slice(hash, key_vals, val, target.line)
15124 }
15125 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
15126 ExprKind::TypeglobExpr(e) => {
15127 let name = self.eval_expr(e)?.to_string();
15128 let synthetic = Expr {
15129 kind: ExprKind::Typeglob(name),
15130 line: target.line,
15131 };
15132 self.assign_value(&synthetic, val)
15133 }
15134 ExprKind::AnonymousListSlice { source, indices } => {
15135 if let ExprKind::Deref {
15136 expr: inner,
15137 kind: Sigil::Array,
15138 } = &source.kind
15139 {
15140 let container = self.eval_arrow_array_base(inner, target.line)?;
15141 let vals = val.to_list();
15142 let n = indices.len().min(vals.len());
15143 for i in 0..n {
15144 let idx = self.eval_expr(&indices[i])?.to_int();
15145 self.assign_arrow_array_deref(
15146 container.clone(),
15147 idx,
15148 vals[i].clone(),
15149 target.line,
15150 )?;
15151 }
15152 return Ok(StrykeValue::UNDEF);
15153 }
15154 Err(
15155 StrykeError::runtime("assign to list slice: unsupported base", target.line)
15156 .into(),
15157 )
15158 }
15159 ExprKind::ArrowDeref {
15160 expr,
15161 index,
15162 kind: DerefKind::Hash,
15163 } => {
15164 let key = self.eval_expr(index)?.to_string();
15165 let container = self.eval_expr(expr)?;
15166 self.assign_arrow_hash_deref(container, key, val, target.line)
15167 }
15168 ExprKind::ArrowDeref {
15169 expr,
15170 index,
15171 kind: DerefKind::Array,
15172 } => {
15173 let container = self.eval_arrow_array_base(expr, target.line)?;
15174 if let ExprKind::List(indices) = &index.kind {
15175 let vals = val.to_list();
15176 let n = indices.len().min(vals.len());
15177 for i in 0..n {
15178 let idx = self.eval_expr(&indices[i])?.to_int();
15179 self.assign_arrow_array_deref(
15180 container.clone(),
15181 idx,
15182 vals[i].clone(),
15183 target.line,
15184 )?;
15185 }
15186 return Ok(StrykeValue::UNDEF);
15187 }
15188 let idx = self.eval_expr(index)?.to_int();
15189 self.assign_arrow_array_deref(container, idx, val, target.line)
15190 }
15191 ExprKind::HashSliceDeref { container, keys } => {
15192 let href = self.eval_expr(container)?;
15193 let mut key_vals = Vec::with_capacity(keys.len());
15194 for key_expr in keys {
15195 key_vals.push(self.eval_expr(key_expr)?);
15196 }
15197 self.assign_hash_slice_deref(href, key_vals, val, target.line)
15198 }
15199 ExprKind::Deref {
15200 expr,
15201 kind: Sigil::Scalar,
15202 } => {
15203 let ref_val = self.eval_expr(expr)?;
15204 self.assign_scalar_ref_deref(ref_val, val, target.line)
15205 }
15206 ExprKind::Deref {
15207 expr,
15208 kind: Sigil::Array,
15209 } => {
15210 let ref_val = self.eval_expr(expr)?;
15211 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
15212 }
15213 ExprKind::Deref {
15214 expr,
15215 kind: Sigil::Hash,
15216 } => {
15217 let ref_val = self.eval_expr(expr)?;
15218 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
15219 }
15220 ExprKind::Deref {
15221 expr,
15222 kind: Sigil::Typeglob,
15223 } => {
15224 let ref_val = self.eval_expr(expr)?;
15225 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
15226 }
15227 ExprKind::Pos(inner) => {
15228 let key = match inner {
15229 None => "_".to_string(),
15230 Some(expr) => match &expr.kind {
15231 ExprKind::ScalarVar(n) => n.clone(),
15232 _ => self.eval_expr(expr)?.to_string(),
15233 },
15234 };
15235 if val.is_undef() {
15236 self.regex_pos.insert(key, None);
15237 } else {
15238 let u = val.to_int().max(0) as usize;
15239 self.regex_pos.insert(key, Some(u));
15240 }
15241 Ok(StrykeValue::UNDEF)
15242 }
15243 ExprKind::List(targets) => {
15246 let items = val.to_list();
15247 for (i, t) in targets.iter().enumerate() {
15248 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15249 self.assign_value(t, v)?;
15250 }
15251 Ok(StrykeValue::UNDEF)
15252 }
15253 ExprKind::Assign { target, .. } => self.assign_value(target, val),
15256 _ => Ok(StrykeValue::UNDEF),
15257 }
15258 }
15259
15260 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
15262 (name.starts_with('#') && name.len() > 1)
15263 || name.starts_with('^')
15264 || matches!(
15265 name,
15266 "$$" | "0"
15267 | "!"
15268 | "@"
15269 | "/"
15270 | "\\"
15271 | ","
15272 | "."
15273 | "]"
15274 | ";"
15275 | "ARGV"
15276 | "^I"
15277 | "^D"
15278 | "^P"
15279 | "^S"
15280 | "^W"
15281 | "^O"
15282 | "^T"
15283 | "^V"
15284 | "^E"
15285 | "^H"
15286 | "^WARNING_BITS"
15287 | "^GLOBAL_PHASE"
15288 | "^MATCH"
15289 | "^PREMATCH"
15290 | "^POSTMATCH"
15291 | "^LAST_SUBMATCH_RESULT"
15292 | "<"
15293 | ">"
15294 | "("
15295 | ")"
15296 | "?"
15297 | "|"
15298 | "\""
15299 | "+"
15300 | "%"
15301 | "="
15302 | "-"
15303 | ":"
15304 | "*"
15305 | "INC"
15306 )
15307 || crate::english::is_known_alias(name)
15308 }
15309
15310 #[inline]
15315 pub(crate) fn resolved_scalar_storage_name(&self, name: &str) -> String {
15320 self.tree_scalar_storage_name(self.english_scalar_name(name))
15321 }
15322
15323 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
15324 if !self.english_enabled {
15325 return name;
15326 }
15327 if self
15328 .english_lexical_scalars
15329 .iter()
15330 .any(|s| s.contains(name))
15331 {
15332 return name;
15333 }
15334 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
15335 return short;
15336 }
15337 name
15338 }
15339
15340 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
15342 (name.starts_with('#') && name.len() > 1)
15347 || name.starts_with('^')
15348 || matches!(
15349 name,
15350 "0" | "/"
15351 | "\\"
15352 | ","
15353 | ";"
15354 | "\""
15355 | "%"
15356 | "="
15357 | "-"
15358 | ":"
15359 | "*"
15360 | "INC"
15361 | "^I"
15362 | "^D"
15363 | "^P"
15364 | "^W"
15365 | "^H"
15366 | "^WARNING_BITS"
15367 | "$$"
15368 | "]"
15369 | "^S"
15370 | "ARGV"
15371 | "|"
15372 | "+"
15373 | "?"
15374 | "!"
15375 | "@"
15376 | "."
15377 )
15378 || crate::english::is_known_alias(name)
15379 }
15380
15381 pub(crate) fn get_special_var(&self, name: &str) -> StrykeValue {
15382 let name = if !crate::compat_mode() {
15384 match name {
15385 "NR" => ".",
15386 "RS" => "/",
15387 "OFS" => ",",
15388 "ORS" => "\\",
15389 "NF" => {
15390 let len = self.scope.array_len("F");
15391 return StrykeValue::integer(len as i64);
15392 }
15393 _ => self.english_scalar_name(name),
15394 }
15395 } else {
15396 self.english_scalar_name(name)
15397 };
15398 match name {
15399 "$$" => StrykeValue::integer(std::process::id() as i64),
15400 "_" => self.scope.get_scalar("_"),
15401 "^MATCH" => StrykeValue::string(self.last_match.clone()),
15402 "^PREMATCH" => StrykeValue::string(self.prematch.clone()),
15403 "^POSTMATCH" => StrykeValue::string(self.postmatch.clone()),
15404 "^LAST_SUBMATCH_RESULT" => StrykeValue::string(self.last_paren_match.clone()),
15405 "0" => StrykeValue::string(self.program_name.clone()),
15406 "!" => StrykeValue::errno_dual(self.errno_code, self.errno.clone()),
15407 "@" => {
15408 if let Some(ref v) = self.eval_error_value {
15409 v.clone()
15410 } else {
15411 StrykeValue::errno_dual(self.eval_error_code, self.eval_error.clone())
15412 }
15413 }
15414 "/" => match &self.irs {
15415 Some(s) => StrykeValue::string(s.clone()),
15416 None => StrykeValue::UNDEF,
15417 },
15418 "\\" => StrykeValue::string(self.ors.clone()),
15419 "," => StrykeValue::string(self.ofs.clone()),
15420 "." => {
15421 if self.last_readline_handle.is_empty() {
15423 if self.line_number == 0 {
15424 StrykeValue::UNDEF
15425 } else {
15426 StrykeValue::integer(self.line_number)
15427 }
15428 } else {
15429 StrykeValue::integer(
15430 *self
15431 .handle_line_numbers
15432 .get(&self.last_readline_handle)
15433 .unwrap_or(&0),
15434 )
15435 }
15436 }
15437 "]" => StrykeValue::float(perl_bracket_version()),
15438 ";" => StrykeValue::string(self.subscript_sep.clone()),
15439 "ARGV" => StrykeValue::string(self.argv_current_file.clone()),
15440 "^I" => StrykeValue::string(self.inplace_edit.clone()),
15441 "^D" => StrykeValue::integer(self.debug_flags),
15442 "^P" => StrykeValue::integer(self.perl_debug_flags),
15443 "^S" => StrykeValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
15444 "^W" => StrykeValue::integer(if self.warnings { 1 } else { 0 }),
15445 "^O" => StrykeValue::string(perl_osname()),
15446 "^T" => StrykeValue::integer(self.script_start_time),
15447 "^V" => StrykeValue::string(perl_version_v_string()),
15448 "^E" => StrykeValue::string(extended_os_error_string()),
15449 "^H" => StrykeValue::integer(self.compile_hints),
15450 "^WARNING_BITS" => StrykeValue::integer(self.warning_bits),
15451 "^GLOBAL_PHASE" => StrykeValue::string(self.global_phase.clone()),
15452 "<" | ">" => StrykeValue::integer(unix_id_for_special(name)),
15453 "(" | ")" => StrykeValue::string(unix_group_list_for_special(name)),
15454 "?" => StrykeValue::integer(self.child_exit_status),
15455 "|" => StrykeValue::integer(if self.output_autoflush { 1 } else { 0 }),
15456 "\"" => StrykeValue::string(self.list_separator.clone()),
15457 "+" => StrykeValue::string(self.last_paren_match.clone()),
15458 "%" => StrykeValue::integer(self.format_page_number),
15459 "=" => StrykeValue::integer(self.format_lines_per_page),
15460 "-" => StrykeValue::integer(self.format_lines_left),
15461 ":" => StrykeValue::string(self.format_line_break_chars.clone()),
15462 "*" => StrykeValue::integer(if self.multiline_match { 1 } else { 0 }),
15463 "^" => StrykeValue::string(self.format_top_name.clone()),
15464 "INC" => StrykeValue::integer(self.inc_hook_index),
15465 "^A" => StrykeValue::string(self.accumulator_format.clone()),
15466 "^C" => StrykeValue::integer(if self.sigint_pending_caret.replace(false) {
15467 1
15468 } else {
15469 0
15470 }),
15471 "^F" => StrykeValue::integer(self.max_system_fd),
15472 "^L" => StrykeValue::string(self.formfeed_string.clone()),
15473 "^M" => StrykeValue::string(self.emergency_memory.clone()),
15474 "^N" => StrykeValue::string(self.last_subpattern_name.clone()),
15475 "^X" => StrykeValue::string(self.executable_path.clone()),
15476 "^TAINT" | "^TAINTED" => StrykeValue::integer(0),
15478 "^UNICODE" => StrykeValue::integer(if self.utf8_pragma { 1 } else { 0 }),
15479 "^OPEN" => StrykeValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
15480 "^UTF8LOCALE" => StrykeValue::integer(0),
15481 "^UTF8CACHE" => StrykeValue::integer(-1),
15482 _ if name.starts_with('^') && name.len() > 1 => self
15483 .special_caret_scalars
15484 .get(name)
15485 .cloned()
15486 .unwrap_or(StrykeValue::UNDEF),
15487 _ if name.starts_with('#') && name.len() > 1 => {
15488 let arr = &name[1..];
15489 let aname = self.stash_array_name_for_package(arr);
15490 let len = self.scope.array_len(&aname);
15491 StrykeValue::integer(len as i64 - 1)
15492 }
15493 _ => self.scope.get_scalar(name),
15494 }
15495 }
15496
15497 pub(crate) fn set_special_var(
15498 &mut self,
15499 name: &str,
15500 val: &StrykeValue,
15501 ) -> Result<(), StrykeError> {
15502 let name = self.english_scalar_name(name);
15503 match name {
15504 "!" => {
15505 let code = val.to_int() as i32;
15506 self.errno_code = code;
15507 self.errno = if code == 0 {
15508 String::new()
15509 } else {
15510 std::io::Error::from_raw_os_error(code).to_string()
15511 };
15512 }
15513 "@" => {
15514 if let Some((code, msg)) = val.errno_dual_parts() {
15515 self.eval_error_code = code;
15516 self.eval_error = msg;
15517 } else {
15518 self.eval_error = val.to_string();
15519 let mut code = val.to_int() as i32;
15520 if code == 0 && !self.eval_error.is_empty() {
15521 code = 1;
15522 }
15523 self.eval_error_code = code;
15524 }
15525 }
15526 "." => {
15527 let n = val.to_int();
15530 if self.last_readline_handle.is_empty() {
15531 self.line_number = n;
15532 } else {
15533 self.handle_line_numbers
15534 .insert(self.last_readline_handle.clone(), n);
15535 }
15536 }
15537 "0" => self.program_name = val.to_string(),
15538 "/" => {
15539 self.irs = if val.is_undef() {
15540 None
15541 } else {
15542 Some(val.to_string())
15543 }
15544 }
15545 "\\" => self.ors = val.to_string(),
15546 "," => self.ofs = val.to_string(),
15547 ";" => self.subscript_sep = val.to_string(),
15548 "\"" => self.list_separator = val.to_string(),
15549 "%" => self.format_page_number = val.to_int(),
15550 "=" => self.format_lines_per_page = val.to_int(),
15551 "-" => self.format_lines_left = val.to_int(),
15552 ":" => self.format_line_break_chars = val.to_string(),
15553 "*" => self.multiline_match = val.to_int() != 0,
15554 "^" => self.format_top_name = val.to_string(),
15555 "INC" => self.inc_hook_index = val.to_int(),
15556 "^A" => self.accumulator_format = val.to_string(),
15557 "^F" => self.max_system_fd = val.to_int(),
15558 "^L" => self.formfeed_string = val.to_string(),
15559 "^M" => self.emergency_memory = val.to_string(),
15560 "^I" => self.inplace_edit = val.to_string(),
15561 "^D" => self.debug_flags = val.to_int(),
15562 "^P" => self.perl_debug_flags = val.to_int(),
15563 "^W" => self.warnings = val.to_int() != 0,
15564 "^H" => self.compile_hints = val.to_int(),
15565 "^WARNING_BITS" => self.warning_bits = val.to_int(),
15566 "|" => {
15567 self.output_autoflush = val.to_int() != 0;
15568 if self.output_autoflush {
15569 let _ = io::stdout().flush();
15570 }
15571 }
15572 "$$"
15574 | "]"
15575 | "^S"
15576 | "ARGV"
15577 | "?"
15578 | "^O"
15579 | "^T"
15580 | "^V"
15581 | "^E"
15582 | "^GLOBAL_PHASE"
15583 | "^MATCH"
15584 | "^PREMATCH"
15585 | "^POSTMATCH"
15586 | "^LAST_SUBMATCH_RESULT"
15587 | "^C"
15588 | "^N"
15589 | "^X"
15590 | "^TAINT"
15591 | "^TAINTED"
15592 | "^UNICODE"
15593 | "^UTF8LOCALE"
15594 | "^UTF8CACHE"
15595 | "+"
15596 | "<"
15597 | ">"
15598 | "("
15599 | ")" => {}
15600 _ if name.starts_with('^') && name.len() > 1 => {
15601 self.special_caret_scalars
15602 .insert(name.to_string(), val.clone());
15603 }
15604 _ if name.starts_with('#') && name.len() > 1 => {
15605 let arr = &name[1..];
15608 let aname = self.stash_array_name_for_package(arr);
15609 let new_last = val.to_int();
15610 let new_len = if new_last < 0 {
15611 0
15612 } else {
15613 (new_last as usize) + 1
15614 };
15615 let mut current = self.scope.get_array(&aname);
15616 current.resize(new_len, StrykeValue::UNDEF);
15617 self.scope.set_array(&aname, current)?;
15618 }
15619 _ => self.scope.set_scalar(name, val.clone())?,
15620 }
15621 Ok(())
15622 }
15623
15624 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
15625 match &expr.kind {
15626 ExprKind::ArrayVar(name) => Ok(name.clone()),
15627 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(StrykeError::runtime("Expected array", expr.line).into()),
15629 }
15630 }
15631
15632 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
15634 match &expr.kind {
15635 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
15636 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
15637 _ => expr,
15638 }
15639 }
15640
15641 fn try_eval_array_deref_container(
15643 &mut self,
15644 expr: &Expr,
15645 ) -> Result<Option<StrykeValue>, FlowOrError> {
15646 let e = Self::peel_array_builtin_operand(expr);
15647 if let ExprKind::Deref {
15648 expr: inner,
15649 kind: Sigil::Array,
15650 } = &e.kind
15651 {
15652 return Ok(Some(self.eval_or_autoviv_array_ref(inner)?));
15653 }
15654 Ok(None)
15655 }
15656
15657 fn eval_or_autoviv_array_ref(&mut self, inner: &Expr) -> Result<StrykeValue, FlowOrError> {
15661 let line = inner.line;
15662 let val = self.eval_expr(inner)?;
15663 if !val.is_undef() {
15664 return Ok(val);
15665 }
15666 let new_ref = StrykeValue::array_ref(Arc::new(RwLock::new(Vec::new())));
15667 match &inner.kind {
15668 ExprKind::ScalarVar(name) => {
15669 self.scope
15670 .set_scalar(name, new_ref.clone())
15671 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15672 Ok(new_ref)
15673 }
15674 ExprKind::HashElement { hash, key } => {
15675 let k = self.eval_expr(key)?.to_string();
15676 self.scope
15677 .set_hash_element(hash, &k, new_ref.clone())
15678 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15679 Ok(new_ref)
15680 }
15681 ExprKind::ArrayElement { array, index } => {
15682 let i = self.eval_expr(index)?.to_int();
15683 self.scope
15684 .set_array_element(array, i, new_ref.clone())
15685 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15686 Ok(new_ref)
15687 }
15688 _ => Ok(val),
15689 }
15690 }
15691
15692 fn current_package(&self) -> String {
15694 let s = self.scope.get_scalar("__PACKAGE__").to_string();
15695 if s.is_empty() {
15696 "main".to_string()
15697 } else {
15698 s
15699 }
15700 }
15701
15702 pub(crate) fn package_version_scalar(
15705 &mut self,
15706 package: &str,
15707 ) -> StrykeResult<Option<StrykeValue>> {
15708 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
15709 let _ = self
15710 .scope
15711 .set_scalar("__PACKAGE__", StrykeValue::string(package.to_string()));
15712 let ver = self.get_special_var("VERSION");
15713 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
15714 Ok(if ver.is_undef() { None } else { Some(ver) })
15715 }
15716
15717 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<StrykeSub>> {
15719 let root = if start_package.is_empty() {
15720 "main"
15721 } else {
15722 start_package
15723 };
15724 for pkg in self.mro_linearize(root) {
15725 let key = if pkg == "main" {
15726 "AUTOLOAD".to_string()
15727 } else {
15728 format!("{}::AUTOLOAD", pkg)
15729 };
15730 if let Some(s) = self.subs.get(&key) {
15731 return Some(s.clone());
15732 }
15733 }
15734 None
15735 }
15736
15737 pub(crate) fn try_autoload_call(
15742 &mut self,
15743 missing_name: &str,
15744 args: Vec<StrykeValue>,
15745 line: usize,
15746 want: WantarrayCtx,
15747 method_invocant_class: Option<&str>,
15748 ) -> Option<ExecResult> {
15749 let pkg = self.current_package();
15750 let full = if missing_name.contains("::") {
15751 missing_name.to_string()
15752 } else {
15753 format!("{}::{}", pkg, missing_name)
15754 };
15755 let start_pkg = method_invocant_class.unwrap_or_else(|| {
15756 full.rsplit_once("::")
15757 .map(|(p, _)| p)
15758 .filter(|p| !p.is_empty())
15759 .unwrap_or("main")
15760 });
15761 let sub = self.resolve_autoload_sub(start_pkg)?;
15762 if let Err(e) = self
15763 .scope
15764 .set_scalar("AUTOLOAD", StrykeValue::string(full.clone()))
15765 {
15766 return Some(Err(e.into()));
15767 }
15768 Some(self.call_sub(&sub, args, want, line))
15769 }
15770
15771 pub(crate) fn with_topic_default_args(&self, args: Vec<StrykeValue>) -> Vec<StrykeValue> {
15772 if args.is_empty() {
15773 vec![self.scope.get_scalar("_").clone()]
15774 } else {
15775 args
15776 }
15777 }
15778
15779 pub(crate) fn dispatch_indirect_call(
15782 &mut self,
15783 target: StrykeValue,
15784 arg_vals: Vec<StrykeValue>,
15785 want: WantarrayCtx,
15786 line: usize,
15787 ) -> ExecResult {
15788 if let Some(sub) = target.as_code_ref() {
15789 return self.call_sub(&sub, arg_vals, want, line);
15790 }
15791 if let Some(name) = target.as_str() {
15792 return self.call_named_sub(&name, arg_vals, line, want);
15793 }
15794 Err(StrykeError::runtime("Can't use non-code reference as a subroutine", line).into())
15795 }
15796
15797 pub(crate) fn call_bare_list_builtin(
15802 &mut self,
15803 name: &str,
15804 args: Vec<StrykeValue>,
15805 line: usize,
15806 want: WantarrayCtx,
15807 ) -> ExecResult {
15808 let canonical = match name {
15809 "distinct" | "uq" => "uniq",
15810 "shuf" => "shuffle",
15811 "chk" => "chunked",
15812 "win" => "windowed",
15813 "zp" => "zip",
15814 "fst" => "first",
15815 "rd" => "reduce",
15816 "med" => "median",
15817 "std" => "stddev",
15818 "var" => "variance",
15819 other => other,
15820 };
15821 match crate::list_builtins::dispatch_by_name(self, canonical, &args, want) {
15826 Some(r) => r,
15827 None => Err(StrykeError::runtime(
15828 format!("internal: not a stryke list builtin: {name}"),
15829 line,
15830 )
15831 .into()),
15832 }
15833 }
15834
15835 fn call_named_sub(
15836 &mut self,
15837 name: &str,
15838 args: Vec<StrykeValue>,
15839 line: usize,
15840 want: WantarrayCtx,
15841 ) -> ExecResult {
15842 if let Some(sub) = self.resolve_sub_by_name(name) {
15843 let args = self.with_topic_default_args(args);
15844 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
15848 return self.call_sub_with_package(&sub, args, want, line, pkg);
15849 }
15850 match name {
15851 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
15852 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
15853 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
15854 | "none" | "notall" | "first" | "fst" | "reduce" | "rd" | "reductions" | "sum"
15855 | "sum0" | "product" | "min" | "max" | "minstr" | "maxstr" | "mean" | "median"
15856 | "med" | "mode" | "stddev" | "std" | "variance" | "var" | "pairs" | "unpairs"
15857 | "pairkeys" | "pairvalues" | "pairgrep" | "pairmap" | "pairfirst" | "blessed"
15858 | "refaddr" | "reftype" | "weaken" | "unweaken" | "isweak" | "set_subname"
15859 | "subname" | "unicode_to_native" => {
15860 self.call_bare_list_builtin(name, args, line, want)
15861 }
15862 "deque" => {
15863 if !args.is_empty() {
15864 return Err(StrykeError::runtime("deque() takes no arguments", line).into());
15865 }
15866 Ok(StrykeValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
15867 }
15868 "defer__internal" => {
15869 if args.len() != 1 {
15870 return Err(StrykeError::runtime(
15871 "defer__internal expects one coderef argument",
15872 line,
15873 )
15874 .into());
15875 }
15876 self.scope.push_defer(args[0].clone());
15877 Ok(StrykeValue::UNDEF)
15878 }
15879 "heap" => {
15880 if args.len() != 1 {
15881 return Err(
15882 StrykeError::runtime("heap() expects one comparator sub", line).into(),
15883 );
15884 }
15885 if let Some(sub) = args[0].as_code_ref() {
15886 Ok(StrykeValue::heap(Arc::new(Mutex::new(PerlHeap {
15887 items: Vec::new(),
15888 cmp: Arc::clone(&sub),
15889 }))))
15890 } else {
15891 Err(StrykeError::runtime("heap() requires a code reference", line).into())
15892 }
15893 }
15894 "pipeline" => {
15895 let mut items = Vec::new();
15896 for v in args {
15897 if let Some(a) = v.as_array_vec() {
15898 items.extend(a);
15899 } else {
15900 items.push(v);
15901 }
15902 }
15903 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
15904 source: items,
15905 ops: Vec::new(),
15906 has_scalar_terminal: false,
15907 par_stream: false,
15908 streaming: false,
15909 streaming_workers: 0,
15910 streaming_buffer: 256,
15911 }))))
15912 }
15913 "par_pipeline" => {
15914 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
15915 return crate::par_pipeline::run_par_pipeline(self, &args, line)
15916 .map_err(Into::into);
15917 }
15918 Ok(self.builtin_par_pipeline_stream(&args, line)?)
15919 }
15920 "par_pipeline_stream" => {
15921 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
15922 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
15923 .map_err(Into::into);
15924 }
15925 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
15926 }
15927 "ppool" => {
15928 if args.len() != 1 {
15929 return Err(StrykeError::runtime(
15930 "ppool() expects one argument (worker count)",
15931 line,
15932 )
15933 .into());
15934 }
15935 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
15936 }
15937 "barrier" => {
15938 if args.len() != 1 {
15939 return Err(StrykeError::runtime(
15940 "barrier() expects one argument (party count)",
15941 line,
15942 )
15943 .into());
15944 }
15945 let n = args[0].to_int().max(1) as usize;
15946 Ok(StrykeValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
15947 }
15948 "cluster" => {
15949 let items = if args.len() == 1 {
15950 args[0].to_list()
15951 } else {
15952 args.to_vec()
15953 };
15954 let c = RemoteCluster::from_list_args(&items)
15955 .map_err(|msg| StrykeError::runtime(msg, line))?;
15956 Ok(StrykeValue::remote_cluster(Arc::new(c)))
15957 }
15958 _ => {
15959 if let Some(method_name) = name.strip_prefix("static::") {
15961 let self_val = self.scope.get_scalar("self");
15962 if let Some(c) = self_val.as_class_inst() {
15963 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
15964 if let Some(ref body) = m.body {
15965 let params = m.params.clone();
15966 let mut call_args = vec![self_val.clone()];
15967 call_args.extend(args);
15968 return match self.call_class_method(body, ¶ms, call_args, line)
15969 {
15970 Ok(v) => Ok(v),
15971 Err(FlowOrError::Error(e)) => Err(e.into()),
15972 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
15973 Err(e) => Err(e),
15974 };
15975 }
15976 }
15977 return Err(StrykeError::runtime(
15978 format!(
15979 "static::{} — method not found on class {}",
15980 method_name, c.def.name
15981 ),
15982 line,
15983 )
15984 .into());
15985 }
15986 return Err(StrykeError::runtime(
15987 "static:: can only be used inside a class method",
15988 line,
15989 )
15990 .into());
15991 }
15992 if let Some(def) = self.struct_defs.get(name).cloned() {
15994 return self.struct_construct(&def, args, line);
15995 }
15996 if let Some(def) = self.class_defs.get(name).cloned() {
15998 return self.class_construct(&def, args, line);
15999 }
16000 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
16002 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
16003 return self.enum_construct(&def, variant_name, args, line);
16004 }
16005 }
16006 if let Some((class_name, member_name)) = name.rsplit_once("::") {
16008 if let Some(def) = self.class_defs.get(class_name).cloned() {
16009 if let Some(m) = def.method(member_name) {
16011 if m.is_static {
16012 if let Some(ref body) = m.body {
16013 let params = m.params.clone();
16014 return match self.call_static_class_method(
16015 body,
16016 ¶ms,
16017 args.clone(),
16018 line,
16019 ) {
16020 Ok(v) => Ok(v),
16021 Err(FlowOrError::Error(e)) => Err(e.into()),
16022 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16023 Err(e) => Err(e),
16024 };
16025 }
16026 }
16027 }
16028 if def.static_fields.iter().any(|sf| sf.name == member_name) {
16030 let key = format!("{}::{}", class_name, member_name);
16031 match args.len() {
16032 0 => {
16033 let val = self.scope.get_scalar(&key);
16034 return Ok(val);
16035 }
16036 1 => {
16037 let _ = self.scope.set_scalar(&key, args[0].clone());
16038 return Ok(args[0].clone());
16039 }
16040 _ => {
16041 return Err(StrykeError::runtime(
16042 format!(
16043 "static field `{}::{}` takes 0 or 1 arguments",
16044 class_name, member_name
16045 ),
16046 line,
16047 )
16048 .into());
16049 }
16050 }
16051 }
16052 }
16053 }
16054 let args = self.with_topic_default_args(args);
16055 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
16056 return r;
16057 }
16058 Err(StrykeError::runtime(self.undefined_subroutine_call_message(name), line).into())
16059 }
16060 }
16061 }
16062
16063 pub(crate) fn struct_construct(
16065 &mut self,
16066 def: &Arc<StructDef>,
16067 args: Vec<StrykeValue>,
16068 line: usize,
16069 ) -> ExecResult {
16070 let is_named = args.len() >= 2
16073 && args.len().is_multiple_of(2)
16074 && args.iter().step_by(2).all(|v| {
16075 let s = v.to_string();
16076 def.field_index(&s).is_some()
16077 });
16078
16079 let provided = if is_named {
16080 let mut pairs = Vec::new();
16082 let mut i = 0;
16083 while i + 1 < args.len() {
16084 let k = args[i].to_string();
16085 let v = args[i + 1].clone();
16086 pairs.push((k, v));
16087 i += 2;
16088 }
16089 pairs
16090 } else {
16091 def.fields
16093 .iter()
16094 .zip(args.iter())
16095 .map(|(f, v)| (f.name.clone(), v.clone()))
16096 .collect()
16097 };
16098
16099 let mut defaults = Vec::with_capacity(def.fields.len());
16101 for field in &def.fields {
16102 if let Some(ref expr) = field.default {
16103 let val = self.eval_expr(expr)?;
16104 defaults.push(Some(val));
16105 } else {
16106 defaults.push(None);
16107 }
16108 }
16109
16110 Ok(crate::native_data::struct_new_with_defaults(
16111 def, &provided, &defaults, line,
16112 )?)
16113 }
16114
16115 pub(crate) fn class_construct(
16117 &mut self,
16118 def: &Arc<ClassDef>,
16119 args: Vec<StrykeValue>,
16120 _line: usize,
16121 ) -> ExecResult {
16122 use crate::value::ClassInstance;
16123
16124 if def.is_abstract {
16126 return Err(StrykeError::runtime(
16127 format!("cannot instantiate abstract class `{}`", def.name),
16128 _line,
16129 )
16130 .into());
16131 }
16132
16133 let all_fields = self.collect_class_fields(def);
16135
16136 let is_named = args.len() >= 2
16138 && args.len().is_multiple_of(2)
16139 && args.iter().step_by(2).all(|v| {
16140 let s = v.to_string();
16141 all_fields.iter().any(|(name, _, _)| name == &s)
16142 });
16143
16144 let provided: Vec<(String, StrykeValue)> = if is_named {
16145 let mut pairs = Vec::new();
16146 let mut i = 0;
16147 while i + 1 < args.len() {
16148 let k = args[i].to_string();
16149 let v = args[i + 1].clone();
16150 pairs.push((k, v));
16151 i += 2;
16152 }
16153 pairs
16154 } else {
16155 all_fields
16156 .iter()
16157 .zip(args.iter())
16158 .map(|((name, _, _), v)| (name.clone(), v.clone()))
16159 .collect()
16160 };
16161
16162 let mut values = Vec::with_capacity(all_fields.len());
16164 for (name, default, ty) in &all_fields {
16165 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
16166 val.clone()
16167 } else if let Some(ref expr) = default {
16168 self.eval_expr(expr)?
16169 } else {
16170 StrykeValue::UNDEF
16171 };
16172 ty.check_value(&val).map_err(|msg| {
16173 StrykeError::type_error(
16174 format!("class {} field `{}`: {}", def.name, name, msg),
16175 _line,
16176 )
16177 })?;
16178 values.push(val);
16179 }
16180
16181 let isa_chain = self.mro_linearize(&def.name);
16183 let instance = StrykeValue::class_inst(Arc::new(ClassInstance::new_with_isa(
16184 Arc::clone(def),
16185 values,
16186 isa_chain,
16187 )));
16188
16189 let build_chain = self.collect_build_chain(def);
16191 if !build_chain.is_empty() {
16192 for (body, params) in &build_chain {
16193 let call_args = vec![instance.clone()];
16194 match self.call_class_method(body, params, call_args, _line) {
16195 Ok(_) => {}
16196 Err(FlowOrError::Flow(Flow::Return(_))) => {}
16197 Err(e) => return Err(e),
16198 }
16199 }
16200 }
16201
16202 Ok(instance)
16203 }
16204
16205 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
16207 let mut chain = Vec::new();
16208 for parent_name in &def.extends {
16210 if let Some(parent_def) = self.class_defs.get(parent_name) {
16211 chain.extend(self.collect_build_chain(parent_def));
16212 }
16213 }
16214 if let Some(m) = def.method("BUILD") {
16216 if let Some(ref body) = m.body {
16217 chain.push((body.clone(), m.params.clone()));
16218 }
16219 }
16220 chain
16221 }
16222
16223 pub(crate) fn deep_to_hash_value(&self, v: &StrykeValue) -> StrykeValue {
16229 if let Some(c) = v.as_class_inst() {
16231 let all_fields = self.collect_class_fields_full(&c.def);
16232 let values = c.get_values();
16233 let mut map = IndexMap::new();
16234 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
16235 if let Some(elem) = values.get(i) {
16236 map.insert(name.clone(), self.deep_to_hash_value(elem));
16237 }
16238 }
16239 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16240 }
16241 if let Some(s) = v.as_struct_inst() {
16243 let values = s.get_values();
16244 let mut map = IndexMap::new();
16245 for (i, field) in s.def.fields.iter().enumerate() {
16246 if let Some(elem) = values.get(i) {
16247 map.insert(field.name.clone(), self.deep_to_hash_value(elem));
16248 }
16249 }
16250 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16251 }
16252 if let Some(r) = v.as_hash_ref() {
16254 let inner = r.read().clone();
16255 let mut map = IndexMap::new();
16256 for (k, val) in inner.into_iter() {
16257 map.insert(k, self.deep_to_hash_value(&val));
16258 }
16259 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16260 }
16261 if let Some(r) = v.as_array_ref() {
16263 let inner = r.read().clone();
16264 let out: Vec<StrykeValue> = inner.iter().map(|e| self.deep_to_hash_value(e)).collect();
16265 return StrykeValue::array_ref(Arc::new(RwLock::new(out)));
16266 }
16267 v.clone()
16271 }
16272
16273 fn collect_class_fields(
16276 &self,
16277 def: &ClassDef,
16278 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
16279 self.collect_class_fields_full(def)
16280 .into_iter()
16281 .map(|(name, default, ty, _, _)| (name, default, ty))
16282 .collect()
16283 }
16284
16285 fn collect_class_fields_full(
16287 &self,
16288 def: &ClassDef,
16289 ) -> Vec<(
16290 String,
16291 Option<Expr>,
16292 crate::ast::PerlTypeName,
16293 crate::ast::Visibility,
16294 String,
16295 )> {
16296 let mut all_fields = Vec::new();
16297
16298 for parent_name in &def.extends {
16299 if let Some(parent_def) = self.class_defs.get(parent_name) {
16300 let parent_fields = self.collect_class_fields_full(parent_def);
16301 all_fields.extend(parent_fields);
16302 }
16303 }
16304
16305 for field in &def.fields {
16306 all_fields.push((
16307 field.name.clone(),
16308 field.default.clone(),
16309 field.ty.clone(),
16310 field.visibility,
16311 def.name.clone(),
16312 ));
16313 }
16314
16315 all_fields
16316 }
16317
16318 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
16320 for parent_name in &def.extends {
16322 if let Some(parent_def) = self.class_defs.get(parent_name) {
16323 self.collect_class_method_names(parent_def, names);
16324 }
16325 }
16326 for m in &def.methods {
16328 if !m.is_static && !names.contains(&m.name) {
16329 names.push(m.name.clone());
16330 }
16331 }
16332 }
16333
16334 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
16336 let mut chain = Vec::new();
16337 if let Some(m) = def.method("DESTROY") {
16339 if let Some(ref body) = m.body {
16340 chain.push((body.clone(), m.params.clone()));
16341 }
16342 }
16343 for parent_name in &def.extends {
16345 if let Some(parent_def) = self.class_defs.get(parent_name) {
16346 chain.extend(self.collect_destroy_chain(parent_def));
16347 }
16348 }
16349 chain
16350 }
16351
16352 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
16354 if let Some(def) = self.class_defs.get(child) {
16355 for parent in &def.extends {
16356 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
16357 return true;
16358 }
16359 }
16360 }
16361 false
16362 }
16363
16364 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
16366 if let Some(m) = def.method(method) {
16368 return Some((m.clone(), def.name.clone()));
16369 }
16370 for parent_name in &def.extends {
16372 if let Some(parent_def) = self.class_defs.get(parent_name) {
16373 if let Some(result) = self.find_class_method(parent_def, method) {
16374 return Some(result);
16375 }
16376 }
16377 }
16378 None
16379 }
16380
16381 pub(crate) fn enum_construct(
16383 &mut self,
16384 def: &Arc<EnumDef>,
16385 variant_name: &str,
16386 args: Vec<StrykeValue>,
16387 line: usize,
16388 ) -> ExecResult {
16389 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
16390 FlowOrError::Error(StrykeError::runtime(
16391 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
16392 line,
16393 ))
16394 })?;
16395 let variant = &def.variants[variant_idx];
16396 let data = if variant.ty.is_some() {
16397 if args.is_empty() {
16398 return Err(StrykeError::runtime(
16399 format!(
16400 "enum variant `{}::{}` requires data",
16401 def.name, variant_name
16402 ),
16403 line,
16404 )
16405 .into());
16406 }
16407 if args.len() == 1 {
16408 args.into_iter().next().unwrap()
16409 } else {
16410 StrykeValue::array(args)
16411 }
16412 } else {
16413 if !args.is_empty() {
16414 return Err(StrykeError::runtime(
16415 format!(
16416 "enum variant `{}::{}` does not take data",
16417 def.name, variant_name
16418 ),
16419 line,
16420 )
16421 .into());
16422 }
16423 StrykeValue::UNDEF
16424 };
16425 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
16426 Ok(StrykeValue::enum_inst(Arc::new(inst)))
16427 }
16428
16429 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
16431 matches!(name, "STDIN" | "STDOUT" | "STDERR")
16432 || self.input_handles.contains_key(name)
16433 || self.output_handles.contains_key(name)
16434 || self.io_file_slots.contains_key(name)
16435 || self.pipe_children.contains_key(name)
16436 }
16437
16438 pub(crate) fn io_handle_method(
16440 &mut self,
16441 name: &str,
16442 method: &str,
16443 args: &[StrykeValue],
16444 line: usize,
16445 ) -> StrykeResult<StrykeValue> {
16446 match method {
16447 "print" => self.io_handle_print(name, args, false, line),
16448 "say" => self.io_handle_print(name, args, true, line),
16449 "printf" => self.io_handle_printf(name, args, line),
16450 "getline" | "readline" => {
16451 if !args.is_empty() {
16452 return Err(StrykeError::runtime(
16453 format!("{}: too many arguments", method),
16454 line,
16455 ));
16456 }
16457 self.readline_builtin_execute(Some(name))
16458 }
16459 "close" => {
16460 if !args.is_empty() {
16461 return Err(StrykeError::runtime("close: too many arguments", line));
16462 }
16463 self.close_builtin_execute(name.to_string())
16464 }
16465 "eof" => {
16466 if !args.is_empty() {
16467 return Err(StrykeError::runtime("eof: too many arguments", line));
16468 }
16469 let at_eof = !self.has_input_handle(name);
16470 Ok(StrykeValue::integer(if at_eof { 1 } else { 0 }))
16471 }
16472 "getc" => {
16473 if !args.is_empty() {
16474 return Err(StrykeError::runtime("getc: too many arguments", line));
16475 }
16476 match crate::builtins::try_builtin(
16477 self,
16478 "getc",
16479 &[StrykeValue::string(name.to_string())],
16480 line,
16481 ) {
16482 Some(r) => r,
16483 None => Err(StrykeError::runtime("getc: not available", line)),
16484 }
16485 }
16486 "binmode" => match crate::builtins::try_builtin(
16487 self,
16488 "binmode",
16489 &[StrykeValue::string(name.to_string())],
16490 line,
16491 ) {
16492 Some(r) => r,
16493 None => Err(StrykeError::runtime("binmode: not available", line)),
16494 },
16495 "fileno" => match crate::builtins::try_builtin(
16496 self,
16497 "fileno",
16498 &[StrykeValue::string(name.to_string())],
16499 line,
16500 ) {
16501 Some(r) => r,
16502 None => Err(StrykeError::runtime("fileno: not available", line)),
16503 },
16504 "flush" => {
16505 if !args.is_empty() {
16506 return Err(StrykeError::runtime("flush: too many arguments", line));
16507 }
16508 self.io_handle_flush(name, line)
16509 }
16510 _ => Err(StrykeError::runtime(
16511 format!("Unknown method for filehandle: {}", method),
16512 line,
16513 )),
16514 }
16515 }
16516
16517 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> StrykeResult<StrykeValue> {
16518 match handle_name {
16519 "STDOUT" => {
16520 let _ = IoWrite::flush(&mut io::stdout());
16521 }
16522 "STDERR" => {
16523 let _ = IoWrite::flush(&mut io::stderr());
16524 }
16525 name => {
16526 if let Some(writer) = self.output_handles.get_mut(name) {
16527 let _ = IoWrite::flush(&mut *writer);
16528 } else {
16529 return Err(StrykeError::runtime(
16530 format!("flush on unopened filehandle {}", name),
16531 line,
16532 ));
16533 }
16534 }
16535 }
16536 Ok(StrykeValue::integer(1))
16537 }
16538
16539 fn io_handle_print(
16540 &mut self,
16541 handle_name: &str,
16542 args: &[StrykeValue],
16543 newline: bool,
16544 line: usize,
16545 ) -> StrykeResult<StrykeValue> {
16546 if newline && (self.feature_bits & FEAT_SAY) == 0 {
16547 return Err(StrykeError::runtime(
16548 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
16549 line,
16550 ));
16551 }
16552 let mut output = String::new();
16553 if args.is_empty() {
16554 output.push_str(&self.scope.get_scalar("_").to_string());
16556 } else {
16557 for (i, val) in args.iter().enumerate() {
16558 if i > 0 && !self.ofs.is_empty() {
16559 output.push_str(&self.ofs);
16560 }
16561 output.push_str(&val.to_string());
16562 }
16563 }
16564 if newline {
16565 output.push('\n');
16566 }
16567 output.push_str(&self.ors);
16568
16569 self.write_formatted_print(handle_name, &output, line)?;
16570 Ok(StrykeValue::integer(1))
16571 }
16572
16573 pub(crate) fn write_formatted_print(
16576 &mut self,
16577 handle_name: &str,
16578 output: &str,
16579 line: usize,
16580 ) -> StrykeResult<()> {
16581 match handle_name {
16582 "STDOUT" => {
16583 if !self.suppress_stdout {
16584 print!("{}", output);
16585 if self.output_autoflush {
16586 let _ = io::stdout().flush();
16587 }
16588 }
16589 }
16590 "STDERR" => {
16591 eprint!("{}", output);
16592 let _ = io::stderr().flush();
16593 }
16594 name => {
16595 if let Some(writer) = self.output_handles.get_mut(name) {
16596 let _ = writer.write_all(output.as_bytes());
16597 if self.output_autoflush {
16598 let _ = writer.flush();
16599 }
16600 } else {
16601 return Err(StrykeError::runtime(
16602 format!("print on unopened filehandle {}", name),
16603 line,
16604 ));
16605 }
16606 }
16607 }
16608 Ok(())
16609 }
16610
16611 fn io_handle_printf(
16612 &mut self,
16613 handle_name: &str,
16614 args: &[StrykeValue],
16615 line: usize,
16616 ) -> StrykeResult<StrykeValue> {
16617 let (fmt, rest): (String, &[StrykeValue]) = if args.is_empty() {
16618 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
16619 Ok(s) => s,
16620 Err(FlowOrError::Error(e)) => return Err(e),
16621 Err(FlowOrError::Flow(_)) => {
16622 return Err(StrykeError::runtime(
16623 "printf: unexpected control flow in sprintf",
16624 line,
16625 ));
16626 }
16627 };
16628 (s, &[])
16629 } else {
16630 (args[0].to_string(), &args[1..])
16631 };
16632 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
16633 Ok(s) => s,
16634 Err(FlowOrError::Error(e)) => return Err(e),
16635 Err(FlowOrError::Flow(_)) => {
16636 return Err(StrykeError::runtime(
16637 "printf: unexpected control flow in sprintf",
16638 line,
16639 ));
16640 }
16641 };
16642 match handle_name {
16643 "STDOUT" => {
16644 if !self.suppress_stdout {
16645 print!("{}", output);
16646 if self.output_autoflush {
16647 let _ = IoWrite::flush(&mut io::stdout());
16648 }
16649 }
16650 }
16651 "STDERR" => {
16652 eprint!("{}", output);
16653 let _ = IoWrite::flush(&mut io::stderr());
16654 }
16655 name => {
16656 if let Some(writer) = self.output_handles.get_mut(name) {
16657 let _ = writer.write_all(output.as_bytes());
16658 if self.output_autoflush {
16659 let _ = writer.flush();
16660 }
16661 } else {
16662 return Err(StrykeError::runtime(
16663 format!("printf on unopened filehandle {}", name),
16664 line,
16665 ));
16666 }
16667 }
16668 }
16669 Ok(StrykeValue::integer(1))
16670 }
16671
16672 pub(crate) fn try_native_method(
16674 &mut self,
16675 receiver: &StrykeValue,
16676 method: &str,
16677 args: &[StrykeValue],
16678 line: usize,
16679 ) -> Option<StrykeResult<StrykeValue>> {
16680 if let Some(name) = receiver.as_io_handle_name() {
16681 return Some(self.io_handle_method(&name, method, args, line));
16682 }
16683 if let Some(ref s) = receiver.as_str() {
16684 if self.is_bound_handle(s) {
16685 return Some(self.io_handle_method(s, method, args, line));
16686 }
16687 }
16688 if let Some(c) = receiver.as_sqlite_conn() {
16689 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
16690 }
16691 if let Some(s) = receiver.as_struct_inst() {
16692 if let Some(idx) = s.def.field_index(method) {
16694 match args.len() {
16695 0 => {
16696 return Some(Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF)));
16697 }
16698 1 => {
16699 let field = &s.def.fields[idx];
16700 let new_val = args[0].clone();
16701 if let Err(msg) = field.ty.check_value(&new_val) {
16702 return Some(Err(StrykeError::type_error(
16703 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
16704 line,
16705 )));
16706 }
16707 s.set_field(idx, new_val.clone());
16708 return Some(Ok(new_val));
16709 }
16710 _ => {
16711 return Some(Err(StrykeError::runtime(
16712 format!(
16713 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
16714 method,
16715 args.len()
16716 ),
16717 line,
16718 )));
16719 }
16720 }
16721 }
16722 match method {
16724 "with" => {
16725 let mut new_values = s.get_values();
16727 let mut i = 0;
16728 while i + 1 < args.len() {
16729 let k = args[i].to_string();
16730 let v = args[i + 1].clone();
16731 if let Some(idx) = s.def.field_index(&k) {
16732 let field = &s.def.fields[idx];
16733 if let Err(msg) = field.ty.check_value(&v) {
16734 return Some(Err(StrykeError::type_error(
16735 format!(
16736 "struct {} field `{}`: {}",
16737 s.def.name, field.name, msg
16738 ),
16739 line,
16740 )));
16741 }
16742 new_values[idx] = v;
16743 } else {
16744 return Some(Err(StrykeError::runtime(
16745 format!("struct {}: unknown field `{}`", s.def.name, k),
16746 line,
16747 )));
16748 }
16749 i += 2;
16750 }
16751 return Some(Ok(StrykeValue::struct_inst(Arc::new(
16752 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
16753 ))));
16754 }
16755 "to_hash" => {
16756 if !args.is_empty() {
16758 return Some(Err(StrykeError::runtime(
16759 "struct to_hash takes no arguments",
16760 line,
16761 )));
16762 }
16763 let mut map = IndexMap::new();
16764 let values = s.get_values();
16765 for (i, field) in s.def.fields.iter().enumerate() {
16766 map.insert(field.name.clone(), values[i].clone());
16767 }
16768 return Some(Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map)))));
16769 }
16770 "to_hash_rec" | "to_hash_deep" => {
16771 if !args.is_empty() {
16774 return Some(Err(StrykeError::runtime(
16775 "struct to_hash_rec takes no arguments",
16776 line,
16777 )));
16778 }
16779 return Some(Ok(self.deep_to_hash_value(receiver)));
16780 }
16781 "fields" => {
16782 if !args.is_empty() {
16784 return Some(Err(StrykeError::runtime(
16785 "struct fields takes no arguments",
16786 line,
16787 )));
16788 }
16789 let names: Vec<StrykeValue> = s
16790 .def
16791 .fields
16792 .iter()
16793 .map(|f| StrykeValue::string(f.name.clone()))
16794 .collect();
16795 return Some(Ok(StrykeValue::array(names)));
16796 }
16797 "clone" => {
16798 if !args.is_empty() {
16800 return Some(Err(StrykeError::runtime(
16801 "struct clone takes no arguments",
16802 line,
16803 )));
16804 }
16805 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
16806 return Some(Ok(StrykeValue::struct_inst(Arc::new(
16807 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
16808 ))));
16809 }
16810 _ => {}
16811 }
16812 if let Some(m) = s.def.method(method) {
16814 let body = m.body.clone();
16815 let params = m.params.clone();
16816 let mut call_args = vec![receiver.clone()];
16818 call_args.extend(args.iter().cloned());
16819 return Some(
16820 match self.call_struct_method(&body, ¶ms, call_args, line) {
16821 Ok(v) => Ok(v),
16822 Err(FlowOrError::Error(e)) => Err(e),
16823 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16824 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
16825 "unexpected control flow in struct method",
16826 line,
16827 )),
16828 },
16829 );
16830 }
16831 return None;
16832 }
16833 if let Some(c) = receiver.as_class_inst() {
16835 let all_fields_full = self.collect_class_fields_full(&c.def);
16837 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
16838 .iter()
16839 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
16840 .collect();
16841
16842 if let Some(idx) = all_fields_full
16844 .iter()
16845 .position(|(name, _, _, _, _)| name == method)
16846 {
16847 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
16848
16849 match vis {
16851 crate::ast::Visibility::Private => {
16852 let caller_class = self
16854 .scope
16855 .get_scalar("self")
16856 .as_class_inst()
16857 .map(|ci| ci.def.name.clone());
16858 if caller_class.as_deref() != Some(owner_class.as_str()) {
16859 return Some(Err(StrykeError::runtime(
16860 format!("field `{}` of class {} is private", method, owner_class),
16861 line,
16862 )));
16863 }
16864 }
16865 crate::ast::Visibility::Protected => {
16866 let caller_class = self
16868 .scope
16869 .get_scalar("self")
16870 .as_class_inst()
16871 .map(|ci| ci.def.name.clone());
16872 let allowed = caller_class.as_deref().is_some_and(|caller| {
16873 caller == owner_class || self.class_inherits_from(caller, owner_class)
16874 });
16875 if !allowed {
16876 return Some(Err(StrykeError::runtime(
16877 format!("field `{}` of class {} is protected", method, owner_class),
16878 line,
16879 )));
16880 }
16881 }
16882 crate::ast::Visibility::Public => {}
16883 }
16884
16885 match args.len() {
16886 0 => {
16887 return Some(Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF)));
16888 }
16889 1 => {
16890 let new_val = args[0].clone();
16891 if let Err(msg) = ty.check_value(&new_val) {
16892 return Some(Err(StrykeError::type_error(
16893 format!("class {} field `{}`: {}", c.def.name, method, msg),
16894 line,
16895 )));
16896 }
16897 c.set_field(idx, new_val.clone());
16898 return Some(Ok(new_val));
16899 }
16900 _ => {
16901 return Some(Err(StrykeError::runtime(
16902 format!(
16903 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
16904 method,
16905 args.len()
16906 ),
16907 line,
16908 )));
16909 }
16910 }
16911 }
16912 match method {
16914 "with" => {
16915 let mut new_values = c.get_values();
16916 let mut i = 0;
16917 while i + 1 < args.len() {
16918 let k = args[i].to_string();
16919 let v = args[i + 1].clone();
16920 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
16921 let (_, _, ref ty) = all_fields[idx];
16922 if let Err(msg) = ty.check_value(&v) {
16923 return Some(Err(StrykeError::type_error(
16924 format!("class {} field `{}`: {}", c.def.name, k, msg),
16925 line,
16926 )));
16927 }
16928 new_values[idx] = v;
16929 } else {
16930 return Some(Err(StrykeError::runtime(
16931 format!("class {}: unknown field `{}`", c.def.name, k),
16932 line,
16933 )));
16934 }
16935 i += 2;
16936 }
16937 return Some(Ok(StrykeValue::class_inst(Arc::new(
16938 crate::value::ClassInstance::new_with_isa(
16939 Arc::clone(&c.def),
16940 new_values,
16941 c.isa_chain.clone(),
16942 ),
16943 ))));
16944 }
16945 "to_hash" => {
16946 if !args.is_empty() {
16947 return Some(Err(StrykeError::runtime(
16948 "class to_hash takes no arguments",
16949 line,
16950 )));
16951 }
16952 let mut map = IndexMap::new();
16953 let values = c.get_values();
16954 for (i, (name, _, _)) in all_fields.iter().enumerate() {
16955 if let Some(v) = values.get(i) {
16956 map.insert(name.clone(), v.clone());
16957 }
16958 }
16959 return Some(Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map)))));
16960 }
16961 "to_hash_rec" | "to_hash_deep" => {
16962 if !args.is_empty() {
16967 return Some(Err(StrykeError::runtime(
16968 "class to_hash_rec takes no arguments",
16969 line,
16970 )));
16971 }
16972 return Some(Ok(self.deep_to_hash_value(receiver)));
16973 }
16974 "fields" => {
16975 if !args.is_empty() {
16976 return Some(Err(StrykeError::runtime(
16977 "class fields takes no arguments",
16978 line,
16979 )));
16980 }
16981 let names: Vec<StrykeValue> = all_fields
16982 .iter()
16983 .map(|(name, _, _)| StrykeValue::string(name.clone()))
16984 .collect();
16985 return Some(Ok(StrykeValue::array(names)));
16986 }
16987 "clone" => {
16988 if !args.is_empty() {
16989 return Some(Err(StrykeError::runtime(
16990 "class clone takes no arguments",
16991 line,
16992 )));
16993 }
16994 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
16995 return Some(Ok(StrykeValue::class_inst(Arc::new(
16996 crate::value::ClassInstance::new_with_isa(
16997 Arc::clone(&c.def),
16998 new_values,
16999 c.isa_chain.clone(),
17000 ),
17001 ))));
17002 }
17003 "isa" => {
17004 if args.len() != 1 {
17005 return Some(Err(StrykeError::runtime("isa requires one argument", line)));
17006 }
17007 let class_name = args[0].to_string();
17008 let is_a = c.isa(&class_name);
17009 return Some(Ok(if is_a {
17010 StrykeValue::integer(1)
17011 } else {
17012 StrykeValue::string(String::new())
17013 }));
17014 }
17015 "does" => {
17016 if args.len() != 1 {
17017 return Some(Err(StrykeError::runtime(
17018 "does requires one argument",
17019 line,
17020 )));
17021 }
17022 let trait_name = args[0].to_string();
17023 let implements = c.def.implements.contains(&trait_name);
17024 return Some(Ok(if implements {
17025 StrykeValue::integer(1)
17026 } else {
17027 StrykeValue::string(String::new())
17028 }));
17029 }
17030 "methods" => {
17031 if !args.is_empty() {
17032 return Some(Err(StrykeError::runtime(
17033 "methods takes no arguments",
17034 line,
17035 )));
17036 }
17037 let mut names = Vec::new();
17038 self.collect_class_method_names(&c.def, &mut names);
17039 let values: Vec<StrykeValue> =
17040 names.into_iter().map(StrykeValue::string).collect();
17041 return Some(Ok(StrykeValue::array(values)));
17042 }
17043 "superclass" => {
17044 if !args.is_empty() {
17045 return Some(Err(StrykeError::runtime(
17046 "superclass takes no arguments",
17047 line,
17048 )));
17049 }
17050 let parents: Vec<StrykeValue> = c
17051 .def
17052 .extends
17053 .iter()
17054 .map(|s| StrykeValue::string(s.clone()))
17055 .collect();
17056 return Some(Ok(StrykeValue::array(parents)));
17057 }
17058 "destroy" => {
17059 let destroy_chain = self.collect_destroy_chain(&c.def);
17061 for (body, params) in &destroy_chain {
17062 let call_args = vec![receiver.clone()];
17063 match self.call_class_method(body, params, call_args, line) {
17064 Ok(_) => {}
17065 Err(FlowOrError::Flow(Flow::Return(_))) => {}
17066 Err(FlowOrError::Error(e)) => return Some(Err(e)),
17067 Err(_) => {}
17068 }
17069 }
17070 return Some(Ok(StrykeValue::UNDEF));
17071 }
17072 _ => {}
17073 }
17074 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
17076 match m.visibility {
17078 crate::ast::Visibility::Private => {
17079 let caller_class = self
17080 .scope
17081 .get_scalar("self")
17082 .as_class_inst()
17083 .map(|ci| ci.def.name.clone());
17084 if caller_class.as_deref() != Some(owner_class.as_str()) {
17085 return Some(Err(StrykeError::runtime(
17086 format!("method `{}` of class {} is private", method, owner_class),
17087 line,
17088 )));
17089 }
17090 }
17091 crate::ast::Visibility::Protected => {
17092 let caller_class = self
17093 .scope
17094 .get_scalar("self")
17095 .as_class_inst()
17096 .map(|ci| ci.def.name.clone());
17097 let allowed = caller_class.as_deref().is_some_and(|caller| {
17098 caller == owner_class.as_str()
17099 || self.class_inherits_from(caller, owner_class)
17100 });
17101 if !allowed {
17102 return Some(Err(StrykeError::runtime(
17103 format!(
17104 "method `{}` of class {} is protected",
17105 method, owner_class
17106 ),
17107 line,
17108 )));
17109 }
17110 }
17111 crate::ast::Visibility::Public => {}
17112 }
17113 if let Some(ref body) = m.body {
17114 let params = m.params.clone();
17115 let mut call_args = vec![receiver.clone()];
17116 call_args.extend(args.iter().cloned());
17117 return Some(
17118 match self.call_class_method(body, ¶ms, call_args, line) {
17119 Ok(v) => Ok(v),
17120 Err(FlowOrError::Error(e)) => Err(e),
17121 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17122 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
17123 "unexpected control flow in class method",
17124 line,
17125 )),
17126 },
17127 );
17128 }
17129 }
17130 return None;
17131 }
17132 if let Some(d) = receiver.as_dataframe() {
17133 return Some(self.dataframe_method(d, method, args, line));
17134 }
17135 if let Some(s) = crate::value::set_payload(receiver) {
17136 return Some(self.set_method(s, method, args, line));
17137 }
17138 if let Some(d) = receiver.as_deque() {
17139 return Some(self.deque_method(d, method, args, line));
17140 }
17141 if let Some(h) = receiver.as_heap_pq() {
17142 return Some(self.heap_method(h, method, args, line));
17143 }
17144 if let Some(p) = receiver.as_pipeline() {
17145 return Some(self.pipeline_method(p, method, args, line));
17146 }
17147 if let Some(c) = receiver.as_capture() {
17148 return Some(self.capture_method(c, method, args, line));
17149 }
17150 if let Some(p) = receiver.as_ppool() {
17151 return Some(self.ppool_method(p, method, args, line));
17152 }
17153 if let Some(b) = receiver.as_barrier() {
17154 return Some(self.barrier_method(b, method, args, line));
17155 }
17156 if let Some(g) = receiver.as_generator() {
17157 if method == "next" {
17158 if !args.is_empty() {
17159 return Some(Err(StrykeError::runtime(
17160 "generator->next takes no arguments",
17161 line,
17162 )));
17163 }
17164 return Some(self.generator_next(&g));
17165 }
17166 return None;
17167 }
17168 if let Some(arc) = receiver.as_atomic_arc() {
17169 let inner = arc.lock().clone();
17170 if let Some(d) = inner.as_deque() {
17171 return Some(self.deque_method(d, method, args, line));
17172 }
17173 if let Some(h) = inner.as_heap_pq() {
17174 return Some(self.heap_method(h, method, args, line));
17175 }
17176 }
17177 None
17178 }
17179
17180 fn dataframe_method(
17182 &mut self,
17183 d: Arc<Mutex<PerlDataFrame>>,
17184 method: &str,
17185 args: &[StrykeValue],
17186 line: usize,
17187 ) -> StrykeResult<StrykeValue> {
17188 match method {
17189 "nrow" | "nrows" => {
17190 if !args.is_empty() {
17191 return Err(StrykeError::runtime(
17192 format!("dataframe {} takes no arguments", method),
17193 line,
17194 ));
17195 }
17196 Ok(StrykeValue::integer(d.lock().nrows() as i64))
17197 }
17198 "ncol" | "ncols" => {
17199 if !args.is_empty() {
17200 return Err(StrykeError::runtime(
17201 format!("dataframe {} takes no arguments", method),
17202 line,
17203 ));
17204 }
17205 Ok(StrykeValue::integer(d.lock().ncols() as i64))
17206 }
17207 "filter" => {
17208 if args.len() != 1 {
17209 return Err(StrykeError::runtime(
17210 "dataframe filter expects 1 argument (sub)",
17211 line,
17212 ));
17213 }
17214 let Some(sub) = args[0].as_code_ref() else {
17215 return Err(StrykeError::runtime(
17216 "dataframe filter expects a code reference",
17217 line,
17218 ));
17219 };
17220 let df_guard = d.lock();
17221 let n = df_guard.nrows();
17222 let mut keep = vec![false; n];
17223 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
17224 let row = df_guard.row_hashref(r);
17225 self.scope_push_hook();
17226 self.scope.set_topic(row);
17227 if let Some(ref env) = sub.closure_env {
17228 self.scope.restore_capture(env);
17229 }
17230 let pass = match self.exec_block_no_scope(&sub.body) {
17231 Ok(v) => v.is_true(),
17232 Err(_) => false,
17233 };
17234 self.scope_pop_hook();
17235 *row_keep = pass;
17236 }
17237 let columns = df_guard.columns.clone();
17238 let cols: Vec<Vec<StrykeValue>> = (0..df_guard.ncols())
17239 .map(|i| {
17240 let mut out = Vec::new();
17241 for (r, pass_row) in keep.iter().enumerate().take(n) {
17242 if *pass_row {
17243 out.push(df_guard.cols[i][r].clone());
17244 }
17245 }
17246 out
17247 })
17248 .collect();
17249 let group_by = df_guard.group_by.clone();
17250 drop(df_guard);
17251 let new_df = PerlDataFrame {
17252 columns,
17253 cols,
17254 group_by,
17255 };
17256 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(new_df))))
17257 }
17258 "group_by" => {
17259 if args.len() != 1 {
17260 return Err(StrykeError::runtime(
17261 "dataframe group_by expects 1 column name",
17262 line,
17263 ));
17264 }
17265 let key = args[0].to_string();
17266 let inner = d.lock();
17267 if inner.col_index(&key).is_none() {
17268 return Err(StrykeError::runtime(
17269 format!("dataframe group_by: unknown column \"{}\"", key),
17270 line,
17271 ));
17272 }
17273 let new_df = PerlDataFrame {
17274 columns: inner.columns.clone(),
17275 cols: inner.cols.clone(),
17276 group_by: Some(key),
17277 };
17278 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(new_df))))
17279 }
17280 "sum" => {
17281 if args.len() != 1 {
17282 return Err(StrykeError::runtime(
17283 "dataframe sum expects 1 column name",
17284 line,
17285 ));
17286 }
17287 let col_name = args[0].to_string();
17288 let inner = d.lock();
17289 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
17290 StrykeError::runtime(
17291 format!("dataframe sum: unknown column \"{}\"", col_name),
17292 line,
17293 )
17294 })?;
17295 match &inner.group_by {
17296 Some(gcol) => {
17297 let gi = inner.col_index(gcol).ok_or_else(|| {
17298 StrykeError::runtime(
17299 format!("dataframe sum: unknown group column \"{}\"", gcol),
17300 line,
17301 )
17302 })?;
17303 let mut acc: IndexMap<String, f64> = IndexMap::new();
17304 for r in 0..inner.nrows() {
17305 let k = inner.cols[gi][r].to_string();
17306 let v = inner.cols[val_idx][r].to_number();
17307 *acc.entry(k).or_insert(0.0) += v;
17308 }
17309 let keys: Vec<String> = acc.keys().cloned().collect();
17310 let sums: Vec<f64> = acc.values().copied().collect();
17311 let cols = vec![
17312 keys.into_iter().map(StrykeValue::string).collect(),
17313 sums.into_iter().map(StrykeValue::float).collect(),
17314 ];
17315 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
17316 let out = PerlDataFrame {
17317 columns,
17318 cols,
17319 group_by: None,
17320 };
17321 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(out))))
17322 }
17323 None => {
17324 let total: f64 = (0..inner.nrows())
17325 .map(|r| inner.cols[val_idx][r].to_number())
17326 .sum();
17327 Ok(StrykeValue::float(total))
17328 }
17329 }
17330 }
17331 _ => Err(StrykeError::runtime(
17332 format!("Unknown method for dataframe: {}", method),
17333 line,
17334 )),
17335 }
17336 }
17337
17338 fn set_method(
17340 &self,
17341 s: Arc<crate::value::PerlSet>,
17342 method: &str,
17343 args: &[StrykeValue],
17344 line: usize,
17345 ) -> StrykeResult<StrykeValue> {
17346 match method {
17347 "has" | "contains" | "member" => {
17348 if args.len() != 1 {
17349 return Err(StrykeError::runtime(
17350 "set->has expects one argument (element)",
17351 line,
17352 ));
17353 }
17354 let k = crate::value::set_member_key(&args[0]);
17355 Ok(StrykeValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
17356 }
17357 "size" | "len" | "count" => {
17358 if !args.is_empty() {
17359 return Err(StrykeError::runtime("set->size takes no arguments", line));
17360 }
17361 Ok(StrykeValue::integer(s.len() as i64))
17362 }
17363 "values" | "list" | "elements" => {
17364 if !args.is_empty() {
17365 return Err(StrykeError::runtime("set->values takes no arguments", line));
17366 }
17367 Ok(StrykeValue::array(s.values().cloned().collect()))
17368 }
17369 _ => Err(StrykeError::runtime(
17370 format!("Unknown method for set: {}", method),
17371 line,
17372 )),
17373 }
17374 }
17375
17376 fn deque_method(
17377 &mut self,
17378 d: Arc<Mutex<VecDeque<StrykeValue>>>,
17379 method: &str,
17380 args: &[StrykeValue],
17381 line: usize,
17382 ) -> StrykeResult<StrykeValue> {
17383 match method {
17384 "push_back" => {
17385 if args.len() != 1 {
17386 return Err(StrykeError::runtime("push_back expects 1 argument", line));
17387 }
17388 d.lock().push_back(args[0].clone());
17389 Ok(StrykeValue::integer(d.lock().len() as i64))
17390 }
17391 "push_front" => {
17392 if args.len() != 1 {
17393 return Err(StrykeError::runtime("push_front expects 1 argument", line));
17394 }
17395 d.lock().push_front(args[0].clone());
17396 Ok(StrykeValue::integer(d.lock().len() as i64))
17397 }
17398 "pop_back" => Ok(d.lock().pop_back().unwrap_or(StrykeValue::UNDEF)),
17399 "pop_front" => Ok(d.lock().pop_front().unwrap_or(StrykeValue::UNDEF)),
17400 "size" | "len" => Ok(StrykeValue::integer(d.lock().len() as i64)),
17401 _ => Err(StrykeError::runtime(
17402 format!("Unknown method for deque: {}", method),
17403 line,
17404 )),
17405 }
17406 }
17407
17408 fn heap_method(
17409 &mut self,
17410 h: Arc<Mutex<PerlHeap>>,
17411 method: &str,
17412 args: &[StrykeValue],
17413 line: usize,
17414 ) -> StrykeResult<StrykeValue> {
17415 match method {
17416 "push" => {
17417 if args.len() != 1 {
17418 return Err(StrykeError::runtime("heap push expects 1 argument", line));
17419 }
17420 let mut g = h.lock();
17421 let n = g.items.len();
17422 g.items.push(args[0].clone());
17423 let cmp = g.cmp.clone();
17424 drop(g);
17425 let mut g = h.lock();
17426 self.heap_sift_up(&mut g.items, &cmp, n);
17427 Ok(StrykeValue::integer(g.items.len() as i64))
17428 }
17429 "pop" => {
17430 let mut g = h.lock();
17431 if g.items.is_empty() {
17432 return Ok(StrykeValue::UNDEF);
17433 }
17434 let cmp = g.cmp.clone();
17435 let n = g.items.len();
17436 g.items.swap(0, n - 1);
17437 let v = g.items.pop().unwrap();
17438 if !g.items.is_empty() {
17439 self.heap_sift_down(&mut g.items, &cmp, 0);
17440 }
17441 Ok(v)
17442 }
17443 "peek" => Ok(h
17444 .lock()
17445 .items
17446 .first()
17447 .cloned()
17448 .unwrap_or(StrykeValue::UNDEF)),
17449 _ => Err(StrykeError::runtime(
17450 format!("Unknown method for heap: {}", method),
17451 line,
17452 )),
17453 }
17454 }
17455
17456 fn ppool_method(
17457 &mut self,
17458 pool: PerlPpool,
17459 method: &str,
17460 args: &[StrykeValue],
17461 line: usize,
17462 ) -> StrykeResult<StrykeValue> {
17463 match method {
17464 "submit" => pool.submit(self, args, line),
17465 "collect" => {
17466 if !args.is_empty() {
17467 return Err(StrykeError::runtime("collect() takes no arguments", line));
17468 }
17469 pool.collect(line)
17470 }
17471 _ => Err(StrykeError::runtime(
17472 format!("Unknown method for ppool: {}", method),
17473 line,
17474 )),
17475 }
17476 }
17477
17478 fn barrier_method(
17479 &self,
17480 barrier: PerlBarrier,
17481 method: &str,
17482 args: &[StrykeValue],
17483 line: usize,
17484 ) -> StrykeResult<StrykeValue> {
17485 match method {
17486 "wait" => {
17487 if !args.is_empty() {
17488 return Err(StrykeError::runtime("wait() takes no arguments", line));
17489 }
17490 let _ = barrier.0.wait();
17491 Ok(StrykeValue::integer(1))
17492 }
17493 _ => Err(StrykeError::runtime(
17494 format!("Unknown method for barrier: {}", method),
17495 line,
17496 )),
17497 }
17498 }
17499
17500 fn capture_method(
17501 &self,
17502 c: Arc<CaptureResult>,
17503 method: &str,
17504 args: &[StrykeValue],
17505 line: usize,
17506 ) -> StrykeResult<StrykeValue> {
17507 if !args.is_empty() {
17508 return Err(StrykeError::runtime(
17509 format!("capture: {} takes no arguments", method),
17510 line,
17511 ));
17512 }
17513 match method {
17514 "stdout" => Ok(StrykeValue::string(c.stdout.clone())),
17515 "stderr" => Ok(StrykeValue::string(c.stderr.clone())),
17516 "exitcode" => Ok(StrykeValue::integer(c.exitcode)),
17517 "failed" => Ok(StrykeValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
17518 _ => Err(StrykeError::runtime(
17519 format!("Unknown method for capture: {}", method),
17520 line,
17521 )),
17522 }
17523 }
17524
17525 pub(crate) fn builtin_par_pipeline_stream(
17526 &mut self,
17527 args: &[StrykeValue],
17528 _line: usize,
17529 ) -> StrykeResult<StrykeValue> {
17530 let mut items = Vec::new();
17531 for v in args {
17532 if let Some(a) = v.as_array_vec() {
17533 items.extend(a);
17534 } else {
17535 items.push(v.clone());
17536 }
17537 }
17538 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
17539 source: items,
17540 ops: Vec::new(),
17541 has_scalar_terminal: false,
17542 par_stream: true,
17543 streaming: false,
17544 streaming_workers: 0,
17545 streaming_buffer: 256,
17546 }))))
17547 }
17548
17549 pub(crate) fn builtin_par_pipeline_stream_new(
17552 &mut self,
17553 args: &[StrykeValue],
17554 _line: usize,
17555 ) -> StrykeResult<StrykeValue> {
17556 let mut items = Vec::new();
17557 let mut workers: usize = 0;
17558 let mut buffer: usize = 256;
17559 let mut i = 0;
17561 while i < args.len() {
17562 let s = args[i].to_string();
17563 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
17564 let val = args[i + 1].to_int().max(1) as usize;
17565 if s == "workers" {
17566 workers = val;
17567 } else {
17568 buffer = val;
17569 }
17570 i += 2;
17571 } else if let Some(a) = args[i].as_array_vec() {
17572 items.extend(a);
17573 i += 1;
17574 } else {
17575 items.push(args[i].clone());
17576 i += 1;
17577 }
17578 }
17579 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
17580 source: items,
17581 ops: Vec::new(),
17582 has_scalar_terminal: false,
17583 par_stream: false,
17584 streaming: true,
17585 streaming_workers: workers,
17586 streaming_buffer: buffer,
17587 }))))
17588 }
17589
17590 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<StrykeSub> {
17592 let line = 1usize;
17593 let body = vec![Statement {
17594 label: None,
17595 kind: StmtKind::Expression(Expr {
17596 kind: ExprKind::BinOp {
17597 left: Box::new(Expr {
17598 kind: ExprKind::ScalarVar("_".into()),
17599 line,
17600 }),
17601 op: BinOp::Mul,
17602 right: Box::new(Expr {
17603 kind: ExprKind::Integer(k),
17604 line,
17605 }),
17606 },
17607 line,
17608 }),
17609 line,
17610 }];
17611 Arc::new(StrykeSub {
17612 name: "__pipeline_int_mul__".into(),
17613 params: vec![],
17614 body,
17615 closure_env: None,
17616 prototype: None,
17617 fib_like: None,
17618 })
17619 }
17620
17621 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<StrykeSub> {
17622 let captured = self.scope.capture();
17623 Arc::new(StrykeSub {
17624 name: "__ANON__".into(),
17625 params: vec![],
17626 body: block.clone(),
17627 closure_env: Some(captured),
17628 prototype: None,
17629 fib_like: None,
17630 })
17631 }
17632
17633 pub(crate) fn builtin_collect_execute(
17634 &mut self,
17635 args: &[StrykeValue],
17636 line: usize,
17637 ) -> StrykeResult<StrykeValue> {
17638 if args.is_empty() {
17639 return Err(StrykeError::runtime(
17640 "collect() expects at least one argument",
17641 line,
17642 ));
17643 }
17644 if args.len() == 1 {
17647 if let Some(p) = args[0].as_pipeline() {
17648 return self.pipeline_collect(&p, line);
17649 }
17650 return Ok(StrykeValue::array(args[0].to_list()));
17651 }
17652 Ok(StrykeValue::array(args.to_vec()))
17653 }
17654
17655 pub(crate) fn pipeline_push(
17656 &self,
17657 p: &Arc<Mutex<PipelineInner>>,
17658 op: PipelineOp,
17659 line: usize,
17660 ) -> StrykeResult<()> {
17661 let mut g = p.lock();
17662 if g.has_scalar_terminal {
17663 return Err(StrykeError::runtime(
17664 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
17665 line,
17666 ));
17667 }
17668 if matches!(
17669 &op,
17670 PipelineOp::PReduce { .. }
17671 | PipelineOp::PReduceInit { .. }
17672 | PipelineOp::PMapReduce { .. }
17673 ) {
17674 g.has_scalar_terminal = true;
17675 }
17676 g.ops.push(op);
17677 Ok(())
17678 }
17679
17680 fn pipeline_parse_sub_progress(
17681 args: &[StrykeValue],
17682 line: usize,
17683 name: &str,
17684 ) -> StrykeResult<(Arc<StrykeSub>, bool)> {
17685 if args.is_empty() {
17686 return Err(StrykeError::runtime(
17687 format!("pipeline {}: expects at least 1 argument (code ref)", name),
17688 line,
17689 ));
17690 }
17691 let Some(sub) = args[0].as_code_ref() else {
17692 return Err(StrykeError::runtime(
17693 format!("pipeline {}: first argument must be a code reference", name),
17694 line,
17695 ));
17696 };
17697 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
17698 if args.len() > 2 {
17699 return Err(StrykeError::runtime(
17700 format!(
17701 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
17702 name
17703 ),
17704 line,
17705 ));
17706 }
17707 Ok((sub, progress))
17708 }
17709
17710 pub(crate) fn pipeline_method(
17711 &mut self,
17712 p: Arc<Mutex<PipelineInner>>,
17713 method: &str,
17714 args: &[StrykeValue],
17715 line: usize,
17716 ) -> StrykeResult<StrykeValue> {
17717 match method {
17718 "filter" | "f" | "grep" => {
17719 if args.len() != 1 {
17720 return Err(StrykeError::runtime(
17721 "pipeline filter/grep expects 1 argument (sub)",
17722 line,
17723 ));
17724 }
17725 let Some(sub) = args[0].as_code_ref() else {
17726 return Err(StrykeError::runtime(
17727 "pipeline filter/grep expects a code reference",
17728 line,
17729 ));
17730 };
17731 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
17732 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17733 }
17734 "map" => {
17735 if args.len() != 1 {
17736 return Err(StrykeError::runtime(
17737 "pipeline map expects 1 argument (sub)",
17738 line,
17739 ));
17740 }
17741 let Some(sub) = args[0].as_code_ref() else {
17742 return Err(StrykeError::runtime(
17743 "pipeline map expects a code reference",
17744 line,
17745 ));
17746 };
17747 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
17748 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17749 }
17750 "tap" | "peek" => {
17751 if args.len() != 1 {
17752 return Err(StrykeError::runtime(
17753 "pipeline tap/peek expects 1 argument (sub)",
17754 line,
17755 ));
17756 }
17757 let Some(sub) = args[0].as_code_ref() else {
17758 return Err(StrykeError::runtime(
17759 "pipeline tap/peek expects a code reference",
17760 line,
17761 ));
17762 };
17763 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
17764 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17765 }
17766 "take" => {
17767 if args.len() != 1 {
17768 return Err(StrykeError::runtime(
17769 "pipeline take expects 1 argument",
17770 line,
17771 ));
17772 }
17773 let n = args[0].to_int();
17774 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
17775 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17776 }
17777 "pmap" => {
17778 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
17779 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
17780 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17781 }
17782 "pgrep" => {
17783 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
17784 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
17785 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17786 }
17787 "pfor" => {
17788 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
17789 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
17790 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17791 }
17792 "pmap_chunked" => {
17793 if args.len() < 2 {
17794 return Err(StrykeError::runtime(
17795 "pipeline pmap_chunked expects chunk size and a code reference",
17796 line,
17797 ));
17798 }
17799 let chunk = args[0].to_int().max(1);
17800 let Some(sub) = args[1].as_code_ref() else {
17801 return Err(StrykeError::runtime(
17802 "pipeline pmap_chunked: second argument must be a code reference",
17803 line,
17804 ));
17805 };
17806 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
17807 if args.len() > 3 {
17808 return Err(StrykeError::runtime(
17809 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
17810 line,
17811 ));
17812 }
17813 self.pipeline_push(
17814 &p,
17815 PipelineOp::PMapChunked {
17816 chunk,
17817 sub,
17818 progress,
17819 },
17820 line,
17821 )?;
17822 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17823 }
17824 "psort" => {
17825 let (cmp, progress) = match args.len() {
17826 0 => (None, false),
17827 1 => {
17828 if let Some(s) = args[0].as_code_ref() {
17829 (Some(s), false)
17830 } else {
17831 (None, args[0].is_true())
17832 }
17833 }
17834 2 => {
17835 let Some(s) = args[0].as_code_ref() else {
17836 return Err(StrykeError::runtime(
17837 "pipeline psort: with two arguments, the first must be a comparator sub",
17838 line,
17839 ));
17840 };
17841 (Some(s), args[1].is_true())
17842 }
17843 _ => {
17844 return Err(StrykeError::runtime(
17845 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
17846 line,
17847 ));
17848 }
17849 };
17850 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
17851 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17852 }
17853 "pcache" => {
17854 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
17855 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
17856 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17857 }
17858 "preduce" => {
17859 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
17860 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
17861 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17862 }
17863 "preduce_init" => {
17864 if args.len() < 2 {
17865 return Err(StrykeError::runtime(
17866 "pipeline preduce_init expects init value and a code reference",
17867 line,
17868 ));
17869 }
17870 let init = args[0].clone();
17871 let Some(sub) = args[1].as_code_ref() else {
17872 return Err(StrykeError::runtime(
17873 "pipeline preduce_init: second argument must be a code reference",
17874 line,
17875 ));
17876 };
17877 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
17878 if args.len() > 3 {
17879 return Err(StrykeError::runtime(
17880 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
17881 line,
17882 ));
17883 }
17884 self.pipeline_push(
17885 &p,
17886 PipelineOp::PReduceInit {
17887 init,
17888 sub,
17889 progress,
17890 },
17891 line,
17892 )?;
17893 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17894 }
17895 "pmap_reduce" => {
17896 if args.len() < 2 {
17897 return Err(StrykeError::runtime(
17898 "pipeline pmap_reduce expects map sub and reduce sub",
17899 line,
17900 ));
17901 }
17902 let Some(map) = args[0].as_code_ref() else {
17903 return Err(StrykeError::runtime(
17904 "pipeline pmap_reduce: first argument must be a code reference (map)",
17905 line,
17906 ));
17907 };
17908 let Some(reduce) = args[1].as_code_ref() else {
17909 return Err(StrykeError::runtime(
17910 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
17911 line,
17912 ));
17913 };
17914 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
17915 if args.len() > 3 {
17916 return Err(StrykeError::runtime(
17917 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
17918 line,
17919 ));
17920 }
17921 self.pipeline_push(
17922 &p,
17923 PipelineOp::PMapReduce {
17924 map,
17925 reduce,
17926 progress,
17927 },
17928 line,
17929 )?;
17930 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17931 }
17932 "collect" => {
17933 if !args.is_empty() {
17934 return Err(StrykeError::runtime(
17935 "pipeline collect takes no arguments",
17936 line,
17937 ));
17938 }
17939 self.pipeline_collect(&p, line)
17940 }
17941 _ => {
17942 if let Some(sub) = self.resolve_sub_by_name(method) {
17945 if !args.is_empty() {
17946 return Err(StrykeError::runtime(
17947 format!(
17948 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
17949 method
17950 ),
17951 line,
17952 ));
17953 }
17954 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
17955 Ok(StrykeValue::pipeline(Arc::clone(&p)))
17956 } else {
17957 Err(StrykeError::runtime(
17958 format!("Unknown method for pipeline: {}", method),
17959 line,
17960 ))
17961 }
17962 }
17963 }
17964 }
17965
17966 fn pipeline_parallel_map(
17967 &mut self,
17968 items: Vec<StrykeValue>,
17969 sub: &Arc<StrykeSub>,
17970 progress: bool,
17971 ) -> Vec<StrykeValue> {
17972 let subs = self.subs.clone();
17973 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
17974 let pmap_progress = PmapProgress::new(progress, items.len());
17975 let results: Vec<StrykeValue> = items
17976 .into_par_iter()
17977 .map(|item| {
17978 let mut local_interp = VMHelper::new();
17979 local_interp.subs = subs.clone();
17980 local_interp.scope.restore_capture(&scope_capture);
17981 local_interp
17982 .scope
17983 .restore_atomics(&atomic_arrays, &atomic_hashes);
17984 local_interp.enable_parallel_guard();
17985 local_interp.scope.set_topic(item);
17986 local_interp.scope_push_hook();
17987 let val = match local_interp.exec_block_no_scope(&sub.body) {
17988 Ok(val) => val,
17989 Err(_) => StrykeValue::UNDEF,
17990 };
17991 local_interp.scope_pop_hook();
17992 pmap_progress.tick();
17993 val
17994 })
17995 .collect();
17996 pmap_progress.finish();
17997 results
17998 }
17999
18000 fn pipeline_par_stream_filter(
18002 &mut self,
18003 items: Vec<StrykeValue>,
18004 sub: &Arc<StrykeSub>,
18005 ) -> Vec<StrykeValue> {
18006 if items.is_empty() {
18007 return items;
18008 }
18009 let subs = self.subs.clone();
18010 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18011 let indexed: Vec<(usize, StrykeValue)> = items.into_iter().enumerate().collect();
18012 let mut kept: Vec<(usize, StrykeValue)> = indexed
18013 .into_par_iter()
18014 .filter_map(|(i, item)| {
18015 let mut local_interp = VMHelper::new();
18016 local_interp.subs = subs.clone();
18017 local_interp.scope.restore_capture(&scope_capture);
18018 local_interp
18019 .scope
18020 .restore_atomics(&atomic_arrays, &atomic_hashes);
18021 local_interp.enable_parallel_guard();
18022 local_interp.scope.set_topic(item.clone());
18023 local_interp.scope_push_hook();
18024 let keep = match local_interp.exec_block_no_scope(&sub.body) {
18025 Ok(val) => val.is_true(),
18026 Err(_) => false,
18027 };
18028 local_interp.scope_pop_hook();
18029 if keep {
18030 Some((i, item))
18031 } else {
18032 None
18033 }
18034 })
18035 .collect();
18036 kept.sort_by_key(|(i, _)| *i);
18037 kept.into_iter().map(|(_, x)| x).collect()
18038 }
18039
18040 fn pipeline_par_stream_map(
18042 &mut self,
18043 items: Vec<StrykeValue>,
18044 sub: &Arc<StrykeSub>,
18045 ) -> Vec<StrykeValue> {
18046 if items.is_empty() {
18047 return items;
18048 }
18049 let subs = self.subs.clone();
18050 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18051 let indexed: Vec<(usize, StrykeValue)> = items.into_iter().enumerate().collect();
18052 let mut mapped: Vec<(usize, StrykeValue)> = indexed
18053 .into_par_iter()
18054 .map(|(i, item)| {
18055 let mut local_interp = VMHelper::new();
18056 local_interp.subs = subs.clone();
18057 local_interp.scope.restore_capture(&scope_capture);
18058 local_interp
18059 .scope
18060 .restore_atomics(&atomic_arrays, &atomic_hashes);
18061 local_interp.enable_parallel_guard();
18062 local_interp.scope.set_topic(item);
18063 local_interp.scope_push_hook();
18064 let val = match local_interp.exec_block_no_scope(&sub.body) {
18065 Ok(val) => val,
18066 Err(_) => StrykeValue::UNDEF,
18067 };
18068 local_interp.scope_pop_hook();
18069 (i, val)
18070 })
18071 .collect();
18072 mapped.sort_by_key(|(i, _)| *i);
18073 mapped.into_iter().map(|(_, x)| x).collect()
18074 }
18075
18076 fn pipeline_collect(
18077 &mut self,
18078 p: &Arc<Mutex<PipelineInner>>,
18079 line: usize,
18080 ) -> StrykeResult<StrykeValue> {
18081 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
18082 let g = p.lock();
18083 (
18084 g.source.clone(),
18085 g.ops.clone(),
18086 g.par_stream,
18087 g.streaming,
18088 g.streaming_workers,
18089 g.streaming_buffer,
18090 )
18091 };
18092 if streaming {
18093 return self.pipeline_collect_streaming(
18094 v,
18095 &ops,
18096 streaming_workers,
18097 streaming_buffer,
18098 line,
18099 );
18100 }
18101 for op in ops {
18102 match op {
18103 PipelineOp::Filter(sub) => {
18104 if par_stream {
18105 v = self.pipeline_par_stream_filter(v, &sub);
18106 } else {
18107 let mut out = Vec::new();
18108 for item in v {
18109 self.scope_push_hook();
18110 self.scope.set_topic(item.clone());
18111 if let Some(ref env) = sub.closure_env {
18112 self.scope.restore_capture(env);
18113 }
18114 let keep = match self.exec_block_no_scope(&sub.body) {
18115 Ok(val) => val.is_true(),
18116 Err(_) => false,
18117 };
18118 self.scope_pop_hook();
18119 if keep {
18120 out.push(item);
18121 }
18122 }
18123 v = out;
18124 }
18125 }
18126 PipelineOp::Map(sub) => {
18127 if par_stream {
18128 v = self.pipeline_par_stream_map(v, &sub);
18129 } else {
18130 let mut out = Vec::new();
18131 for item in v {
18132 self.scope_push_hook();
18133 self.scope.set_topic(item);
18134 if let Some(ref env) = sub.closure_env {
18135 self.scope.restore_capture(env);
18136 }
18137 let mapped = match self.exec_block_no_scope(&sub.body) {
18138 Ok(val) => val,
18139 Err(_) => StrykeValue::UNDEF,
18140 };
18141 self.scope_pop_hook();
18142 out.push(mapped);
18143 }
18144 v = out;
18145 }
18146 }
18147 PipelineOp::Tap(sub) => {
18148 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
18149 Ok(_) => {}
18150 Err(FlowOrError::Error(e)) => return Err(e),
18151 Err(FlowOrError::Flow(_)) => {
18152 return Err(StrykeError::runtime(
18153 "tap: unsupported control flow in block",
18154 line,
18155 ));
18156 }
18157 }
18158 }
18159 PipelineOp::Take(n) => {
18160 let n = n.max(0) as usize;
18161 if v.len() > n {
18162 v.truncate(n);
18163 }
18164 }
18165 PipelineOp::PMap { sub, progress } => {
18166 v = self.pipeline_parallel_map(v, &sub, progress);
18167 }
18168 PipelineOp::PGrep { sub, progress } => {
18169 let subs = self.subs.clone();
18170 let (scope_capture, atomic_arrays, atomic_hashes) =
18171 self.scope.capture_with_atomics();
18172 let pmap_progress = PmapProgress::new(progress, v.len());
18173 v = v
18174 .into_par_iter()
18175 .filter_map(|item| {
18176 let mut local_interp = VMHelper::new();
18177 local_interp.subs = subs.clone();
18178 local_interp.scope.restore_capture(&scope_capture);
18179 local_interp
18180 .scope
18181 .restore_atomics(&atomic_arrays, &atomic_hashes);
18182 local_interp.enable_parallel_guard();
18183 local_interp.scope.set_topic(item.clone());
18184 local_interp.scope_push_hook();
18185 let keep = match local_interp.exec_block_no_scope(&sub.body) {
18186 Ok(val) => val.is_true(),
18187 Err(_) => false,
18188 };
18189 local_interp.scope_pop_hook();
18190 pmap_progress.tick();
18191 if keep {
18192 Some(item)
18193 } else {
18194 None
18195 }
18196 })
18197 .collect();
18198 pmap_progress.finish();
18199 }
18200 PipelineOp::PFor { sub, progress } => {
18201 let subs = self.subs.clone();
18202 let (scope_capture, atomic_arrays, atomic_hashes) =
18203 self.scope.capture_with_atomics();
18204 let pmap_progress = PmapProgress::new(progress, v.len());
18205 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
18206 v.clone().into_par_iter().for_each(|item| {
18207 if first_err.lock().is_some() {
18208 return;
18209 }
18210 let mut local_interp = VMHelper::new();
18211 local_interp.subs = subs.clone();
18212 local_interp.scope.restore_capture(&scope_capture);
18213 local_interp
18214 .scope
18215 .restore_atomics(&atomic_arrays, &atomic_hashes);
18216 local_interp.enable_parallel_guard();
18217 local_interp.scope.set_topic(item);
18218 local_interp.scope_push_hook();
18219 match local_interp.exec_block_no_scope(&sub.body) {
18220 Ok(_) => {}
18221 Err(e) => {
18222 let stryke = match e {
18223 FlowOrError::Error(stryke) => stryke,
18224 FlowOrError::Flow(_) => StrykeError::runtime(
18225 "return/last/next/redo not supported inside pipeline pfor block",
18226 line,
18227 ),
18228 };
18229 let mut g = first_err.lock();
18230 if g.is_none() {
18231 *g = Some(stryke);
18232 }
18233 }
18234 }
18235 local_interp.scope_pop_hook();
18236 pmap_progress.tick();
18237 });
18238 pmap_progress.finish();
18239 let pfor_err = first_err.lock().take();
18240 if let Some(e) = pfor_err {
18241 return Err(e);
18242 }
18243 }
18244 PipelineOp::PMapChunked {
18245 chunk,
18246 sub,
18247 progress,
18248 } => {
18249 let chunk_n = chunk.max(1) as usize;
18250 let subs = self.subs.clone();
18251 let (scope_capture, atomic_arrays, atomic_hashes) =
18252 self.scope.capture_with_atomics();
18253 let indexed_chunks: Vec<(usize, Vec<StrykeValue>)> = v
18254 .chunks(chunk_n)
18255 .enumerate()
18256 .map(|(i, c)| (i, c.to_vec()))
18257 .collect();
18258 let n_chunks = indexed_chunks.len();
18259 let pmap_progress = PmapProgress::new(progress, n_chunks);
18260 let mut chunk_results: Vec<(usize, Vec<StrykeValue>)> = indexed_chunks
18261 .into_par_iter()
18262 .map(|(chunk_idx, chunk)| {
18263 let mut local_interp = VMHelper::new();
18264 local_interp.subs = subs.clone();
18265 local_interp.scope.restore_capture(&scope_capture);
18266 local_interp
18267 .scope
18268 .restore_atomics(&atomic_arrays, &atomic_hashes);
18269 local_interp.enable_parallel_guard();
18270 let mut out = Vec::with_capacity(chunk.len());
18271 for item in chunk {
18272 local_interp.scope.set_topic(item);
18273 local_interp.scope_push_hook();
18274 match local_interp.exec_block_no_scope(&sub.body) {
18275 Ok(val) => {
18276 local_interp.scope_pop_hook();
18277 out.push(val);
18278 }
18279 Err(_) => {
18280 local_interp.scope_pop_hook();
18281 out.push(StrykeValue::UNDEF);
18282 }
18283 }
18284 }
18285 pmap_progress.tick();
18286 (chunk_idx, out)
18287 })
18288 .collect();
18289 pmap_progress.finish();
18290 chunk_results.sort_by_key(|(i, _)| *i);
18291 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
18292 }
18293 PipelineOp::PSort { cmp, progress } => {
18294 let pmap_progress = PmapProgress::new(progress, 2);
18295 pmap_progress.tick();
18296 match cmp {
18297 Some(cmp_block) => {
18298 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
18299 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
18300 } else {
18301 let subs = self.subs.clone();
18302 let scope_capture = self.scope.capture();
18303 v.par_sort_by(|a, b| {
18304 let mut local_interp = VMHelper::new();
18305 local_interp.subs = subs.clone();
18306 local_interp.scope.restore_capture(&scope_capture);
18307 local_interp.enable_parallel_guard();
18308 local_interp.scope.set_sort_pair(a.clone(), b.clone());
18309 local_interp.scope_push_hook();
18310 let ord =
18311 match local_interp.exec_block_no_scope(&cmp_block.body) {
18312 Ok(v) => {
18313 let n = v.to_int();
18314 if n < 0 {
18315 std::cmp::Ordering::Less
18316 } else if n > 0 {
18317 std::cmp::Ordering::Greater
18318 } else {
18319 std::cmp::Ordering::Equal
18320 }
18321 }
18322 Err(_) => std::cmp::Ordering::Equal,
18323 };
18324 local_interp.scope_pop_hook();
18325 ord
18326 });
18327 }
18328 }
18329 None => {
18330 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
18331 }
18332 }
18333 pmap_progress.tick();
18334 pmap_progress.finish();
18335 }
18336 PipelineOp::PCache { sub, progress } => {
18337 let subs = self.subs.clone();
18338 let scope_capture = self.scope.capture();
18339 let cache = &*crate::pcache::GLOBAL_PCACHE;
18340 let pmap_progress = PmapProgress::new(progress, v.len());
18341 v = v
18342 .into_par_iter()
18343 .map(|item| {
18344 let k = crate::pcache::cache_key(&item);
18345 if let Some(cached) = cache.get(&k) {
18346 pmap_progress.tick();
18347 return cached.clone();
18348 }
18349 let mut local_interp = VMHelper::new();
18350 local_interp.subs = subs.clone();
18351 local_interp.scope.restore_capture(&scope_capture);
18352 local_interp.enable_parallel_guard();
18353 local_interp.scope.set_topic(item.clone());
18354 local_interp.scope_push_hook();
18355 let val = match local_interp.exec_block_no_scope(&sub.body) {
18356 Ok(v) => v,
18357 Err(_) => StrykeValue::UNDEF,
18358 };
18359 local_interp.scope_pop_hook();
18360 cache.insert(k, val.clone());
18361 pmap_progress.tick();
18362 val
18363 })
18364 .collect();
18365 pmap_progress.finish();
18366 }
18367 PipelineOp::PReduce { sub, progress } => {
18368 if v.is_empty() {
18369 return Ok(StrykeValue::UNDEF);
18370 }
18371 if v.len() == 1 {
18372 return Ok(v.into_iter().next().unwrap());
18373 }
18374 let block = sub.body.clone();
18375 let subs = self.subs.clone();
18376 let scope_capture = self.scope.capture();
18377 let pmap_progress = PmapProgress::new(progress, v.len());
18378 let result = v
18379 .into_par_iter()
18380 .map(|x| {
18381 pmap_progress.tick();
18382 x
18383 })
18384 .reduce_with(|a, b| {
18385 let mut local_interp = VMHelper::new();
18386 local_interp.subs = subs.clone();
18387 local_interp.scope.restore_capture(&scope_capture);
18388 local_interp.enable_parallel_guard();
18389 local_interp.scope.set_sort_pair(a, b);
18390 match local_interp.exec_block(&block) {
18391 Ok(val) => val,
18392 Err(_) => StrykeValue::UNDEF,
18393 }
18394 });
18395 pmap_progress.finish();
18396 return Ok(result.unwrap_or(StrykeValue::UNDEF));
18397 }
18398 PipelineOp::PReduceInit {
18399 init,
18400 sub,
18401 progress,
18402 } => {
18403 if v.is_empty() {
18404 return Ok(init);
18405 }
18406 let block = sub.body.clone();
18407 let subs = self.subs.clone();
18408 let scope_capture = self.scope.capture();
18409 let cap: &[(String, StrykeValue)] = scope_capture.as_slice();
18410 if v.len() == 1 {
18411 return Ok(fold_preduce_init_step(
18412 &subs,
18413 cap,
18414 &block,
18415 preduce_init_fold_identity(&init),
18416 v.into_iter().next().unwrap(),
18417 ));
18418 }
18419 let pmap_progress = PmapProgress::new(progress, v.len());
18420 let result = v
18421 .into_par_iter()
18422 .fold(
18423 || preduce_init_fold_identity(&init),
18424 |acc, item| {
18425 pmap_progress.tick();
18426 fold_preduce_init_step(&subs, cap, &block, acc, item)
18427 },
18428 )
18429 .reduce(
18430 || preduce_init_fold_identity(&init),
18431 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
18432 );
18433 pmap_progress.finish();
18434 return Ok(result);
18435 }
18436 PipelineOp::PMapReduce {
18437 map,
18438 reduce,
18439 progress,
18440 } => {
18441 if v.is_empty() {
18442 return Ok(StrykeValue::UNDEF);
18443 }
18444 let map_block = map.body.clone();
18445 let reduce_block = reduce.body.clone();
18446 let subs = self.subs.clone();
18447 let scope_capture = self.scope.capture();
18448 if v.len() == 1 {
18449 let mut local_interp = VMHelper::new();
18450 local_interp.subs = subs.clone();
18451 local_interp.scope.restore_capture(&scope_capture);
18452 local_interp.scope.set_topic(v[0].clone());
18453 return match local_interp.exec_block_no_scope(&map_block) {
18454 Ok(val) => Ok(val),
18455 Err(_) => Ok(StrykeValue::UNDEF),
18456 };
18457 }
18458 let pmap_progress = PmapProgress::new(progress, v.len());
18459 let result = v
18460 .into_par_iter()
18461 .map(|item| {
18462 let mut local_interp = VMHelper::new();
18463 local_interp.subs = subs.clone();
18464 local_interp.scope.restore_capture(&scope_capture);
18465 local_interp.scope.set_topic(item);
18466 let val = match local_interp.exec_block_no_scope(&map_block) {
18467 Ok(val) => val,
18468 Err(_) => StrykeValue::UNDEF,
18469 };
18470 pmap_progress.tick();
18471 val
18472 })
18473 .reduce_with(|a, b| {
18474 let mut local_interp = VMHelper::new();
18475 local_interp.subs = subs.clone();
18476 local_interp.scope.restore_capture(&scope_capture);
18477 local_interp.scope.set_sort_pair(a, b);
18478 match local_interp.exec_block_no_scope(&reduce_block) {
18479 Ok(val) => val,
18480 Err(_) => StrykeValue::UNDEF,
18481 }
18482 });
18483 pmap_progress.finish();
18484 return Ok(result.unwrap_or(StrykeValue::UNDEF));
18485 }
18486 }
18487 }
18488 Ok(StrykeValue::array(v))
18489 }
18490
18491 fn pipeline_collect_streaming(
18494 &mut self,
18495 source: Vec<StrykeValue>,
18496 ops: &[PipelineOp],
18497 workers_per_stage: usize,
18498 buffer: usize,
18499 line: usize,
18500 ) -> StrykeResult<StrykeValue> {
18501 use crossbeam::channel::{bounded, Receiver, Sender};
18502
18503 for op in ops {
18505 match op {
18506 PipelineOp::PSort { .. }
18507 | PipelineOp::PReduce { .. }
18508 | PipelineOp::PReduceInit { .. }
18509 | PipelineOp::PMapReduce { .. }
18510 | PipelineOp::PMapChunked { .. } => {
18511 return Err(StrykeError::runtime(
18512 format!(
18513 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
18514 std::mem::discriminant(op)
18515 ),
18516 line,
18517 ));
18518 }
18519 _ => {}
18520 }
18521 }
18522
18523 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
18526 if streamable_ops.is_empty() {
18527 return Ok(StrykeValue::array(source));
18528 }
18529
18530 let n_stages = streamable_ops.len();
18531 let wn = if workers_per_stage > 0 {
18532 workers_per_stage
18533 } else {
18534 self.parallel_thread_count()
18535 };
18536 let subs = self.subs.clone();
18537 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18538
18539 let mut channels: Vec<(Sender<StrykeValue>, Receiver<StrykeValue>)> =
18544 (0..=n_stages).map(|_| bounded(buffer)).collect();
18545
18546 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
18547 let take_done: Arc<std::sync::atomic::AtomicBool> =
18548 Arc::new(std::sync::atomic::AtomicBool::new(false));
18549
18550 let source_tx = channels[0].0.clone();
18553 let result_rx = channels[n_stages].1.clone();
18554 let results: Arc<Mutex<Vec<StrykeValue>>> = Arc::new(Mutex::new(Vec::new()));
18555
18556 std::thread::scope(|scope| {
18557 let result_rx_c = result_rx.clone();
18560 let results_c = Arc::clone(&results);
18561 scope.spawn(move || {
18562 while let Ok(item) = result_rx_c.recv() {
18563 results_c.lock().push(item);
18564 }
18565 });
18566
18567 let err_s = Arc::clone(&err);
18569 let take_done_s = Arc::clone(&take_done);
18570 scope.spawn(move || {
18571 for item in source {
18572 if err_s.lock().is_some()
18573 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
18574 {
18575 break;
18576 }
18577 if source_tx.send(item).is_err() {
18578 break;
18579 }
18580 }
18581 });
18582
18583 for (stage_idx, op) in streamable_ops.iter().enumerate() {
18585 let rx = channels[stage_idx].1.clone();
18586 let tx = channels[stage_idx + 1].0.clone();
18587
18588 for _ in 0..wn {
18589 let rx = rx.clone();
18590 let tx = tx.clone();
18591 let subs = subs.clone();
18592 let capture = capture.clone();
18593 let atomic_arrays = atomic_arrays.clone();
18594 let atomic_hashes = atomic_hashes.clone();
18595 let err_w = Arc::clone(&err);
18596 let take_done_w = Arc::clone(&take_done);
18597
18598 match *op {
18599 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
18600 let sub = Arc::clone(sub);
18601 scope.spawn(move || {
18602 while let Ok(item) = rx.recv() {
18603 if err_w.lock().is_some() {
18604 break;
18605 }
18606 let mut interp = VMHelper::new();
18607 interp.subs = subs.clone();
18608 interp.scope.restore_capture(&capture);
18609 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
18610 interp.enable_parallel_guard();
18611 interp.scope.set_topic(item.clone());
18612 interp.scope_push_hook();
18613 let keep = match interp.exec_block_no_scope(&sub.body) {
18614 Ok(val) => val.is_true(),
18615 Err(_) => false,
18616 };
18617 interp.scope_pop_hook();
18618 if keep && tx.send(item).is_err() {
18619 break;
18620 }
18621 }
18622 });
18623 }
18624 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
18625 let sub = Arc::clone(sub);
18626 scope.spawn(move || {
18627 while let Ok(item) = rx.recv() {
18628 if err_w.lock().is_some() {
18629 break;
18630 }
18631 let mut interp = VMHelper::new();
18632 interp.subs = subs.clone();
18633 interp.scope.restore_capture(&capture);
18634 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
18635 interp.enable_parallel_guard();
18636 interp.scope.set_topic(item);
18637 interp.scope_push_hook();
18638 let mapped = match interp.exec_block_no_scope(&sub.body) {
18639 Ok(val) => val,
18640 Err(_) => StrykeValue::UNDEF,
18641 };
18642 interp.scope_pop_hook();
18643 if tx.send(mapped).is_err() {
18644 break;
18645 }
18646 }
18647 });
18648 }
18649 PipelineOp::Take(n) => {
18650 let limit = (*n).max(0) as usize;
18651 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
18652 let count_w = Arc::clone(&count);
18653 scope.spawn(move || {
18654 while let Ok(item) = rx.recv() {
18655 let prev =
18656 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
18657 if prev >= limit {
18658 take_done_w
18659 .store(true, std::sync::atomic::Ordering::Relaxed);
18660 break;
18661 }
18662 if tx.send(item).is_err() {
18663 break;
18664 }
18665 }
18666 });
18667 break;
18669 }
18670 PipelineOp::PFor { ref sub, .. } => {
18671 let sub = Arc::clone(sub);
18672 scope.spawn(move || {
18673 while let Ok(item) = rx.recv() {
18674 if err_w.lock().is_some() {
18675 break;
18676 }
18677 let mut interp = VMHelper::new();
18678 interp.subs = subs.clone();
18679 interp.scope.restore_capture(&capture);
18680 interp
18681 .scope
18682 .restore_atomics(&atomic_arrays, &atomic_hashes);
18683 interp.enable_parallel_guard();
18684 interp.scope.set_topic(item.clone());
18685 interp.scope_push_hook();
18686 match interp.exec_block_no_scope(&sub.body) {
18687 Ok(_) => {}
18688 Err(e) => {
18689 let msg = match e {
18690 FlowOrError::Error(stryke) => stryke.to_string(),
18691 FlowOrError::Flow(_) => {
18692 "unexpected control flow in par_pipeline_stream pfor".into()
18693 }
18694 };
18695 let mut g = err_w.lock();
18696 if g.is_none() {
18697 *g = Some(msg);
18698 }
18699 interp.scope_pop_hook();
18700 break;
18701 }
18702 }
18703 interp.scope_pop_hook();
18704 if tx.send(item).is_err() {
18705 break;
18706 }
18707 }
18708 });
18709 }
18710 PipelineOp::Tap(ref sub) => {
18711 let sub = Arc::clone(sub);
18712 scope.spawn(move || {
18713 while let Ok(item) = rx.recv() {
18714 if err_w.lock().is_some() {
18715 break;
18716 }
18717 let mut interp = VMHelper::new();
18718 interp.subs = subs.clone();
18719 interp.scope.restore_capture(&capture);
18720 interp
18721 .scope
18722 .restore_atomics(&atomic_arrays, &atomic_hashes);
18723 interp.enable_parallel_guard();
18724 match interp.call_sub(
18725 &sub,
18726 vec![item.clone()],
18727 WantarrayCtx::Void,
18728 line,
18729 )
18730 {
18731 Ok(_) => {}
18732 Err(e) => {
18733 let msg = match e {
18734 FlowOrError::Error(stryke) => stryke.to_string(),
18735 FlowOrError::Flow(_) => {
18736 "unexpected control flow in par_pipeline_stream tap"
18737 .into()
18738 }
18739 };
18740 let mut g = err_w.lock();
18741 if g.is_none() {
18742 *g = Some(msg);
18743 }
18744 break;
18745 }
18746 }
18747 if tx.send(item).is_err() {
18748 break;
18749 }
18750 }
18751 });
18752 }
18753 PipelineOp::PCache { ref sub, .. } => {
18754 let sub = Arc::clone(sub);
18755 scope.spawn(move || {
18756 while let Ok(item) = rx.recv() {
18757 if err_w.lock().is_some() {
18758 break;
18759 }
18760 let k = crate::pcache::cache_key(&item);
18761 let val = if let Some(cached) =
18762 crate::pcache::GLOBAL_PCACHE.get(&k)
18763 {
18764 cached.clone()
18765 } else {
18766 let mut interp = VMHelper::new();
18767 interp.subs = subs.clone();
18768 interp.scope.restore_capture(&capture);
18769 interp
18770 .scope
18771 .restore_atomics(&atomic_arrays, &atomic_hashes);
18772 interp.enable_parallel_guard();
18773 interp.scope.set_topic(item);
18774 interp.scope_push_hook();
18775 let v = match interp.exec_block_no_scope(&sub.body) {
18776 Ok(v) => v,
18777 Err(_) => StrykeValue::UNDEF,
18778 };
18779 interp.scope_pop_hook();
18780 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
18781 v
18782 };
18783 if tx.send(val).is_err() {
18784 break;
18785 }
18786 }
18787 });
18788 }
18789 _ => unreachable!(),
18791 }
18792 }
18793 }
18794
18795 channels.clear();
18799 drop(result_rx);
18800 });
18801
18802 if let Some(msg) = err.lock().take() {
18803 return Err(StrykeError::runtime(msg, line));
18804 }
18805
18806 let results = std::mem::take(&mut *results.lock());
18807 Ok(StrykeValue::array(results))
18808 }
18809
18810 fn heap_compare(&mut self, cmp: &Arc<StrykeSub>, a: &StrykeValue, b: &StrykeValue) -> Ordering {
18811 self.scope_push_hook();
18812 if let Some(ref env) = cmp.closure_env {
18813 self.scope.restore_capture(env);
18814 }
18815 self.scope.set_sort_pair(a.clone(), b.clone());
18816 let ord = match self.exec_block_no_scope(&cmp.body) {
18817 Ok(v) => {
18818 let n = v.to_int();
18819 if n < 0 {
18820 Ordering::Less
18821 } else if n > 0 {
18822 Ordering::Greater
18823 } else {
18824 Ordering::Equal
18825 }
18826 }
18827 Err(_) => Ordering::Equal,
18828 };
18829 self.scope_pop_hook();
18830 ord
18831 }
18832
18833 fn heap_sift_up(&mut self, items: &mut [StrykeValue], cmp: &Arc<StrykeSub>, mut i: usize) {
18834 while i > 0 {
18835 let p = (i - 1) / 2;
18836 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
18837 break;
18838 }
18839 items.swap(i, p);
18840 i = p;
18841 }
18842 }
18843
18844 fn heap_sift_down(&mut self, items: &mut [StrykeValue], cmp: &Arc<StrykeSub>, mut i: usize) {
18845 let n = items.len();
18846 loop {
18847 let mut sm = i;
18848 let l = 2 * i + 1;
18849 let r = 2 * i + 2;
18850 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
18851 sm = l;
18852 }
18853 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
18854 sm = r;
18855 }
18856 if sm == i {
18857 break;
18858 }
18859 items.swap(i, sm);
18860 i = sm;
18861 }
18862 }
18863
18864 fn hash_for_signature_destruct(
18865 &mut self,
18866 v: &StrykeValue,
18867 line: usize,
18868 ) -> StrykeResult<IndexMap<String, StrykeValue>> {
18869 let Some(m) = self.match_subject_as_hash(v) else {
18870 return Err(StrykeError::runtime(
18871 format!(
18872 "sub signature hash destruct: expected HASH or HASH reference, got {}",
18873 v.ref_type()
18874 ),
18875 line,
18876 ));
18877 };
18878 Ok(m)
18879 }
18880
18881 pub(crate) fn apply_sub_signature(
18883 &mut self,
18884 sub: &StrykeSub,
18885 argv: &[StrykeValue],
18886 line: usize,
18887 ) -> StrykeResult<()> {
18888 if sub.params.is_empty() {
18889 return Ok(());
18890 }
18891 let mut i = 0usize;
18892 for p in &sub.params {
18893 match p {
18894 SubSigParam::Scalar(name, ty, default) => {
18895 let val = if i < argv.len() {
18896 argv[i].clone()
18897 } else if let Some(default_expr) = default {
18898 match self.eval_expr(default_expr) {
18899 Ok(v) => v,
18900 Err(FlowOrError::Error(e)) => return Err(e),
18901 Err(FlowOrError::Flow(_)) => {
18902 return Err(StrykeError::runtime(
18903 "unexpected control flow in parameter default",
18904 line,
18905 ))
18906 }
18907 }
18908 } else {
18909 StrykeValue::UNDEF
18910 };
18911 i += 1;
18912 if let Some(t) = ty {
18913 if let Err(e) = t.check_value(&val) {
18914 return Err(StrykeError::runtime(
18915 format!("sub parameter ${}: {}", name, e),
18916 line,
18917 ));
18918 }
18919 }
18920 let n = self.english_scalar_name(name);
18921 self.scope.declare_scalar(n, val);
18922 }
18923 SubSigParam::Array(name, default) => {
18924 let rest: Vec<StrykeValue> = if i < argv.len() {
18925 let r = argv[i..].to_vec();
18926 i = argv.len();
18927 r
18928 } else if let Some(default_expr) = default {
18929 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
18930 Ok(v) => v,
18931 Err(FlowOrError::Error(e)) => return Err(e),
18932 Err(FlowOrError::Flow(_)) => {
18933 return Err(StrykeError::runtime(
18934 "unexpected control flow in parameter default",
18935 line,
18936 ))
18937 }
18938 };
18939 val.to_list()
18940 } else {
18941 vec![]
18942 };
18943 let aname = self.stash_array_name_for_package(name);
18944 self.scope.declare_array(&aname, rest);
18945 }
18946 SubSigParam::Hash(name, default) => {
18947 let rest: Vec<StrykeValue> = if i < argv.len() {
18948 let r = argv[i..].to_vec();
18949 i = argv.len();
18950 r
18951 } else if let Some(default_expr) = default {
18952 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
18953 Ok(v) => v,
18954 Err(FlowOrError::Error(e)) => return Err(e),
18955 Err(FlowOrError::Flow(_)) => {
18956 return Err(StrykeError::runtime(
18957 "unexpected control flow in parameter default",
18958 line,
18959 ))
18960 }
18961 };
18962 val.to_list()
18963 } else {
18964 vec![]
18965 };
18966 let mut map = IndexMap::new();
18967 let mut j = 0;
18968 while j + 1 < rest.len() {
18969 map.insert(rest[j].to_string(), rest[j + 1].clone());
18970 j += 2;
18971 }
18972 self.scope.declare_hash(name, map);
18973 }
18974 SubSigParam::ArrayDestruct(elems) => {
18975 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
18976 i += 1;
18977 let Some(arr) = self.match_subject_as_array(&arg) else {
18978 return Err(StrykeError::runtime(
18979 format!(
18980 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
18981 arg.ref_type()
18982 ),
18983 line,
18984 ));
18985 };
18986 let binds = self
18987 .match_array_pattern_elems(&arr, elems, line)
18988 .map_err(|e| match e {
18989 FlowOrError::Error(stryke) => stryke,
18990 FlowOrError::Flow(_) => StrykeError::runtime(
18991 "unexpected flow in sub signature array destruct",
18992 line,
18993 ),
18994 })?;
18995 let Some(binds) = binds else {
18996 return Err(StrykeError::runtime(
18997 "sub signature array destruct: length or element mismatch",
18998 line,
18999 ));
19000 };
19001 for b in binds {
19002 match b {
19003 PatternBinding::Scalar(name, v) => {
19004 let n = self.english_scalar_name(&name);
19005 self.scope.declare_scalar(n, v);
19006 }
19007 PatternBinding::Array(name, elems) => {
19008 self.scope.declare_array(&name, elems);
19009 }
19010 }
19011 }
19012 }
19013 SubSigParam::HashDestruct(pairs) => {
19014 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19015 i += 1;
19016 let map = self.hash_for_signature_destruct(&arg, line)?;
19017 for (key, varname) in pairs {
19018 let v = map.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
19019 let n = self.english_scalar_name(varname);
19020 self.scope.declare_scalar(n, v);
19021 }
19022 }
19023 }
19024 }
19025 Ok(())
19026 }
19027
19028 pub(crate) fn try_hof_dispatch(
19032 &mut self,
19033 sub: &StrykeSub,
19034 args: &[StrykeValue],
19035 want: WantarrayCtx,
19036 line: usize,
19037 ) -> Option<ExecResult> {
19038 let env = sub.closure_env.as_ref()?;
19039 fn env_get<'a>(env: &'a [(String, StrykeValue)], key: &str) -> Option<&'a StrykeValue> {
19040 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
19041 }
19042
19043 match sub.name.as_str() {
19044 "__comp__" => {
19046 let fns = env_get(env, "__comp_fns__")?.to_list();
19047 let mut val = args.first().cloned().unwrap_or(StrykeValue::UNDEF);
19048 for f in fns.iter().rev() {
19049 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
19050 Ok(v) => val = v,
19051 Err(e) => return Some(Err(e)),
19052 }
19053 }
19054 Some(Ok(val))
19055 }
19056 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
19058 "__juxt__" => {
19060 let fns = env_get(env, "__juxt_fns__")?.to_list();
19061 let mut results = Vec::with_capacity(fns.len());
19062 for f in &fns {
19063 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
19064 Ok(v) => results.push(v),
19065 Err(e) => return Some(Err(e)),
19066 }
19067 }
19068 Some(Ok(StrykeValue::array(results)))
19069 }
19070 "__partial__" => {
19072 let fn_val = env_get(env, "__partial_fn__")?.clone();
19073 let bound = env_get(env, "__partial_args__")?.to_list();
19074 let mut all_args = bound;
19075 all_args.extend_from_slice(args);
19076 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
19077 }
19078 "__complement__" => {
19080 let fn_val = env_get(env, "__complement_fn__")?.clone();
19081 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19082 Ok(v) => Some(Ok(StrykeValue::integer(if v.is_true() { 0 } else { 1 }))),
19083 Err(e) => Some(Err(e)),
19084 }
19085 }
19086 "__fnil__" => {
19088 let fn_val = env_get(env, "__fnil_fn__")?.clone();
19089 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
19090 let mut patched = args.to_vec();
19091 for (i, d) in defaults.iter().enumerate() {
19092 if i < patched.len() {
19093 if patched[i].is_undef() {
19094 patched[i] = d.clone();
19095 }
19096 } else {
19097 patched.push(d.clone());
19098 }
19099 }
19100 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
19101 }
19102 "__memoize__" => {
19104 let fn_val = env_get(env, "__memoize_fn__")?.clone();
19105 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
19106 let key = args
19107 .iter()
19108 .map(|a| a.to_string())
19109 .collect::<Vec<_>>()
19110 .join("\x00");
19111 if let Some(href) = cache_ref.as_hash_ref() {
19112 if let Some(cached) = href.read().get(&key) {
19113 return Some(Ok(cached.clone()));
19114 }
19115 }
19116 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19117 Ok(v) => {
19118 if let Some(href) = cache_ref.as_hash_ref() {
19119 href.write().insert(key, v.clone());
19120 }
19121 Some(Ok(v))
19122 }
19123 Err(e) => Some(Err(e)),
19124 }
19125 }
19126 "__curry__" => {
19128 let fn_val = env_get(env, "__curry_fn__")?.clone();
19129 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
19130 let bound = env_get(env, "__curry_bound__")?.to_list();
19131 let mut all = bound;
19132 all.extend_from_slice(args);
19133 if all.len() >= arity {
19134 Some(self.dispatch_indirect_call(fn_val, all, want, line))
19135 } else {
19136 let curry_sub = StrykeSub {
19137 name: "__curry__".to_string(),
19138 params: vec![],
19139 body: vec![],
19140 closure_env: Some(vec![
19141 ("__curry_fn__".to_string(), fn_val),
19142 (
19143 "__curry_arity__".to_string(),
19144 StrykeValue::integer(arity as i64),
19145 ),
19146 ("__curry_bound__".to_string(), StrykeValue::array(all)),
19147 ]),
19148 prototype: None,
19149 fib_like: None,
19150 };
19151 Some(Ok(StrykeValue::code_ref(Arc::new(curry_sub))))
19152 }
19153 }
19154 "__once__" => {
19156 let cache_ref = env_get(env, "__once_cache__")?.clone();
19157 if let Some(href) = cache_ref.as_hash_ref() {
19158 let r = href.read();
19159 if r.contains_key("done") {
19160 return Some(Ok(r.get("val").cloned().unwrap_or(StrykeValue::UNDEF)));
19161 }
19162 }
19163 let fn_val = env_get(env, "__once_fn__")?.clone();
19164 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19165 Ok(v) => {
19166 if let Some(href) = cache_ref.as_hash_ref() {
19167 let mut w = href.write();
19168 w.insert("done".to_string(), StrykeValue::integer(1));
19169 w.insert("val".to_string(), v.clone());
19170 }
19171 Some(Ok(v))
19172 }
19173 Err(e) => Some(Err(e)),
19174 }
19175 }
19176 _ => None,
19177 }
19178 }
19179
19180 pub(crate) fn call_sub(
19181 &mut self,
19182 sub: &StrykeSub,
19183 args: Vec<StrykeValue>,
19184 want: WantarrayCtx,
19185 line: usize,
19186 ) -> ExecResult {
19187 let pkg = sub.name.rsplit_once("::").map(|(p, _)| p.to_string());
19190 self.call_sub_with_package(sub, args, want, line, pkg)
19191 }
19192
19193 fn call_sub_with_package(
19197 &mut self,
19198 sub: &StrykeSub,
19199 args: Vec<StrykeValue>,
19200 want: WantarrayCtx,
19201 _line: usize,
19202 home_package: Option<String>,
19203 ) -> ExecResult {
19204 self.current_sub_stack.push(Arc::new(sub.clone()));
19206
19207 self.scope_push_hook();
19210 self.scope.declare_array("_", args.clone());
19211 if let Some(ref env) = sub.closure_env {
19212 self.scope.restore_capture(env);
19213 }
19214 if let Some(pkg) = home_package {
19220 self.scope
19221 .declare_scalar("__PACKAGE__", StrykeValue::string(pkg));
19222 }
19223 self.scope.set_closure_args(&args);
19227 let argv = self.scope.take_sub_underscore().unwrap_or_default();
19229 self.apply_sub_signature(sub, &argv, _line)?;
19230 let saved = self.wantarray_kind;
19231 self.wantarray_kind = want;
19232 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
19233 self.wantarray_kind = saved;
19234 self.scope_pop_hook();
19235 self.current_sub_stack.pop();
19236 return match r {
19237 Ok(v) => Ok(v),
19238 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19239 Err(e) => Err(e),
19240 };
19241 }
19242 if let Some(pat) = sub.fib_like.as_ref() {
19243 if argv.len() == 1 {
19244 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
19245 let t0 = self.profiler.is_some().then(std::time::Instant::now);
19246 if let Some(p) = &mut self.profiler {
19247 p.enter_sub(&sub.name);
19248 }
19249 self.debugger_enter_sub(&sub.name);
19250 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
19251 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
19252 p.exit_sub(t0.elapsed());
19253 }
19254 self.debugger_leave_sub();
19255 self.wantarray_kind = saved;
19256 self.scope_pop_hook();
19257 self.current_sub_stack.pop();
19258 return Ok(StrykeValue::integer(n));
19259 }
19260 }
19261 }
19262 self.scope.declare_array("_", argv.clone());
19263 let t0 = self.profiler.is_some().then(std::time::Instant::now);
19266 if let Some(p) = &mut self.profiler {
19267 p.enter_sub(&sub.name);
19268 }
19269 self.debugger_enter_sub(&sub.name);
19270 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
19274 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
19275 p.exit_sub(t0.elapsed());
19276 }
19277 self.debugger_leave_sub();
19278 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
19280 Some(self.scope.get_array("_"))
19281 } else {
19282 None
19283 };
19284 self.wantarray_kind = saved;
19285 self.scope_pop_hook();
19286 self.current_sub_stack.pop();
19287 match result {
19288 Ok(v) => Ok(v),
19289 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19290 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
19291 let goto_args = goto_args.unwrap_or_default();
19293 let fqn = if target_name.contains("::") {
19294 target_name.clone()
19295 } else {
19296 format!("{}::{}", self.current_package(), target_name)
19297 };
19298 if let Some(target_sub) = self
19299 .subs
19300 .get(&fqn)
19301 .cloned()
19302 .or_else(|| self.subs.get(&target_name).cloned())
19303 {
19304 self.call_sub(&target_sub, goto_args, want, _line)
19305 } else {
19306 Err(StrykeError::runtime(
19307 format!("Undefined subroutine &{}", target_name),
19308 _line,
19309 )
19310 .into())
19311 }
19312 }
19313 Err(FlowOrError::Flow(Flow::Yield(_))) => {
19314 Err(StrykeError::runtime("yield is only valid inside gen { }", 0).into())
19315 }
19316 Err(e) => Err(e),
19317 }
19318 }
19319
19320 fn call_struct_method(
19322 &mut self,
19323 body: &Block,
19324 params: &[SubSigParam],
19325 args: Vec<StrykeValue>,
19326 line: usize,
19327 ) -> ExecResult {
19328 self.scope_push_hook();
19329 self.scope.declare_array("_", args.clone());
19330 if let Some(self_val) = args.first() {
19332 self.scope.declare_scalar("self", self_val.clone());
19333 }
19334 if args.len() > 1 {
19340 self.scope.set_closure_args(&args[1..]);
19341 }
19342 let user_args: Vec<StrykeValue> = args.iter().skip(1).cloned().collect();
19344 self.apply_params_to_argv(params, &user_args, line)?;
19345 let result = self.exec_block_no_scope(body);
19346 self.scope_pop_hook();
19347 match result {
19348 Ok(v) => Ok(v),
19349 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19350 Err(e) => Err(e),
19351 }
19352 }
19353
19354 pub(crate) fn call_class_method(
19356 &mut self,
19357 body: &Block,
19358 params: &[SubSigParam],
19359 args: Vec<StrykeValue>,
19360 line: usize,
19361 ) -> ExecResult {
19362 self.call_class_method_inner(body, params, args, line, false)
19363 }
19364
19365 pub(crate) fn call_static_class_method(
19367 &mut self,
19368 body: &Block,
19369 params: &[SubSigParam],
19370 args: Vec<StrykeValue>,
19371 line: usize,
19372 ) -> ExecResult {
19373 self.call_class_method_inner(body, params, args, line, true)
19374 }
19375
19376 fn call_class_method_inner(
19377 &mut self,
19378 body: &Block,
19379 params: &[SubSigParam],
19380 args: Vec<StrykeValue>,
19381 line: usize,
19382 is_static: bool,
19383 ) -> ExecResult {
19384 self.scope_push_hook();
19385 self.scope.declare_array("_", args.clone());
19386 if !is_static {
19387 if let Some(self_val) = args.first() {
19389 self.scope.declare_scalar("self", self_val.clone());
19390 }
19391 }
19392 if is_static {
19399 self.scope.set_closure_args(&args);
19400 } else if args.len() > 1 {
19401 self.scope.set_closure_args(&args[1..]);
19402 }
19403 let user_args: Vec<StrykeValue> = if is_static {
19405 args.clone()
19406 } else {
19407 args.iter().skip(1).cloned().collect()
19408 };
19409 self.apply_params_to_argv(params, &user_args, line)?;
19410 let result = self.exec_block_no_scope(body);
19411 self.scope_pop_hook();
19412 match result {
19413 Ok(v) => Ok(v),
19414 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19415 Err(e) => Err(e),
19416 }
19417 }
19418
19419 fn apply_params_to_argv(
19421 &mut self,
19422 params: &[SubSigParam],
19423 argv: &[StrykeValue],
19424 line: usize,
19425 ) -> StrykeResult<()> {
19426 let mut i = 0;
19427 for param in params {
19428 match param {
19429 SubSigParam::Scalar(name, ty_opt, default) => {
19430 let v = if i < argv.len() {
19431 argv[i].clone()
19432 } else if let Some(default_expr) = default {
19433 match self.eval_expr(default_expr) {
19434 Ok(v) => v,
19435 Err(FlowOrError::Error(e)) => return Err(e),
19436 Err(FlowOrError::Flow(_)) => {
19437 return Err(StrykeError::runtime(
19438 "unexpected control flow in parameter default",
19439 line,
19440 ))
19441 }
19442 }
19443 } else {
19444 StrykeValue::UNDEF
19445 };
19446 i += 1;
19447 if let Some(ty) = ty_opt {
19448 ty.check_value(&v).map_err(|msg| {
19449 StrykeError::type_error(
19450 format!("method parameter ${}: {}", name, msg),
19451 line,
19452 )
19453 })?;
19454 }
19455 let n = self.english_scalar_name(name);
19456 self.scope.declare_scalar(n, v);
19457 }
19458 SubSigParam::Array(name, default) => {
19459 let rest: Vec<StrykeValue> = if i < argv.len() {
19460 let r = argv[i..].to_vec();
19461 i = argv.len();
19462 r
19463 } else if let Some(default_expr) = default {
19464 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19465 Ok(v) => v,
19466 Err(FlowOrError::Error(e)) => return Err(e),
19467 Err(FlowOrError::Flow(_)) => {
19468 return Err(StrykeError::runtime(
19469 "unexpected control flow in parameter default",
19470 line,
19471 ))
19472 }
19473 };
19474 val.to_list()
19475 } else {
19476 vec![]
19477 };
19478 let aname = self.stash_array_name_for_package(name);
19479 self.scope.declare_array(&aname, rest);
19480 }
19481 SubSigParam::Hash(name, default) => {
19482 let rest: Vec<StrykeValue> = if i < argv.len() {
19483 let r = argv[i..].to_vec();
19484 i = argv.len();
19485 r
19486 } else if let Some(default_expr) = default {
19487 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19488 Ok(v) => v,
19489 Err(FlowOrError::Error(e)) => return Err(e),
19490 Err(FlowOrError::Flow(_)) => {
19491 return Err(StrykeError::runtime(
19492 "unexpected control flow in parameter default",
19493 line,
19494 ))
19495 }
19496 };
19497 val.to_list()
19498 } else {
19499 vec![]
19500 };
19501 let mut map = IndexMap::new();
19502 let mut j = 0;
19503 while j + 1 < rest.len() {
19504 map.insert(rest[j].to_string(), rest[j + 1].clone());
19505 j += 2;
19506 }
19507 self.scope.declare_hash(name, map);
19508 }
19509 SubSigParam::ArrayDestruct(elems) => {
19510 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19511 i += 1;
19512 let Some(arr) = self.match_subject_as_array(&arg) else {
19513 return Err(StrykeError::runtime(
19514 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
19515 line,
19516 ));
19517 };
19518 let binds = self
19519 .match_array_pattern_elems(&arr, elems, line)
19520 .map_err(|e| match e {
19521 FlowOrError::Error(stryke) => stryke,
19522 FlowOrError::Flow(_) => StrykeError::runtime(
19523 "unexpected flow in method array destruct",
19524 line,
19525 ),
19526 })?;
19527 let Some(binds) = binds else {
19528 return Err(StrykeError::runtime(
19529 format!(
19530 "method parameter: array destructure failed at position {}",
19531 i
19532 ),
19533 line,
19534 ));
19535 };
19536 for b in binds {
19537 match b {
19538 PatternBinding::Scalar(name, v) => {
19539 let n = self.english_scalar_name(&name);
19540 self.scope.declare_scalar(n, v);
19541 }
19542 PatternBinding::Array(name, elems) => {
19543 self.scope.declare_array(&name, elems);
19544 }
19545 }
19546 }
19547 }
19548 SubSigParam::HashDestruct(pairs) => {
19549 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19550 i += 1;
19551 let map = self.hash_for_signature_destruct(&arg, line)?;
19552 for (key, varname) in pairs {
19553 let v = map.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
19554 let n = self.english_scalar_name(varname);
19555 self.scope.declare_scalar(n, v);
19556 }
19557 }
19558 }
19559 }
19560 Ok(())
19561 }
19562
19563 fn builtin_new(&mut self, class: &str, args: Vec<StrykeValue>, line: usize) -> ExecResult {
19564 if class == "Set" {
19565 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
19566 }
19567 if let Some(def) = self.struct_defs.get(class).cloned() {
19568 let mut provided = Vec::new();
19569 let mut i = 1;
19570 while i + 1 < args.len() {
19571 let k = args[i].to_string();
19572 let v = args[i + 1].clone();
19573 provided.push((k, v));
19574 i += 2;
19575 }
19576 let mut defaults = Vec::with_capacity(def.fields.len());
19577 for field in &def.fields {
19578 if let Some(ref expr) = field.default {
19579 let val = self.eval_expr(expr)?;
19580 defaults.push(Some(val));
19581 } else {
19582 defaults.push(None);
19583 }
19584 }
19585 return Ok(crate::native_data::struct_new_with_defaults(
19586 &def, &provided, &defaults, line,
19587 )?);
19588 }
19589 if let Some(def) = self.class_defs.get(class).cloned() {
19598 let user_args: Vec<StrykeValue> = args.into_iter().skip(1).collect();
19599 return self.class_construct(&def, user_args, line);
19600 }
19601 let mut map = IndexMap::new();
19603 let mut i = 1; while i + 1 < args.len() {
19605 let k = args[i].to_string();
19606 let v = args[i + 1].clone();
19607 map.insert(k, v);
19608 i += 2;
19609 }
19610 Ok(StrykeValue::blessed(Arc::new(
19611 crate::value::BlessedRef::new_blessed(class.to_string(), StrykeValue::hash(map)),
19612 )))
19613 }
19614
19615 fn exec_print(
19616 &mut self,
19617 handle: Option<&str>,
19618 args: &[Expr],
19619 newline: bool,
19620 line: usize,
19621 ) -> ExecResult {
19622 if newline && (self.feature_bits & FEAT_SAY) == 0 {
19623 return Err(StrykeError::runtime(
19624 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
19625 line,
19626 )
19627 .into());
19628 }
19629 let mut output = String::new();
19630 if args.is_empty() {
19631 let topic = self.scope.get_scalar("_").clone();
19633 let s = self.stringify_value(topic, line)?;
19634 output.push_str(&s);
19635 } else {
19636 for (i, a) in args.iter().enumerate() {
19639 if i > 0 {
19640 output.push_str(&self.ofs);
19641 }
19642 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
19643 for item in val.to_list() {
19644 let s = self.stringify_value(item, line)?;
19645 output.push_str(&s);
19646 }
19647 }
19648 }
19649 if newline {
19650 output.push('\n');
19651 }
19652 output.push_str(&self.ors);
19653
19654 let handle_name =
19655 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
19656 self.write_formatted_print(handle_name.as_str(), &output, line)?;
19657 Ok(StrykeValue::integer(1))
19658 }
19659
19660 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
19661 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
19662 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
19664 (s, &[])
19665 } else {
19666 (self.eval_expr(&args[0])?.to_string(), &args[1..])
19667 };
19668 let mut arg_vals = Vec::new();
19672 for a in rest {
19673 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
19674 if let Some(items) = v.as_array_vec() {
19675 arg_vals.extend(items);
19676 } else {
19677 arg_vals.push(v);
19678 }
19679 }
19680 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
19681 let handle_name =
19682 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
19683 match handle_name.as_str() {
19684 "STDOUT" => {
19685 if !self.suppress_stdout {
19686 print!("{}", output);
19687 if self.output_autoflush {
19688 let _ = io::stdout().flush();
19689 }
19690 }
19691 }
19692 "STDERR" => {
19693 eprint!("{}", output);
19694 let _ = io::stderr().flush();
19695 }
19696 name => {
19697 if let Some(writer) = self.output_handles.get_mut(name) {
19698 let _ = writer.write_all(output.as_bytes());
19699 if self.output_autoflush {
19700 let _ = writer.flush();
19701 }
19702 }
19703 }
19704 }
19705 Ok(StrykeValue::integer(1))
19706 }
19707
19708 pub(crate) fn eval_substr_expr(
19710 &mut self,
19711 string: &Expr,
19712 offset: &Expr,
19713 length: Option<&Expr>,
19714 replacement: Option<&Expr>,
19715 _line: usize,
19716 ) -> Result<StrykeValue, FlowOrError> {
19717 let s = self.eval_expr(string)?.to_string();
19718 let off = self.eval_expr(offset)?.to_int();
19719 let start = if off < 0 {
19720 (s.len() as i64 + off).max(0) as usize
19721 } else {
19722 off as usize
19723 };
19724 let len = if let Some(l) = length {
19725 let len_val = self.eval_expr(l)?.to_int();
19726 if len_val < 0 {
19727 let remaining = s.len().saturating_sub(start) as i64;
19729 (remaining + len_val).max(0) as usize
19730 } else {
19731 len_val as usize
19732 }
19733 } else {
19734 s.len().saturating_sub(start)
19735 };
19736 let end = start.saturating_add(len).min(s.len());
19737 let result = s.get(start..end).unwrap_or("").to_string();
19738 if let Some(rep) = replacement {
19739 let rep_s = self.eval_expr(rep)?.to_string();
19740 let mut new_s = String::new();
19741 new_s.push_str(&s[..start]);
19742 new_s.push_str(&rep_s);
19743 new_s.push_str(&s[end..]);
19744 self.assign_value(string, StrykeValue::string(new_s))?;
19745 }
19746 Ok(StrykeValue::string(result))
19747 }
19748
19749 pub(crate) fn eval_push_expr(
19750 &mut self,
19751 array: &Expr,
19752 values: &[Expr],
19753 line: usize,
19754 ) -> Result<StrykeValue, FlowOrError> {
19755 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19756 for v in values {
19757 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19758 self.push_array_deref_value(aref.clone(), val, line)?;
19759 }
19760 let len = self.array_deref_len(aref, line)?;
19761 return Ok(StrykeValue::integer(len));
19762 }
19763 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19764 if self.scope.is_array_frozen(&arr_name) {
19765 return Err(StrykeError::runtime(
19766 format!("Modification of a frozen value: @{}", arr_name),
19767 line,
19768 )
19769 .into());
19770 }
19771 for v in values {
19772 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19773 if let Some(items) = val.as_array_vec() {
19774 for item in items {
19775 self.scope
19776 .push_to_array(&arr_name, item)
19777 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19778 }
19779 } else {
19780 self.scope
19781 .push_to_array(&arr_name, val)
19782 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19783 }
19784 }
19785 let len = self.scope.array_len(&arr_name);
19786 Ok(StrykeValue::integer(len as i64))
19787 }
19788
19789 pub(crate) fn eval_pop_expr(
19790 &mut self,
19791 array: &Expr,
19792 line: usize,
19793 ) -> Result<StrykeValue, FlowOrError> {
19794 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19795 return self.pop_array_deref(aref, line);
19796 }
19797 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19798 self.scope
19799 .pop_from_array(&arr_name)
19800 .map_err(|e| FlowOrError::Error(e.at_line(line)))
19801 }
19802
19803 pub(crate) fn eval_shift_expr(
19804 &mut self,
19805 array: &Expr,
19806 line: usize,
19807 ) -> Result<StrykeValue, FlowOrError> {
19808 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19809 return self.shift_array_deref(aref, line);
19810 }
19811 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19812 self.scope
19813 .shift_from_array(&arr_name)
19814 .map_err(|e| FlowOrError::Error(e.at_line(line)))
19815 }
19816
19817 pub(crate) fn eval_unshift_expr(
19818 &mut self,
19819 array: &Expr,
19820 values: &[Expr],
19821 line: usize,
19822 ) -> Result<StrykeValue, FlowOrError> {
19823 if let Some(aref) = self.try_eval_array_deref_container(array)? {
19824 let mut vals = Vec::new();
19825 for v in values {
19826 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19827 if let Some(items) = val.as_array_vec() {
19828 vals.extend(items);
19829 } else {
19830 vals.push(val);
19831 }
19832 }
19833 let len = self.unshift_array_deref_multi(aref, vals, line)?;
19834 return Ok(StrykeValue::integer(len));
19835 }
19836 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
19837 let mut vals = Vec::new();
19838 for v in values {
19839 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
19840 if let Some(items) = val.as_array_vec() {
19841 vals.extend(items);
19842 } else {
19843 vals.push(val);
19844 }
19845 }
19846 let arr = self
19847 .scope
19848 .get_array_mut(&arr_name)
19849 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19850 for (i, v) in vals.into_iter().enumerate() {
19851 arr.insert(i, v);
19852 }
19853 let len = arr.len();
19854 Ok(StrykeValue::integer(len as i64))
19855 }
19856
19857 pub(crate) fn push_array_deref_value(
19859 &mut self,
19860 arr_ref: StrykeValue,
19861 val: StrykeValue,
19862 line: usize,
19863 ) -> Result<(), FlowOrError> {
19864 let val = self.scope.resolve_container_binding_ref(val);
19867 if let Some(r) = arr_ref.as_array_ref() {
19868 let mut w = r.write();
19869 if let Some(items) = val.as_array_vec() {
19870 w.extend(items.iter().cloned());
19871 } else {
19872 w.push(val);
19873 }
19874 return Ok(());
19875 }
19876 if let Some(name) = arr_ref.as_array_binding_name() {
19877 if let Some(items) = val.as_array_vec() {
19878 for item in items {
19879 self.scope
19880 .push_to_array(&name, item)
19881 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19882 }
19883 } else {
19884 self.scope
19885 .push_to_array(&name, val)
19886 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19887 }
19888 return Ok(());
19889 }
19890 if let Some(s) = arr_ref.as_str() {
19891 if self.strict_refs {
19892 return Err(StrykeError::runtime(
19893 format!(
19894 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19895 s
19896 ),
19897 line,
19898 )
19899 .into());
19900 }
19901 let name = s.to_string();
19902 if let Some(items) = val.as_array_vec() {
19903 for item in items {
19904 self.scope
19905 .push_to_array(&name, item)
19906 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19907 }
19908 } else {
19909 self.scope
19910 .push_to_array(&name, val)
19911 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
19912 }
19913 return Ok(());
19914 }
19915 Err(StrykeError::runtime("push argument is not an ARRAY reference", line).into())
19916 }
19917
19918 pub(crate) fn array_deref_len(
19919 &self,
19920 arr_ref: StrykeValue,
19921 line: usize,
19922 ) -> Result<i64, FlowOrError> {
19923 if let Some(r) = arr_ref.as_array_ref() {
19924 return Ok(r.read().len() as i64);
19925 }
19926 if let Some(name) = arr_ref.as_array_binding_name() {
19927 return Ok(self.scope.array_len(&name) as i64);
19928 }
19929 if let Some(s) = arr_ref.as_str() {
19930 if self.strict_refs {
19931 return Err(StrykeError::runtime(
19932 format!(
19933 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19934 s
19935 ),
19936 line,
19937 )
19938 .into());
19939 }
19940 return Ok(self.scope.array_len(&s) as i64);
19941 }
19942 Err(StrykeError::runtime("argument is not an ARRAY reference", line).into())
19943 }
19944
19945 pub(crate) fn pop_array_deref(
19946 &mut self,
19947 arr_ref: StrykeValue,
19948 line: usize,
19949 ) -> Result<StrykeValue, FlowOrError> {
19950 if let Some(r) = arr_ref.as_array_ref() {
19951 let mut w = r.write();
19952 return Ok(w.pop().unwrap_or(StrykeValue::UNDEF));
19953 }
19954 if let Some(name) = arr_ref.as_array_binding_name() {
19955 return self
19956 .scope
19957 .pop_from_array(&name)
19958 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19959 }
19960 if let Some(s) = arr_ref.as_str() {
19961 if self.strict_refs {
19962 return Err(StrykeError::runtime(
19963 format!(
19964 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
19965 s
19966 ),
19967 line,
19968 )
19969 .into());
19970 }
19971 return self
19972 .scope
19973 .pop_from_array(&s)
19974 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19975 }
19976 Err(StrykeError::runtime("pop argument is not an ARRAY reference", line).into())
19977 }
19978
19979 pub(crate) fn shift_array_deref(
19980 &mut self,
19981 arr_ref: StrykeValue,
19982 line: usize,
19983 ) -> Result<StrykeValue, FlowOrError> {
19984 if let Some(r) = arr_ref.as_array_ref() {
19985 let mut w = r.write();
19986 return Ok(if w.is_empty() {
19987 StrykeValue::UNDEF
19988 } else {
19989 w.remove(0)
19990 });
19991 }
19992 if let Some(name) = arr_ref.as_array_binding_name() {
19993 return self
19994 .scope
19995 .shift_from_array(&name)
19996 .map_err(|e| FlowOrError::Error(e.at_line(line)));
19997 }
19998 if let Some(s) = arr_ref.as_str() {
19999 if self.strict_refs {
20000 return Err(StrykeError::runtime(
20001 format!(
20002 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20003 s
20004 ),
20005 line,
20006 )
20007 .into());
20008 }
20009 return self
20010 .scope
20011 .shift_from_array(&s)
20012 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20013 }
20014 Err(StrykeError::runtime("shift argument is not an ARRAY reference", line).into())
20015 }
20016
20017 pub(crate) fn unshift_array_deref_multi(
20018 &mut self,
20019 arr_ref: StrykeValue,
20020 vals: Vec<StrykeValue>,
20021 line: usize,
20022 ) -> Result<i64, FlowOrError> {
20023 let mut flat: Vec<StrykeValue> = Vec::new();
20024 for v in vals {
20025 if let Some(items) = v.as_array_vec() {
20026 flat.extend(items);
20027 } else {
20028 flat.push(v);
20029 }
20030 }
20031 if let Some(r) = arr_ref.as_array_ref() {
20032 let mut w = r.write();
20033 for (i, v) in flat.into_iter().enumerate() {
20034 w.insert(i, v);
20035 }
20036 return Ok(w.len() as i64);
20037 }
20038 if let Some(name) = arr_ref.as_array_binding_name() {
20039 let arr = self
20040 .scope
20041 .get_array_mut(&name)
20042 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20043 for (i, v) in flat.into_iter().enumerate() {
20044 arr.insert(i, v);
20045 }
20046 return Ok(arr.len() as i64);
20047 }
20048 if let Some(s) = arr_ref.as_str() {
20049 if self.strict_refs {
20050 return Err(StrykeError::runtime(
20051 format!(
20052 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20053 s
20054 ),
20055 line,
20056 )
20057 .into());
20058 }
20059 let name = s.to_string();
20060 let arr = self
20061 .scope
20062 .get_array_mut(&name)
20063 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20064 for (i, v) in flat.into_iter().enumerate() {
20065 arr.insert(i, v);
20066 }
20067 return Ok(arr.len() as i64);
20068 }
20069 Err(StrykeError::runtime("unshift argument is not an ARRAY reference", line).into())
20070 }
20071
20072 pub(crate) fn splice_array_deref(
20075 &mut self,
20076 aref: StrykeValue,
20077 offset_val: StrykeValue,
20078 length_val: StrykeValue,
20079 rep_vals: Vec<StrykeValue>,
20080 line: usize,
20081 ) -> Result<StrykeValue, FlowOrError> {
20082 let ctx = self.wantarray_kind;
20083 if let Some(r) = aref.as_array_ref() {
20084 let arr_len = r.read().len();
20085 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20086 let mut w = r.write();
20087 let removed: Vec<StrykeValue> = w.drain(off..end).collect();
20088 for (i, v) in rep_vals.into_iter().enumerate() {
20089 w.insert(off + i, v);
20090 }
20091 return Ok(match ctx {
20092 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20093 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20094 });
20095 }
20096 if let Some(name) = aref.as_array_binding_name() {
20097 let arr_len = self.scope.array_len(&name);
20098 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20099 let removed = self
20100 .scope
20101 .splice_in_place(&name, off, end, rep_vals)
20102 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20103 return Ok(match ctx {
20104 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20105 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20106 });
20107 }
20108 if let Some(s) = aref.as_str() {
20109 if self.strict_refs {
20110 return Err(StrykeError::runtime(
20111 format!(
20112 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20113 s
20114 ),
20115 line,
20116 )
20117 .into());
20118 }
20119 let arr_len = self.scope.array_len(&s);
20120 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20121 let removed = self
20122 .scope
20123 .splice_in_place(&s, off, end, rep_vals)
20124 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20125 return Ok(match ctx {
20126 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20127 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20128 });
20129 }
20130 Err(StrykeError::runtime("splice argument is not an ARRAY reference", line).into())
20131 }
20132
20133 pub(crate) fn eval_splice_expr(
20134 &mut self,
20135 array: &Expr,
20136 offset: Option<&Expr>,
20137 length: Option<&Expr>,
20138 replacement: &[Expr],
20139 ctx: WantarrayCtx,
20140 line: usize,
20141 ) -> Result<StrykeValue, FlowOrError> {
20142 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20143 let offset_val = if let Some(o) = offset {
20144 self.eval_expr(o)?
20145 } else {
20146 StrykeValue::integer(0)
20147 };
20148 let length_val = if let Some(l) = length {
20149 self.eval_expr(l)?
20150 } else {
20151 StrykeValue::UNDEF
20152 };
20153 let mut rep_vals = Vec::new();
20154 for r in replacement {
20155 rep_vals.push(self.eval_expr(r)?);
20156 }
20157 let saved = self.wantarray_kind;
20158 self.wantarray_kind = ctx;
20159 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
20160 self.wantarray_kind = saved;
20161 return out;
20162 }
20163 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20164 let arr_len = self.scope.array_len(&arr_name);
20165 let offset_val = if let Some(o) = offset {
20166 self.eval_expr(o)?
20167 } else {
20168 StrykeValue::integer(0)
20169 };
20170 let length_val = if let Some(l) = length {
20171 self.eval_expr(l)?
20172 } else {
20173 StrykeValue::UNDEF
20174 };
20175 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20176 let mut rep_vals = Vec::new();
20177 for r in replacement {
20178 rep_vals.push(self.eval_expr(r)?);
20179 }
20180 let removed = self
20181 .scope
20182 .splice_in_place(&arr_name, off, end, rep_vals)
20183 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20184 Ok(match ctx {
20185 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20186 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20187 })
20188 }
20189
20190 pub(crate) fn keys_from_value(
20192 val: StrykeValue,
20193 line: usize,
20194 ) -> Result<StrykeValue, FlowOrError> {
20195 if let Some(h) = val.as_hash_map() {
20196 Ok(StrykeValue::array(
20197 h.keys().map(|k| StrykeValue::string(k.clone())).collect(),
20198 ))
20199 } else if let Some(r) = val.as_hash_ref() {
20200 Ok(StrykeValue::array(
20201 r.read()
20202 .keys()
20203 .map(|k| StrykeValue::string(k.clone()))
20204 .collect(),
20205 ))
20206 } else {
20207 Err(StrykeError::runtime("keys requires hash", line).into())
20208 }
20209 }
20210
20211 pub(crate) fn eval_keys_expr(
20212 &mut self,
20213 expr: &Expr,
20214 line: usize,
20215 ) -> Result<StrykeValue, FlowOrError> {
20216 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
20219 Self::keys_from_value(val, line)
20220 }
20221
20222 pub(crate) fn values_from_value(
20224 val: StrykeValue,
20225 line: usize,
20226 ) -> Result<StrykeValue, FlowOrError> {
20227 if let Some(h) = val.as_hash_map() {
20228 Ok(StrykeValue::array(h.values().cloned().collect()))
20229 } else if let Some(r) = val.as_hash_ref() {
20230 Ok(StrykeValue::array(r.read().values().cloned().collect()))
20231 } else {
20232 Err(StrykeError::runtime("values requires hash", line).into())
20233 }
20234 }
20235
20236 pub(crate) fn eval_values_expr(
20237 &mut self,
20238 expr: &Expr,
20239 line: usize,
20240 ) -> Result<StrykeValue, FlowOrError> {
20241 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
20242 Self::values_from_value(val, line)
20243 }
20244
20245 pub(crate) fn eval_delete_operand(
20246 &mut self,
20247 expr: &Expr,
20248 line: usize,
20249 ) -> Result<StrykeValue, FlowOrError> {
20250 match &expr.kind {
20251 ExprKind::HashElement { hash, key } => {
20252 let k = self.eval_expr(key)?.to_string();
20253 self.touch_env_hash(hash);
20254 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
20255 let class = obj
20256 .as_blessed_ref()
20257 .map(|b| b.class.clone())
20258 .unwrap_or_default();
20259 let full = format!("{}::DELETE", class);
20260 if let Some(sub) = self.subs.get(&full).cloned() {
20261 return self.call_sub(
20262 &sub,
20263 vec![obj, StrykeValue::string(k)],
20264 WantarrayCtx::Scalar,
20265 line,
20266 );
20267 }
20268 }
20269 self.scope
20270 .delete_hash_element(hash, &k)
20271 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20272 }
20273 ExprKind::ArrayElement { array, index } => {
20274 self.check_strict_array_var(array, line)?;
20275 let idx = self.eval_expr(index)?.to_int();
20276 let aname = self.stash_array_name_for_package(array);
20277 self.scope
20278 .delete_array_element(&aname, idx)
20279 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20280 }
20281 ExprKind::ArrowDeref {
20282 expr: inner,
20283 index,
20284 kind: DerefKind::Hash,
20285 } => {
20286 let k = self.eval_expr(index)?.to_string();
20287 let container = self.eval_expr(inner)?;
20288 self.delete_arrow_hash_element(container, &k, line)
20289 .map_err(Into::into)
20290 }
20291 ExprKind::ArrowDeref {
20292 expr: inner,
20293 index,
20294 kind: DerefKind::Array,
20295 } => {
20296 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
20297 return Err(StrykeError::runtime(
20298 "delete on array element needs scalar subscript",
20299 line,
20300 )
20301 .into());
20302 }
20303 let container = self.eval_expr(inner)?;
20304 let idx = self.eval_expr(index)?.to_int();
20305 self.delete_arrow_array_element(container, idx, line)
20306 .map_err(Into::into)
20307 }
20308 _ => Err(StrykeError::runtime("delete requires hash or array element", line).into()),
20309 }
20310 }
20311
20312 fn eval_expr_exists_mode(&mut self, expr: &Expr) -> Result<StrykeValue, FlowOrError> {
20318 match &expr.kind {
20319 ExprKind::ArrowDeref {
20320 expr: inner,
20321 index,
20322 kind: DerefKind::Hash,
20323 } => {
20324 let inner_val = self.eval_expr_exists_mode(inner)?;
20325 if inner_val.is_undef() {
20326 return Ok(StrykeValue::UNDEF);
20327 }
20328 if let Some(r) = inner_val.as_hash_ref() {
20329 let k = self.eval_expr(index)?.to_string();
20330 return Ok(r.read().get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
20331 }
20332 if let Some(b) = inner_val.as_blessed_ref() {
20333 let data = b.data.read();
20334 if let Some(r) = data.as_hash_ref() {
20335 let k = self.eval_expr(index)?.to_string();
20336 return Ok(r.read().get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
20337 }
20338 }
20339 if let Some(s) = inner_val.as_struct_inst() {
20343 let k = self.eval_expr(index)?.to_string();
20344 if let Some(idx) = s.def.field_index(&k) {
20345 return Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF));
20346 }
20347 return Ok(StrykeValue::UNDEF);
20348 }
20349 if let Some(c) = inner_val.as_class_inst() {
20350 let k = self.eval_expr(index)?.to_string();
20351 if let Some(idx) = c.def.field_index(&k) {
20352 return Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF));
20353 }
20354 return Ok(StrykeValue::UNDEF);
20355 }
20356 Ok(StrykeValue::UNDEF)
20357 }
20358 ExprKind::ArrowDeref {
20359 expr: inner,
20360 index,
20361 kind: DerefKind::Array,
20362 } => {
20363 let inner_val = self.eval_expr_exists_mode(inner)?;
20364 if inner_val.is_undef() {
20365 return Ok(StrykeValue::UNDEF);
20366 }
20367 if let Some(r) = inner_val.as_array_ref() {
20368 let idx = self.eval_expr(index)?.to_int();
20369 let arr = r.read();
20370 let i = if idx < 0 {
20371 (arr.len() as i64 + idx).max(0) as usize
20372 } else {
20373 idx as usize
20374 };
20375 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
20376 }
20377 Ok(StrykeValue::UNDEF)
20378 }
20379 _ => self.eval_expr(expr),
20380 }
20381 }
20382
20383 pub(crate) fn eval_exists_operand(
20384 &mut self,
20385 expr: &Expr,
20386 line: usize,
20387 ) -> Result<StrykeValue, FlowOrError> {
20388 match &expr.kind {
20389 ExprKind::HashElement { hash, key } => {
20390 let k = self.eval_expr(key)?.to_string();
20391 self.touch_env_hash(hash);
20392 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
20393 let class = obj
20394 .as_blessed_ref()
20395 .map(|b| b.class.clone())
20396 .unwrap_or_default();
20397 let full = format!("{}::EXISTS", class);
20398 if let Some(sub) = self.subs.get(&full).cloned() {
20399 return self.call_sub(
20400 &sub,
20401 vec![obj, StrykeValue::string(k)],
20402 WantarrayCtx::Scalar,
20403 line,
20404 );
20405 }
20406 }
20407 Ok(StrykeValue::integer(
20408 if self.scope.exists_hash_element(hash, &k) {
20409 1
20410 } else {
20411 0
20412 },
20413 ))
20414 }
20415 ExprKind::ArrayElement { array, index } => {
20416 self.check_strict_array_var(array, line)?;
20417 let idx = self.eval_expr(index)?.to_int();
20418 let aname = self.stash_array_name_for_package(array);
20419 Ok(StrykeValue::integer(
20420 if self.scope.exists_array_element(&aname, idx) {
20421 1
20422 } else {
20423 0
20424 },
20425 ))
20426 }
20427 ExprKind::ArrowDeref {
20428 expr: inner,
20429 index,
20430 kind: DerefKind::Hash,
20431 } => {
20432 let k = self.eval_expr(index)?.to_string();
20433 let container = match self.eval_expr_exists_mode(inner) {
20438 Ok(v) => v,
20439 Err(_) => return Ok(StrykeValue::integer(0)),
20440 };
20441 if container.is_undef() {
20442 return Ok(StrykeValue::integer(0));
20443 }
20444 let yes = self.exists_arrow_hash_element(container, &k, line)?;
20445 Ok(StrykeValue::integer(if yes { 1 } else { 0 }))
20446 }
20447 ExprKind::ArrowDeref {
20448 expr: inner,
20449 index,
20450 kind: DerefKind::Array,
20451 } => {
20452 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
20453 return Err(StrykeError::runtime(
20454 "exists on array element needs scalar subscript",
20455 line,
20456 )
20457 .into());
20458 }
20459 let container = match self.eval_expr_exists_mode(inner) {
20460 Ok(v) => v,
20461 Err(_) => return Ok(StrykeValue::integer(0)),
20462 };
20463 if container.is_undef() {
20464 return Ok(StrykeValue::integer(0));
20465 }
20466 let idx = self.eval_expr(index)?.to_int();
20467 let yes = self.exists_arrow_array_element(container, idx, line)?;
20468 Ok(StrykeValue::integer(if yes { 1 } else { 0 }))
20469 }
20470 _ => Err(StrykeError::runtime("exists requires hash or array element", line).into()),
20471 }
20472 }
20473
20474 pub(crate) fn eval_pmap_remote(
20482 &mut self,
20483 cluster_pv: StrykeValue,
20484 list_pv: StrykeValue,
20485 show_progress: bool,
20486 block: &Block,
20487 flat_outputs: bool,
20488 line: usize,
20489 ) -> Result<StrykeValue, FlowOrError> {
20490 let Some(cluster) = cluster_pv.as_remote_cluster() else {
20491 return Err(StrykeError::runtime("pmap_on: expected cluster(...) value", line).into());
20492 };
20493 let items = list_pv.to_list();
20494 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20495 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
20496 return Err(StrykeError::runtime(
20497 "pmap_on: mysync/atomic capture is not supported for remote workers",
20498 line,
20499 )
20500 .into());
20501 }
20502 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
20503 .map_err(|e| StrykeError::runtime(e, line))?;
20504 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
20505 let block_src = crate::fmt::format_block(block);
20506 let item_jsons = crate::cluster::perl_items_to_json(&items)
20507 .map_err(|e| StrykeError::runtime(e, line))?;
20508
20509 let pmap_progress = PmapProgress::new(show_progress, items.len());
20512 let result_values =
20513 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
20514 .map_err(|e| StrykeError::runtime(format!("pmap_on remote: {e}"), line))?;
20515 for _ in 0..result_values.len() {
20516 pmap_progress.tick();
20517 }
20518 pmap_progress.finish();
20519
20520 if flat_outputs {
20521 let flattened: Vec<StrykeValue> = result_values
20522 .into_iter()
20523 .flat_map(|v| v.map_flatten_outputs(true))
20524 .collect();
20525 Ok(StrykeValue::array(flattened))
20526 } else {
20527 Ok(StrykeValue::array(result_values))
20528 }
20529 }
20530
20531 pub(crate) fn eval_par_lines_expr(
20533 &mut self,
20534 path: &Expr,
20535 callback: &Expr,
20536 progress: Option<&Expr>,
20537 line: usize,
20538 ) -> Result<StrykeValue, FlowOrError> {
20539 let show_progress = progress
20540 .map(|p| self.eval_expr(p))
20541 .transpose()?
20542 .map(|v| v.is_true())
20543 .unwrap_or(false);
20544 let raw = self.eval_expr(path)?.to_string();
20545 let path_s = self.resolve_stryke_path_string(&raw);
20546 let cb_val = self.eval_expr(callback)?;
20547 let sub = if let Some(s) = cb_val.as_code_ref() {
20548 s
20549 } else {
20550 return Err(StrykeError::runtime(
20551 "par_lines: second argument must be a code reference",
20552 line,
20553 )
20554 .into());
20555 };
20556 let subs = self.subs.clone();
20557 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20558 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
20559 FlowOrError::Error(StrykeError::runtime(format!("par_lines: {}", e), line))
20560 })?;
20561 let mmap = unsafe {
20562 memmap2::Mmap::map(&file).map_err(|e| {
20563 FlowOrError::Error(StrykeError::runtime(
20564 format!("par_lines: mmap: {}", e),
20565 line,
20566 ))
20567 })?
20568 };
20569 let data: &[u8] = &mmap;
20570 if data.is_empty() {
20571 return Ok(StrykeValue::UNDEF);
20572 }
20573 let line_total = crate::par_lines::line_count_bytes(data);
20574 let pmap_progress = PmapProgress::new(show_progress, line_total);
20575 if self.num_threads == 0 {
20576 self.num_threads = rayon::current_num_threads();
20577 }
20578 let num_chunks = self.num_threads.saturating_mul(8).max(1);
20579 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
20580 chunks.into_par_iter().try_for_each(|(start, end)| {
20581 let slice = &data[start..end];
20582 let mut s = 0usize;
20583 while s < slice.len() {
20584 let e = slice[s..]
20585 .iter()
20586 .position(|&b| b == b'\n')
20587 .map(|p| s + p)
20588 .unwrap_or(slice.len());
20589 let line_bytes = &slice[s..e];
20590 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
20591 let mut local_interp = VMHelper::new();
20592 local_interp.subs = subs.clone();
20593 local_interp.scope.restore_capture(&scope_capture);
20594 local_interp
20595 .scope
20596 .restore_atomics(&atomic_arrays, &atomic_hashes);
20597 local_interp.enable_parallel_guard();
20598 local_interp.scope.set_topic(StrykeValue::string(line_str));
20599 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
20600 Ok(_) => {}
20601 Err(e) => return Err(e),
20602 }
20603 pmap_progress.tick();
20604 if e >= slice.len() {
20605 break;
20606 }
20607 s = e + 1;
20608 }
20609 Ok(())
20610 })?;
20611 pmap_progress.finish();
20612 Ok(StrykeValue::UNDEF)
20613 }
20614
20615 pub(crate) fn eval_par_walk_expr(
20617 &mut self,
20618 path: &Expr,
20619 callback: &Expr,
20620 progress: Option<&Expr>,
20621 line: usize,
20622 ) -> Result<StrykeValue, FlowOrError> {
20623 let show_progress = progress
20624 .map(|p| self.eval_expr(p))
20625 .transpose()?
20626 .map(|v| v.is_true())
20627 .unwrap_or(false);
20628 let path_val = self.eval_expr(path)?;
20629 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
20630 arr.into_iter()
20631 .map(|v| PathBuf::from(v.to_string()))
20632 .collect()
20633 } else {
20634 vec![PathBuf::from(path_val.to_string())]
20635 };
20636 let cb_val = self.eval_expr(callback)?;
20637 let sub = if let Some(s) = cb_val.as_code_ref() {
20638 s
20639 } else {
20640 return Err(StrykeError::runtime(
20641 "par_walk: second argument must be a code reference",
20642 line,
20643 )
20644 .into());
20645 };
20646 let subs = self.subs.clone();
20647 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20648
20649 if show_progress {
20650 let paths = crate::par_walk::collect_paths(&roots);
20651 let pmap_progress = PmapProgress::new(true, paths.len());
20652 paths.into_par_iter().try_for_each(|p| {
20653 let s = p.to_string_lossy().into_owned();
20654 let mut local_interp = VMHelper::new();
20655 local_interp.subs = subs.clone();
20656 local_interp.scope.restore_capture(&scope_capture);
20657 local_interp
20658 .scope
20659 .restore_atomics(&atomic_arrays, &atomic_hashes);
20660 local_interp.enable_parallel_guard();
20661 local_interp.scope.set_topic(StrykeValue::string(s));
20662 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
20663 Ok(_) => {}
20664 Err(e) => return Err(e),
20665 }
20666 pmap_progress.tick();
20667 Ok(())
20668 })?;
20669 pmap_progress.finish();
20670 } else {
20671 for r in &roots {
20672 par_walk_recursive(
20673 r.as_path(),
20674 &sub,
20675 &subs,
20676 &scope_capture,
20677 &atomic_arrays,
20678 &atomic_hashes,
20679 line,
20680 )?;
20681 }
20682 }
20683 Ok(StrykeValue::UNDEF)
20684 }
20685
20686 pub(crate) fn builtin_par_sed(
20688 &mut self,
20689 args: &[StrykeValue],
20690 line: usize,
20691 has_progress: bool,
20692 ) -> StrykeResult<StrykeValue> {
20693 let show_progress = if has_progress {
20694 args.last().map(|v| v.is_true()).unwrap_or(false)
20695 } else {
20696 false
20697 };
20698 let slice = if has_progress {
20699 &args[..args.len().saturating_sub(1)]
20700 } else {
20701 args
20702 };
20703 if slice.len() < 3 {
20704 return Err(StrykeError::runtime(
20705 "par_sed: need pattern, replacement, and at least one file path",
20706 line,
20707 ));
20708 }
20709 let pat_val = &slice[0];
20710 let repl = slice[1].to_string();
20711 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
20712
20713 let re = if let Some(rx) = pat_val.as_regex() {
20714 rx
20715 } else {
20716 let pattern = pat_val.to_string();
20717 match self.compile_regex(&pattern, "g", line) {
20718 Ok(r) => r,
20719 Err(FlowOrError::Error(e)) => return Err(e),
20720 Err(FlowOrError::Flow(f)) => {
20721 return Err(StrykeError::runtime(format!("par_sed: {:?}", f), line))
20722 }
20723 }
20724 };
20725
20726 let pmap = PmapProgress::new(show_progress, files.len());
20727 let touched = AtomicUsize::new(0);
20728 files.par_iter().try_for_each(|path| {
20729 let content = read_file_text_perl_compat(path)
20730 .map_err(|e| StrykeError::runtime(format!("par_sed {}: {}", path, e), line))?;
20731 let new_s = re.replace_all(&content, &repl);
20732 if new_s != content {
20733 std::fs::write(path, new_s.as_bytes())
20734 .map_err(|e| StrykeError::runtime(format!("par_sed {}: {}", path, e), line))?;
20735 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
20736 }
20737 pmap.tick();
20738 Ok(())
20739 })?;
20740 pmap.finish();
20741 Ok(StrykeValue::integer(
20742 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
20743 ))
20744 }
20745
20746 pub(crate) fn eval_pwatch_expr(
20748 &mut self,
20749 path: &Expr,
20750 callback: &Expr,
20751 line: usize,
20752 ) -> Result<StrykeValue, FlowOrError> {
20753 let pattern_s = self.eval_expr(path)?.to_string();
20754 let cb_val = self.eval_expr(callback)?;
20755 let sub = if let Some(s) = cb_val.as_code_ref() {
20756 s
20757 } else {
20758 return Err(StrykeError::runtime(
20759 "pwatch: second argument must be a code reference",
20760 line,
20761 )
20762 .into());
20763 };
20764 let subs = self.subs.clone();
20765 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20766 crate::pwatch::run_pwatch(
20767 &pattern_s,
20768 sub,
20769 subs,
20770 scope_capture,
20771 atomic_arrays,
20772 atomic_hashes,
20773 line,
20774 )
20775 .map_err(FlowOrError::Error)
20776 }
20777
20778 fn interpolate_replacement_string(&self, replacement: &str) -> String {
20780 let mut out = String::with_capacity(replacement.len());
20781 let chars: Vec<char> = replacement.chars().collect();
20782 let mut i = 0;
20783 while i < chars.len() {
20784 if chars[i] == '\\' && i + 1 < chars.len() {
20785 out.push(chars[i]);
20786 out.push(chars[i + 1]);
20787 i += 2;
20788 continue;
20789 }
20790 if chars[i] == '$' && i + 1 < chars.len() {
20791 let start = i;
20792 i += 1;
20793 if chars[i].is_ascii_digit() {
20794 out.push('$');
20795 while i < chars.len() && chars[i].is_ascii_digit() {
20796 out.push(chars[i]);
20797 i += 1;
20798 }
20799 continue;
20800 }
20801 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
20802 out.push('$');
20803 out.push(chars[i]);
20804 i += 1;
20805 continue;
20806 }
20807 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
20808 out.push('$');
20809 continue;
20810 }
20811 let mut name = String::new();
20812 if chars[i] == '{' {
20813 i += 1;
20814 while i < chars.len() && chars[i] != '}' {
20815 name.push(chars[i]);
20816 i += 1;
20817 }
20818 if i < chars.len() {
20819 i += 1;
20820 }
20821 } else {
20822 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
20823 name.push(chars[i]);
20824 i += 1;
20825 }
20826 }
20827 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
20828 let val = self.scope.get_scalar(&name);
20829 out.push_str(&val.to_string());
20830 } else if !name.is_empty() {
20831 out.push_str(&replacement[start..i]);
20832 } else {
20833 out.push('$');
20834 }
20835 continue;
20836 }
20837 out.push(chars[i]);
20838 i += 1;
20839 }
20840 out
20841 }
20842
20843 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
20845 let mut out = String::with_capacity(pattern.len());
20846 let chars: Vec<char> = pattern.chars().collect();
20847 let mut i = 0;
20848 while i < chars.len() {
20849 if chars[i] == '\\' && i + 1 < chars.len() {
20850 out.push(chars[i]);
20852 out.push(chars[i + 1]);
20853 i += 2;
20854 continue;
20855 }
20856 if chars[i] == '$' && i + 1 < chars.len() {
20857 i += 1;
20858 if i >= chars.len()
20860 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
20861 {
20862 out.push('$');
20863 continue;
20864 }
20865 let mut name = String::new();
20866 if chars[i] == '{' {
20867 i += 1;
20868 while i < chars.len() && chars[i] != '}' {
20869 name.push(chars[i]);
20870 i += 1;
20871 }
20872 if i < chars.len() {
20873 i += 1;
20874 } } else {
20876 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
20877 name.push(chars[i]);
20878 i += 1;
20879 }
20880 }
20881 if !name.is_empty() {
20882 let val = self.scope.get_scalar(&name);
20883 out.push_str(&val.to_string());
20884 } else {
20885 out.push('$');
20886 }
20887 continue;
20888 }
20889 out.push(chars[i]);
20890 i += 1;
20891 }
20892 out
20893 }
20894
20895 pub(crate) fn compile_regex(
20896 &mut self,
20897 pattern: &str,
20898 flags: &str,
20899 line: usize,
20900 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
20901 let pattern = if pattern.contains('$') || pattern.contains('@') {
20903 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
20904 } else {
20905 std::borrow::Cow::Borrowed(pattern)
20906 };
20907 let pattern = pattern.as_ref();
20908 let multiline = self.multiline_match;
20911 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
20912 if lp == pattern && lf == flags && *lm == multiline {
20913 return Ok(lr.clone());
20914 }
20915 }
20916 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
20918 if let Some(cached) = self.regex_cache.get(&key) {
20919 self.regex_last = Some((
20920 pattern.to_string(),
20921 flags.to_string(),
20922 multiline,
20923 cached.clone(),
20924 ));
20925 return Ok(cached.clone());
20926 }
20927 let expanded = expand_perl_regex_quotemeta(pattern);
20928 let expanded = expand_perl_regex_octal_escapes(&expanded);
20929 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
20930 let mut re_str = String::new();
20931 if flags.contains('i') {
20932 re_str.push_str("(?i)");
20933 }
20934 if flags.contains('s') {
20935 re_str.push_str("(?s)");
20936 }
20937 if flags.contains('m') {
20938 re_str.push_str("(?m)");
20939 }
20940 if flags.contains('x') {
20941 re_str.push_str("(?x)");
20942 }
20943 if multiline {
20945 re_str.push_str("(?s)");
20946 }
20947 re_str.push_str(&expanded);
20948 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
20949 FlowOrError::Error(StrykeError::runtime(
20950 format!("Invalid regex /{}/: {}", pattern, e),
20951 line,
20952 ))
20953 })?;
20954 let arc = re;
20955 self.regex_last = Some((
20956 pattern.to_string(),
20957 flags.to_string(),
20958 multiline,
20959 arc.clone(),
20960 ));
20961 self.regex_cache.insert(key, arc.clone());
20962 Ok(arc)
20963 }
20964
20965 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
20967 if self.last_readline_handle.is_empty() {
20968 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
20969 }
20970 let n = *self
20971 .handle_line_numbers
20972 .get(&self.last_readline_handle)
20973 .unwrap_or(&0);
20974 if n <= 0 {
20975 return None;
20976 }
20977 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
20978 {
20979 return Some(("<>".to_string(), n));
20980 }
20981 if self.last_readline_handle == "STDIN" {
20982 return Some((self.last_stdin_die_bracket.clone(), n));
20983 }
20984 Some((format!("<{}>", self.last_readline_handle), n))
20985 }
20986
20987 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
20989 let mut s = format!(" at {} line {}", self.file, source_line);
20990 if let Some((bracket, n)) = self.die_warn_io_annotation() {
20991 s.push_str(&format!(", {} line {}.", bracket, n));
20992 } else {
20993 s.push('.');
20994 }
20995 s
20996 }
20997
20998 pub fn process_line(
21003 &mut self,
21004 line_str: &str,
21005 _program: &Program,
21006 is_last_input_line: bool,
21007 ) -> StrykeResult<Option<String>> {
21008 let chunk = self
21009 .line_mode_chunk
21010 .as_ref()
21011 .expect("process_line called without compiled chunk — execute() must run first")
21012 .clone();
21013 crate::run_line_body(&chunk, self, line_str, is_last_input_line)
21014 }
21015}
21016
21017fn both_non_numeric_strings_iv(a: &StrykeValue, b: &StrykeValue) -> bool {
21021 if !a.is_string_like() || !b.is_string_like() {
21022 return false;
21023 }
21024 let sa = a.to_string();
21025 let sb = b.to_string();
21026 let looks = |s: &str| {
21027 let t = s.trim();
21028 !t.is_empty() && t.parse::<f64>().is_ok()
21029 };
21030 !looks(&sa) && !looks(&sb)
21031}
21032
21033fn par_walk_invoke_entry(
21034 path: &Path,
21035 sub: &Arc<StrykeSub>,
21036 subs: &HashMap<String, Arc<StrykeSub>>,
21037 scope_capture: &[(String, StrykeValue)],
21038 atomic_arrays: &[(String, crate::scope::AtomicArray)],
21039 atomic_hashes: &[(String, crate::scope::AtomicHash)],
21040 line: usize,
21041) -> Result<(), FlowOrError> {
21042 let s = path.to_string_lossy().into_owned();
21043 let mut local_interp = VMHelper::new();
21044 local_interp.subs = subs.clone();
21045 local_interp.scope.restore_capture(scope_capture);
21046 local_interp
21047 .scope
21048 .restore_atomics(atomic_arrays, atomic_hashes);
21049 local_interp.enable_parallel_guard();
21050 local_interp.scope.set_topic(StrykeValue::string(s));
21051 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
21052 Ok(())
21053}
21054
21055fn par_walk_recursive(
21056 path: &Path,
21057 sub: &Arc<StrykeSub>,
21058 subs: &HashMap<String, Arc<StrykeSub>>,
21059 scope_capture: &[(String, StrykeValue)],
21060 atomic_arrays: &[(String, crate::scope::AtomicArray)],
21061 atomic_hashes: &[(String, crate::scope::AtomicHash)],
21062 line: usize,
21063) -> Result<(), FlowOrError> {
21064 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
21065 return par_walk_invoke_entry(
21066 path,
21067 sub,
21068 subs,
21069 scope_capture,
21070 atomic_arrays,
21071 atomic_hashes,
21072 line,
21073 );
21074 }
21075 if !path.is_dir() {
21076 return Ok(());
21077 }
21078 par_walk_invoke_entry(
21079 path,
21080 sub,
21081 subs,
21082 scope_capture,
21083 atomic_arrays,
21084 atomic_hashes,
21085 line,
21086 )?;
21087 let read = match std::fs::read_dir(path) {
21088 Ok(r) => r,
21089 Err(_) => return Ok(()),
21090 };
21091 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
21092 entries.par_iter().try_for_each(|e| {
21093 par_walk_recursive(
21094 &e.path(),
21095 sub,
21096 subs,
21097 scope_capture,
21098 atomic_arrays,
21099 atomic_hashes,
21100 line,
21101 )
21102 })?;
21103 Ok(())
21104}
21105
21106fn par_chunk_value(v: &StrykeValue, n_threads: usize) -> Vec<StrykeValue> {
21134 let n = n_threads.max(1);
21135 if let Some(s) = v.as_str() {
21137 let bytes = s.as_bytes();
21138 if bytes.len() < 16_384 || n < 2 {
21139 return vec![StrykeValue::string(s)];
21140 }
21141 let target = bytes.len().div_ceil(n);
21142 let mut splits = vec![0usize];
21143 let mut cursor = target;
21144 while cursor < bytes.len() {
21145 while cursor < bytes.len() && (bytes[cursor] & 0xC0) == 0x80 {
21147 cursor += 1;
21148 }
21149 splits.push(cursor);
21150 cursor += target;
21151 }
21152 splits.push(bytes.len());
21153 return splits
21154 .windows(2)
21155 .map(|w| {
21156 let chunk = std::str::from_utf8(&bytes[w[0]..w[1]]).unwrap_or("");
21157 StrykeValue::string(chunk.to_string())
21158 })
21159 .collect();
21160 }
21161 if let Some(arr) = v.as_array_vec() {
21163 if arr.len() < 32 || n < 2 {
21164 return vec![StrykeValue::array(arr)];
21165 }
21166 let target = arr.len().div_ceil(n);
21167 let mut chunks = Vec::with_capacity(n);
21168 for slice in arr.chunks(target) {
21169 chunks.push(StrykeValue::array(slice.to_vec()));
21170 }
21171 return chunks;
21172 }
21173 if let Some(arr_ref) = v.as_array_ref() {
21174 let arr = arr_ref.read().clone();
21175 if arr.len() < 32 || n < 2 {
21176 return vec![StrykeValue::array(arr)];
21177 }
21178 let target = arr.len().div_ceil(n);
21179 let mut chunks = Vec::with_capacity(n);
21180 for slice in arr.chunks(target) {
21181 chunks.push(StrykeValue::array(slice.to_vec()));
21182 }
21183 return chunks;
21184 }
21185 vec![v.clone()]
21187}
21188
21189fn par_reduce_auto_merge(chunks: Vec<StrykeValue>) -> StrykeValue {
21199 if chunks.is_empty() {
21200 return StrykeValue::UNDEF;
21201 }
21202 let first = &chunks[0];
21203 if let Some(_h) = first.as_hash_ref() {
21205 let mut out: indexmap::IndexMap<String, f64> = indexmap::IndexMap::new();
21206 for chunk in &chunks {
21207 if let Some(hr) = chunk.as_hash_ref() {
21208 for (k, v) in hr.read().iter() {
21209 *out.entry(k.clone()).or_insert(0.0) += v.to_number();
21210 }
21211 }
21212 }
21213 let mut indexmap_out: indexmap::IndexMap<String, StrykeValue> = indexmap::IndexMap::new();
21216 for (k, v) in out {
21217 let pv = if v == v.trunc() && v.abs() < 1e15 {
21218 StrykeValue::integer(v as i64)
21219 } else {
21220 StrykeValue::float(v)
21221 };
21222 indexmap_out.insert(k, pv);
21223 }
21224 return StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(indexmap_out)));
21225 }
21226 if first.is_integer_like() || first.is_float_like() {
21228 let s: f64 = chunks.iter().map(|v| v.to_number()).sum();
21229 if s == s.trunc() && s.abs() < 1e15 {
21230 return StrykeValue::integer(s as i64);
21231 }
21232 return StrykeValue::float(s);
21233 }
21234 if first.as_array_vec().is_some() || first.as_array_ref().is_some() {
21236 let mut out = Vec::new();
21237 for v in &chunks {
21238 out.extend(v.map_flatten_outputs(true));
21239 }
21240 return StrykeValue::array(out);
21241 }
21242 if first.is_string_like() {
21244 let mut out = String::new();
21245 for v in &chunks {
21246 out.push_str(&v.to_string());
21247 }
21248 return StrykeValue::string(out);
21249 }
21250 StrykeValue::array(chunks)
21252}
21253
21254fn perl_magic_str_inc(s: &str) -> Option<String> {
21257 if s.is_empty() {
21258 return Some("1".to_string());
21259 }
21260 let bytes = s.as_bytes();
21261 let mut i = 0;
21262 while i < bytes.len() && bytes[i].is_ascii_alphabetic() {
21263 i += 1;
21264 }
21265 let letters_end = i;
21266 while i < bytes.len() && bytes[i].is_ascii_digit() {
21267 i += 1;
21268 }
21269 if i != bytes.len() {
21270 return None;
21271 }
21272 if letters_end == 0 {
21273 return None;
21275 }
21276
21277 let mut result: Vec<u8> = bytes.to_vec();
21278 let mut carry = true;
21279 let mut idx = result.len();
21280
21281 while carry && idx > letters_end {
21283 idx -= 1;
21284 if result[idx] == b'9' {
21285 result[idx] = b'0';
21286 } else {
21288 result[idx] += 1;
21289 carry = false;
21290 }
21291 }
21292
21293 while carry && idx > 0 {
21295 idx -= 1;
21296 let c = result[idx];
21297 if c == b'z' {
21298 result[idx] = b'a';
21299 } else if c == b'Z' {
21300 result[idx] = b'A';
21301 } else {
21302 result[idx] += 1;
21303 carry = false;
21304 }
21305 }
21306
21307 if carry {
21309 let prepend = if bytes[0].is_ascii_uppercase() {
21310 b'A'
21311 } else {
21312 b'a'
21313 };
21314 let mut grown = Vec::with_capacity(result.len() + 1);
21315 grown.push(prepend);
21316 grown.extend_from_slice(&result);
21317 return String::from_utf8(grown).ok();
21318 }
21319
21320 String::from_utf8(result).ok()
21321}
21322
21323pub(crate) fn perl_inc(v: &StrykeValue) -> StrykeValue {
21327 if let Some(s) = v.as_str() {
21328 if let Some(new_s) = perl_magic_str_inc(&s) {
21329 return StrykeValue::string(new_s);
21330 }
21331 }
21332 StrykeValue::integer(v.to_int() + 1)
21333}
21334
21335fn perl_exponent_form(rust_repr: &str, upper: bool) -> String {
21336 let marker = if upper { 'E' } else { 'e' };
21337 if let Some(pos) = rust_repr.find(marker) {
21338 let (mantissa, after) = rust_repr.split_at(pos);
21339 let exp_part = &after[1..]; let (sign, digits) = match exp_part.chars().next() {
21341 Some('+') => ("+", &exp_part[1..]),
21342 Some('-') => ("-", &exp_part[1..]),
21343 _ => ("+", exp_part),
21344 };
21345 let padded = if digits.len() < 2 {
21346 format!("0{}", digits)
21347 } else {
21348 digits.to_string()
21349 };
21350 return format!("{}{}{}{}", mantissa, marker, sign, padded);
21351 }
21352 rust_repr.to_string()
21353}
21354
21355fn perl_hex_float(n: f64, upper: bool) -> String {
21359 if n.is_nan() {
21360 return if upper { "NAN" } else { "nan" }.to_string();
21361 }
21362 if n.is_infinite() {
21363 let sign = if n.is_sign_negative() { "-" } else { "" };
21364 let body = if upper { "INF" } else { "inf" };
21365 return format!("{}{}", sign, body);
21366 }
21367 let prefix = if upper { "0X" } else { "0x" };
21368 let p_letter = if upper { 'P' } else { 'p' };
21369 let bits = n.to_bits();
21370 let sign_bit = bits >> 63;
21371 let exp_bits = (bits >> 52) & 0x7FF;
21372 let mant_bits = bits & 0x000F_FFFF_FFFF_FFFF;
21373 let sign_str = if sign_bit == 1 { "-" } else { "" };
21374 if exp_bits == 0 && mant_bits == 0 {
21375 return format!("{}{}{}{}{}", sign_str, prefix, "0", p_letter, "+0");
21376 }
21377 let (lead_digit, exp_unbiased): (u64, i32) = if exp_bits == 0 {
21378 (0, -1022)
21380 } else {
21381 (1, (exp_bits as i32) - 1023)
21382 };
21383 let exp_sign = if exp_unbiased >= 0 { "+" } else { "-" };
21384 let exp_abs = exp_unbiased.unsigned_abs();
21385 if mant_bits == 0 {
21386 return format!(
21387 "{}{}{}{}{}{}",
21388 sign_str, prefix, lead_digit, p_letter, exp_sign, exp_abs
21389 );
21390 }
21391 let mant_hex = format!("{:013x}", mant_bits);
21393 let trimmed = mant_hex.trim_end_matches('0');
21394 let mant_str = if upper {
21395 trimmed.to_uppercase()
21396 } else {
21397 trimmed.to_string()
21398 };
21399 format!(
21400 "{}{}{}.{}{}{}{}",
21401 sign_str, prefix, lead_digit, mant_str, p_letter, exp_sign, exp_abs
21402 )
21403}
21404
21405fn perl_g_form(n: f64, prec: usize, upper: bool) -> String {
21408 let prec = prec.max(1);
21409 if !n.is_finite() {
21410 return if upper {
21411 format!("{}", n).to_uppercase()
21412 } else {
21413 format!("{}", n)
21414 };
21415 }
21416 let abs = n.abs();
21418 let x = if abs == 0.0 {
21419 0i32
21420 } else {
21421 abs.log10().floor() as i32
21422 };
21423 let use_e = x < -4 || x >= prec as i32;
21425 let formatted = if use_e {
21428 let raw = format!("{:.*e}", prec - 1, n);
21429 perl_exponent_form(&raw, false)
21430 } else {
21431 let f_prec = (prec as i32 - 1 - x).max(0) as usize;
21432 format!("{:.*}", f_prec, n)
21433 };
21434 let (mant, exp) = if let Some(pos) = formatted.find('e') {
21437 (formatted[..pos].to_string(), formatted[pos..].to_string())
21438 } else {
21439 (formatted.clone(), String::new())
21440 };
21441 let trimmed = if mant.contains('.') {
21442 let t = mant.trim_end_matches('0');
21443 let t = t.trim_end_matches('.');
21444 t.to_string()
21445 } else {
21446 mant
21447 };
21448 let combined = format!("{}{}", trimmed, exp);
21449 if upper {
21450 combined.replace('e', "E")
21451 } else {
21452 combined
21453 }
21454}
21455
21456pub(crate) fn perl_sprintf_format_full<F>(
21461 fmt: &str,
21462 args: &[StrykeValue],
21463 string_for_s: &mut F,
21464) -> Result<(String, Vec<(StrykeValue, i64)>), FlowOrError>
21465where
21466 F: FnMut(&StrykeValue) -> Result<String, FlowOrError>,
21467{
21468 let mut pending_n: Vec<(StrykeValue, i64)> = Vec::new();
21469 let mut result = String::new();
21470 let mut arg_idx = 0;
21471 let chars: Vec<char> = fmt.chars().collect();
21472 let mut i = 0;
21473
21474 let take_arg_int = |args: &[StrykeValue], idx: &mut usize| -> i64 {
21476 let v = args.get(*idx).cloned().unwrap_or(StrykeValue::UNDEF);
21477 *idx += 1;
21478 v.to_int()
21479 };
21480
21481 while i < chars.len() {
21482 if chars[i] == '%' {
21483 i += 1;
21484 if i >= chars.len() {
21485 break;
21486 }
21487 if chars[i] == '%' {
21488 result.push('%');
21489 i += 1;
21490 continue;
21491 }
21492
21493 let mut positional: Option<usize> = None;
21498 {
21499 let saved = i;
21500 let mut digits = String::new();
21501 let mut j = i;
21502 while j < chars.len() && chars[j].is_ascii_digit() {
21503 digits.push(chars[j]);
21504 j += 1;
21505 }
21506 if j < chars.len() && chars[j] == '$' && !digits.is_empty() {
21507 if let Ok(n) = digits.parse::<usize>() {
21508 if n >= 1 {
21509 positional = Some(n - 1);
21510 i = j + 1; }
21512 }
21513 }
21514 if positional.is_none() {
21515 i = saved;
21516 }
21517 }
21518
21519 let mut flags = String::new();
21521 while i < chars.len() && "-+ #0".contains(chars[i]) {
21522 flags.push(chars[i]);
21523 i += 1;
21524 }
21525 let mut vector_sep: Option<String> = None;
21529 if i < chars.len() && chars[i] == 'v' {
21530 vector_sep = Some(".".to_string());
21531 i += 1;
21532 } else if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == 'v' {
21533 let sep_arg = args.get(arg_idx).cloned().unwrap_or(StrykeValue::UNDEF);
21534 arg_idx += 1;
21535 vector_sep = Some(sep_arg.to_string());
21536 i += 2;
21537 }
21538 let mut width = String::new();
21540 let mut left_align = flags.contains('-');
21541 if i < chars.len() && chars[i] == '*' {
21542 let n = take_arg_int(args, &mut arg_idx);
21543 if n < 0 {
21544 left_align = true;
21546 width = (-n).to_string();
21547 } else {
21548 width = n.to_string();
21549 }
21550 i += 1;
21551 } else {
21552 while i < chars.len() && chars[i].is_ascii_digit() {
21553 width.push(chars[i]);
21554 i += 1;
21555 }
21556 }
21557 let mut precision = String::new();
21559 if i < chars.len() && chars[i] == '.' {
21560 i += 1;
21561 if i < chars.len() && chars[i] == '*' {
21562 let n = take_arg_int(args, &mut arg_idx);
21563 precision = n.max(0).to_string();
21564 i += 1;
21565 } else {
21566 while i < chars.len() && chars[i].is_ascii_digit() {
21567 precision.push(chars[i]);
21568 i += 1;
21569 }
21570 if precision.is_empty() {
21572 precision = "0".to_string();
21573 }
21574 }
21575 }
21576 if i >= chars.len() {
21577 break;
21578 }
21579 let spec = chars[i];
21580 i += 1;
21581
21582 let arg = if let Some(idx) = positional {
21587 args.get(idx).cloned().unwrap_or(StrykeValue::UNDEF)
21588 } else {
21589 let v = args.get(arg_idx).cloned().unwrap_or(StrykeValue::UNDEF);
21590 arg_idx += 1;
21591 v
21592 };
21593
21594 let w: usize = width.parse().unwrap_or(0);
21595 let p: usize = precision.parse().unwrap_or(6);
21596
21597 let zero_pad = flags.contains('0') && !left_align;
21598 let plus = flags.contains('+');
21599 let space = flags.contains(' ');
21600 let hash = flags.contains('#');
21601
21602 let pad_align = |body: &str, width: usize, left: bool, zero: bool| -> String {
21606 if width == 0 || body.len() >= width {
21607 return body.to_string();
21608 }
21609 if zero && !left {
21610 if let Some(rest) = body.strip_prefix('-') {
21611 return format!("-{:0>width$}", rest, width = width - 1);
21612 }
21613 if let Some(rest) = body.strip_prefix('+') {
21614 return format!("+{:0>width$}", rest, width = width - 1);
21615 }
21616 return format!("{:0>width$}", body, width = width);
21617 }
21618 if left {
21619 format!("{:<width$}", body, width = width)
21620 } else {
21621 format!("{:>width$}", body, width = width)
21622 }
21623 };
21624
21625 let format_int_for_vector = |n: i64, spec: char| -> String {
21629 match spec {
21630 'd' | 'i' => format!("{}", n),
21631 'u' => format!("{}", n as u64),
21632 'x' => {
21633 if hash && n != 0 {
21634 format!("0x{:x}", n)
21635 } else {
21636 format!("{:x}", n)
21637 }
21638 }
21639 'X' => {
21640 if hash && n != 0 {
21641 format!("0X{:X}", n)
21642 } else {
21643 format!("{:X}", n)
21644 }
21645 }
21646 'o' => {
21647 if hash && n != 0 {
21648 format!("0{:o}", n)
21649 } else {
21650 format!("{:o}", n)
21651 }
21652 }
21653 'b' => {
21654 if hash && n != 0 {
21655 format!("0b{:b}", n)
21656 } else {
21657 format!("{:b}", n)
21658 }
21659 }
21660 'c' => char::from_u32(n as u32)
21661 .map(|c| c.to_string())
21662 .unwrap_or_default(),
21663 _ => format!("{}", n),
21664 }
21665 };
21666
21667 if let Some(ref sep) = vector_sep {
21671 let s = arg.to_string();
21672 let parts: Vec<String> = s
21673 .bytes()
21674 .map(|b| format_int_for_vector(b as i64, spec))
21675 .collect();
21676 let body = parts.join(sep);
21677 let final_body = if width.is_empty() {
21678 body
21679 } else if left_align {
21680 format!("{:<width$}", body, width = w)
21681 } else {
21682 format!("{:>width$}", body, width = w)
21683 };
21684 result.push_str(&final_body);
21685 continue;
21686 }
21687
21688 let formatted = match spec {
21689 'd' | 'i' => {
21690 let v = arg.to_int();
21691 let body = if plus && v >= 0 {
21692 format!("+{}", v)
21693 } else if space && v >= 0 {
21694 format!(" {}", v)
21695 } else {
21696 format!("{}", v)
21697 };
21698 pad_align(&body, w, left_align, zero_pad)
21699 }
21700 'u' => {
21701 let v = arg.to_int() as u64;
21702 pad_align(&format!("{}", v), w, left_align, zero_pad)
21703 }
21704 'f' => {
21705 let n = arg.to_number();
21706 let body = if plus && n.is_sign_positive() {
21707 format!("+{:.*}", p, n)
21708 } else if space && n.is_sign_positive() {
21709 format!(" {:.*}", p, n)
21710 } else {
21711 format!("{:.*}", p, n)
21712 };
21713 pad_align(&body, w, left_align, zero_pad)
21714 }
21715 'e' => {
21716 let n = arg.to_number();
21717 let raw = format!("{:.*e}", p, n);
21718 let body0 = perl_exponent_form(&raw, false);
21719 let body = if plus && n.is_sign_positive() {
21720 format!("+{}", body0)
21721 } else if space && n.is_sign_positive() {
21722 format!(" {}", body0)
21723 } else {
21724 body0
21725 };
21726 pad_align(&body, w, left_align, zero_pad)
21727 }
21728 'E' => {
21729 let n = arg.to_number();
21730 let raw = format!("{:.*E}", p, n);
21731 let body0 = perl_exponent_form(&raw, true);
21732 let body = if plus && n.is_sign_positive() {
21733 format!("+{}", body0)
21734 } else if space && n.is_sign_positive() {
21735 format!(" {}", body0)
21736 } else {
21737 body0
21738 };
21739 pad_align(&body, w, left_align, zero_pad)
21740 }
21741 'g' => {
21742 let n = arg.to_number();
21743 let prec_g = if precision.is_empty() { 6 } else { p };
21745 let body0 = perl_g_form(n, prec_g, false);
21746 let body = if plus && n.is_sign_positive() {
21747 format!("+{}", body0)
21748 } else if space && n.is_sign_positive() {
21749 format!(" {}", body0)
21750 } else {
21751 body0
21752 };
21753 pad_align(&body, w, left_align, zero_pad)
21754 }
21755 'G' => {
21756 let n = arg.to_number();
21757 let prec_g = if precision.is_empty() { 6 } else { p };
21758 let body0 = perl_g_form(n, prec_g, true);
21759 let body = if plus && n.is_sign_positive() {
21760 format!("+{}", body0)
21761 } else if space && n.is_sign_positive() {
21762 format!(" {}", body0)
21763 } else {
21764 body0
21765 };
21766 pad_align(&body, w, left_align, zero_pad)
21767 }
21768 's' => {
21769 let s = string_for_s(&arg)?;
21770 let body = if !precision.is_empty() {
21771 s.chars().take(p).collect::<String>()
21772 } else {
21773 s
21774 };
21775 if left_align {
21776 format!("{:<width$}", body, width = w)
21777 } else {
21778 format!("{:>width$}", body, width = w)
21779 }
21780 }
21781 'x' => {
21782 let v = arg.to_int();
21783 let body = if hash && v != 0 {
21784 format!("0x{:x}", v)
21785 } else {
21786 format!("{:x}", v)
21787 };
21788 pad_align(&body, w, left_align, zero_pad)
21789 }
21790 'X' => {
21791 let v = arg.to_int();
21792 let body = if hash && v != 0 {
21793 format!("0X{:X}", v)
21794 } else {
21795 format!("{:X}", v)
21796 };
21797 pad_align(&body, w, left_align, zero_pad)
21798 }
21799 'o' => {
21800 let v = arg.to_int();
21801 let body = if hash && v != 0 {
21802 format!("0{:o}", v)
21803 } else {
21804 format!("{:o}", v)
21805 };
21806 pad_align(&body, w, left_align, zero_pad)
21807 }
21808 'b' => {
21809 let v = arg.to_int();
21810 let body = if hash && v != 0 {
21811 format!("0b{:b}", v)
21812 } else {
21813 format!("{:b}", v)
21814 };
21815 pad_align(&body, w, left_align, zero_pad)
21816 }
21817 'c' => char::from_u32(arg.to_int() as u32)
21818 .map(|c| c.to_string())
21819 .unwrap_or_default(),
21820 'a' | 'A' => {
21821 let upper = spec == 'A';
21822 let body0 = perl_hex_float(arg.to_number(), upper);
21823 let body = if plus && !body0.starts_with('-') {
21824 format!("+{}", body0)
21825 } else if space && !body0.starts_with('-') {
21826 format!(" {}", body0)
21827 } else {
21828 body0
21829 };
21830 pad_align(&body, w, left_align, zero_pad)
21831 }
21832 'p' => {
21833 pad_align("0x...", w, left_align, false)
21837 }
21838 'n' => {
21839 pending_n.push((arg.clone(), result.len() as i64));
21847 String::new()
21848 }
21849 _ => arg.to_string(),
21850 };
21851
21852 result.push_str(&formatted);
21853 } else {
21854 result.push(chars[i]);
21855 i += 1;
21856 }
21857 }
21858 Ok((result, pending_n))
21859}
21860
21861#[cfg(test)]
21862mod regex_expand_tests {
21863 use super::VMHelper;
21864
21865 #[test]
21866 fn compile_regex_quotemeta_qe_matches_literal() {
21867 let mut i = VMHelper::new();
21868 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
21869 assert!(re.is_match("a.c"));
21870 assert!(!re.is_match("abc"));
21871 }
21872
21873 #[test]
21876 fn compile_regex_char_class_leading_close_bracket_is_literal() {
21877 let mut i = VMHelper::new();
21878 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
21879 assert!(re.is_match("$"));
21880 assert!(re.is_match("]"));
21881 assert!(!re.is_match("x"));
21882 }
21883}
21884
21885#[cfg(test)]
21886mod special_scalar_name_tests {
21887 use super::VMHelper;
21888
21889 #[test]
21890 fn special_scalar_name_for_get_matches_magic_globals() {
21891 assert!(VMHelper::is_special_scalar_name_for_get("0"));
21892 assert!(VMHelper::is_special_scalar_name_for_get("!"));
21893 assert!(VMHelper::is_special_scalar_name_for_get("^W"));
21894 assert!(VMHelper::is_special_scalar_name_for_get("^O"));
21895 assert!(VMHelper::is_special_scalar_name_for_get("^MATCH"));
21896 assert!(VMHelper::is_special_scalar_name_for_get("<"));
21897 assert!(VMHelper::is_special_scalar_name_for_get("?"));
21898 assert!(VMHelper::is_special_scalar_name_for_get("|"));
21899 assert!(VMHelper::is_special_scalar_name_for_get("^UNICODE"));
21900 assert!(VMHelper::is_special_scalar_name_for_get("\""));
21901 assert!(!VMHelper::is_special_scalar_name_for_get("foo"));
21902 assert!(!VMHelper::is_special_scalar_name_for_get("plainvar"));
21903 }
21904
21905 #[test]
21906 fn special_scalar_name_for_set_matches_set_special_var_arms() {
21907 assert!(VMHelper::is_special_scalar_name_for_set("0"));
21908 assert!(VMHelper::is_special_scalar_name_for_set("^D"));
21909 assert!(VMHelper::is_special_scalar_name_for_set("^H"));
21910 assert!(VMHelper::is_special_scalar_name_for_set("^WARNING_BITS"));
21911 assert!(VMHelper::is_special_scalar_name_for_set("ARGV"));
21912 assert!(VMHelper::is_special_scalar_name_for_set("|"));
21913 assert!(VMHelper::is_special_scalar_name_for_set("?"));
21914 assert!(VMHelper::is_special_scalar_name_for_set("^UNICODE"));
21915 assert!(VMHelper::is_special_scalar_name_for_set("."));
21916 assert!(!VMHelper::is_special_scalar_name_for_set("foo"));
21917 assert!(!VMHelper::is_special_scalar_name_for_set("__PACKAGE__"));
21918 }
21919
21920 #[test]
21921 fn caret_and_id_specials_roundtrip_get() {
21922 let i = VMHelper::new();
21923 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
21924 assert_eq!(
21925 i.get_special_var("^V").to_string(),
21926 format!("v{}", env!("CARGO_PKG_VERSION"))
21927 );
21928 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
21929 assert!(i.get_special_var("^T").to_int() >= 0);
21930 #[cfg(unix)]
21931 {
21932 assert!(i.get_special_var("<").to_int() >= 0);
21933 }
21934 }
21935
21936 #[test]
21937 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
21938 let mut i = VMHelper::new();
21939 i.last_readline_handle.clear();
21940 i.line_number = 3;
21941 i.prepare_flip_flop_vm_slots(1);
21942 assert_eq!(
21943 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
21944 1
21945 );
21946 assert!(i.flip_flop_active[0]);
21947 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
21948 assert_eq!(
21950 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
21951 1
21952 );
21953 assert!(i.flip_flop_active[0]);
21954 }
21955
21956 #[test]
21957 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
21958 let mut i = VMHelper::new();
21959 i.last_readline_handle.clear();
21960 i.line_number = 2;
21961 i.prepare_flip_flop_vm_slots(1);
21962 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
21963 assert!(i.flip_flop_active[0]);
21964 i.line_number = 3;
21965 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
21966 assert!(!i.flip_flop_active[0]);
21967 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
21968 }
21969}