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, perl_shl_i64, perl_shr_i64, CaptureResult, PerlBarrier, PerlDataFrame,
37 PerlGenerator, PerlHeap, PerlPpool, PipelineInner, PipelineOp, RemoteCluster, StrykeAsyncTask,
38 StrykeSub, StrykeValue,
39};
40
41pub(crate) fn preduce_init_merge_maps(
44 mut acc: IndexMap<String, StrykeValue>,
45 b: IndexMap<String, StrykeValue>,
46) -> StrykeValue {
47 for (k, v2) in b {
48 acc.entry(k)
49 .and_modify(|v1| *v1 = StrykeValue::float(v1.to_number() + v2.to_number()))
50 .or_insert(v2);
51 }
52 StrykeValue::hash_ref(Arc::new(RwLock::new(acc)))
53}
54
55#[inline]
57fn splice_compute_range(
58 arr_len: usize,
59 offset_val: &StrykeValue,
60 length_val: &StrykeValue,
61) -> (usize, usize) {
62 let off_i = offset_val.to_int();
63 let off = if off_i < 0 {
64 arr_len.saturating_sub((-off_i) as usize)
65 } else {
66 (off_i as usize).min(arr_len)
67 };
68 let rest = arr_len.saturating_sub(off);
69 let take = if length_val.is_undef() {
70 rest
71 } else {
72 let l = length_val.to_int();
73 if l < 0 {
74 rest.saturating_sub((-l) as usize)
75 } else {
76 (l as usize).min(rest)
77 }
78 };
79 let end = (off + take).min(arr_len);
80 (off, end)
81}
82
83pub(crate) fn merge_preduce_init_partials(
86 a: StrykeValue,
87 b: StrykeValue,
88 block: &Block,
89 subs: &HashMap<String, Arc<StrykeSub>>,
90 scope_capture: &[(String, StrykeValue)],
91) -> StrykeValue {
92 if let (Some(m1), Some(m2)) = (a.as_hash_map(), b.as_hash_map()) {
93 return preduce_init_merge_maps(m1, m2);
94 }
95 if let (Some(r1), Some(r2)) = (a.as_hash_ref(), b.as_hash_ref()) {
96 let m1 = r1.read().clone();
97 let m2 = r2.read().clone();
98 return preduce_init_merge_maps(m1, m2);
99 }
100 if let Some(m1) = a.as_hash_map() {
101 if let Some(r2) = b.as_hash_ref() {
102 let m2 = r2.read().clone();
103 return preduce_init_merge_maps(m1, m2);
104 }
105 }
106 if let Some(r1) = a.as_hash_ref() {
107 if let Some(m2) = b.as_hash_map() {
108 let m1 = r1.read().clone();
109 return preduce_init_merge_maps(m1, m2);
110 }
111 }
112 let mut local_interp = VMHelper::new();
113 local_interp.subs = subs.clone();
114 local_interp.scope.restore_capture(scope_capture);
115 local_interp.enable_parallel_guard();
116 local_interp
117 .scope
118 .declare_array("_", vec![a.clone(), b.clone()]);
119 local_interp.scope.set_sort_pair(a, b);
120 match local_interp.exec_block(block) {
121 Ok(val) => val,
122 Err(_) => StrykeValue::UNDEF,
123 }
124}
125
126pub(crate) fn preduce_init_fold_identity(init: &StrykeValue) -> StrykeValue {
129 if let Some(m) = init.as_hash_map() {
130 return StrykeValue::hash(m.clone());
131 }
132 if let Some(r) = init.as_hash_ref() {
133 return StrykeValue::hash_ref(Arc::new(RwLock::new(r.read().clone())));
134 }
135 init.clone()
136}
137
138pub(crate) fn fold_preduce_init_step(
139 subs: &HashMap<String, Arc<StrykeSub>>,
140 scope_capture: &[(String, StrykeValue)],
141 block: &Block,
142 acc: StrykeValue,
143 item: StrykeValue,
144) -> StrykeValue {
145 let mut local_interp = VMHelper::new();
146 local_interp.subs = subs.clone();
147 local_interp.scope.restore_capture(scope_capture);
148 local_interp.enable_parallel_guard();
149 local_interp
150 .scope
151 .declare_array("_", vec![acc.clone(), item.clone()]);
152 local_interp.scope.set_sort_pair(acc, item);
153 match local_interp.exec_block(block) {
154 Ok(val) => val,
155 Err(_) => StrykeValue::UNDEF,
156 }
157}
158
159pub const FEAT_SAY: u64 = 1 << 0;
161pub const FEAT_STATE: u64 = 1 << 1;
163pub const FEAT_SWITCH: u64 = 1 << 2;
165pub const FEAT_UNICODE_STRINGS: u64 = 1 << 3;
167
168#[derive(Debug)]
170pub(crate) enum Flow {
171 Return(StrykeValue),
172 Last(Option<String>),
173 Next(Option<String>),
174 Redo(Option<String>),
175 Yield(StrykeValue),
176 GotoSub(String),
178}
179
180pub(crate) type ExecResult = Result<StrykeValue, FlowOrError>;
181
182#[derive(Debug)]
183pub(crate) enum FlowOrError {
184 Flow(Flow),
185 Error(StrykeError),
186}
187
188impl From<StrykeError> for FlowOrError {
189 fn from(e: StrykeError) -> Self {
190 FlowOrError::Error(e)
191 }
192}
193
194impl From<Flow> for FlowOrError {
195 fn from(f: Flow) -> Self {
196 FlowOrError::Flow(f)
197 }
198}
199
200enum PatternBinding {
202 Scalar(String, StrykeValue),
203 Array(String, Vec<StrykeValue>),
204}
205
206pub fn perl_bracket_version() -> f64 {
209 const PERL_EMUL_MINOR: u32 = 38;
210 const PERL_EMUL_PATCH: u32 = 0;
211 5.0 + (PERL_EMUL_MINOR as f64) / 1000.0 + (PERL_EMUL_PATCH as f64) / 1_000_000.0
212}
213
214#[inline]
216fn fast_rng_seed() -> u64 {
217 let local: u8 = 0;
218 let addr = &local as *const u8 as u64;
219 (std::process::id() as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ addr
220}
221
222fn cached_executable_path() -> String {
224 static CACHED: OnceLock<String> = OnceLock::new();
225 CACHED
226 .get_or_init(|| {
227 std::env::current_exe()
228 .map(|p| p.to_string_lossy().into_owned())
229 .unwrap_or_else(|_| "stryke".to_string())
230 })
231 .clone()
232}
233
234fn build_term_hash() -> IndexMap<String, StrykeValue> {
235 let mut m = IndexMap::new();
236 m.insert(
237 "TERM".into(),
238 StrykeValue::string(std::env::var("TERM").unwrap_or_default()),
239 );
240 m.insert(
241 "COLORTERM".into(),
242 StrykeValue::string(std::env::var("COLORTERM").unwrap_or_default()),
243 );
244
245 let (rows, cols) = term_size();
246 m.insert("rows".into(), StrykeValue::integer(rows));
247 m.insert("cols".into(), StrykeValue::integer(cols));
248
249 #[cfg(unix)]
250 let is_tty = unsafe { libc::isatty(1) != 0 };
251 #[cfg(not(unix))]
252 let is_tty = false;
253 m.insert(
254 "is_tty".into(),
255 StrykeValue::integer(if is_tty { 1 } else { 0 }),
256 );
257
258 m
259}
260
261fn term_size() -> (i64, i64) {
262 #[cfg(unix)]
263 {
264 unsafe {
265 let mut ws: libc::winsize = std::mem::zeroed();
266 if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 {
267 return (ws.ws_row as i64, ws.ws_col as i64);
268 }
269 }
270 }
271 let rows = std::env::var("LINES")
272 .ok()
273 .and_then(|s| s.parse().ok())
274 .unwrap_or(24);
275 let cols = std::env::var("COLUMNS")
276 .ok()
277 .and_then(|s| s.parse().ok())
278 .unwrap_or(80);
279 (rows, cols)
280}
281
282#[cfg(unix)]
283fn build_uname_hash() -> IndexMap<String, StrykeValue> {
284 fn uts_field(slice: &[libc::c_char]) -> String {
285 let n = slice.iter().take_while(|&&c| c != 0).count();
286 let bytes: Vec<u8> = slice[..n].iter().map(|&c| c as u8).collect();
287 String::from_utf8_lossy(&bytes).into_owned()
288 }
289 let mut m = IndexMap::new();
290 let mut uts: libc::utsname = unsafe { std::mem::zeroed() };
291 if unsafe { libc::uname(&mut uts) } == 0 {
292 m.insert(
293 "sysname".into(),
294 StrykeValue::string(uts_field(uts.sysname.as_slice())),
295 );
296 m.insert(
297 "nodename".into(),
298 StrykeValue::string(uts_field(uts.nodename.as_slice())),
299 );
300 m.insert(
301 "release".into(),
302 StrykeValue::string(uts_field(uts.release.as_slice())),
303 );
304 m.insert(
305 "version".into(),
306 StrykeValue::string(uts_field(uts.version.as_slice())),
307 );
308 m.insert(
309 "machine".into(),
310 StrykeValue::string(uts_field(uts.machine.as_slice())),
311 );
312 }
313 m
314}
315
316#[cfg(unix)]
317fn build_limits_hash() -> IndexMap<String, StrykeValue> {
318 use libc::{getrlimit, rlimit, RLIM_INFINITY};
319 #[cfg(all(target_os = "linux", target_env = "gnu"))]
324 type RlimitResource = libc::__rlimit_resource_t;
325 #[cfg(all(target_os = "linux", not(target_env = "gnu")))]
326 type RlimitResource = libc::c_int;
327 #[cfg(not(target_os = "linux"))]
328 type RlimitResource = libc::c_int;
329 fn get_limit(resource: RlimitResource) -> (i64, i64) {
330 let mut rlim = rlimit {
331 rlim_cur: 0,
332 rlim_max: 0,
333 };
334 if unsafe { getrlimit(resource, &mut rlim) } == 0 {
335 let cur = if rlim.rlim_cur == RLIM_INFINITY {
336 -1
337 } else {
338 rlim.rlim_cur as i64
339 };
340 let max = if rlim.rlim_max == RLIM_INFINITY {
341 -1
342 } else {
343 rlim.rlim_max as i64
344 };
345 (cur, max)
346 } else {
347 (-1, -1)
348 }
349 }
350 let mut m = IndexMap::new();
351 let (cur, max) = get_limit(libc::RLIMIT_NOFILE);
352 m.insert("nofile".into(), StrykeValue::integer(cur));
353 m.insert("nofile_max".into(), StrykeValue::integer(max));
354 let (cur, max) = get_limit(libc::RLIMIT_STACK);
355 m.insert("stack".into(), StrykeValue::integer(cur));
356 m.insert("stack_max".into(), StrykeValue::integer(max));
357 let (cur, max) = get_limit(libc::RLIMIT_AS);
358 m.insert("as".into(), StrykeValue::integer(cur));
359 m.insert("as_max".into(), StrykeValue::integer(max));
360 let (cur, max) = get_limit(libc::RLIMIT_DATA);
361 m.insert("data".into(), StrykeValue::integer(cur));
362 m.insert("data_max".into(), StrykeValue::integer(max));
363 let (cur, max) = get_limit(libc::RLIMIT_FSIZE);
364 m.insert("fsize".into(), StrykeValue::integer(cur));
365 m.insert("fsize_max".into(), StrykeValue::integer(max));
366 let (cur, max) = get_limit(libc::RLIMIT_CORE);
367 m.insert("core".into(), StrykeValue::integer(cur));
368 m.insert("core_max".into(), StrykeValue::integer(max));
369 let (cur, max) = get_limit(libc::RLIMIT_CPU);
370 m.insert("cpu".into(), StrykeValue::integer(cur));
371 m.insert("cpu_max".into(), StrykeValue::integer(max));
372 let (cur, max) = get_limit(libc::RLIMIT_NPROC);
373 m.insert("nproc".into(), StrykeValue::integer(cur));
374 m.insert("nproc_max".into(), StrykeValue::integer(max));
375 #[cfg(target_os = "linux")]
376 {
377 let (cur, max) = get_limit(libc::RLIMIT_MEMLOCK);
378 m.insert("memlock".into(), StrykeValue::integer(cur));
379 m.insert("memlock_max".into(), StrykeValue::integer(max));
380 }
381 m
382}
383
384#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
386pub(crate) enum WantarrayCtx {
387 #[default]
388 Scalar,
389 List,
390 Void,
391}
392
393impl WantarrayCtx {
394 #[inline]
395 pub(crate) fn from_byte(b: u8) -> Self {
396 match b {
397 1 => Self::List,
398 2 => Self::Void,
399 _ => Self::Scalar,
400 }
401 }
402
403 #[inline]
404 pub(crate) fn as_byte(self) -> u8 {
405 match self {
406 Self::Scalar => 0,
407 Self::List => 1,
408 Self::Void => 2,
409 }
410 }
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
415pub(crate) enum LogLevelFilter {
416 Trace,
417 Debug,
418 Info,
419 Warn,
420 Error,
421}
422
423impl LogLevelFilter {
424 pub(crate) fn parse(s: &str) -> Option<Self> {
425 match s.trim().to_ascii_lowercase().as_str() {
426 "trace" => Some(Self::Trace),
427 "debug" => Some(Self::Debug),
428 "info" => Some(Self::Info),
429 "warn" | "warning" => Some(Self::Warn),
430 "error" => Some(Self::Error),
431 _ => None,
432 }
433 }
434
435 pub(crate) fn as_str(self) -> &'static str {
436 match self {
437 Self::Trace => "trace",
438 Self::Debug => "debug",
439 Self::Info => "info",
440 Self::Warn => "warn",
441 Self::Error => "error",
442 }
443 }
444}
445
446fn arrow_deref_array_assign_rhs_list_ctx(index: &Expr) -> bool {
448 match &index.kind {
449 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => true,
450 ExprKind::QW(ws) => ws.len() > 1,
451 ExprKind::List(el) => {
452 if el.len() > 1 {
453 true
454 } else if el.len() == 1 {
455 arrow_deref_array_assign_rhs_list_ctx(&el[0])
456 } else {
457 false
458 }
459 }
460 _ => false,
461 }
462}
463
464pub(crate) fn assign_rhs_wantarray(target: &Expr) -> WantarrayCtx {
467 match &target.kind {
468 ExprKind::ArrayVar(_) | ExprKind::HashVar(_) => WantarrayCtx::List,
469 ExprKind::ScalarVar(_) | ExprKind::ArrayElement { .. } | ExprKind::HashElement { .. } => {
470 WantarrayCtx::Scalar
471 }
472 ExprKind::Deref { kind, .. } => match kind {
473 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
474 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
475 },
476 ExprKind::ArrowDeref {
477 index,
478 kind: DerefKind::Array,
479 ..
480 } => {
481 if arrow_deref_array_assign_rhs_list_ctx(index) {
482 WantarrayCtx::List
483 } else {
484 WantarrayCtx::Scalar
485 }
486 }
487 ExprKind::ArrowDeref {
488 kind: DerefKind::Hash,
489 ..
490 }
491 | ExprKind::ArrowDeref {
492 kind: DerefKind::Call,
493 ..
494 } => WantarrayCtx::Scalar,
495 ExprKind::HashSliceDeref { .. }
496 | ExprKind::HashSlice { .. }
497 | ExprKind::HashKvSlice { .. } => WantarrayCtx::List,
498 ExprKind::ArraySlice { indices, .. } => {
499 if indices.len() > 1 {
500 WantarrayCtx::List
501 } else if indices.len() == 1 {
502 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
503 WantarrayCtx::List
504 } else {
505 WantarrayCtx::Scalar
506 }
507 } else {
508 WantarrayCtx::Scalar
509 }
510 }
511 ExprKind::AnonymousListSlice { indices, .. } => {
512 if indices.len() > 1 {
513 WantarrayCtx::List
514 } else if indices.len() == 1 {
515 if arrow_deref_array_assign_rhs_list_ctx(&indices[0]) {
516 WantarrayCtx::List
517 } else {
518 WantarrayCtx::Scalar
519 }
520 } else {
521 WantarrayCtx::Scalar
522 }
523 }
524 ExprKind::Typeglob(_) | ExprKind::TypeglobExpr(_) => WantarrayCtx::Scalar,
525 ExprKind::List(_) => WantarrayCtx::List,
526 _ => WantarrayCtx::Scalar,
527 }
528}
529
530#[derive(Clone)]
535pub(crate) struct RegexMatchMemo {
536 pub pattern: String,
537 pub flags: String,
538 pub multiline: bool,
539 pub haystack: String,
540 pub result: StrykeValue,
541}
542
543#[derive(Clone, Copy, Default)]
545struct FlipFlopTreeState {
546 active: bool,
547 exclusive_left_line: Option<i64>,
551}
552
553#[derive(Clone)]
555pub(crate) struct IoSharedFile(pub Arc<Mutex<File>>);
556
557impl Read for IoSharedFile {
558 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
559 self.0.lock().read(buf)
560 }
561}
562
563pub(crate) struct IoSharedFileWrite(pub Arc<Mutex<File>>);
564
565impl IoWrite for IoSharedFileWrite {
566 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
567 self.0.lock().write(buf)
568 }
569
570 fn flush(&mut self) -> io::Result<()> {
571 self.0.lock().flush()
572 }
573}
574
575pub struct VMHelper {
577 pub scope: Scope,
578 pub(crate) subs: HashMap<String, Arc<StrykeSub>>,
579 pub(crate) intercepts: Vec<crate::aop::Intercept>,
581 pub(crate) next_intercept_id: u32,
583 pub(crate) intercept_ctx_stack: Vec<crate::aop::InterceptCtx>,
585 pub(crate) intercept_active_names: Vec<String>,
589 pub(crate) file: String,
590 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
592 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
593 pub ofs: String,
595 pub ors: String,
597 pub irs: Option<String>,
600 pub errno: String,
602 pub errno_code: i32,
604 pub eval_error: String,
606 pub eval_error_code: i32,
608 pub eval_error_value: Option<StrykeValue>,
610 pub argv: Vec<String>,
612 pub env: IndexMap<String, StrykeValue>,
614 pub env_materialized: bool,
616 pub program_name: String,
618 pub line_number: i64,
620 pub last_readline_handle: String,
622 pub(crate) last_stdin_die_bracket: String,
624 pub handle_line_numbers: HashMap<String, i64>,
626 pub(crate) flip_flop_active: Vec<bool>,
630 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
633 pub(crate) flip_flop_sequence: Vec<i64>,
637 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
641 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
643 pub sigint_pending_caret: Cell<bool>,
645 pub auto_split: bool,
647 pub field_separator: Option<String>,
649 begin_blocks: Vec<Block>,
651 unit_check_blocks: Vec<Block>,
653 check_blocks: Vec<Block>,
655 init_blocks: Vec<Block>,
657 end_blocks: Vec<Block>,
659 pub warnings: bool,
661 pub output_autoflush: bool,
663 pub default_print_handle: String,
665 pub suppress_stdout: bool,
667 pub test_pass_count: std::sync::atomic::AtomicUsize,
674 pub test_fail_count: std::sync::atomic::AtomicUsize,
675 pub test_skip_count: std::sync::atomic::AtomicUsize,
676 pub test_pass_total: std::sync::atomic::AtomicUsize,
686 pub test_fail_total: std::sync::atomic::AtomicUsize,
687 pub test_skip_total: std::sync::atomic::AtomicUsize,
688 pub test_run_failed: std::sync::atomic::AtomicBool,
694 pub child_exit_status: i64,
696 pub last_match: String,
698 pub prematch: String,
700 pub postmatch: String,
702 pub last_paren_match: String,
704 pub list_separator: String,
706 pub script_start_time: i64,
708 pub compile_hints: i64,
710 pub warning_bits: i64,
712 pub global_phase: String,
714 pub subscript_sep: String,
716 pub inplace_edit: String,
719 pub debug_flags: i64,
721 pub perl_debug_flags: i64,
723 pub eval_nesting: u32,
725 pub argv_current_file: String,
727 pub(crate) diamond_next_idx: usize,
729 pub(crate) diamond_reader: Option<BufReader<File>>,
731 pub strict_refs: bool,
733 pub strict_subs: bool,
734 pub strict_vars: bool,
735 pub utf8_pragma: bool,
737 pub open_pragma_utf8: bool,
739 pub feature_bits: u64,
741 pub num_threads: usize,
743 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
745 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
748 regex_match_memo: Option<RegexMatchMemo>,
757 regex_capture_scope_fresh: bool,
761 pub(crate) regex_pos: HashMap<String, Option<usize>>,
763 pub(crate) state_vars: HashMap<String, StrykeValue>,
765 pub(crate) state_bindings_stack: Vec<Vec<(String, String)>>,
767 pub(crate) rand_rng: StdRng,
769 pub(crate) dir_handles: HashMap<String, DirHandleState>,
771 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
773 pub(crate) pipe_children: HashMap<String, Child>,
775 pub(crate) socket_handles: HashMap<String, StrykeSocket>,
777 pub(crate) wantarray_kind: WantarrayCtx,
779 pub struct_defs: HashMap<String, Arc<StructDef>>,
781 pub enum_defs: HashMap<String, Arc<EnumDef>>,
783 pub class_defs: HashMap<String, Arc<ClassDef>>,
785 pub trait_defs: HashMap<String, Arc<TraitDef>>,
787 pub profiler: Option<Profiler>,
790 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
792 pub(crate) virtual_modules: HashMap<String, String>,
794 pub(crate) tied_hashes: HashMap<String, StrykeValue>,
796 pub(crate) tied_scalars: HashMap<String, StrykeValue>,
798 pub(crate) tied_arrays: HashMap<String, StrykeValue>,
800 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
802 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
804 pub(crate) special_caret_scalars: HashMap<String, StrykeValue>,
806 pub format_page_number: i64,
808 pub format_lines_per_page: i64,
810 pub format_lines_left: i64,
812 pub format_line_break_chars: String,
814 pub format_top_name: String,
816 pub accumulator_format: String,
818 pub max_system_fd: i64,
820 pub emergency_memory: String,
822 pub last_subpattern_name: String,
824 pub inc_hook_index: i64,
826 pub multiline_match: bool,
828 pub executable_path: String,
830 pub formfeed_string: String,
832 pub(crate) glob_handle_alias: HashMap<String, String>,
834 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
836 pub(crate) special_var_restore_frames: Vec<Vec<(String, StrykeValue)>>,
841 pub(crate) reflection_hashes_ready: bool,
845 pub(crate) english_enabled: bool,
846 pub(crate) english_no_match_vars: bool,
848 pub(crate) english_match_vars_ever_enabled: bool,
852 english_lexical_scalars: Vec<HashSet<String>>,
854 our_lexical_scalars: Vec<HashSet<String>>,
856 our_lexical_arrays: Vec<HashSet<String>>,
860 our_lexical_hashes: Vec<HashSet<String>>,
863 pub vm_jit_enabled: bool,
866 pub disasm_bytecode: bool,
868 pub cached_chunk: Option<crate::bytecode::Chunk>,
872 pub cache_script_path: Option<std::path::PathBuf>,
874 pub(crate) stryke_pwd: PathBuf,
876 pub(crate) in_generator: bool,
878 pub line_mode_skip_main: bool,
880 pub line_mode_chunk: Option<crate::bytecode::Chunk>,
883 pub(crate) line_mode_eof_pending: bool,
887 pub line_mode_stdin_pending: VecDeque<String>,
890 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
892 pub(crate) log_level_override: Option<LogLevelFilter>,
894 pub(crate) current_sub_stack: Vec<Arc<StrykeSub>>,
897 pub debugger: Option<crate::debugger::Debugger>,
899 pub(crate) debug_call_stack: Vec<(String, usize)>,
901}
902
903#[derive(Debug, Clone, Default)]
905pub struct ReplCompletionSnapshot {
906 pub subs: Vec<String>,
907 pub blessed_scalars: HashMap<String, String>,
908 pub isa_for_class: HashMap<String, Vec<String>>,
909}
910
911impl ReplCompletionSnapshot {
912 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
914 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
915 let mro = linearize_c3(class, &parents, 0);
916 let mut names = HashSet::new();
917 for pkg in &mro {
918 if pkg == "UNIVERSAL" {
919 continue;
920 }
921 let prefix = format!("{}::", pkg);
922 for k in &self.subs {
923 if k.starts_with(&prefix) {
924 let rest = &k[prefix.len()..];
925 if !rest.contains("::") {
926 names.insert(rest.to_string());
927 }
928 }
929 }
930 }
931 for k in &self.subs {
932 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
933 if !rest.contains("::") {
934 names.insert(rest.to_string());
935 }
936 }
937 }
938 let mut v: Vec<String> = names.into_iter().collect();
939 v.sort();
940 v
941 }
942}
943
944fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
945 let left = left.trim_end();
946 if left.is_empty() {
947 return None;
948 }
949 if let Some(i) = left.rfind('$') {
950 let name = left[i + 1..].trim();
951 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
952 return state.blessed_scalars.get(name).cloned();
953 }
954 }
955 let tok = left.split_whitespace().last()?;
956 if tok.contains("::") {
957 return Some(tok.to_string());
958 }
959 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
960 return Some(tok.to_string());
961 }
962 None
963}
964
965pub fn repl_arrow_method_completions(
967 state: &ReplCompletionSnapshot,
968 line: &str,
969 pos: usize,
970) -> Option<(usize, Vec<String>)> {
971 let pos = pos.min(line.len());
972 let before = &line[..pos];
973 let arrow_idx = before.rfind("->")?;
974 let after_arrow = &before[arrow_idx + 2..];
975 let rest = after_arrow.trim_start();
976 let ws_len = after_arrow.len() - rest.len();
977 let method_start = arrow_idx + 2 + ws_len;
978 let method_prefix = &line[method_start..pos];
979 if !method_prefix
980 .chars()
981 .all(|c| c.is_alphanumeric() || c == '_')
982 {
983 return None;
984 }
985 let left = line[..arrow_idx].trim_end();
986 let class = repl_resolve_class_for_arrow(state, left)?;
987 let mut methods = state.methods_for_class(&class);
988 methods.retain(|m| m.starts_with(method_prefix));
989 Some((method_start, methods))
990}
991
992#[derive(Debug, Clone, Default)]
994pub(crate) struct ModuleExportLists {
995 pub export: Vec<String>,
997 pub export_ok: Vec<String>,
999}
1000
1001fn piped_shell_command(cmd: &str) -> Command {
1003 if cfg!(windows) {
1004 let mut c = Command::new("cmd");
1005 c.arg("/C").arg(cmd);
1006 c
1007 } else {
1008 let mut c = Command::new("sh");
1009 c.arg("-c").arg(cmd);
1010 c
1011 }
1012}
1013
1014fn expand_perl_regex_octal_escapes(pat: &str) -> String {
1021 let mut out = String::with_capacity(pat.len());
1022 let mut it = pat.chars().peekable();
1023 while let Some(c) = it.next() {
1024 if c == '\\' {
1025 if let Some(&'0') = it.peek() {
1026 let mut oct = String::new();
1028 while oct.len() < 3 {
1029 if let Some(&d) = it.peek() {
1030 if ('0'..='7').contains(&d) {
1031 oct.push(d);
1032 it.next();
1033 } else {
1034 break;
1035 }
1036 } else {
1037 break;
1038 }
1039 }
1040 if let Ok(val) = u8::from_str_radix(&oct, 8) {
1041 out.push_str(&format!("\\x{:02x}", val));
1042 } else {
1043 out.push('\\');
1044 out.push_str(&oct);
1045 }
1046 continue;
1047 }
1048 }
1049 out.push(c);
1050 }
1051 out
1052}
1053
1054fn expand_perl_regex_quotemeta(pat: &str) -> String {
1055 let mut out = String::with_capacity(pat.len().saturating_mul(2));
1056 let mut it = pat.chars().peekable();
1057 let mut in_q = false;
1058 while let Some(c) = it.next() {
1059 if in_q {
1060 if c == '\\' && it.peek() == Some(&'E') {
1061 it.next();
1062 in_q = false;
1063 continue;
1064 }
1065 out.push_str(&perl_quotemeta(&c.to_string()));
1066 continue;
1067 }
1068 if c == '\\' && it.peek() == Some(&'Q') {
1069 it.next();
1070 in_q = true;
1071 continue;
1072 }
1073 out.push(c);
1074 }
1075 out
1076}
1077
1078pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
1084 let mut out = String::with_capacity(replacement.len() + 8);
1085 let mut it = replacement.chars().peekable();
1086 while let Some(c) = it.next() {
1087 if c == '\\' {
1088 match it.peek() {
1089 Some(&d) if d.is_ascii_digit() => {
1090 it.next();
1091 out.push_str("${");
1092 out.push(d);
1093 while let Some(&d2) = it.peek() {
1094 if !d2.is_ascii_digit() {
1095 break;
1096 }
1097 it.next();
1098 out.push(d2);
1099 }
1100 out.push('}');
1101 }
1102 Some(&'\\') => {
1103 it.next();
1104 out.push('\\');
1105 }
1106 _ => out.push('\\'),
1107 }
1108 } else if c == '$' {
1109 match it.peek() {
1110 Some(&d) if d.is_ascii_digit() => {
1111 it.next();
1112 out.push_str("${");
1113 out.push(d);
1114 while let Some(&d2) = it.peek() {
1115 if !d2.is_ascii_digit() {
1116 break;
1117 }
1118 it.next();
1119 out.push(d2);
1120 }
1121 out.push('}');
1122 }
1123 Some(&'{') => {
1124 out.push('$');
1126 }
1127 _ => out.push('$'),
1128 }
1129 } else {
1130 out.push(c);
1131 }
1132 }
1133 out
1134}
1135
1136fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
1139 debug_assert_eq!(chars.get(i), Some(&'['));
1140 out.push('[');
1141 i += 1;
1142 if i < chars.len() && chars[i] == '^' {
1143 out.push('^');
1144 i += 1;
1145 }
1146 if i >= chars.len() {
1147 return i;
1148 }
1149 if chars[i] == ']' {
1153 if i + 1 < chars.len() && chars[i + 1] == ']' {
1154 out.push(']');
1156 i += 1;
1157 } else {
1158 let mut scan = i + 1;
1159 let mut found_closing = false;
1160 while scan < chars.len() {
1161 if chars[scan] == '\\' && scan + 1 < chars.len() {
1162 scan += 2;
1163 continue;
1164 }
1165 if chars[scan] == ']' {
1166 found_closing = true;
1167 break;
1168 }
1169 scan += 1;
1170 }
1171 if found_closing {
1172 out.push(']');
1173 i += 1;
1174 } else {
1175 out.push(']');
1176 return i + 1;
1177 }
1178 }
1179 }
1180 while i < chars.len() && chars[i] != ']' {
1181 if chars[i] == '\\' && i + 1 < chars.len() {
1182 out.push(chars[i]);
1183 out.push(chars[i + 1]);
1184 i += 2;
1185 continue;
1186 }
1187 out.push(chars[i]);
1188 i += 1;
1189 }
1190 if i < chars.len() {
1191 out.push(']');
1192 i += 1;
1193 }
1194 i
1195}
1196
1197fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1202 if multiline_flag {
1203 return pat.to_string();
1204 }
1205 let chars: Vec<char> = pat.chars().collect();
1206 let mut out = String::with_capacity(pat.len().saturating_add(16));
1207 let mut i = 0usize;
1208 while i < chars.len() {
1209 let c = chars[i];
1210 if c == '\\' && i + 1 < chars.len() {
1211 out.push(c);
1212 out.push(chars[i + 1]);
1213 i += 2;
1214 continue;
1215 }
1216 if c == '[' {
1217 i = copy_regex_char_class(&chars, i, &mut out);
1218 continue;
1219 }
1220 if c == '$' {
1221 if let Some(&next) = chars.get(i + 1) {
1222 if next.is_ascii_digit() {
1223 out.push(c);
1224 i += 1;
1225 continue;
1226 }
1227 if next == '{' {
1228 out.push(c);
1229 i += 1;
1230 continue;
1231 }
1232 if next.is_ascii_alphanumeric() || next == '_' {
1233 out.push(c);
1234 i += 1;
1235 continue;
1236 }
1237 }
1238 out.push_str("(?=\\n?\\z)");
1239 i += 1;
1240 continue;
1241 }
1242 out.push(c);
1243 i += 1;
1244 }
1245 out
1246}
1247
1248#[derive(Debug, Clone)]
1250pub(crate) struct DirHandleState {
1251 pub entries: Vec<String>,
1252 pub pos: usize,
1253}
1254
1255pub(crate) fn perl_osname() -> String {
1257 match std::env::consts::OS {
1258 "linux" => "linux".to_string(),
1259 "macos" => "darwin".to_string(),
1260 "windows" => "MSWin32".to_string(),
1261 other => other.to_string(),
1262 }
1263}
1264
1265fn perl_version_v_string() -> String {
1266 format!("v{}", env!("CARGO_PKG_VERSION"))
1267}
1268
1269fn extended_os_error_string() -> String {
1270 std::io::Error::last_os_error().to_string()
1271}
1272
1273#[cfg(unix)]
1274fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1275 unsafe {
1276 (
1277 libc::getuid() as i64,
1278 libc::geteuid() as i64,
1279 libc::getgid() as i64,
1280 libc::getegid() as i64,
1281 )
1282 }
1283}
1284
1285#[cfg(not(unix))]
1286fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1287 (0, 0, 0, 0)
1288}
1289
1290fn unix_id_for_special(name: &str) -> i64 {
1291 let (r, e, _, _) = unix_real_effective_ids();
1292 match name {
1293 "<" => r,
1294 ">" => e,
1295 _ => 0,
1296 }
1297}
1298
1299#[cfg(unix)]
1300fn unix_group_list_string(primary: libc::gid_t) -> String {
1301 let mut buf = vec![0 as libc::gid_t; 256];
1302 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1303 if n <= 0 {
1304 return format!("{}", primary);
1305 }
1306 let mut parts = vec![format!("{}", primary)];
1307 for g in buf.iter().take(n as usize) {
1308 parts.push(format!("{}", g));
1309 }
1310 parts.join(" ")
1311}
1312
1313#[cfg(unix)]
1315fn unix_group_list_for_special(name: &str) -> String {
1316 let (_, _, gid, egid) = unix_real_effective_ids();
1317 match name {
1318 "(" => unix_group_list_string(gid as libc::gid_t),
1319 ")" => unix_group_list_string(egid as libc::gid_t),
1320 _ => String::new(),
1321 }
1322}
1323
1324#[cfg(not(unix))]
1325fn unix_group_list_for_special(_name: &str) -> String {
1326 String::new()
1327}
1328
1329#[cfg(unix)]
1332fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1333 use libc::{getpwuid_r, getuid};
1334 use std::ffi::CStr;
1335 use std::os::unix::ffi::OsStringExt;
1336 let uid = unsafe { getuid() };
1337 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1338 let mut result: *mut libc::passwd = std::ptr::null_mut();
1339 let mut buf = vec![0u8; 16_384];
1340 let rc = unsafe {
1341 getpwuid_r(
1342 uid,
1343 &mut pw,
1344 buf.as_mut_ptr().cast::<libc::c_char>(),
1345 buf.len(),
1346 &mut result,
1347 )
1348 };
1349 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1350 return None;
1351 }
1352 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1353 if bytes.is_empty() {
1354 return None;
1355 }
1356 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1357}
1358
1359#[cfg(unix)]
1361fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1362 use libc::getpwnam_r;
1363 use std::ffi::{CStr, CString};
1364 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1365 let bytes = login.as_bytes();
1366 if bytes.is_empty() || bytes.contains(&0) {
1367 return None;
1368 }
1369 let cname = CString::new(bytes).ok()?;
1370 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1371 let mut result: *mut libc::passwd = std::ptr::null_mut();
1372 let mut buf = vec![0u8; 16_384];
1373 let rc = unsafe {
1374 getpwnam_r(
1375 cname.as_ptr(),
1376 &mut pw,
1377 buf.as_mut_ptr().cast::<libc::c_char>(),
1378 buf.len(),
1379 &mut result,
1380 )
1381 };
1382 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1383 return None;
1384 }
1385 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1386 if dir_bytes.is_empty() {
1387 return None;
1388 }
1389 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1390}
1391
1392impl Default for VMHelper {
1393 fn default() -> Self {
1394 Self::new()
1395 }
1396}
1397
1398#[derive(Clone, Copy)]
1400pub(crate) enum CaptureAllMode {
1401 Empty,
1403 Append,
1405 Skip,
1407}
1408
1409impl VMHelper {
1410 pub fn new() -> Self {
1411 let mut scope = Scope::new();
1412 scope.declare_array("INC", vec![StrykeValue::string(".".to_string())]);
1413 scope.declare_hash("INC", IndexMap::new());
1414 scope.declare_array("ARGV", vec![]);
1415 scope.declare_array("_", vec![]);
1416
1417 let path_vec: Vec<StrykeValue> = std::env::var("PATH")
1419 .unwrap_or_default()
1420 .split(if cfg!(windows) { ';' } else { ':' })
1421 .filter(|s| !s.is_empty())
1422 .map(|p| StrykeValue::string(p.to_string()))
1423 .collect();
1424 scope.declare_array_frozen("path", path_vec.clone(), true);
1425 scope.declare_array_frozen("p", path_vec, true);
1426
1427 let fpath_vec: Vec<StrykeValue> = std::env::var("FPATH")
1429 .unwrap_or_default()
1430 .split(':')
1431 .filter(|s| !s.is_empty())
1432 .map(|p| StrykeValue::string(p.to_string()))
1433 .collect();
1434 scope.declare_array_frozen("fpath", fpath_vec.clone(), true);
1435 scope.declare_array_frozen("f", fpath_vec, true);
1436 scope.declare_hash("ENV", IndexMap::new());
1437 scope.declare_hash("SIG", IndexMap::new());
1438
1439 let term_map = build_term_hash();
1441 scope.declare_hash_global_frozen("term", term_map);
1442
1443 #[cfg(unix)]
1445 {
1446 let uname_map = build_uname_hash();
1447 scope.declare_hash_global_frozen("uname", uname_map);
1448 }
1449 #[cfg(not(unix))]
1450 {
1451 scope.declare_hash_global_frozen("uname", IndexMap::new());
1452 }
1453
1454 #[cfg(unix)]
1456 {
1457 let limits_map = build_limits_hash();
1458 scope.declare_hash_global_frozen("limits", limits_map);
1459 }
1460 #[cfg(not(unix))]
1461 {
1462 scope.declare_hash_global_frozen("limits", IndexMap::new());
1463 }
1464
1465 scope.declare_scalar(
1488 "stryke::VERSION",
1489 StrykeValue::string(env!("CARGO_PKG_VERSION").to_string()),
1490 );
1491 scope.declare_array("-", vec![]);
1492 scope.declare_array("+", vec![]);
1493 scope.declare_array("^CAPTURE", vec![]);
1494 scope.declare_array("^CAPTURE_ALL", vec![]);
1495 scope.declare_hash("^HOOK", IndexMap::new());
1496 scope.declare_scalar("~", StrykeValue::string("STDOUT".to_string()));
1497
1498 let script_start_time = std::time::SystemTime::now()
1499 .duration_since(std::time::UNIX_EPOCH)
1500 .map(|d| d.as_secs() as i64)
1501 .unwrap_or(0);
1502
1503 let executable_path = cached_executable_path();
1504
1505 let stryke_pwd_init = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1506 let stryke_pwd = std::fs::canonicalize(&stryke_pwd_init).unwrap_or(stryke_pwd_init);
1507
1508 let mut special_caret_scalars: HashMap<String, StrykeValue> = HashMap::new();
1509 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1510 special_caret_scalars.insert(format!("^{}", name), StrykeValue::UNDEF);
1511 }
1512
1513 let mut s = Self {
1514 scope,
1515 subs: HashMap::new(),
1516 intercepts: Vec::new(),
1517 next_intercept_id: 1,
1518 intercept_ctx_stack: Vec::new(),
1519 intercept_active_names: Vec::new(),
1520 struct_defs: HashMap::new(),
1521 enum_defs: HashMap::new(),
1522 class_defs: HashMap::new(),
1523 trait_defs: HashMap::new(),
1524 file: "-e".to_string(),
1525 output_handles: HashMap::new(),
1526 input_handles: HashMap::new(),
1527 ofs: String::new(),
1528 ors: String::new(),
1529 irs: Some("\n".to_string()),
1530 errno: String::new(),
1531 errno_code: 0,
1532 eval_error: String::new(),
1533 eval_error_code: 0,
1534 eval_error_value: None,
1535 argv: Vec::new(),
1536 env: IndexMap::new(),
1537 env_materialized: false,
1538 program_name: "stryke".to_string(),
1539 line_number: 0,
1540 last_readline_handle: String::new(),
1541 last_stdin_die_bracket: "<STDIN>".to_string(),
1542 handle_line_numbers: HashMap::new(),
1543 flip_flop_active: Vec::new(),
1544 flip_flop_exclusive_left_line: Vec::new(),
1545 flip_flop_sequence: Vec::new(),
1546 flip_flop_last_dot: Vec::new(),
1547 flip_flop_tree: HashMap::new(),
1548 sigint_pending_caret: Cell::new(false),
1549 auto_split: false,
1550 field_separator: None,
1551 begin_blocks: Vec::new(),
1552 unit_check_blocks: Vec::new(),
1553 check_blocks: Vec::new(),
1554 init_blocks: Vec::new(),
1555 end_blocks: Vec::new(),
1556 warnings: false,
1557 output_autoflush: false,
1558 default_print_handle: "STDOUT".to_string(),
1559 suppress_stdout: false,
1560 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1561 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1562 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1563 test_pass_total: std::sync::atomic::AtomicUsize::new(0),
1564 test_fail_total: std::sync::atomic::AtomicUsize::new(0),
1565 test_skip_total: std::sync::atomic::AtomicUsize::new(0),
1566 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1567 child_exit_status: 0,
1568 last_match: String::new(),
1569 prematch: String::new(),
1570 postmatch: String::new(),
1571 last_paren_match: String::new(),
1572 list_separator: " ".to_string(),
1573 script_start_time,
1574 compile_hints: 0,
1575 warning_bits: 0,
1576 global_phase: "RUN".to_string(),
1577 subscript_sep: "\x1c".to_string(),
1578 inplace_edit: String::new(),
1579 debug_flags: 0,
1580 perl_debug_flags: 0,
1581 eval_nesting: 0,
1582 argv_current_file: String::new(),
1583 diamond_next_idx: 0,
1584 diamond_reader: None,
1585 strict_refs: false,
1586 strict_subs: false,
1587 strict_vars: false,
1588 utf8_pragma: false,
1589 open_pragma_utf8: false,
1590 feature_bits: FEAT_SAY,
1592 num_threads: 0, regex_cache: HashMap::new(),
1594 regex_last: None,
1595 regex_match_memo: None,
1596 regex_capture_scope_fresh: false,
1597 regex_pos: HashMap::new(),
1598 state_vars: HashMap::new(),
1599 state_bindings_stack: Vec::new(),
1600 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1601 dir_handles: HashMap::new(),
1602 io_file_slots: HashMap::new(),
1603 pipe_children: HashMap::new(),
1604 socket_handles: HashMap::new(),
1605 wantarray_kind: WantarrayCtx::Scalar,
1606 profiler: None,
1607 module_export_lists: HashMap::new(),
1608 virtual_modules: HashMap::new(),
1609 tied_hashes: HashMap::new(),
1610 tied_scalars: HashMap::new(),
1611 tied_arrays: HashMap::new(),
1612 overload_table: HashMap::new(),
1613 format_templates: HashMap::new(),
1614 special_caret_scalars,
1615 format_page_number: 0,
1616 format_lines_per_page: 60,
1617 format_lines_left: 0,
1618 format_line_break_chars: "\n".to_string(),
1619 format_top_name: String::new(),
1620 accumulator_format: String::new(),
1621 max_system_fd: 2,
1622 emergency_memory: String::new(),
1623 last_subpattern_name: String::new(),
1624 inc_hook_index: 0,
1625 multiline_match: false,
1626 executable_path,
1627 formfeed_string: "\x0c".to_string(),
1628 glob_handle_alias: HashMap::new(),
1629 glob_restore_frames: vec![Vec::new()],
1630 special_var_restore_frames: vec![Vec::new()],
1631 reflection_hashes_ready: false,
1632 english_enabled: false,
1633 english_no_match_vars: false,
1634 english_match_vars_ever_enabled: false,
1635 english_lexical_scalars: vec![HashSet::new()],
1636 our_lexical_scalars: vec![HashSet::new()],
1637 our_lexical_arrays: vec![HashSet::new()],
1638 our_lexical_hashes: vec![HashSet::new()],
1639 vm_jit_enabled: !matches!(
1640 std::env::var("STRYKE_NO_JIT"),
1641 Ok(v)
1642 if v == "1"
1643 || v.eq_ignore_ascii_case("true")
1644 || v.eq_ignore_ascii_case("yes")
1645 ),
1646 disasm_bytecode: false,
1647 cached_chunk: None,
1648 cache_script_path: None,
1649 stryke_pwd,
1650 in_generator: false,
1651 line_mode_skip_main: false,
1652 line_mode_chunk: None,
1653 line_mode_eof_pending: false,
1654 line_mode_stdin_pending: VecDeque::new(),
1655 rate_limit_slots: Vec::new(),
1656 log_level_override: None,
1657 current_sub_stack: Vec::new(),
1658 debugger: None,
1659 debug_call_stack: Vec::new(),
1660 };
1661 s.install_overload_pragma_stubs();
1662 s
1663 }
1664
1665 pub(crate) fn ensure_reflection_hashes(&mut self) {
1669 if self.reflection_hashes_ready {
1670 return;
1671 }
1672 self.reflection_hashes_ready = true;
1673 self.refresh_package_stashes();
1676 if crate::compat_mode() {
1680 return;
1681 }
1682 let builtins_map = crate::builtins::builtins_hash_map();
1683 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1684 let extensions_map = crate::builtins::extensions_hash_map();
1685 let aliases_map = crate::builtins::aliases_hash_map();
1686 let descriptions_map = crate::builtins::descriptions_hash_map();
1687 let categories_map = crate::builtins::categories_hash_map();
1688 let primaries_map = crate::builtins::primaries_hash_map();
1689 let keywords_map = crate::builtins::keywords_hash_map();
1690 let operators_map = crate::builtins::operators_hash_map();
1691 let special_vars_map = crate::builtins::special_vars_hash_map();
1692 let all_map = crate::builtins::all_hash_map();
1693 self.scope
1694 .declare_hash_global_frozen("stryke::builtins", builtins_map.clone());
1695 self.scope
1696 .declare_hash_global_frozen("stryke::perl_compats", perl_compats_map.clone());
1697 self.scope
1698 .declare_hash_global_frozen("stryke::extensions", extensions_map.clone());
1699 self.scope
1700 .declare_hash_global_frozen("stryke::aliases", aliases_map.clone());
1701 self.scope
1702 .declare_hash_global_frozen("stryke::descriptions", descriptions_map.clone());
1703 self.scope
1704 .declare_hash_global_frozen("stryke::categories", categories_map.clone());
1705 self.scope
1706 .declare_hash_global_frozen("stryke::primaries", primaries_map.clone());
1707 self.scope
1708 .declare_hash_global_frozen("stryke::keywords", keywords_map.clone());
1709 self.scope
1710 .declare_hash_global_frozen("stryke::operators", operators_map.clone());
1711 self.scope
1712 .declare_hash_global_frozen("stryke::special_vars", special_vars_map.clone());
1713 self.scope
1714 .declare_hash_global_frozen("stryke::all", all_map.clone());
1715 for (name, val) in [
1718 ("b", builtins_map),
1719 ("pc", perl_compats_map),
1720 ("e", extensions_map),
1721 ("a", aliases_map),
1722 ("d", descriptions_map),
1723 ("c", categories_map),
1724 ("p", primaries_map),
1725 ("k", keywords_map),
1726 ("o", operators_map),
1727 ("v", special_vars_map),
1728 ("all", all_map),
1729 ] {
1730 if !self.scope.any_frame_has_hash(name) {
1731 self.scope.declare_hash_global_frozen(name, val);
1732 }
1733 }
1734 if !self.scope.has_lexical_hash("parameters") {
1737 self.refresh_parameters_hash();
1738 }
1739 }
1740
1741 pub fn refresh_parameters_hash(&mut self) {
1750 let pairs = self.scope.parameters_pairs();
1751 let mut h: indexmap::IndexMap<String, StrykeValue> =
1752 indexmap::IndexMap::with_capacity(pairs.len());
1753 for (name, kind) in pairs {
1754 h.insert(name, StrykeValue::string(kind.to_string()));
1755 }
1756 self.scope.declare_hash_global_frozen("parameters", h);
1759 }
1760
1761 pub fn refresh_package_stashes(&mut self) {
1771 use indexmap::IndexMap;
1772
1773 let mut by_pkg: std::collections::HashMap<String, IndexMap<String, StrykeValue>> =
1774 std::collections::HashMap::new();
1775
1776 let record = |pkg: &str,
1777 sym: &str,
1778 kind: &str,
1779 map: &mut std::collections::HashMap<
1780 String,
1781 IndexMap<String, StrykeValue>,
1782 >| {
1783 map.entry(pkg.to_string())
1784 .or_default()
1785 .insert(sym.to_string(), StrykeValue::string(kind.to_string()));
1786 };
1787
1788 for key in self.subs.keys() {
1790 if let Some(idx) = key.rfind("::") {
1791 let (pkg, rest) = key.split_at(idx);
1792 let sym = &rest[2..];
1793 if pkg.is_empty() || sym.is_empty() {
1794 continue;
1795 }
1796 record(pkg, sym, "sub", &mut by_pkg);
1797 } else {
1798 record("main", key, "sub", &mut by_pkg);
1800 }
1801 }
1802
1803 for frame in self.scope.frames_for_introspection() {
1805 let (scalars, arrays, hashes) = frame;
1806 for name in scalars {
1807 if let Some(idx) = name.rfind("::") {
1808 let (pkg, rest) = name.split_at(idx);
1809 let sym = &rest[2..];
1810 if !pkg.is_empty() && !sym.is_empty() {
1811 record(pkg, sym, "scalar", &mut by_pkg);
1812 }
1813 }
1814 }
1815 for name in arrays {
1816 if let Some(idx) = name.rfind("::") {
1817 let (pkg, rest) = name.split_at(idx);
1818 let sym = &rest[2..];
1819 if !pkg.is_empty() && !sym.is_empty() {
1820 record(pkg, sym, "array", &mut by_pkg);
1821 }
1822 }
1823 }
1824 for name in hashes {
1825 if let Some(idx) = name.rfind("::") {
1826 let (pkg, rest) = name.split_at(idx);
1827 let sym = &rest[2..];
1828 if !pkg.is_empty() && !sym.is_empty() {
1829 record(pkg, sym, "hash", &mut by_pkg);
1830 }
1831 }
1832 }
1833 }
1834
1835 for (pkg, mut entries) in by_pkg {
1839 entries.sort_keys();
1840 let key = format!("{}::", pkg);
1841 self.scope.declare_hash_global_frozen(&key, entries);
1842 }
1843 }
1844
1845 fn install_overload_pragma_stubs(&mut self) {
1849 let empty: Block = vec![];
1850 for key in ["overload::import", "overload::unimport"] {
1851 let name = key.to_string();
1852 self.subs.insert(
1853 name.clone(),
1854 Arc::new(StrykeSub {
1855 name,
1856 params: vec![],
1857 body: empty.clone(),
1858 prototype: None,
1859 closure_env: None,
1860 fib_like: None,
1861 }),
1862 );
1863 }
1864 }
1865
1866 pub fn line_mode_worker_clone(&self) -> VMHelper {
1869 VMHelper {
1870 scope: self.scope.clone(),
1871 subs: self.subs.clone(),
1872 intercepts: self.intercepts.clone(),
1873 next_intercept_id: self.next_intercept_id,
1874 intercept_ctx_stack: self.intercept_ctx_stack.clone(),
1875 intercept_active_names: self.intercept_active_names.clone(),
1876 struct_defs: self.struct_defs.clone(),
1877 enum_defs: self.enum_defs.clone(),
1878 class_defs: self.class_defs.clone(),
1879 trait_defs: self.trait_defs.clone(),
1880 file: self.file.clone(),
1881 output_handles: HashMap::new(),
1882 input_handles: HashMap::new(),
1883 ofs: self.ofs.clone(),
1884 ors: self.ors.clone(),
1885 irs: self.irs.clone(),
1886 errno: self.errno.clone(),
1887 errno_code: self.errno_code,
1888 eval_error: self.eval_error.clone(),
1889 eval_error_code: self.eval_error_code,
1890 eval_error_value: self.eval_error_value.clone(),
1891 argv: self.argv.clone(),
1892 env: self.env.clone(),
1893 env_materialized: self.env_materialized,
1894 program_name: self.program_name.clone(),
1895 line_number: 0,
1896 last_readline_handle: String::new(),
1897 last_stdin_die_bracket: "<STDIN>".to_string(),
1898 handle_line_numbers: HashMap::new(),
1899 flip_flop_active: Vec::new(),
1900 flip_flop_exclusive_left_line: Vec::new(),
1901 flip_flop_sequence: Vec::new(),
1902 flip_flop_last_dot: Vec::new(),
1903 flip_flop_tree: HashMap::new(),
1904 sigint_pending_caret: Cell::new(false),
1905 auto_split: self.auto_split,
1906 field_separator: self.field_separator.clone(),
1907 begin_blocks: self.begin_blocks.clone(),
1908 unit_check_blocks: self.unit_check_blocks.clone(),
1909 check_blocks: self.check_blocks.clone(),
1910 init_blocks: self.init_blocks.clone(),
1911 end_blocks: self.end_blocks.clone(),
1912 warnings: self.warnings,
1913 output_autoflush: self.output_autoflush,
1914 default_print_handle: self.default_print_handle.clone(),
1915 suppress_stdout: self.suppress_stdout,
1916 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1920 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1921 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1922 test_pass_total: std::sync::atomic::AtomicUsize::new(0),
1923 test_fail_total: std::sync::atomic::AtomicUsize::new(0),
1924 test_skip_total: std::sync::atomic::AtomicUsize::new(0),
1925 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1926 child_exit_status: self.child_exit_status,
1927 last_match: self.last_match.clone(),
1928 prematch: self.prematch.clone(),
1929 postmatch: self.postmatch.clone(),
1930 last_paren_match: self.last_paren_match.clone(),
1931 list_separator: self.list_separator.clone(),
1932 script_start_time: self.script_start_time,
1933 compile_hints: self.compile_hints,
1934 warning_bits: self.warning_bits,
1935 global_phase: self.global_phase.clone(),
1936 subscript_sep: self.subscript_sep.clone(),
1937 inplace_edit: self.inplace_edit.clone(),
1938 debug_flags: self.debug_flags,
1939 perl_debug_flags: self.perl_debug_flags,
1940 eval_nesting: self.eval_nesting,
1941 argv_current_file: String::new(),
1942 diamond_next_idx: 0,
1943 diamond_reader: None,
1944 strict_refs: self.strict_refs,
1945 strict_subs: self.strict_subs,
1946 strict_vars: self.strict_vars,
1947 utf8_pragma: self.utf8_pragma,
1948 open_pragma_utf8: self.open_pragma_utf8,
1949 feature_bits: self.feature_bits,
1950 num_threads: 0,
1951 regex_cache: self.regex_cache.clone(),
1952 regex_last: self.regex_last.clone(),
1953 regex_match_memo: self.regex_match_memo.clone(),
1954 regex_capture_scope_fresh: false,
1955 regex_pos: self.regex_pos.clone(),
1956 state_vars: self.state_vars.clone(),
1957 state_bindings_stack: Vec::new(),
1958 rand_rng: self.rand_rng.clone(),
1959 dir_handles: HashMap::new(),
1960 io_file_slots: HashMap::new(),
1961 pipe_children: HashMap::new(),
1962 socket_handles: HashMap::new(),
1963 wantarray_kind: self.wantarray_kind,
1964 profiler: None,
1965 module_export_lists: self.module_export_lists.clone(),
1966 virtual_modules: self.virtual_modules.clone(),
1967 tied_hashes: self.tied_hashes.clone(),
1968 tied_scalars: self.tied_scalars.clone(),
1969 tied_arrays: self.tied_arrays.clone(),
1970 overload_table: self.overload_table.clone(),
1971 format_templates: self.format_templates.clone(),
1972 special_caret_scalars: self.special_caret_scalars.clone(),
1973 format_page_number: self.format_page_number,
1974 format_lines_per_page: self.format_lines_per_page,
1975 format_lines_left: self.format_lines_left,
1976 format_line_break_chars: self.format_line_break_chars.clone(),
1977 format_top_name: self.format_top_name.clone(),
1978 accumulator_format: self.accumulator_format.clone(),
1979 max_system_fd: self.max_system_fd,
1980 emergency_memory: self.emergency_memory.clone(),
1981 last_subpattern_name: self.last_subpattern_name.clone(),
1982 inc_hook_index: self.inc_hook_index,
1983 multiline_match: self.multiline_match,
1984 executable_path: self.executable_path.clone(),
1985 formfeed_string: self.formfeed_string.clone(),
1986 glob_handle_alias: self.glob_handle_alias.clone(),
1987 glob_restore_frames: self.glob_restore_frames.clone(),
1988 special_var_restore_frames: self.special_var_restore_frames.clone(),
1989 reflection_hashes_ready: self.reflection_hashes_ready,
1990 english_enabled: self.english_enabled,
1991 english_no_match_vars: self.english_no_match_vars,
1992 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
1993 english_lexical_scalars: self.english_lexical_scalars.clone(),
1994 our_lexical_scalars: self.our_lexical_scalars.clone(),
1995 our_lexical_arrays: self.our_lexical_arrays.clone(),
1996 our_lexical_hashes: self.our_lexical_hashes.clone(),
1997 vm_jit_enabled: self.vm_jit_enabled,
1998 disasm_bytecode: self.disasm_bytecode,
1999 cached_chunk: None,
2001 cache_script_path: None,
2002 stryke_pwd: self.stryke_pwd.clone(),
2003 in_generator: false,
2004 line_mode_skip_main: false,
2005 line_mode_chunk: self.line_mode_chunk.clone(),
2006 line_mode_eof_pending: false,
2007 line_mode_stdin_pending: VecDeque::new(),
2008 rate_limit_slots: Vec::new(),
2009 log_level_override: self.log_level_override,
2010 current_sub_stack: Vec::new(),
2011 debugger: None,
2012 debug_call_stack: Vec::new(),
2013 }
2014 }
2015
2016 pub(crate) fn parallel_thread_count(&mut self) -> usize {
2018 if self.num_threads == 0 {
2019 self.num_threads = rayon::current_num_threads();
2020 }
2021 self.num_threads
2022 }
2023
2024 pub(crate) fn eval_par_list_call(
2026 &mut self,
2027 name: &str,
2028 args: &[StrykeValue],
2029 ctx: WantarrayCtx,
2030 line: usize,
2031 ) -> StrykeResult<StrykeValue> {
2032 match name {
2033 "puniq" => {
2034 let (list_src, show_prog) = match args.len() {
2035 0 => return Err(StrykeError::runtime("puniq: expected LIST", line)),
2036 1 => (&args[0], false),
2037 2 => (&args[0], args[1].is_true()),
2038 _ => {
2039 return Err(StrykeError::runtime(
2040 "puniq: expected LIST [, progress => EXPR]",
2041 line,
2042 ));
2043 }
2044 };
2045 let list = list_src.to_list();
2046 let n_threads = self.parallel_thread_count();
2047 let pmap_progress = PmapProgress::new(show_prog, list.len());
2048 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
2049 pmap_progress.finish();
2050 if ctx == WantarrayCtx::List {
2051 Ok(StrykeValue::array(out))
2052 } else {
2053 Ok(StrykeValue::integer(out.len() as i64))
2054 }
2055 }
2056 "pfirst" => {
2057 let (code_val, list_src, show_prog) = match args.len() {
2058 2 => (&args[0], &args[1], false),
2059 3 => (&args[0], &args[1], args[2].is_true()),
2060 _ => {
2061 return Err(StrykeError::runtime(
2062 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
2063 line,
2064 ));
2065 }
2066 };
2067 let Some(sub) = code_val.as_code_ref() else {
2068 return Err(StrykeError::runtime(
2069 "pfirst: first argument must be a code reference",
2070 line,
2071 ));
2072 };
2073 let sub = sub.clone();
2074 let list = list_src.to_list();
2075 if list.is_empty() {
2076 return Ok(StrykeValue::UNDEF);
2077 }
2078 let pmap_progress = PmapProgress::new(show_prog, list.len());
2079 let subs = self.subs.clone();
2080 let (scope_capture, atomic_arrays, atomic_hashes) =
2081 self.scope.capture_with_atomics();
2082 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
2083 let mut local_interp = VMHelper::new();
2084 local_interp.subs = subs.clone();
2085 local_interp.scope.restore_capture(&scope_capture);
2086 local_interp
2087 .scope
2088 .restore_atomics(&atomic_arrays, &atomic_hashes);
2089 local_interp.enable_parallel_guard();
2090 local_interp.scope.set_topic(item);
2091 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2092 Ok(v) => v.is_true(),
2093 Err(_) => false,
2094 }
2095 });
2096 pmap_progress.finish();
2097 Ok(out.unwrap_or(StrykeValue::UNDEF))
2098 }
2099 "pany" => {
2100 let (code_val, list_src, show_prog) = match args.len() {
2101 2 => (&args[0], &args[1], false),
2102 3 => (&args[0], &args[1], args[2].is_true()),
2103 _ => {
2104 return Err(StrykeError::runtime(
2105 "pany: expected BLOCK, LIST [, progress => EXPR]",
2106 line,
2107 ));
2108 }
2109 };
2110 let Some(sub) = code_val.as_code_ref() else {
2111 return Err(StrykeError::runtime(
2112 "pany: first argument must be a code reference",
2113 line,
2114 ));
2115 };
2116 let sub = sub.clone();
2117 let list = list_src.to_list();
2118 let pmap_progress = PmapProgress::new(show_prog, list.len());
2119 let subs = self.subs.clone();
2120 let (scope_capture, atomic_arrays, atomic_hashes) =
2121 self.scope.capture_with_atomics();
2122 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
2123 let mut local_interp = VMHelper::new();
2124 local_interp.subs = subs.clone();
2125 local_interp.scope.restore_capture(&scope_capture);
2126 local_interp
2127 .scope
2128 .restore_atomics(&atomic_arrays, &atomic_hashes);
2129 local_interp.enable_parallel_guard();
2130 local_interp.scope.set_topic(item);
2131 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2132 Ok(v) => v.is_true(),
2133 Err(_) => false,
2134 }
2135 });
2136 pmap_progress.finish();
2137 Ok(StrykeValue::integer(if b { 1 } else { 0 }))
2138 }
2139 _ => Err(StrykeError::runtime(
2140 format!("internal: unknown par_list builtin {name}"),
2141 line,
2142 )),
2143 }
2144 }
2145
2146 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
2147 #[cfg(unix)]
2148 if let Some(sig) = s.signal() {
2149 return sig as i64 & 0x7f;
2150 }
2151 let code = s.code().unwrap_or(0) as i64;
2152 code << 8
2153 }
2154
2155 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
2156 self.child_exit_status = self.encode_exit_status(s);
2157 }
2158
2159 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
2161 let s = e.to_string();
2164 let stripped = s
2165 .rfind(" (os error ")
2166 .map(|i| s[..i].to_string())
2167 .unwrap_or(s);
2168 self.errno = stripped;
2169 self.errno_code = e.raw_os_error().unwrap_or(0);
2170 }
2171
2172 pub(crate) fn ssh_builtin_execute(
2183 &mut self,
2184 args: &[StrykeValue],
2185 ) -> StrykeResult<StrykeValue> {
2186 use std::process::Command;
2187 let mut cmd = Command::new("ssh");
2188 #[cfg(unix)]
2189 {
2190 use libc::geteuid;
2191 let home_for_ssh = if unsafe { geteuid() } == 0 {
2192 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
2193 } else {
2194 None
2195 };
2196 if let Some(h) = home_for_ssh {
2197 cmd.env("HOME", h);
2198 } else if std::env::var_os("HOME").is_none() {
2199 if let Some(h) = pw_home_dir_for_current_uid() {
2200 cmd.env("HOME", h);
2201 }
2202 }
2203 }
2204 for a in args {
2205 cmd.arg(a.to_string());
2206 }
2207 match cmd.status() {
2208 Ok(s) => {
2209 self.record_child_exit_status(s);
2210 Ok(StrykeValue::integer(s.code().unwrap_or(-1) as i64))
2211 }
2212 Err(e) => {
2213 self.apply_io_error_to_errno(&e);
2214 Ok(StrykeValue::integer(-1))
2215 }
2216 }
2217 }
2218
2219 pub(crate) fn set_eval_error(&mut self, msg: String) {
2221 self.eval_error = msg;
2222 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2223 self.eval_error_value = None;
2224 }
2225
2226 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &StrykeError) {
2227 self.eval_error = e.to_string();
2228 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2229 self.eval_error_value = e.die_value.clone();
2230 }
2231
2232 pub(crate) fn clear_eval_error(&mut self) {
2233 self.eval_error = String::new();
2234 self.eval_error_code = 0;
2235 self.eval_error_value = None;
2236 }
2237
2238 fn bump_line_for_handle(&mut self, handle_key: &str) {
2240 self.last_readline_handle = handle_key.to_string();
2241 *self
2242 .handle_line_numbers
2243 .entry(handle_key.to_string())
2244 .or_insert(0) += 1;
2245 }
2246
2247 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
2249 if name.starts_with('^') {
2250 return name.to_string();
2251 }
2252 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
2253 let pkg = self.current_package();
2254 if !pkg.is_empty() && pkg != "main" {
2255 return format!("{}::{}", pkg, name);
2256 }
2257 }
2258 name.to_string()
2259 }
2260
2261 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
2263 if name.contains("::") {
2264 return name.to_string();
2265 }
2266 let pkg = self.current_package();
2267 if pkg.is_empty() || pkg == "main" {
2268 format!("main::{}", name)
2269 } else {
2270 format!("{}::{}", pkg, name)
2271 }
2272 }
2273
2274 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
2276 if name.contains("::") {
2277 return name.to_string();
2278 }
2279 for (lex, our) in self
2280 .english_lexical_scalars
2281 .iter()
2282 .zip(self.our_lexical_scalars.iter())
2283 .rev()
2284 {
2285 if lex.contains(name) {
2286 if our.contains(name) {
2287 return self.stash_scalar_name_for_package(name);
2288 }
2289 return name.to_string();
2290 }
2291 }
2292 name.to_string()
2293 }
2294
2295 pub(crate) fn tie_execute(
2297 &mut self,
2298 target_kind: u8,
2299 target_name: &str,
2300 class_and_args: Vec<StrykeValue>,
2301 line: usize,
2302 ) -> StrykeResult<StrykeValue> {
2303 let mut it = class_and_args.into_iter();
2304 let class = it.next().unwrap_or(StrykeValue::UNDEF);
2305 let pkg = class.to_string();
2306 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
2307 let tie_ctor = match target_kind {
2308 0 => "TIESCALAR",
2309 1 => "TIEARRAY",
2310 2 => "TIEHASH",
2311 _ => return Err(StrykeError::runtime("tie: invalid target kind", line)),
2312 };
2313 let tie_fn = format!("{}::{}", pkg, tie_ctor);
2314 let sub =
2315 self.subs.get(&tie_fn).cloned().ok_or_else(|| {
2316 StrykeError::runtime(format!("tie: cannot find &{}", tie_fn), line)
2317 })?;
2318 let mut call_args = vec![StrykeValue::string(pkg.clone())];
2319 call_args.extend(it);
2320 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
2321 Ok(v) => v,
2322 Err(FlowOrError::Flow(_)) => StrykeValue::UNDEF,
2323 Err(FlowOrError::Error(e)) => return Err(e),
2324 };
2325 match target_kind {
2326 0 => {
2327 self.tied_scalars.insert(target_name.to_string(), obj);
2328 }
2329 1 => {
2330 let key = self.stash_array_name_for_package(target_name);
2331 self.tied_arrays.insert(key, obj);
2332 }
2333 2 => {
2334 self.tied_hashes.insert(target_name.to_string(), obj);
2335 }
2336 _ => return Err(StrykeError::runtime("tie: invalid target kind", line)),
2337 }
2338 Ok(StrykeValue::UNDEF)
2339 }
2340
2341 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
2343 let key = format!("{}::ISA", class);
2344 self.scope
2345 .get_array(&key)
2346 .into_iter()
2347 .map(|v| v.to_string())
2348 .collect()
2349 }
2350
2351 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
2352 let p = |c: &str| self.parents_of_class(c);
2353 linearize_c3(class, &p, 0)
2354 }
2355
2356 pub(crate) fn resolve_method_full_name(
2358 &self,
2359 invocant_class: &str,
2360 method: &str,
2361 super_mode: bool,
2362 ) -> Option<String> {
2363 let mro = self.mro_linearize(invocant_class);
2364 let start = if super_mode {
2368 mro.iter()
2369 .position(|p| p == invocant_class)
2370 .map(|i| i + 1)
2371 .unwrap_or(1)
2374 } else {
2375 0
2376 };
2377 for pkg in mro.iter().skip(start) {
2378 if pkg == "UNIVERSAL" {
2379 continue;
2380 }
2381 let fq = format!("{}::{}", pkg, method);
2382 if self.subs.contains_key(&fq) {
2383 return Some(fq);
2384 }
2385 }
2386 mro.iter()
2387 .skip(start)
2388 .find(|p| *p != "UNIVERSAL")
2389 .map(|pkg| format!("{}::{}", pkg, method))
2390 }
2391
2392 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
2393 if let Some(alias) = self.glob_handle_alias.get(name) {
2394 return alias.clone();
2395 }
2396 if let Some(var_name) = name.strip_prefix('$') {
2399 let val = self.scope.get_scalar(var_name);
2400 let s = val.to_string();
2401 if !s.is_empty() {
2402 return self.resolve_io_handle_name(&s);
2403 }
2404 }
2405 name.to_string()
2406 }
2407
2408 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2410 if name.contains("::") {
2411 name.to_string()
2412 } else {
2413 self.qualify_sub_key(name)
2414 }
2415 }
2416
2417 pub(crate) fn copy_typeglob_slots(
2419 &mut self,
2420 lhs: &str,
2421 rhs: &str,
2422 line: usize,
2423 ) -> StrykeResult<()> {
2424 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2425 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2426 match self.subs.get(&rhs_sub).cloned() {
2427 Some(s) => {
2428 self.subs.insert(lhs_sub, s);
2429 }
2430 None => {
2431 self.subs.remove(&lhs_sub);
2432 }
2433 }
2434 let sv = self.scope.get_scalar(rhs);
2435 self.scope
2436 .set_scalar(lhs, sv.clone())
2437 .map_err(|e| e.at_line(line))?;
2438 let lhs_an = self.stash_array_name_for_package(lhs);
2439 let rhs_an = self.stash_array_name_for_package(rhs);
2440 let av = self.scope.get_array(&rhs_an);
2441 self.scope
2442 .set_array(&lhs_an, av.clone())
2443 .map_err(|e| e.at_line(line))?;
2444 let hv = self.scope.get_hash(rhs);
2445 self.scope
2446 .set_hash(lhs, hv.clone())
2447 .map_err(|e| e.at_line(line))?;
2448 match self.glob_handle_alias.get(rhs).cloned() {
2449 Some(t) => {
2450 self.glob_handle_alias.insert(lhs.to_string(), t);
2451 }
2452 None => {
2453 self.glob_handle_alias.remove(lhs);
2454 }
2455 }
2456 Ok(())
2457 }
2458
2459 pub(crate) fn install_format_decl(
2461 &mut self,
2462 basename: &str,
2463 lines: &[String],
2464 line: usize,
2465 ) -> StrykeResult<()> {
2466 let pkg = self.current_package();
2467 let key = format!("{}::{}", pkg, basename);
2468 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2469 self.format_templates.insert(key, Arc::new(tmpl));
2470 Ok(())
2471 }
2472
2473 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2479 let pkg = self.current_package();
2480 for (_, v) in pairs {
2481 if v.starts_with("__overload_anon_") {
2482 let pkg_key = format!("{}::{}", pkg, v);
2489 if !self.subs.contains_key(&pkg_key) {
2490 let src = if let Some(s) = self.subs.get(v) {
2491 Some(s.clone())
2492 } else {
2493 self.subs.get(&format!("main::{}", v)).cloned()
2494 };
2495 if let Some(sub) = src {
2496 self.subs.insert(pkg_key, sub);
2497 }
2498 }
2499 }
2500 }
2501 let ent = self.overload_table.entry(pkg).or_default();
2502 for (k, v) in pairs {
2503 ent.insert(k.clone(), v.clone());
2504 }
2505 }
2506
2507 pub(crate) fn local_declare_typeglob(
2510 &mut self,
2511 lhs: &str,
2512 rhs: Option<&str>,
2513 line: usize,
2514 ) -> StrykeResult<()> {
2515 let old = self.glob_handle_alias.remove(lhs);
2516 let Some(frame) = self.glob_restore_frames.last_mut() else {
2517 return Err(StrykeError::runtime(
2518 "internal: no glob restore frame for local *GLOB",
2519 line,
2520 ));
2521 };
2522 frame.push((lhs.to_string(), old));
2523 if let Some(r) = rhs {
2524 self.glob_handle_alias
2525 .insert(lhs.to_string(), r.to_string());
2526 }
2527 Ok(())
2528 }
2529
2530 pub(crate) fn scope_push_hook(&mut self) {
2531 self.scope.push_frame();
2532 self.glob_restore_frames.push(Vec::new());
2533 self.special_var_restore_frames.push(Vec::new());
2534 self.english_lexical_scalars.push(HashSet::new());
2535 self.our_lexical_scalars.push(HashSet::new());
2536 self.our_lexical_arrays.push(HashSet::new());
2537 self.our_lexical_hashes.push(HashSet::new());
2538 self.state_bindings_stack.push(Vec::new());
2539 }
2540
2541 #[inline]
2542 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2543 if let Some(s) = self.english_lexical_scalars.last_mut() {
2544 s.insert(name.to_string());
2545 }
2546 }
2547
2548 #[inline]
2551 pub(crate) fn english_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2552 self.english_lexical_scalars.clone()
2553 }
2554
2555 #[inline]
2558 pub(crate) fn our_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2559 self.our_lexical_scalars.clone()
2560 }
2561
2562 #[inline]
2564 pub(crate) fn set_english_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2565 self.english_lexical_scalars = v;
2566 }
2567
2568 #[inline]
2570 pub(crate) fn set_our_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2571 self.our_lexical_scalars = v;
2572 }
2573
2574 #[inline]
2575 fn note_our_scalar(&mut self, bare_name: &str) {
2576 if let Some(s) = self.our_lexical_scalars.last_mut() {
2577 s.insert(bare_name.to_string());
2578 }
2579 }
2580
2581 #[inline]
2582 fn note_our_array(&mut self, bare_name: &str) {
2583 if let Some(s) = self.our_lexical_arrays.last_mut() {
2584 s.insert(bare_name.to_string());
2585 }
2586 }
2587
2588 #[inline]
2589 fn note_our_hash(&mut self, bare_name: &str) {
2590 if let Some(s) = self.our_lexical_hashes.last_mut() {
2591 s.insert(bare_name.to_string());
2592 }
2593 }
2594
2595 pub(crate) fn stash_array_full_name_for_package(&self, name: &str) -> String {
2600 if name.contains("::") {
2601 return name.to_string();
2602 }
2603 let pkg = self.current_package();
2604 if pkg.is_empty() || pkg == "main" {
2605 format!("main::{}", name)
2606 } else {
2607 format!("{}::{}", pkg, name)
2608 }
2609 }
2610
2611 pub(crate) fn stash_hash_full_name_for_package(&self, name: &str) -> String {
2614 if name.contains("::") {
2615 return name.to_string();
2616 }
2617 let pkg = self.current_package();
2618 if pkg.is_empty() || pkg == "main" {
2619 format!("main::{}", name)
2620 } else {
2621 format!("{}::{}", pkg, name)
2622 }
2623 }
2624
2625 pub(crate) fn tree_array_storage_name(&self, name: &str) -> String {
2629 if name.contains("::") {
2630 return name.to_string();
2631 }
2632 for ours in self.our_lexical_arrays.iter().rev() {
2633 if ours.contains(name) {
2634 return self.stash_array_full_name_for_package(name);
2635 }
2636 }
2637 name.to_string()
2638 }
2639
2640 pub(crate) fn tree_hash_storage_name(&self, name: &str) -> String {
2643 if name.contains("::") {
2644 return name.to_string();
2645 }
2646 for ours in self.our_lexical_hashes.iter().rev() {
2647 if ours.contains(name) {
2648 return self.stash_hash_full_name_for_package(name);
2649 }
2650 }
2651 name.to_string()
2652 }
2653
2654 #[inline]
2658 pub(crate) fn english_note_lexical_scalar_pub(&mut self, name: &str) {
2659 self.english_note_lexical_scalar(name);
2660 }
2661
2662 #[inline]
2664 pub(crate) fn note_our_scalar_pub(&mut self, bare_name: &str) {
2665 self.note_our_scalar(bare_name);
2666 }
2667
2668 pub(crate) fn scope_pop_hook(&mut self) {
2669 if !self.scope.can_pop_frame() {
2670 return;
2671 }
2672 let defers = self.scope.take_defers();
2676 for coderef in defers {
2677 if let Some(sub) = coderef.as_code_ref() {
2678 let saved_wa = self.wantarray_kind;
2682 self.wantarray_kind = WantarrayCtx::Void;
2683 let _ = self.exec_block_no_scope(&sub.body);
2684 self.wantarray_kind = saved_wa;
2685 }
2686 }
2687 if let Some(bindings) = self.state_bindings_stack.pop() {
2689 for (var_name, state_key) in &bindings {
2690 let val = self.scope.get_scalar(var_name).clone();
2691 self.state_vars.insert(state_key.clone(), val);
2692 }
2693 }
2694 if let Some(entries) = self.special_var_restore_frames.pop() {
2697 for (name, old) in entries.into_iter().rev() {
2698 let _ = self.set_special_var(&name, &old);
2699 }
2700 }
2701 if let Some(entries) = self.glob_restore_frames.pop() {
2702 for (name, old) in entries.into_iter().rev() {
2703 match old {
2704 Some(s) => {
2705 self.glob_handle_alias.insert(name, s);
2706 }
2707 None => {
2708 self.glob_handle_alias.remove(&name);
2709 }
2710 }
2711 }
2712 }
2713 self.scope.pop_frame();
2714 let _ = self.english_lexical_scalars.pop();
2715 let _ = self.our_lexical_scalars.pop();
2716 let _ = self.our_lexical_arrays.pop();
2717 let _ = self.our_lexical_hashes.pop();
2718 }
2719
2720 #[inline]
2723 pub(crate) fn enable_parallel_guard(&mut self) {
2724 self.scope.set_parallel_guard(true);
2725 }
2726
2727 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2729 self.begin_blocks.clear();
2730 self.unit_check_blocks.clear();
2731 self.check_blocks.clear();
2732 self.init_blocks.clear();
2733 self.end_blocks.clear();
2734 }
2735
2736 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2742 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2743 self.scope_pop_hook();
2744 }
2745 }
2746
2747 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> StrykeResult<()> {
2754 self.touch_env_hash("SIG");
2755 let v = self.scope.get_hash_element("SIG", sig);
2756 if v.is_undef() {
2757 return Self::default_sig_action(sig);
2758 }
2759 if let Some(s) = v.as_str() {
2760 if s == "IGNORE" {
2761 return Ok(());
2762 }
2763 if s == "DEFAULT" {
2764 return Self::default_sig_action(sig);
2765 }
2766 }
2767 if let Some(sub) = v.as_code_ref() {
2768 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2769 Ok(_) => Ok(()),
2770 Err(FlowOrError::Flow(_)) => Ok(()),
2771 Err(FlowOrError::Error(e)) => Err(e),
2772 }
2773 } else {
2774 Self::default_sig_action(sig)
2775 }
2776 }
2777
2778 pub(crate) fn fire_pseudosig_warn(&mut self, msg: &str, line: usize) -> StrykeResult<()> {
2782 self.touch_env_hash("SIG");
2783 let slot = self.scope.get_hash_element("SIG", "__WARN__");
2784 if let Some(sub) = slot.as_code_ref() {
2785 let prev = slot;
2786 let _ = self
2787 .scope
2788 .set_hash_element("SIG", "__WARN__", StrykeValue::UNDEF);
2789 let arg = StrykeValue::string(msg.to_string());
2790 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2791 let _ = self.scope.set_hash_element("SIG", "__WARN__", prev);
2792 return match r {
2793 Ok(_) => Ok(()),
2794 Err(FlowOrError::Flow(_)) => Ok(()),
2795 Err(FlowOrError::Error(e)) => Err(e),
2796 };
2797 }
2798 eprint!("{}", msg);
2799 Ok(())
2800 }
2801
2802 pub(crate) fn fire_pseudosig_die(&mut self, msg: &str, line: usize) -> StrykeResult<()> {
2808 self.touch_env_hash("SIG");
2809 let slot = self.scope.get_hash_element("SIG", "__DIE__");
2810 if let Some(sub) = slot.as_code_ref() {
2811 let prev = slot;
2812 let _ = self
2813 .scope
2814 .set_hash_element("SIG", "__DIE__", StrykeValue::UNDEF);
2815 let arg = StrykeValue::string(msg.to_string());
2816 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2817 let _ = self.scope.set_hash_element("SIG", "__DIE__", prev);
2818 return match r {
2819 Ok(_) => Ok(()),
2820 Err(FlowOrError::Flow(_)) => Ok(()),
2821 Err(FlowOrError::Error(e)) => Err(e),
2822 };
2823 }
2824 Ok(())
2825 }
2826
2827 #[inline]
2829 fn default_sig_action(sig: &str) -> StrykeResult<()> {
2830 match sig {
2831 "INT" => std::process::exit(130),
2833 "TERM" => std::process::exit(143),
2834 "ALRM" => std::process::exit(142),
2835 "CHLD" => Ok(()),
2837 _ => Ok(()),
2838 }
2839 }
2840
2841 #[inline]
2846 pub fn debugger_enter_sub(&mut self, name: &str) {
2847 if let Some(dbg) = &mut self.debugger {
2848 dbg.enter_sub(name);
2849 }
2850 }
2851
2852 #[inline]
2854 pub fn debugger_leave_sub(&mut self) {
2855 if let Some(dbg) = &mut self.debugger {
2856 dbg.leave_sub();
2857 }
2858 }
2859
2860 pub fn materialize_env_if_needed(&mut self) {
2863 if self.env_materialized {
2864 return;
2865 }
2866 self.env = std::env::vars()
2867 .map(|(k, v)| (k, StrykeValue::string(v)))
2868 .collect();
2869 self.scope
2870 .set_hash("ENV", self.env.clone())
2871 .expect("set %ENV");
2872 self.env_materialized = true;
2873 }
2874
2875 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2877 self.materialize_env_if_needed();
2878 if let Some(x) = self.log_level_override {
2879 return x;
2880 }
2881 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2882 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2883 }
2884
2885 pub(crate) fn no_color_effective(&mut self) -> bool {
2887 self.materialize_env_if_needed();
2888 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2889 if v.is_undef() {
2890 return false;
2891 }
2892 !v.to_string().is_empty()
2893 }
2894
2895 #[inline]
2896 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2897 let hash_name: &str = crate::scope::strip_main_prefix(hash_name).unwrap_or(hash_name);
2904 if hash_name == "ENV" {
2905 self.materialize_env_if_needed();
2906 } else if hash_name == "parameters"
2907 && !crate::compat_mode()
2908 && !self.scope.has_lexical_hash("parameters")
2909 {
2910 self.ensure_reflection_hashes();
2916 self.refresh_parameters_hash();
2917 } else if hash_name.ends_with("::") && hash_name.len() > 2 {
2918 self.refresh_package_stashes();
2923 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2924 match hash_name {
2925 "b"
2926 | "pc"
2927 | "e"
2928 | "a"
2929 | "d"
2930 | "c"
2931 | "p"
2932 | "k"
2933 | "o"
2934 | "v"
2935 | "all"
2936 | "stryke::builtins"
2937 | "stryke::perl_compats"
2938 | "stryke::extensions"
2939 | "stryke::aliases"
2940 | "stryke::descriptions"
2941 | "stryke::categories"
2942 | "stryke::primaries"
2943 | "stryke::keywords"
2944 | "stryke::operators"
2945 | "stryke::special_vars"
2946 | "stryke::all" => {
2947 self.ensure_reflection_hashes();
2948 }
2949 _ => {}
2950 }
2951 }
2952 }
2953
2954 pub(crate) fn exists_arrow_hash_element(
2956 &self,
2957 container: StrykeValue,
2958 key: &str,
2959 line: usize,
2960 ) -> StrykeResult<bool> {
2961 if let Some(r) = container.as_hash_ref() {
2962 return Ok(r.read().contains_key(key));
2963 }
2964 if let Some(b) = container.as_blessed_ref() {
2965 let data = b.data.read();
2966 if let Some(r) = data.as_hash_ref() {
2967 return Ok(r.read().contains_key(key));
2968 }
2969 if let Some(hm) = data.as_hash_map() {
2970 return Ok(hm.contains_key(key));
2971 }
2972 return Err(StrykeError::runtime(
2973 "exists argument is not a HASH reference",
2974 line,
2975 ));
2976 }
2977 let _ = line;
2981 Ok(false)
2982 }
2983
2984 pub(crate) fn delete_arrow_hash_element(
2986 &self,
2987 container: StrykeValue,
2988 key: &str,
2989 line: usize,
2990 ) -> StrykeResult<StrykeValue> {
2991 if let Some(r) = container.as_hash_ref() {
2992 return Ok(r.write().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2993 }
2994 if let Some(b) = container.as_blessed_ref() {
2995 let mut data = b.data.write();
2996 if let Some(r) = data.as_hash_ref() {
2997 return Ok(r.write().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2998 }
2999 if let Some(mut map) = data.as_hash_map() {
3000 let v = map.shift_remove(key).unwrap_or(StrykeValue::UNDEF);
3001 *data = StrykeValue::hash(map);
3002 return Ok(v);
3003 }
3004 return Err(StrykeError::runtime(
3005 "delete argument is not a HASH reference",
3006 line,
3007 ));
3008 }
3009 Err(StrykeError::runtime(
3010 "delete argument is not a HASH reference",
3011 line,
3012 ))
3013 }
3014
3015 pub(crate) fn exists_arrow_array_element(
3017 &self,
3018 container: StrykeValue,
3019 idx: i64,
3020 line: usize,
3021 ) -> StrykeResult<bool> {
3022 if let Some(a) = container.as_array_ref() {
3023 let arr = a.read();
3024 let i = if idx < 0 {
3025 (arr.len() as i64 + idx) as usize
3026 } else {
3027 idx as usize
3028 };
3029 return Ok(i < arr.len());
3030 }
3031 let _ = line;
3034 Ok(false)
3035 }
3036
3037 pub(crate) fn delete_arrow_array_element(
3039 &self,
3040 container: StrykeValue,
3041 idx: i64,
3042 line: usize,
3043 ) -> StrykeResult<StrykeValue> {
3044 if let Some(a) = container.as_array_ref() {
3045 let mut arr = a.write();
3046 let i = if idx < 0 {
3047 (arr.len() as i64 + idx) as usize
3048 } else {
3049 idx as usize
3050 };
3051 if i >= arr.len() {
3052 return Ok(StrykeValue::UNDEF);
3053 }
3054 let old = arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
3055 arr[i] = StrykeValue::UNDEF;
3056 return Ok(old);
3057 }
3058 Err(StrykeError::runtime(
3059 "delete argument is not an ARRAY reference",
3060 line,
3061 ))
3062 }
3063
3064 pub(crate) fn inc_directories(&self) -> Vec<String> {
3066 let mut v: Vec<String> = self
3067 .scope
3068 .get_array("INC")
3069 .into_iter()
3070 .map(|x| x.to_string())
3071 .filter(|s| !s.is_empty())
3072 .collect();
3073 if v.is_empty() {
3074 v.push(".".to_string());
3075 }
3076 v
3077 }
3078
3079 #[inline]
3080 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
3081 matches!(
3082 name,
3083 "_" | "0"
3084 | "!"
3085 | "@"
3086 | "/"
3087 | "\\"
3088 | ","
3089 | "."
3090 | "__PACKAGE__"
3091 | "$$"
3092 | "|"
3093 | "?"
3094 | "\""
3095 | "&"
3096 | "`"
3097 | "'"
3098 | "+"
3099 | "<"
3100 | ">"
3101 | "("
3102 | ")"
3103 | "]"
3104 | ";"
3105 | "ARGV"
3106 | "%"
3107 | "="
3108 | "-"
3109 | ":"
3110 | "*"
3111 | "INC"
3112 | "a"
3116 | "b"
3117 ) || name.chars().all(|c| c.is_ascii_digit())
3118 || name.starts_with('^')
3119 || (name.starts_with('#') && name.len() > 1)
3120 || (name.starts_with('_')
3127 && name.len() > 1
3128 && name[1..].chars().all(|c| c.is_ascii_digit()))
3129 }
3130
3131 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3132 if !self.strict_vars
3133 || Self::strict_scalar_exempt(name)
3134 || name.contains("::")
3135 || self.scope.scalar_binding_exists(name)
3136 {
3137 return Ok(());
3138 }
3139 Err(StrykeError::runtime(
3140 format!(
3141 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
3142 name, name
3143 ),
3144 line,
3145 )
3146 .into())
3147 }
3148
3149 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3150 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
3151 return Ok(());
3152 }
3153 Err(StrykeError::runtime(
3154 format!(
3155 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
3156 name, name
3157 ),
3158 line,
3159 )
3160 .into())
3161 }
3162
3163 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3164 if !self.strict_vars
3166 || name.contains("::")
3167 || self.scope.hash_binding_exists(name)
3168 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
3169 {
3170 return Ok(());
3171 }
3172 Err(StrykeError::runtime(
3173 format!(
3174 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
3175 name, name
3176 ),
3177 line,
3178 )
3179 .into())
3180 }
3181
3182 fn looks_like_version_only(spec: &str) -> bool {
3183 let t = spec.trim();
3184 !t.is_empty()
3185 && !t.contains('/')
3186 && !t.contains('\\')
3187 && !t.contains("::")
3188 && t.chars()
3189 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
3190 && t.chars().any(|c| c.is_ascii_digit())
3191 }
3192
3193 fn module_spec_to_relpath(spec: &str) -> String {
3194 let t = spec.trim();
3195 if t.contains("::") {
3196 format!("{}.pm", t.replace("::", "/"))
3197 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
3198 t.replace('\\', "/")
3199 } else {
3200 format!("{}.pm", t)
3201 }
3202 }
3203
3204 fn try_resolve_via_lockfile(relpath: &str) -> Option<std::path::PathBuf> {
3211 let cwd = std::env::current_dir().ok()?;
3212 let project_root = crate::pkg::commands::find_project_root(&cwd)?;
3213
3214 let stem = relpath
3217 .strip_suffix(".pm")
3218 .or_else(|| relpath.strip_suffix(".pl"))
3219 .or_else(|| relpath.strip_suffix(".stk"))
3220 .unwrap_or(relpath);
3221 let logical = stem.replace('/', "::");
3222
3223 crate::pkg::commands::resolve_module(&project_root, &logical).unwrap_or_default()
3224 }
3225
3226 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
3231 if name.contains("::") {
3232 return name.to_string();
3233 }
3234 let pkg = self.current_package();
3235 if pkg.is_empty() || pkg == "main" {
3236 name.to_string()
3237 } else {
3238 format!("{}::{}", pkg, name)
3239 }
3240 }
3241
3242 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
3244 let mut msg = format!("Undefined subroutine &{}", name);
3245 if self.strict_subs {
3246 msg.push_str(
3247 " (strict subs: declare the sub or use a fully qualified name before calling)",
3248 );
3249 }
3250 msg
3251 }
3252
3253 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
3255 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
3256 if self.strict_subs {
3257 msg.push_str(
3258 " (strict subs: declare the sub or use a fully qualified name before calling)",
3259 );
3260 }
3261 msg
3262 }
3263
3264 fn import_alias_key(&self, short: &str) -> String {
3266 self.qualify_sub_key(short)
3267 }
3268
3269 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
3271 if imports.len() == 1 {
3272 match &imports[0].kind {
3273 ExprKind::QW(ws) => return ws.is_empty(),
3274 ExprKind::List(xs) => return xs.is_empty(),
3276 _ => {}
3277 }
3278 }
3279 false
3280 }
3281
3282 fn apply_module_import(
3284 &mut self,
3285 module: &str,
3286 imports: &[Expr],
3287 line: usize,
3288 ) -> StrykeResult<()> {
3289 if imports.is_empty() {
3290 return self.import_all_from_module(module, line);
3291 }
3292 if Self::is_explicit_empty_import_list(imports) {
3293 return Ok(());
3294 }
3295 let names = Self::pragma_import_strings(imports, line)?;
3296 if names.is_empty() {
3297 return Ok(());
3298 }
3299 for name in names {
3300 self.import_one_symbol(module, &name, line)?;
3301 }
3302 Ok(())
3303 }
3304
3305 fn import_all_from_module(&mut self, module: &str, line: usize) -> StrykeResult<()> {
3306 if let Some(lists) = self.module_export_lists.get(module) {
3307 let export: Vec<String> = lists.export.clone();
3308 for short in export {
3309 self.import_named_sub(module, &short, line)?;
3310 }
3311 return Ok(());
3312 }
3313 let prefix = format!("{}::", module);
3315 let keys: Vec<String> = self
3316 .subs
3317 .keys()
3318 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
3319 .cloned()
3320 .collect();
3321 for k in keys {
3322 let short = k[prefix.len()..].to_string();
3323 if let Some(sub) = self.subs.get(&k).cloned() {
3324 let alias = self.import_alias_key(&short);
3325 self.subs.insert(alias, sub);
3326 }
3327 }
3328 Ok(())
3329 }
3330
3331 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> StrykeResult<()> {
3333 let qual = format!("{}::{}", module, short);
3334 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
3335 StrykeError::runtime(
3336 format!(
3337 "`{}` is not defined in module `{}` (expected `{}`)",
3338 short, module, qual
3339 ),
3340 line,
3341 )
3342 })?;
3343 let alias = self.import_alias_key(short);
3344 self.subs.insert(alias, sub);
3345 Ok(())
3346 }
3347
3348 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> StrykeResult<()> {
3349 if let Some(lists) = self.module_export_lists.get(module) {
3350 let allowed: HashSet<&str> = lists
3351 .export
3352 .iter()
3353 .map(|s| s.as_str())
3354 .chain(lists.export_ok.iter().map(|s| s.as_str()))
3355 .collect();
3356 if !allowed.contains(export) {
3357 return Err(StrykeError::runtime(
3358 format!(
3359 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
3360 export, module
3361 ),
3362 line,
3363 ));
3364 }
3365 }
3366 self.import_named_sub(module, export, line)
3367 }
3368
3369 fn record_exporter_our_array_name(&mut self, name: &str, items: &[StrykeValue]) {
3371 if name != "EXPORT" && name != "EXPORT_OK" {
3372 return;
3373 }
3374 let pkg = self.current_package();
3375 if pkg.is_empty() || pkg == "main" {
3376 return;
3377 }
3378 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
3379 let ent = self.module_export_lists.entry(pkg).or_default();
3380 if name == "EXPORT" {
3381 ent.export = names;
3382 } else {
3383 ent.export_ok = names;
3384 }
3385 }
3386
3387 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
3391 let key = self.qualify_sub_key(name);
3392 let Some(sub) = self.subs.get(&key).cloned() else {
3393 return;
3394 };
3395 let captured = self.scope.capture();
3396 let closure_env = if captured.is_empty() {
3397 None
3398 } else {
3399 Some(captured)
3400 };
3401 let mut new_sub = (*sub).clone();
3402 new_sub.closure_env = closure_env;
3403 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
3404 self.subs.insert(key, Arc::new(new_sub));
3405 }
3406
3407 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<StrykeSub>> {
3408 if let Some(s) = self.subs.get(name) {
3409 return Some(s.clone());
3410 }
3411 if !name.contains("::") {
3412 let pkg = self.current_package();
3414 if !pkg.is_empty() && pkg != "main" {
3415 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
3416 q.push_str(&pkg);
3417 q.push_str("::");
3418 q.push_str(name);
3419 return self.subs.get(&q).cloned();
3420 }
3421 return None;
3422 }
3423 if let Some(rest) = name.strip_prefix("main::") {
3428 if !rest.contains("::") {
3429 return self.subs.get(rest).cloned();
3430 }
3431 }
3432 None
3433 }
3434
3435 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
3438 if let Some(first) = imports.first() {
3439 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
3440 return &imports[1..];
3441 }
3442 }
3443 imports
3444 }
3445
3446 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> StrykeResult<Vec<String>> {
3448 let mut out = Vec::new();
3449 for e in imports {
3450 match &e.kind {
3451 ExprKind::String(s) => out.push(s.clone()),
3452 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
3453 ExprKind::Integer(n) => out.push(n.to_string()),
3454 ExprKind::InterpolatedString(parts) => {
3457 let mut s = String::new();
3458 for p in parts {
3459 match p {
3460 StringPart::Literal(l) => s.push_str(l),
3461 StringPart::ScalarVar(v) => {
3462 s.push('$');
3463 s.push_str(v);
3464 }
3465 StringPart::ArrayVar(v) => {
3466 s.push('@');
3467 s.push_str(v);
3468 }
3469 _ => {
3470 return Err(StrykeError::runtime(
3471 "pragma import must be a compile-time string, qw(), or integer",
3472 e.line.max(default_line),
3473 ));
3474 }
3475 }
3476 }
3477 out.push(s);
3478 }
3479 _ => {
3480 return Err(StrykeError::runtime(
3481 "pragma import must be a compile-time string, qw(), or integer",
3482 e.line.max(default_line),
3483 ));
3484 }
3485 }
3486 }
3487 Ok(out)
3488 }
3489
3490 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3491 if imports.is_empty() {
3492 self.strict_refs = true;
3493 self.strict_subs = true;
3494 self.strict_vars = true;
3495 return Ok(());
3496 }
3497 let names = Self::pragma_import_strings(imports, line)?;
3498 for name in names {
3499 match name.as_str() {
3500 "refs" => self.strict_refs = true,
3501 "subs" => self.strict_subs = true,
3502 "vars" => self.strict_vars = true,
3503 _ => {
3504 return Err(StrykeError::runtime(
3505 format!("Unknown strict mode `{}`", name),
3506 line,
3507 ));
3508 }
3509 }
3510 }
3511 Ok(())
3512 }
3513
3514 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3515 if imports.is_empty() {
3516 self.strict_refs = false;
3517 self.strict_subs = false;
3518 self.strict_vars = false;
3519 return Ok(());
3520 }
3521 let names = Self::pragma_import_strings(imports, line)?;
3522 for name in names {
3523 match name.as_str() {
3524 "refs" => self.strict_refs = false,
3525 "subs" => self.strict_subs = false,
3526 "vars" => self.strict_vars = false,
3527 _ => {
3528 return Err(StrykeError::runtime(
3529 format!("Unknown strict mode `{}`", name),
3530 line,
3531 ));
3532 }
3533 }
3534 }
3535 Ok(())
3536 }
3537
3538 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3539 let items = Self::pragma_import_strings(imports, line)?;
3540 if items.is_empty() {
3541 return Err(StrykeError::runtime(
3542 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
3543 line,
3544 ));
3545 }
3546 for item in items {
3547 let s = item.trim();
3548 if let Some(rest) = s.strip_prefix(':') {
3549 self.apply_feature_bundle(rest, line)?;
3550 } else {
3551 self.apply_feature_name(s, true, line)?;
3552 }
3553 }
3554 Ok(())
3555 }
3556
3557 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3558 if imports.is_empty() {
3559 self.feature_bits = 0;
3560 return Ok(());
3561 }
3562 let items = Self::pragma_import_strings(imports, line)?;
3563 for item in items {
3564 let s = item.trim();
3565 if let Some(rest) = s.strip_prefix(':') {
3566 self.clear_feature_bundle(rest);
3567 } else {
3568 self.apply_feature_name(s, false, line)?;
3569 }
3570 }
3571 Ok(())
3572 }
3573
3574 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> StrykeResult<()> {
3575 let key = v.trim();
3576 match key {
3577 "5.10" | "5.010" | "5.10.0" => {
3578 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3579 }
3580 "5.12" | "5.012" | "5.12.0" => {
3581 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3582 }
3583 _ => {
3584 return Err(StrykeError::runtime(
3585 format!("unsupported feature bundle :{}", key),
3586 line,
3587 ));
3588 }
3589 }
3590 Ok(())
3591 }
3592
3593 fn clear_feature_bundle(&mut self, v: &str) {
3594 let key = v.trim();
3595 if matches!(
3596 key,
3597 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
3598 ) {
3599 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
3600 }
3601 }
3602
3603 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> StrykeResult<()> {
3604 let bit = match name {
3605 "say" => FEAT_SAY,
3606 "state" => FEAT_STATE,
3607 "switch" => FEAT_SWITCH,
3608 "unicode_strings" => FEAT_UNICODE_STRINGS,
3609 "postderef"
3613 | "postderef_qq"
3614 | "evalbytes"
3615 | "current_sub"
3616 | "fc"
3617 | "lexical_subs"
3618 | "signatures"
3619 | "refaliasing"
3620 | "bitwise"
3621 | "isa"
3622 | "indirect"
3623 | "multidimensional"
3624 | "bareword_filehandles"
3625 | "try"
3626 | "defer"
3627 | "extra_paired_delimiters"
3628 | "module_true"
3629 | "class"
3630 | "array_base" => return Ok(()),
3631 _ => {
3632 return Err(StrykeError::runtime(
3633 format!("unknown feature `{}`", name),
3634 line,
3635 ));
3636 }
3637 };
3638 if enable {
3639 self.feature_bits |= bit;
3640 } else {
3641 self.feature_bits &= !bit;
3642 }
3643 Ok(())
3644 }
3645
3646 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> StrykeResult<StrykeValue> {
3648 let t = spec.trim();
3649 if t.is_empty() {
3650 return Err(StrykeError::runtime("require: empty argument", line));
3651 }
3652 match t {
3653 "strict" => {
3654 self.apply_use_strict(&[], line)?;
3655 return Ok(StrykeValue::integer(1));
3656 }
3657 "utf8" => {
3658 self.utf8_pragma = true;
3659 return Ok(StrykeValue::integer(1));
3660 }
3661 "feature" | "v5" => {
3662 return Ok(StrykeValue::integer(1));
3663 }
3664 "warnings" => {
3665 self.warnings = true;
3666 return Ok(StrykeValue::integer(1));
3667 }
3668 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
3669 return Ok(StrykeValue::integer(1));
3670 }
3671 _ => {}
3672 }
3673 let p = Path::new(t);
3674 if p.is_absolute() {
3675 return self.require_absolute_path(p, line);
3676 }
3677 if t.starts_with("./") || t.starts_with("../") {
3678 return self.require_relative_path(p, line);
3679 }
3680 if Self::looks_like_version_only(t) {
3681 return Ok(StrykeValue::integer(1));
3682 }
3683 let relpath = Self::module_spec_to_relpath(t);
3684 self.require_from_inc(&relpath, line)
3685 }
3686
3687 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> StrykeResult<()> {
3689 let v = self.scope.get_hash_element("^HOOK", key);
3690 if v.is_undef() {
3691 return Ok(());
3692 }
3693 let Some(sub) = v.as_code_ref() else {
3694 return Ok(());
3695 };
3696 let r = self.call_sub(
3697 sub.as_ref(),
3698 vec![StrykeValue::string(path.to_string())],
3699 WantarrayCtx::Scalar,
3700 line,
3701 );
3702 match r {
3703 Ok(_) => Ok(()),
3704 Err(FlowOrError::Error(e)) => Err(e),
3705 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3706 Err(FlowOrError::Flow(other)) => Err(StrykeError::runtime(
3707 format!(
3708 "require hook {:?} returned unexpected control flow: {:?}",
3709 key, other
3710 ),
3711 line,
3712 )),
3713 }
3714 }
3715
3716 fn require_absolute_path(&mut self, path: &Path, line: usize) -> StrykeResult<StrykeValue> {
3717 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3718 let key = canon.to_string_lossy().into_owned();
3719 if self.scope.exists_hash_element("INC", &key) {
3720 return Ok(StrykeValue::integer(1));
3721 }
3722 self.invoke_require_hook("require__before", &key, line)?;
3723 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3724 StrykeError::runtime(
3725 format!("Can't open {} for reading: {}", canon.display(), e),
3726 line,
3727 )
3728 })?;
3729 let code = crate::data_section::strip_perl_end_marker(&code);
3730 self.scope
3731 .set_hash_element("INC", &key, StrykeValue::string(key.clone()))?;
3732 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3733 let r = crate::parse_and_run_module_in_file(code, self, &key);
3734 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3735 r?;
3736 self.invoke_require_hook("require__after", &key, line)?;
3737 Ok(StrykeValue::integer(1))
3738 }
3739
3740 fn require_relative_path(&mut self, path: &Path, line: usize) -> StrykeResult<StrykeValue> {
3741 if path.exists() {
3745 return self.require_absolute_path(path, line);
3746 }
3747 if !self.file.is_empty() {
3758 let caller = Path::new(&self.file);
3759 let mut anchor = caller.parent();
3760 while let Some(dir) = anchor {
3761 let candidate = dir.join(path);
3762 if candidate.exists() {
3763 return self.require_absolute_path(&candidate, line);
3764 }
3765 anchor = dir.parent();
3766 if anchor.map(|p| p.as_os_str().is_empty()).unwrap_or(false) {
3769 break;
3770 }
3771 }
3772 }
3773 Err(StrykeError::runtime(
3774 format!(
3775 "Can't locate {} (relative path does not exist)",
3776 path.display()
3777 ),
3778 line,
3779 ))
3780 }
3781
3782 fn require_from_inc(&mut self, relpath: &str, line: usize) -> StrykeResult<StrykeValue> {
3783 if self.scope.exists_hash_element("INC", relpath) {
3784 return Ok(StrykeValue::integer(1));
3785 }
3786 self.invoke_require_hook("require__before", relpath, line)?;
3787
3788 if let Some(found) = Self::try_resolve_via_lockfile(relpath) {
3794 let code = read_file_text_perl_compat(&found).map_err(|e| {
3795 StrykeError::runtime(
3796 format!("Can't open {} for reading: {}", found.display(), e),
3797 line,
3798 )
3799 })?;
3800 let code = crate::data_section::strip_perl_end_marker(&code);
3801 let abs = found.canonicalize().unwrap_or(found);
3802 let abs_s = abs.to_string_lossy().into_owned();
3803 self.scope
3804 .set_hash_element("INC", relpath, StrykeValue::string(abs_s.clone()))?;
3805 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3806 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3807 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3808 r?;
3809 self.invoke_require_hook("require__after", relpath, line)?;
3810 return Ok(StrykeValue::integer(1));
3811 }
3812
3813 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3815 let code = crate::data_section::strip_perl_end_marker(&code);
3816 self.scope.set_hash_element(
3817 "INC",
3818 relpath,
3819 StrykeValue::string(format!("(virtual)/{}", relpath)),
3820 )?;
3821 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3822 let r = crate::parse_and_run_module_in_file(code, self, relpath);
3823 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3824 r?;
3825 self.invoke_require_hook("require__after", relpath, line)?;
3826 return Ok(StrykeValue::integer(1));
3827 }
3828
3829 for dir in self.inc_directories() {
3830 let full = Path::new(&dir).join(relpath);
3831 if full.is_file() {
3832 let code = read_file_text_perl_compat(&full).map_err(|e| {
3833 StrykeError::runtime(
3834 format!("Can't open {} for reading: {}", full.display(), e),
3835 line,
3836 )
3837 })?;
3838 let code = crate::data_section::strip_perl_end_marker(&code);
3839 let abs = full.canonicalize().unwrap_or(full);
3840 let abs_s = abs.to_string_lossy().into_owned();
3841 self.scope
3842 .set_hash_element("INC", relpath, StrykeValue::string(abs_s.clone()))?;
3843 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3844 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3845 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3846 r?;
3847 self.invoke_require_hook("require__after", relpath, line)?;
3848 return Ok(StrykeValue::integer(1));
3849 }
3850 }
3851 Err(StrykeError::runtime(
3852 format!(
3853 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
3854 relpath
3855 ),
3856 line,
3857 ))
3858 }
3859
3860 pub fn register_virtual_module(&mut self, path: String, source: String) {
3862 self.virtual_modules.insert(path, source);
3863 }
3864
3865 pub(crate) fn exec_use_stmt(
3867 &mut self,
3868 module: &str,
3869 imports: &[Expr],
3870 line: usize,
3871 ) -> StrykeResult<()> {
3872 match module {
3873 "strict" => self.apply_use_strict(imports, line),
3874 "utf8" => {
3875 if !imports.is_empty() {
3876 return Err(StrykeError::runtime("use utf8 takes no arguments", line));
3877 }
3878 self.utf8_pragma = true;
3879 Ok(())
3880 }
3881 "feature" => self.apply_use_feature(imports, line),
3882 "v5" => Ok(()),
3883 "warnings" => {
3884 self.warnings = true;
3885 Ok(())
3886 }
3887 "English" => {
3888 self.english_enabled = true;
3889 let args = Self::pragma_import_strings(imports, line)?;
3890 let no_match = args.iter().any(|a| a == "-no_match_vars");
3891 if !no_match {
3895 self.english_match_vars_ever_enabled = true;
3896 }
3897 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
3898 Ok(())
3899 }
3900 "Env" => self.apply_use_env(imports, line),
3901 "open" => self.apply_use_open(imports, line),
3902 "constant" => self.apply_use_constant(imports, line),
3903 "bigint" | "bignum" | "bigrat" => {
3904 crate::set_bigint_pragma(true);
3911 Ok(())
3912 }
3913 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3914 _ => {
3915 self.require_execute(module, line)?;
3916 let imports = Self::imports_after_leading_use_version(imports);
3917 self.apply_module_import(module, imports, line)?;
3918 Ok(())
3919 }
3920 }
3921 }
3922
3923 pub(crate) fn exec_no_stmt(
3925 &mut self,
3926 module: &str,
3927 imports: &[Expr],
3928 line: usize,
3929 ) -> StrykeResult<()> {
3930 match module {
3931 "strict" => self.apply_no_strict(imports, line),
3932 "utf8" => {
3933 if !imports.is_empty() {
3934 return Err(StrykeError::runtime("no utf8 takes no arguments", line));
3935 }
3936 self.utf8_pragma = false;
3937 Ok(())
3938 }
3939 "feature" => self.apply_no_feature(imports, line),
3940 "v5" => Ok(()),
3941 "warnings" => {
3942 self.warnings = false;
3943 Ok(())
3944 }
3945 "English" => {
3946 self.english_enabled = false;
3947 if !self.english_match_vars_ever_enabled {
3950 self.english_no_match_vars = false;
3951 }
3952 Ok(())
3953 }
3954 "open" => {
3955 self.open_pragma_utf8 = false;
3956 Ok(())
3957 }
3958 "bigint" | "bignum" | "bigrat" => {
3959 crate::set_bigint_pragma(false);
3960 Ok(())
3961 }
3962 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
3963 _ => Ok(()),
3964 }
3965 }
3966
3967 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3969 let names = Self::pragma_import_strings(imports, line)?;
3970 for n in names {
3971 let key = n.trim_start_matches('@');
3972 if key.eq_ignore_ascii_case("PATH") {
3973 let path_env = std::env::var("PATH").unwrap_or_default();
3974 let path_vec: Vec<StrykeValue> = std::env::split_paths(&path_env)
3975 .map(|p| StrykeValue::string(p.to_string_lossy().into_owned()))
3976 .collect();
3977 let aname = self.stash_array_name_for_package("PATH");
3978 self.scope.declare_array(&aname, path_vec);
3979 }
3980 }
3981 Ok(())
3982 }
3983
3984 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3986 let items = Self::pragma_import_strings(imports, line)?;
3987 for item in items {
3988 let s = item.trim();
3989 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
3990 self.open_pragma_utf8 = true;
3991 continue;
3992 }
3993 if let Some(rest) = s.strip_prefix(":encoding(") {
3994 if let Some(inner) = rest.strip_suffix(')') {
3995 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
3996 self.open_pragma_utf8 = true;
3997 }
3998 }
3999 }
4000 }
4001 Ok(())
4002 }
4003
4004 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
4006 if imports.is_empty() {
4007 return Ok(());
4008 }
4009 if imports.len() == 1 {
4011 match &imports[0].kind {
4012 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
4013 _ => {}
4014 }
4015 }
4016 for imp in imports {
4017 match &imp.kind {
4018 ExprKind::List(items) => {
4019 if items.len() % 2 != 0 {
4020 return Err(StrykeError::runtime(
4021 format!(
4022 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
4023 items.len()
4024 ),
4025 line,
4026 ));
4027 }
4028 let mut i = 0;
4029 while i < items.len() {
4030 let name = self.use_constant_name_from_expr(&items[i], line)?;
4031 self.install_constant_from_expr(&name, &items[i + 1], line)?;
4032 i += 2;
4033 }
4034 }
4035 ExprKind::HashRef(pairs) => {
4041 for (key_expr, value_expr) in pairs {
4042 let name = self.use_constant_name_from_expr(key_expr, line)?;
4043 self.install_constant_from_expr(&name, value_expr, line)?;
4044 }
4045 }
4046 _ => {
4047 return Err(StrykeError::runtime(
4048 "use constant: expected list of NAME => VALUE pairs",
4049 line,
4050 ));
4051 }
4052 }
4053 }
4054 Ok(())
4055 }
4056
4057 fn use_constant_name_from_expr(&self, e: &Expr, line: usize) -> StrykeResult<String> {
4062 match &e.kind {
4063 ExprKind::String(s) => Ok(s.clone()),
4064 ExprKind::Bareword(s) => Ok(s.clone()),
4065 _ => Err(StrykeError::runtime(
4066 "use constant: constant name must be a string literal",
4067 line,
4068 )),
4069 }
4070 }
4071
4072 fn install_constant_from_expr(
4081 &mut self,
4082 name: &str,
4083 value: &Expr,
4084 line: usize,
4085 ) -> StrykeResult<()> {
4086 if matches!(value.kind, ExprKind::List(_)) {
4094 let key = self.qualify_sub_key(name);
4095 let body = vec![Statement {
4096 label: None,
4097 kind: StmtKind::Expression(value.clone()),
4098 line,
4099 }];
4100 self.subs.insert(
4101 key.clone(),
4102 Arc::new(StrykeSub {
4103 name: key,
4104 params: vec![],
4105 body,
4106 prototype: None,
4107 closure_env: None,
4108 fib_like: None,
4109 }),
4110 );
4111 return Ok(());
4112 }
4113 let val = match self.eval_expr(value) {
4116 Ok(v) => v,
4117 Err(FlowOrError::Error(e)) => return Err(e),
4118 Err(FlowOrError::Flow(_)) => {
4119 return Err(StrykeError::runtime(
4120 "use constant: unexpected control flow in initializer",
4121 line,
4122 ));
4123 }
4124 };
4125 self.install_constant_sub(name, &val, line)
4126 }
4127
4128 fn install_constant_sub(
4129 &mut self,
4130 name: &str,
4131 val: &StrykeValue,
4132 line: usize,
4133 ) -> StrykeResult<()> {
4134 let key = self.qualify_sub_key(name);
4135 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
4136 let body = vec![Statement {
4137 label: None,
4138 kind: StmtKind::Return(Some(ret_expr)),
4139 line,
4140 }];
4141 self.subs.insert(
4142 key.clone(),
4143 Arc::new(StrykeSub {
4144 name: key,
4145 params: vec![],
4146 body,
4147 prototype: None,
4148 closure_env: None,
4149 fib_like: None,
4150 }),
4151 );
4152 Ok(())
4153 }
4154
4155 fn perl_value_to_const_literal_expr(&self, v: &StrykeValue, line: usize) -> StrykeResult<Expr> {
4157 if v.is_undef() {
4158 return Ok(Expr {
4159 kind: ExprKind::Undef,
4160 line,
4161 });
4162 }
4163 if let Some(n) = v.as_integer() {
4164 return Ok(Expr {
4165 kind: ExprKind::Integer(n),
4166 line,
4167 });
4168 }
4169 if let Some(f) = v.as_float() {
4170 return Ok(Expr {
4171 kind: ExprKind::Float(f),
4172 line,
4173 });
4174 }
4175 if let Some(s) = v.as_str() {
4176 return Ok(Expr {
4177 kind: ExprKind::String(s),
4178 line,
4179 });
4180 }
4181 if let Some(arr) = v.as_array_vec() {
4182 let mut elems = Vec::with_capacity(arr.len());
4183 for e in &arr {
4184 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
4185 }
4186 return Ok(Expr {
4187 kind: ExprKind::ArrayRef(elems),
4188 line,
4189 });
4190 }
4191 if let Some(h) = v.as_hash_map() {
4192 let mut pairs = Vec::with_capacity(h.len());
4193 for (k, vv) in h.iter() {
4194 pairs.push((
4195 Expr {
4196 kind: ExprKind::String(k.clone()),
4197 line,
4198 },
4199 self.perl_value_to_const_literal_expr(vv, line)?,
4200 ));
4201 }
4202 return Ok(Expr {
4203 kind: ExprKind::HashRef(pairs),
4204 line,
4205 });
4206 }
4207 if let Some(aref) = v.as_array_ref() {
4208 let arr = aref.read();
4209 let mut elems = Vec::with_capacity(arr.len());
4210 for e in arr.iter() {
4211 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
4212 }
4213 return Ok(Expr {
4214 kind: ExprKind::ArrayRef(elems),
4215 line,
4216 });
4217 }
4218 if let Some(href) = v.as_hash_ref() {
4219 let h = href.read();
4220 let mut pairs = Vec::with_capacity(h.len());
4221 for (k, vv) in h.iter() {
4222 pairs.push((
4223 Expr {
4224 kind: ExprKind::String(k.clone()),
4225 line,
4226 },
4227 self.perl_value_to_const_literal_expr(vv, line)?,
4228 ));
4229 }
4230 return Ok(Expr {
4231 kind: ExprKind::HashRef(pairs),
4232 line,
4233 });
4234 }
4235 Err(StrykeError::runtime(
4236 format!("use constant: unsupported value type ({v:?})"),
4237 line,
4238 ))
4239 }
4240
4241 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> StrykeResult<()> {
4243 self.utf8_pragma = false;
4249 for stmt in &program.statements {
4250 match &stmt.kind {
4251 StmtKind::Package { name } => {
4252 let _ = self
4253 .scope
4254 .set_scalar("__PACKAGE__", StrykeValue::string(name.clone()));
4255 }
4256 StmtKind::SubDecl {
4257 name,
4258 params,
4259 body,
4260 prototype,
4261 } => {
4262 let key = self.qualify_sub_key(name);
4263 let mut sub = StrykeSub {
4264 name: name.clone(),
4265 params: params.clone(),
4266 body: body.clone(),
4267 closure_env: None,
4268 prototype: prototype.clone(),
4269 fib_like: None,
4270 };
4271 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
4272 self.subs.insert(key, Arc::new(sub));
4273 }
4274 StmtKind::UsePerlVersion { .. } => {}
4275 StmtKind::Use { module, imports } => {
4276 self.exec_use_stmt(module, imports, stmt.line)?;
4277 }
4278 StmtKind::UseOverload { pairs } => {
4279 self.install_use_overload_pairs(pairs);
4280 }
4281 StmtKind::FormatDecl { name, lines } => {
4282 self.install_format_decl(name, lines, stmt.line)?;
4283 }
4284 StmtKind::No { module, imports } => {
4285 self.exec_no_stmt(module, imports, stmt.line)?;
4286 }
4287 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
4288 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
4289 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
4290 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
4291 StmtKind::End(block) => self.end_blocks.push(block.clone()),
4292 _ => {}
4293 }
4294 }
4295 Ok(())
4296 }
4297
4298 pub fn install_data_handle(&mut self, data: Vec<u8>) {
4300 self.input_handles.insert(
4301 "DATA".to_string(),
4302 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
4303 );
4304 }
4305
4306 #[inline]
4308 pub(crate) fn resolve_stryke_path(&self, path: &str) -> PathBuf {
4309 if path.is_empty() {
4310 return self.stryke_pwd.clone();
4311 }
4312 let p = Path::new(path);
4313 if p.is_absolute() {
4314 PathBuf::from(path)
4315 } else {
4316 self.stryke_pwd.join(path)
4317 }
4318 }
4319
4320 pub(crate) fn resolve_stryke_path_string(&self, path: &str) -> String {
4321 self.resolve_stryke_path(path)
4322 .to_string_lossy()
4323 .into_owned()
4324 }
4325
4326 pub(crate) fn builtin_cd_execute(
4330 &mut self,
4331 args: &[StrykeValue],
4332 _line: usize,
4333 ) -> StrykeResult<StrykeValue> {
4334 let dest: PathBuf = if args.is_empty() {
4335 let home = std::env::var_os("HOME")
4336 .or_else(|| std::env::var_os("USERPROFILE"))
4337 .map(PathBuf::from);
4338 let Some(h) = home else {
4339 return Ok(StrykeValue::integer(0));
4340 };
4341 h
4342 } else {
4343 let raw = args[0].to_string();
4344 if raw.is_empty() {
4345 return Ok(StrykeValue::integer(0));
4346 }
4347 self.resolve_stryke_path(&raw)
4348 };
4349 match std::fs::metadata(&dest) {
4350 Ok(m) if m.is_dir() => {
4351 self.stryke_pwd = std::fs::canonicalize(&dest).unwrap_or(dest);
4352 Ok(StrykeValue::integer(1))
4353 }
4354 Ok(_) => Ok(StrykeValue::integer(0)),
4355 Err(e) => {
4356 self.apply_io_error_to_errno(&e);
4357 Ok(StrykeValue::integer(0))
4358 }
4359 }
4360 }
4361
4362 pub(crate) fn open_builtin_execute(
4368 &mut self,
4369 handle_name: String,
4370 mode_s: String,
4371 file_opt: Option<String>,
4372 line: usize,
4373 ) -> StrykeResult<StrykeValue> {
4374 let (actual_mode, path) = if let Some(f) = file_opt {
4379 (mode_s, f)
4380 } else {
4381 let trimmed = mode_s.trim();
4382 if let Some(rest) = trimmed.strip_prefix('|') {
4383 ("|-".to_string(), rest.trim_start().to_string())
4384 } else if trimmed.ends_with('|') {
4385 let mut cmd = trimmed.to_string();
4386 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
4388 } else if let Some(rest) = trimmed.strip_prefix(">>") {
4389 (">>".to_string(), rest.trim().to_string())
4390 } else if let Some(rest) = trimmed.strip_prefix('>') {
4391 (">".to_string(), rest.trim().to_string())
4392 } else if let Some(rest) = trimmed.strip_prefix('<') {
4393 ("<".to_string(), rest.trim().to_string())
4394 } else {
4395 ("<".to_string(), trimmed.to_string())
4396 }
4397 };
4398 let handle_return = handle_name.clone();
4399 let file_path = match actual_mode.as_str() {
4400 "<" | ">" | ">>" => self.resolve_stryke_path_string(&path),
4401 _ => path.clone(),
4402 };
4403 match actual_mode.as_str() {
4404 "-|" => {
4405 let mut cmd = piped_shell_command(&path);
4406 cmd.stdout(Stdio::piped());
4407 let mut child = cmd.spawn().map_err(|e| {
4408 self.apply_io_error_to_errno(&e);
4409 StrykeError::runtime(format!("Can't open pipe from command: {}", e), line)
4410 })?;
4411 let stdout = child
4412 .stdout
4413 .take()
4414 .ok_or_else(|| StrykeError::runtime("pipe: child has no stdout", line))?;
4415 self.input_handles
4416 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
4417 self.pipe_children.insert(handle_name, child);
4418 }
4419 "|-" => {
4420 let mut cmd = piped_shell_command(&path);
4421 cmd.stdin(Stdio::piped());
4422 let mut child = cmd.spawn().map_err(|e| {
4423 self.apply_io_error_to_errno(&e);
4424 StrykeError::runtime(format!("Can't open pipe to command: {}", e), line)
4425 })?;
4426 let stdin = child
4427 .stdin
4428 .take()
4429 .ok_or_else(|| StrykeError::runtime("pipe: child has no stdin", line))?;
4430 self.output_handles
4431 .insert(handle_name.clone(), Box::new(stdin));
4432 self.pipe_children.insert(handle_name, child);
4433 }
4434 "<" => {
4435 let file = match std::fs::File::open(&file_path) {
4436 Ok(f) => f,
4437 Err(e) => {
4438 self.apply_io_error_to_errno(&e);
4439 return Ok(StrykeValue::integer(0));
4440 }
4441 };
4442 let shared = Arc::new(Mutex::new(file));
4443 self.io_file_slots
4444 .insert(handle_name.clone(), Arc::clone(&shared));
4445 self.input_handles.insert(
4446 handle_name.clone(),
4447 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
4448 );
4449 }
4450 ">" => {
4451 let file = match std::fs::File::create(&file_path) {
4452 Ok(f) => f,
4453 Err(e) => {
4454 self.apply_io_error_to_errno(&e);
4455 return Ok(StrykeValue::integer(0));
4456 }
4457 };
4458 let shared = Arc::new(Mutex::new(file));
4459 self.io_file_slots
4460 .insert(handle_name.clone(), Arc::clone(&shared));
4461 self.output_handles.insert(
4462 handle_name.clone(),
4463 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4464 );
4465 }
4466 ">>" => {
4467 let file = match std::fs::OpenOptions::new()
4468 .append(true)
4469 .create(true)
4470 .open(&file_path)
4471 {
4472 Ok(f) => f,
4473 Err(e) => {
4474 self.apply_io_error_to_errno(&e);
4475 return Ok(StrykeValue::integer(0));
4476 }
4477 };
4478 let shared = Arc::new(Mutex::new(file));
4479 self.io_file_slots
4480 .insert(handle_name.clone(), Arc::clone(&shared));
4481 self.output_handles.insert(
4482 handle_name.clone(),
4483 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4484 );
4485 }
4486 _ => {
4487 return Err(StrykeError::runtime(
4488 format!("Unknown open mode '{}'", actual_mode),
4489 line,
4490 ));
4491 }
4492 }
4493 Ok(StrykeValue::io_handle(handle_return))
4494 }
4495
4496 pub(crate) fn eval_chunk_by_builtin(
4500 &mut self,
4501 key_spec: &Expr,
4502 list_expr: &Expr,
4503 ctx: WantarrayCtx,
4504 line: usize,
4505 ) -> ExecResult {
4506 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
4507 let chunks = match &key_spec.kind {
4508 ExprKind::CodeRef { .. } => {
4509 let cr = self.eval_expr(key_spec)?;
4510 let Some(sub) = cr.as_code_ref() else {
4511 return Err(StrykeError::runtime(
4512 "group_by/chunk_by: first argument must be { BLOCK }",
4513 line,
4514 )
4515 .into());
4516 };
4517 let sub = sub.clone();
4518 let mut chunks: Vec<StrykeValue> = Vec::new();
4519 let mut run: Vec<StrykeValue> = Vec::new();
4520 let mut prev_key: Option<StrykeValue> = None;
4521 for item in list {
4522 self.scope.set_topic(item.clone());
4523 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
4524 Ok(k) => k,
4525 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
4526 Err(FlowOrError::Flow(Flow::Return(v))) => v,
4527 Err(_) => StrykeValue::UNDEF,
4528 };
4529 match &prev_key {
4530 None => {
4531 run.push(item);
4532 prev_key = Some(key);
4533 }
4534 Some(pk) => {
4535 if key.str_eq(pk) {
4536 run.push(item);
4537 } else {
4538 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(
4539 std::mem::take(&mut run),
4540 ))));
4541 run.push(item);
4542 prev_key = Some(key);
4543 }
4544 }
4545 }
4546 }
4547 if !run.is_empty() {
4548 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(run))));
4549 }
4550 chunks
4551 }
4552 _ => {
4553 let mut chunks: Vec<StrykeValue> = Vec::new();
4554 let mut run: Vec<StrykeValue> = Vec::new();
4555 let mut prev_key: Option<StrykeValue> = None;
4556 for item in list {
4557 self.scope.set_topic(item.clone());
4558 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
4559 match &prev_key {
4560 None => {
4561 run.push(item);
4562 prev_key = Some(key);
4563 }
4564 Some(pk) => {
4565 if key.str_eq(pk) {
4566 run.push(item);
4567 } else {
4568 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(
4569 std::mem::take(&mut run),
4570 ))));
4571 run.push(item);
4572 prev_key = Some(key);
4573 }
4574 }
4575 }
4576 }
4577 if !run.is_empty() {
4578 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(run))));
4579 }
4580 chunks
4581 }
4582 };
4583 Ok(match ctx {
4584 WantarrayCtx::List => StrykeValue::array(chunks),
4585 WantarrayCtx::Scalar => StrykeValue::integer(chunks.len() as i64),
4586 WantarrayCtx::Void => StrykeValue::UNDEF,
4587 })
4588 }
4589
4590 pub(crate) fn list_higher_order_block_builtin(
4592 &mut self,
4593 name: &str,
4594 args: &[StrykeValue],
4595 line: usize,
4596 ) -> StrykeResult<StrykeValue> {
4597 match self.list_higher_order_block_builtin_exec(name, args, line) {
4598 Ok(v) => Ok(v),
4599 Err(FlowOrError::Error(e)) => Err(e),
4600 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
4601 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
4602 format!("{name}: unsupported control flow in block"),
4603 line,
4604 )),
4605 }
4606 }
4607
4608 fn list_higher_order_block_builtin_exec(
4609 &mut self,
4610 name: &str,
4611 args: &[StrykeValue],
4612 line: usize,
4613 ) -> ExecResult {
4614 if args.is_empty() {
4615 return Err(
4616 StrykeError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
4617 );
4618 }
4619 let Some(sub) = args[0].as_code_ref() else {
4620 return Err(StrykeError::runtime(
4621 format!("{name}: first argument must be {{ BLOCK }}"),
4622 line,
4623 )
4624 .into());
4625 };
4626 let sub = sub.clone();
4627 let items: Vec<StrykeValue> = args[1..].to_vec();
4628 if matches!(name, "tap" | "peek") && items.len() == 1 {
4629 if let Some(p) = items[0].as_pipeline() {
4630 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
4631 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
4632 }
4633 let v = &items[0];
4634 if v.is_iterator() || v.as_array_vec().is_some() {
4635 let source = crate::map_stream::into_pull_iter(v.clone());
4636 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
4637 return Ok(StrykeValue::iterator(Arc::new(
4638 crate::map_stream::TapIterator::new(
4639 source,
4640 sub,
4641 self.subs.clone(),
4642 capture,
4643 atomic_arrays,
4644 atomic_hashes,
4645 ),
4646 )));
4647 }
4648 }
4649 let wa = self.wantarray_kind;
4654 match name {
4655 "take_while" => {
4656 let mut out = Vec::new();
4657 for item in items {
4658 self.scope.set_topic(item.clone());
4663 let pred =
4664 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4665 if !pred.is_true() {
4666 break;
4667 }
4668 out.push(item);
4669 }
4670 Ok(match wa {
4671 WantarrayCtx::List => StrykeValue::array(out),
4672 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4673 WantarrayCtx::Void => StrykeValue::UNDEF,
4674 })
4675 }
4676 "drop_while" | "skip_while" => {
4677 let mut i = 0usize;
4678 while i < items.len() {
4679 let it = items[i].clone();
4680 self.scope.set_topic(it.clone());
4681 let pred = self.call_sub(&sub, vec![it], WantarrayCtx::Scalar, line)?;
4682 if !pred.is_true() {
4683 break;
4684 }
4685 i += 1;
4686 }
4687 let rest = items[i..].to_vec();
4688 Ok(match wa {
4689 WantarrayCtx::List => StrykeValue::array(rest),
4690 WantarrayCtx::Scalar => StrykeValue::integer(rest.len() as i64),
4691 WantarrayCtx::Void => StrykeValue::UNDEF,
4692 })
4693 }
4694 "reject" | "grepv" => {
4695 let mut out = Vec::new();
4696 for item in items {
4697 self.scope.set_topic(item.clone());
4698 let pred =
4699 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4700 if !pred.is_true() {
4701 out.push(item);
4702 }
4703 }
4704 Ok(match wa {
4705 WantarrayCtx::List => StrykeValue::array(out),
4706 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4707 WantarrayCtx::Void => StrykeValue::UNDEF,
4708 })
4709 }
4710 "tap" | "peek" => {
4711 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
4712 Ok(match wa {
4713 WantarrayCtx::List => StrykeValue::array(items),
4714 WantarrayCtx::Scalar => StrykeValue::integer(items.len() as i64),
4715 WantarrayCtx::Void => StrykeValue::UNDEF,
4716 })
4717 }
4718 "partition" => {
4719 let mut yes = Vec::new();
4720 let mut no = Vec::new();
4721 for item in items {
4722 self.scope.set_topic(item.clone());
4723 let pred =
4724 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4725 if pred.is_true() {
4726 yes.push(item);
4727 } else {
4728 no.push(item);
4729 }
4730 }
4731 let yes_ref = StrykeValue::array_ref(Arc::new(RwLock::new(yes)));
4732 let no_ref = StrykeValue::array_ref(Arc::new(RwLock::new(no)));
4733 Ok(match wa {
4734 WantarrayCtx::List => StrykeValue::array(vec![yes_ref, no_ref]),
4735 WantarrayCtx::Scalar => StrykeValue::integer(2),
4736 WantarrayCtx::Void => StrykeValue::UNDEF,
4737 })
4738 }
4739 "min_by" => {
4740 let mut best: Option<(StrykeValue, StrykeValue)> = None;
4741 for item in items {
4742 self.scope.set_topic(item.clone());
4743 let key =
4744 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4745 best = Some(match best {
4746 None => (item, key),
4747 Some((bv, bk)) => {
4748 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
4749 (item, key)
4750 } else {
4751 (bv, bk)
4752 }
4753 }
4754 });
4755 }
4756 Ok(best.map(|(v, _)| v).unwrap_or(StrykeValue::UNDEF))
4757 }
4758 "max_by" => {
4759 let mut best: Option<(StrykeValue, StrykeValue)> = None;
4760 for item in items {
4761 self.scope.set_topic(item.clone());
4762 let key =
4763 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4764 best = Some(match best {
4765 None => (item, key),
4766 Some((bv, bk)) => {
4767 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
4768 (item, key)
4769 } else {
4770 (bv, bk)
4771 }
4772 }
4773 });
4774 }
4775 Ok(best.map(|(v, _)| v).unwrap_or(StrykeValue::UNDEF))
4776 }
4777 "zip_with" => {
4778 let flat: Vec<StrykeValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
4781 let refs: Vec<Vec<StrykeValue>> = flat
4782 .iter()
4783 .map(|el| {
4784 if let Some(ar) = el.as_array_ref() {
4785 ar.read().clone()
4786 } else if let Some(name) = el.as_array_binding_name() {
4787 self.scope.get_array(&name)
4788 } else {
4789 vec![el.clone()]
4790 }
4791 })
4792 .collect();
4793 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
4794 let mut out = Vec::with_capacity(max_len);
4795 for i in 0..max_len {
4796 let pair: Vec<StrykeValue> = refs
4797 .iter()
4798 .map(|l| l.get(i).cloned().unwrap_or(StrykeValue::UNDEF))
4799 .collect();
4800 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
4801 out.push(result);
4802 }
4803 Ok(match wa {
4804 WantarrayCtx::List => StrykeValue::array(out),
4805 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4806 WantarrayCtx::Void => StrykeValue::UNDEF,
4807 })
4808 }
4809 "count_by" => {
4810 let mut counts = indexmap::IndexMap::new();
4811 for item in items {
4812 self.scope.set_topic(item.clone());
4813 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4814 let k = key.to_string();
4815 let entry = counts.entry(k).or_insert(StrykeValue::integer(0));
4816 *entry = StrykeValue::integer(entry.to_int() + 1);
4817 }
4818 Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(counts))))
4819 }
4820 _ => Err(StrykeError::runtime(
4821 format!("internal: unknown list block builtin `{name}`"),
4822 line,
4823 )
4824 .into()),
4825 }
4826 }
4827
4828 pub(crate) fn builtin_rmdir_execute(
4830 &mut self,
4831 args: &[StrykeValue],
4832 _line: usize,
4833 ) -> StrykeResult<StrykeValue> {
4834 let mut count = 0i64;
4835 for a in args {
4836 let p = a.to_string();
4837 if p.is_empty() {
4838 continue;
4839 }
4840 let p = self.resolve_stryke_path_string(&p);
4841 if std::fs::remove_dir(&p).is_ok() {
4842 count += 1;
4843 }
4844 }
4845 Ok(StrykeValue::integer(count))
4846 }
4847
4848 pub(crate) fn builtin_touch_execute(
4850 &mut self,
4851 args: &[StrykeValue],
4852 _line: usize,
4853 ) -> StrykeResult<StrykeValue> {
4854 let paths: Vec<String> = args
4855 .iter()
4856 .map(|v| self.resolve_stryke_path_string(&v.to_string()))
4857 .collect();
4858 Ok(StrykeValue::integer(crate::perl_fs::touch_paths(&paths)))
4859 }
4860
4861 pub(crate) fn builtin_utime_execute(
4863 &mut self,
4864 args: &[StrykeValue],
4865 line: usize,
4866 ) -> StrykeResult<StrykeValue> {
4867 if args.len() < 3 {
4868 return Err(StrykeError::runtime(
4869 "utime requires at least three arguments (atime, mtime, files...)",
4870 line,
4871 ));
4872 }
4873 let at = args[0].to_int();
4874 let mt = args[1].to_int();
4875 let paths: Vec<String> = args
4876 .iter()
4877 .skip(2)
4878 .map(|v| self.resolve_stryke_path_string(&v.to_string()))
4879 .collect();
4880 let n = crate::perl_fs::utime_paths(at, mt, &paths);
4881 #[cfg(not(unix))]
4882 if !paths.is_empty() && n == 0 {
4883 return Err(StrykeError::runtime(
4884 "utime is not supported on this platform",
4885 line,
4886 ));
4887 }
4888 Ok(StrykeValue::integer(n))
4889 }
4890
4891 pub(crate) fn builtin_umask_execute(
4893 &mut self,
4894 args: &[StrykeValue],
4895 line: usize,
4896 ) -> StrykeResult<StrykeValue> {
4897 #[cfg(unix)]
4898 {
4899 let _ = line;
4900 if args.is_empty() {
4901 let cur = unsafe { libc::umask(0) };
4902 unsafe { libc::umask(cur) };
4903 return Ok(StrykeValue::integer(cur as i64));
4904 }
4905 let new_m = args[0].to_int() as libc::mode_t;
4906 let old = unsafe { libc::umask(new_m) };
4907 Ok(StrykeValue::integer(old as i64))
4908 }
4909 #[cfg(not(unix))]
4910 {
4911 let _ = args;
4912 Err(StrykeError::runtime(
4913 "umask is not supported on this platform",
4914 line,
4915 ))
4916 }
4917 }
4918
4919 pub(crate) fn builtin_getcwd_execute(
4921 &mut self,
4922 args: &[StrykeValue],
4923 line: usize,
4924 ) -> StrykeResult<StrykeValue> {
4925 if !args.is_empty() {
4926 return Err(StrykeError::runtime("getcwd takes no arguments", line));
4927 }
4928 match std::env::current_dir() {
4929 Ok(p) => Ok(StrykeValue::string(p.to_string_lossy().into_owned())),
4930 Err(e) => {
4931 self.apply_io_error_to_errno(&e);
4932 Ok(StrykeValue::UNDEF)
4933 }
4934 }
4935 }
4936
4937 pub(crate) fn builtin_realpath_execute(
4939 &mut self,
4940 args: &[StrykeValue],
4941 line: usize,
4942 ) -> StrykeResult<StrykeValue> {
4943 let path = args
4944 .first()
4945 .ok_or_else(|| StrykeError::runtime("realpath: need path", line))?
4946 .to_string();
4947 if path.is_empty() {
4948 return Err(StrykeError::runtime("realpath: need path", line));
4949 }
4950 let path = self.resolve_stryke_path_string(&path);
4951 match crate::perl_fs::realpath_resolved(&path) {
4952 Ok(s) => Ok(StrykeValue::string(s)),
4953 Err(e) => {
4954 self.apply_io_error_to_errno(&e);
4955 Ok(StrykeValue::UNDEF)
4956 }
4957 }
4958 }
4959
4960 pub(crate) fn builtin_pipe_execute(
4962 &mut self,
4963 args: &[StrykeValue],
4964 line: usize,
4965 ) -> StrykeResult<StrykeValue> {
4966 if args.len() != 2 {
4967 return Err(StrykeError::runtime(
4968 "pipe requires exactly two arguments",
4969 line,
4970 ));
4971 }
4972 #[cfg(unix)]
4973 {
4974 use std::fs::File;
4975 use std::os::unix::io::FromRawFd;
4976
4977 let read_name = args[0].to_string();
4978 let write_name = args[1].to_string();
4979 if read_name.is_empty() || write_name.is_empty() {
4980 return Err(StrykeError::runtime("pipe: invalid handle name", line));
4981 }
4982 let mut fds = [0i32; 2];
4983 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
4984 let e = std::io::Error::last_os_error();
4985 self.apply_io_error_to_errno(&e);
4986 return Ok(StrykeValue::integer(0));
4987 }
4988 let read_file = unsafe { File::from_raw_fd(fds[0]) };
4989 let write_file = unsafe { File::from_raw_fd(fds[1]) };
4990
4991 let read_shared = Arc::new(Mutex::new(read_file));
4992 let write_shared = Arc::new(Mutex::new(write_file));
4993
4994 self.close_builtin_execute(read_name.clone()).ok();
4995 self.close_builtin_execute(write_name.clone()).ok();
4996
4997 self.io_file_slots
4998 .insert(read_name.clone(), Arc::clone(&read_shared));
4999 self.input_handles.insert(
5000 read_name,
5001 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
5002 );
5003
5004 self.io_file_slots
5005 .insert(write_name.clone(), Arc::clone(&write_shared));
5006 self.output_handles
5007 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
5008
5009 Ok(StrykeValue::integer(1))
5010 }
5011 #[cfg(not(unix))]
5012 {
5013 let _ = args;
5014 Err(StrykeError::runtime(
5015 "pipe is not supported on this platform",
5016 line,
5017 ))
5018 }
5019 }
5020
5021 pub(crate) fn close_builtin_execute(&mut self, name: String) -> StrykeResult<StrykeValue> {
5022 self.output_handles.remove(&name);
5023 self.input_handles.remove(&name);
5024 self.io_file_slots.remove(&name);
5025 if let Some(mut child) = self.pipe_children.remove(&name) {
5026 if let Ok(st) = child.wait() {
5027 self.record_child_exit_status(st);
5028 }
5029 }
5030 Ok(StrykeValue::integer(1))
5031 }
5032
5033 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
5034 self.input_handles.contains_key(name)
5035 }
5036
5037 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
5041 self.line_mode_eof_pending
5042 }
5043
5044 pub(crate) fn eof_builtin_execute(
5048 &mut self,
5049 args: &[StrykeValue],
5050 line: usize,
5051 ) -> StrykeResult<StrykeValue> {
5052 match args.len() {
5053 0 => Ok(StrykeValue::integer(if self.eof_without_arg_is_true() {
5054 1
5055 } else {
5056 0
5057 })),
5058 1 => {
5059 let name = args[0].to_string();
5060 use std::io::BufRead;
5064 let at_eof = match self.input_handles.get_mut(&name) {
5065 None => true,
5066 Some(reader) => match reader.fill_buf() {
5067 Ok(buf) => buf.is_empty(),
5068 Err(_) => true,
5069 },
5070 };
5071 Ok(StrykeValue::integer(if at_eof { 1 } else { 0 }))
5072 }
5073 _ => Err(StrykeError::runtime("eof: too many arguments", line)),
5074 }
5075 }
5076
5077 pub(crate) fn study_return_value(s: &str) -> StrykeValue {
5080 if s.is_empty() {
5081 StrykeValue::string(String::new())
5082 } else {
5083 StrykeValue::integer(1)
5084 }
5085 }
5086
5087 pub(crate) fn readline_builtin_execute(
5088 &mut self,
5089 handle: Option<&str>,
5090 ) -> StrykeResult<StrykeValue> {
5091 if handle.is_none() {
5093 let argv = self.scope.get_array("ARGV");
5094 if !argv.is_empty() {
5095 loop {
5096 if self.diamond_reader.is_none() {
5097 while self.diamond_next_idx < argv.len() {
5098 let path = self.resolve_stryke_path_string(
5099 &argv[self.diamond_next_idx].to_string(),
5100 );
5101 self.diamond_next_idx += 1;
5102 match File::open(&path) {
5103 Ok(f) => {
5104 self.argv_current_file = path;
5105 self.diamond_reader = Some(BufReader::new(f));
5106 break;
5107 }
5108 Err(e) => {
5109 self.apply_io_error_to_errno(&e);
5110 }
5111 }
5112 }
5113 if self.diamond_reader.is_none() {
5114 return Ok(StrykeValue::UNDEF);
5115 }
5116 }
5117 let mut line_str = String::new();
5118 let read_result: Result<usize, io::Error> =
5119 if let Some(reader) = self.diamond_reader.as_mut() {
5120 if self.open_pragma_utf8 {
5121 let mut buf = Vec::new();
5122 reader.read_until(b'\n', &mut buf).inspect(|n| {
5123 if *n > 0 {
5124 line_str = String::from_utf8_lossy(&buf).into_owned();
5125 }
5126 })
5127 } else {
5128 let mut buf = Vec::new();
5129 match reader.read_until(b'\n', &mut buf) {
5130 Ok(n) => {
5131 if n > 0 {
5132 line_str =
5133 crate::perl_decode::decode_utf8_or_latin1_read_until(
5134 &buf,
5135 );
5136 }
5137 Ok(n)
5138 }
5139 Err(e) => Err(e),
5140 }
5141 }
5142 } else {
5143 unreachable!()
5144 };
5145 match read_result {
5146 Ok(0) => {
5147 self.diamond_reader = None;
5148 continue;
5149 }
5150 Ok(_) => {
5151 self.bump_line_for_handle(&self.argv_current_file.clone());
5152 return Ok(StrykeValue::string(line_str));
5153 }
5154 Err(e) => {
5155 self.apply_io_error_to_errno(&e);
5156 self.diamond_reader = None;
5157 continue;
5158 }
5159 }
5160 }
5161 } else {
5162 self.argv_current_file.clear();
5163 }
5164 }
5165
5166 let handle_name = handle.unwrap_or("STDIN");
5167 let mut line_str = String::new();
5168 if handle_name == "STDIN" {
5169 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
5170 self.last_stdin_die_bracket = if handle.is_none() {
5171 "<>".to_string()
5172 } else {
5173 "<STDIN>".to_string()
5174 };
5175 self.bump_line_for_handle("STDIN");
5176 return Ok(StrykeValue::string(queued));
5177 }
5178 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
5179 let mut buf = Vec::new();
5180 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
5181 if *n > 0 {
5182 line_str = String::from_utf8_lossy(&buf).into_owned();
5183 }
5184 })
5185 } else {
5186 let mut buf = Vec::new();
5187 let mut lock = io::stdin().lock();
5188 match lock.read_until(b'\n', &mut buf) {
5189 Ok(n) => {
5190 if n > 0 {
5191 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
5192 }
5193 Ok(n)
5194 }
5195 Err(e) => Err(e),
5196 }
5197 };
5198 match r {
5199 Ok(0) => Ok(StrykeValue::UNDEF),
5200 Ok(_) => {
5201 self.last_stdin_die_bracket = if handle.is_none() {
5202 "<>".to_string()
5203 } else {
5204 "<STDIN>".to_string()
5205 };
5206 self.bump_line_for_handle("STDIN");
5207 Ok(StrykeValue::string(line_str))
5208 }
5209 Err(e) => {
5210 self.apply_io_error_to_errno(&e);
5211 Ok(StrykeValue::UNDEF)
5212 }
5213 }
5214 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
5215 let slurp_mode = self.irs.is_none();
5217 let r: Result<usize, io::Error> = if slurp_mode {
5218 let mut buf = Vec::new();
5220 match reader.read_to_end(&mut buf) {
5221 Ok(n) => {
5222 if n > 0 {
5223 line_str = if self.open_pragma_utf8 {
5224 String::from_utf8_lossy(&buf).into_owned()
5225 } else {
5226 crate::perl_decode::decode_utf8_or_latin1_read_until(&buf)
5227 };
5228 }
5229 Ok(n)
5230 }
5231 Err(e) => Err(e),
5232 }
5233 } else if self.open_pragma_utf8 {
5234 let mut buf = Vec::new();
5235 reader.read_until(b'\n', &mut buf).inspect(|n| {
5236 if *n > 0 {
5237 line_str = String::from_utf8_lossy(&buf).into_owned();
5238 }
5239 })
5240 } else {
5241 let mut buf = Vec::new();
5242 match reader.read_until(b'\n', &mut buf) {
5243 Ok(n) => {
5244 if n > 0 {
5245 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
5246 }
5247 Ok(n)
5248 }
5249 Err(e) => Err(e),
5250 }
5251 };
5252 match r {
5253 Ok(0) => Ok(StrykeValue::UNDEF),
5254 Ok(_) => {
5255 self.bump_line_for_handle(handle_name);
5256 Ok(StrykeValue::string(line_str))
5257 }
5258 Err(e) => {
5259 self.apply_io_error_to_errno(&e);
5260 Ok(StrykeValue::UNDEF)
5261 }
5262 }
5263 } else {
5264 Ok(StrykeValue::UNDEF)
5265 }
5266 }
5267
5268 pub(crate) fn readline_builtin_execute_list(
5270 &mut self,
5271 handle: Option<&str>,
5272 ) -> StrykeResult<StrykeValue> {
5273 let mut lines = Vec::new();
5274 loop {
5275 let v = self.readline_builtin_execute(handle)?;
5276 if v.is_undef() {
5277 break;
5278 }
5279 lines.push(v);
5280 }
5281 Ok(StrykeValue::array(lines))
5282 }
5283
5284 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> StrykeValue {
5285 let path = self.resolve_stryke_path_string(path);
5286 match std::fs::read_dir(&path) {
5287 Ok(rd) => {
5288 let entries: Vec<String> = rd
5289 .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
5290 .collect();
5291 self.dir_handles
5292 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
5293 StrykeValue::integer(1)
5294 }
5295 Err(e) => {
5296 self.apply_io_error_to_errno(&e);
5297 StrykeValue::integer(0)
5298 }
5299 }
5300 }
5301
5302 pub(crate) fn readdir_handle(&mut self, handle: &str) -> StrykeValue {
5303 if let Some(dh) = self.dir_handles.get_mut(handle) {
5304 if dh.pos < dh.entries.len() {
5305 let s = dh.entries[dh.pos].clone();
5306 dh.pos += 1;
5307 StrykeValue::string(s)
5308 } else {
5309 StrykeValue::UNDEF
5310 }
5311 } else {
5312 StrykeValue::UNDEF
5313 }
5314 }
5315
5316 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> StrykeValue {
5318 if let Some(dh) = self.dir_handles.get_mut(handle) {
5319 let rest: Vec<StrykeValue> = dh.entries[dh.pos..]
5320 .iter()
5321 .cloned()
5322 .map(StrykeValue::string)
5323 .collect();
5324 dh.pos = dh.entries.len();
5325 StrykeValue::array(rest)
5326 } else {
5327 StrykeValue::array(Vec::new())
5328 }
5329 }
5330
5331 pub(crate) fn closedir_handle(&mut self, handle: &str) -> StrykeValue {
5332 StrykeValue::integer(if self.dir_handles.remove(handle).is_some() {
5333 1
5334 } else {
5335 0
5336 })
5337 }
5338
5339 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> StrykeValue {
5340 if let Some(dh) = self.dir_handles.get_mut(handle) {
5341 dh.pos = 0;
5342 StrykeValue::integer(1)
5343 } else {
5344 StrykeValue::integer(0)
5345 }
5346 }
5347
5348 pub(crate) fn telldir_handle(&mut self, handle: &str) -> StrykeValue {
5349 self.dir_handles
5350 .get(handle)
5351 .map(|dh| StrykeValue::integer(dh.pos as i64))
5352 .unwrap_or(StrykeValue::UNDEF)
5353 }
5354
5355 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> StrykeValue {
5356 if let Some(dh) = self.dir_handles.get_mut(handle) {
5357 dh.pos = pos.min(dh.entries.len());
5358 StrykeValue::integer(1)
5359 } else {
5360 StrykeValue::integer(0)
5361 }
5362 }
5363
5364 #[inline]
5369 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
5370 crate::special_vars::is_regex_match_scalar_name(name)
5371 }
5372
5373 #[inline]
5377 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
5378 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
5379 self.regex_capture_scope_fresh = false;
5380 }
5381 }
5382
5383 pub(crate) fn apply_regex_captures(
5384 &mut self,
5385 haystack: &str,
5386 offset: usize,
5387 re: &PerlCompiledRegex,
5388 caps: &PerlCaptures<'_>,
5389 capture_all: CaptureAllMode,
5390 ) -> Result<(), FlowOrError> {
5391 let m0 = caps.get(0).expect("regex capture 0");
5392 let s0 = offset + m0.start;
5393 let e0 = offset + m0.end;
5394 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
5395 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
5396 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
5397 let mut last_paren = String::new();
5398 for i in 1..caps.len() {
5399 if let Some(m) = caps.get(i) {
5400 last_paren = m.text.to_string();
5401 }
5402 }
5403 self.last_paren_match = last_paren;
5404 self.last_subpattern_name = String::new();
5405 for n in re.capture_names().flatten() {
5406 if caps.name(n).is_some() {
5407 self.last_subpattern_name = n.to_string();
5408 }
5409 }
5410 self.scope
5411 .set_scalar("&", StrykeValue::string(self.last_match.clone()))?;
5412 self.scope
5413 .set_scalar("`", StrykeValue::string(self.prematch.clone()))?;
5414 self.scope
5415 .set_scalar("'", StrykeValue::string(self.postmatch.clone()))?;
5416 self.scope
5417 .set_scalar("+", StrykeValue::string(self.last_paren_match.clone()))?;
5418 for i in 1..caps.len() {
5419 if let Some(m) = caps.get(i) {
5420 self.scope
5421 .set_scalar(&i.to_string(), StrykeValue::string(m.text.to_string()))?;
5422 }
5423 }
5424 let mut start_arr = vec![StrykeValue::integer(s0 as i64)];
5425 let mut end_arr = vec![StrykeValue::integer(e0 as i64)];
5426 for i in 1..caps.len() {
5427 if let Some(m) = caps.get(i) {
5428 start_arr.push(StrykeValue::integer((offset + m.start) as i64));
5429 end_arr.push(StrykeValue::integer((offset + m.end) as i64));
5430 } else {
5431 start_arr.push(StrykeValue::integer(-1));
5432 end_arr.push(StrykeValue::integer(-1));
5433 }
5434 }
5435 self.scope.set_array("-", start_arr)?;
5436 self.scope.set_array("+", end_arr)?;
5437 let mut named = IndexMap::new();
5438 for name in re.capture_names().flatten() {
5439 if let Some(m) = caps.name(name) {
5440 named.insert(name.to_string(), StrykeValue::string(m.text.to_string()));
5441 }
5442 }
5443 self.scope.set_hash("+", named.clone())?;
5444 let mut named_minus = IndexMap::new();
5446 for (name, val) in &named {
5447 named_minus.insert(
5448 name.clone(),
5449 StrykeValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
5450 );
5451 }
5452 self.scope.set_hash("-", named_minus)?;
5453 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
5454 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
5455 match capture_all {
5456 CaptureAllMode::Empty => {
5457 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5458 }
5459 CaptureAllMode::Append => {
5460 let mut rows = self.scope.get_array("^CAPTURE_ALL");
5461 rows.push(StrykeValue::array(cap_flat));
5462 self.scope.set_array("^CAPTURE_ALL", rows)?;
5463 }
5464 CaptureAllMode::Skip => {}
5465 }
5466 Ok(())
5467 }
5468
5469 pub(crate) fn clear_flip_flop_state(&mut self) {
5470 self.flip_flop_active.clear();
5471 self.flip_flop_exclusive_left_line.clear();
5472 self.flip_flop_sequence.clear();
5473 self.flip_flop_last_dot.clear();
5474 self.flip_flop_tree.clear();
5475 }
5476
5477 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
5478 self.flip_flop_active.resize(slots as usize, false);
5479 self.flip_flop_active.fill(false);
5480 self.flip_flop_exclusive_left_line
5481 .resize(slots as usize, None);
5482 self.flip_flop_exclusive_left_line.fill(None);
5483 self.flip_flop_sequence.resize(slots as usize, 0);
5484 self.flip_flop_sequence.fill(0);
5485 self.flip_flop_last_dot.resize(slots as usize, None);
5486 self.flip_flop_last_dot.fill(None);
5487 }
5488
5489 #[inline]
5493 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
5494 if self.last_readline_handle.is_empty() {
5495 self.line_number
5496 } else {
5497 *self
5498 .handle_line_numbers
5499 .get(&self.last_readline_handle)
5500 .unwrap_or(&0)
5501 }
5502 }
5503
5504 pub(crate) fn scalar_flip_flop_eval(
5513 &mut self,
5514 left: i64,
5515 right: i64,
5516 slot: usize,
5517 exclusive: bool,
5518 ) -> StrykeResult<StrykeValue> {
5519 if self.flip_flop_active.len() <= slot {
5520 self.flip_flop_active.resize(slot + 1, false);
5521 }
5522 if self.flip_flop_exclusive_left_line.len() <= slot {
5523 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5524 }
5525 if self.flip_flop_sequence.len() <= slot {
5526 self.flip_flop_sequence.resize(slot + 1, 0);
5527 }
5528 if self.flip_flop_last_dot.len() <= slot {
5529 self.flip_flop_last_dot.resize(slot + 1, None);
5530 }
5531 let dot = self.scalar_flipflop_dot_line();
5532 let active = &mut self.flip_flop_active[slot];
5533 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5534 let seq = &mut self.flip_flop_sequence[slot];
5535 let last_dot = &mut self.flip_flop_last_dot[slot];
5536 if !*active {
5537 if dot == left {
5538 *active = true;
5539 *seq = 1;
5540 *last_dot = Some(dot);
5541 if exclusive {
5542 *excl_left = Some(dot);
5543 } else {
5544 *excl_left = None;
5545 if dot == right {
5546 *active = false;
5547 return Ok(StrykeValue::string(format!("{}E0", *seq)));
5548 }
5549 }
5550 return Ok(StrykeValue::string(seq.to_string()));
5551 }
5552 *last_dot = Some(dot);
5553 return Ok(StrykeValue::string(String::new()));
5554 }
5555 if *last_dot != Some(dot) {
5558 *seq += 1;
5559 *last_dot = Some(dot);
5560 }
5561 let cur_seq = *seq;
5562 if let Some(ll) = *excl_left {
5563 if dot == right && dot > ll {
5564 *active = false;
5565 *excl_left = None;
5566 *seq = 0;
5567 return Ok(StrykeValue::string(format!("{}E0", cur_seq)));
5568 }
5569 } else if dot == right {
5570 *active = false;
5571 *seq = 0;
5572 return Ok(StrykeValue::string(format!("{}E0", cur_seq)));
5573 }
5574 Ok(StrykeValue::string(cur_seq.to_string()))
5575 }
5576
5577 fn regex_flip_flop_transition(
5578 active: &mut bool,
5579 excl_left: &mut Option<i64>,
5580 exclusive: bool,
5581 dot: i64,
5582 left_m: bool,
5583 right_m: bool,
5584 ) -> i64 {
5585 if !*active {
5586 if left_m {
5587 *active = true;
5588 if exclusive {
5589 *excl_left = Some(dot);
5590 } else {
5591 *excl_left = None;
5592 if right_m {
5593 *active = false;
5594 }
5595 }
5596 return 1;
5597 }
5598 return 0;
5599 }
5600 if let Some(ll) = *excl_left {
5601 if right_m && dot > ll {
5602 *active = false;
5603 *excl_left = None;
5604 }
5605 } else if right_m {
5606 *active = false;
5607 }
5608 1
5609 }
5610
5611 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
5616 &mut self,
5617 left_pat: &str,
5618 left_flags: &str,
5619 right_pat: &str,
5620 right_flags: &str,
5621 slot: usize,
5622 exclusive: bool,
5623 line: usize,
5624 ) -> StrykeResult<StrykeValue> {
5625 let dot = self.scalar_flipflop_dot_line();
5626 let subject = self.scope.get_scalar("_").to_string();
5627 let left_re = self
5628 .compile_regex(left_pat, left_flags, line)
5629 .map_err(|e| match e {
5630 FlowOrError::Error(err) => err,
5631 FlowOrError::Flow(_) => {
5632 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5633 }
5634 })?;
5635 let right_re = self
5636 .compile_regex(right_pat, right_flags, line)
5637 .map_err(|e| match e {
5638 FlowOrError::Error(err) => err,
5639 FlowOrError::Flow(_) => {
5640 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5641 }
5642 })?;
5643 let left_m = left_re.is_match(&subject);
5644 let right_m = right_re.is_match(&subject);
5645 if self.flip_flop_active.len() <= slot {
5646 self.flip_flop_active.resize(slot + 1, false);
5647 }
5648 if self.flip_flop_exclusive_left_line.len() <= slot {
5649 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5650 }
5651 let active = &mut self.flip_flop_active[slot];
5652 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5653 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5654 active, excl_left, exclusive, dot, left_m, right_m,
5655 )))
5656 }
5657
5658 pub(crate) fn regex_flip_flop_eval_dynamic_right(
5660 &mut self,
5661 left_pat: &str,
5662 left_flags: &str,
5663 slot: usize,
5664 exclusive: bool,
5665 line: usize,
5666 right_m: bool,
5667 ) -> StrykeResult<StrykeValue> {
5668 let dot = self.scalar_flipflop_dot_line();
5669 let subject = self.scope.get_scalar("_").to_string();
5670 let left_re = self
5671 .compile_regex(left_pat, left_flags, line)
5672 .map_err(|e| match e {
5673 FlowOrError::Error(err) => err,
5674 FlowOrError::Flow(_) => {
5675 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5676 }
5677 })?;
5678 let left_m = left_re.is_match(&subject);
5679 if self.flip_flop_active.len() <= slot {
5680 self.flip_flop_active.resize(slot + 1, false);
5681 }
5682 if self.flip_flop_exclusive_left_line.len() <= slot {
5683 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5684 }
5685 let active = &mut self.flip_flop_active[slot];
5686 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5687 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5688 active, excl_left, exclusive, dot, left_m, right_m,
5689 )))
5690 }
5691
5692 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
5694 &mut self,
5695 left_pat: &str,
5696 left_flags: &str,
5697 slot: usize,
5698 exclusive: bool,
5699 line: usize,
5700 rhs_line: i64,
5701 ) -> StrykeResult<StrykeValue> {
5702 let dot = self.scalar_flipflop_dot_line();
5703 let subject = self.scope.get_scalar("_").to_string();
5704 let left_re = self
5705 .compile_regex(left_pat, left_flags, line)
5706 .map_err(|e| match e {
5707 FlowOrError::Error(err) => err,
5708 FlowOrError::Flow(_) => {
5709 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5710 }
5711 })?;
5712 let left_m = left_re.is_match(&subject);
5713 let right_m = dot == rhs_line;
5714 if self.flip_flop_active.len() <= slot {
5715 self.flip_flop_active.resize(slot + 1, false);
5716 }
5717 if self.flip_flop_exclusive_left_line.len() <= slot {
5718 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5719 }
5720 let active = &mut self.flip_flop_active[slot];
5721 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5722 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5723 active, excl_left, exclusive, dot, left_m, right_m,
5724 )))
5725 }
5726
5727 pub(crate) fn regex_eof_flip_flop_eval(
5732 &mut self,
5733 left_pat: &str,
5734 left_flags: &str,
5735 slot: usize,
5736 exclusive: bool,
5737 line: usize,
5738 ) -> StrykeResult<StrykeValue> {
5739 let dot = self.scalar_flipflop_dot_line();
5740 let subject = self.scope.get_scalar("_").to_string();
5741 let left_re = self
5742 .compile_regex(left_pat, left_flags, line)
5743 .map_err(|e| match e {
5744 FlowOrError::Error(err) => err,
5745 FlowOrError::Flow(_) => {
5746 StrykeError::runtime("unexpected flow in regex/eof flip-flop", line)
5747 }
5748 })?;
5749 let left_m = left_re.is_match(&subject);
5750 let right_m = self.eof_without_arg_is_true();
5751 if self.flip_flop_active.len() <= slot {
5752 self.flip_flop_active.resize(slot + 1, false);
5753 }
5754 if self.flip_flop_exclusive_left_line.len() <= slot {
5755 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5756 }
5757 let active = &mut self.flip_flop_active[slot];
5758 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5759 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5760 active, excl_left, exclusive, dot, left_m, right_m,
5761 )))
5762 }
5763
5764 pub(crate) fn builtin_read_into(
5768 &mut self,
5769 fh_val: StrykeValue,
5770 var_name: &str,
5771 length: usize,
5772 line: usize,
5773 ) -> ExecResult {
5774 use std::io::Read;
5775 let fh = fh_val
5776 .as_io_handle_name()
5777 .unwrap_or_else(|| fh_val.to_string());
5778 let mut buf = vec![0u8; length];
5779 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
5780 slot.lock().read(&mut buf).unwrap_or(0)
5781 } else if fh == "STDIN" {
5782 std::io::stdin().read(&mut buf).unwrap_or(0)
5783 } else {
5784 return Err(StrykeError::runtime(format!("read: unopened handle {}", fh), line).into());
5785 };
5786 buf.truncate(n);
5787 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
5788 let _ = self
5789 .scope
5790 .set_scalar(var_name, StrykeValue::string(read_str));
5791 Ok(StrykeValue::integer(n as i64))
5792 }
5793
5794 pub(crate) fn chomp_inplace_execute(&mut self, val: StrykeValue, target: &Expr) -> ExecResult {
5795 match &target.kind {
5801 ExprKind::ArrayVar(name) => {
5802 let arr = self.scope.get_array(name);
5803 let mut total = 0i64;
5804 let mut new_arr = Vec::with_capacity(arr.len());
5805 for v in arr {
5806 let mut s = v.to_string();
5807 if s.ends_with('\n') {
5808 s.pop();
5809 total += 1;
5810 }
5811 new_arr.push(StrykeValue::string(s));
5812 }
5813 self.scope
5814 .set_array(name, new_arr)
5815 .map_err(FlowOrError::Error)?;
5816 return Ok(StrykeValue::integer(total));
5817 }
5818 ExprKind::HashVar(name) => {
5819 let h = self.scope.get_hash(name);
5820 let mut total = 0i64;
5821 let mut new_h: indexmap::IndexMap<String, StrykeValue> =
5822 indexmap::IndexMap::with_capacity(h.len());
5823 for (k, v) in h {
5824 let mut s = v.to_string();
5825 if s.ends_with('\n') {
5826 s.pop();
5827 total += 1;
5828 }
5829 new_h.insert(k, StrykeValue::string(s));
5830 }
5831 self.scope
5832 .set_hash(name, new_h)
5833 .map_err(FlowOrError::Error)?;
5834 return Ok(StrykeValue::integer(total));
5835 }
5836 _ => {}
5837 }
5838 let mut s = val.to_string();
5839 let removed = if s.ends_with('\n') {
5840 s.pop();
5841 1i64
5842 } else {
5843 0i64
5844 };
5845 self.assign_value(target, StrykeValue::string(s))?;
5846 Ok(StrykeValue::integer(removed))
5847 }
5848
5849 pub(crate) fn chop_inplace_execute(&mut self, val: StrykeValue, target: &Expr) -> ExecResult {
5851 match &target.kind {
5857 ExprKind::ArrayVar(name) => {
5858 let arr = self.scope.get_array(name);
5859 let mut last = StrykeValue::UNDEF;
5860 let mut new_arr = Vec::with_capacity(arr.len());
5861 for v in arr {
5862 let mut s = v.to_string();
5863 if let Some(c) = s.pop() {
5864 last = StrykeValue::string(c.to_string());
5865 }
5866 new_arr.push(StrykeValue::string(s));
5867 }
5868 self.scope
5869 .set_array(name, new_arr)
5870 .map_err(FlowOrError::Error)?;
5871 return Ok(last);
5872 }
5873 ExprKind::HashVar(name) => {
5874 let h = self.scope.get_hash(name);
5875 let mut last = StrykeValue::UNDEF;
5876 let mut new_h: indexmap::IndexMap<String, StrykeValue> =
5877 indexmap::IndexMap::with_capacity(h.len());
5878 for (k, v) in h {
5879 let mut s = v.to_string();
5880 if let Some(c) = s.pop() {
5881 last = StrykeValue::string(c.to_string());
5882 }
5883 new_h.insert(k, StrykeValue::string(s));
5884 }
5885 self.scope
5886 .set_hash(name, new_h)
5887 .map_err(FlowOrError::Error)?;
5888 return Ok(last);
5889 }
5890 _ => {}
5891 }
5892 let mut s = val.to_string();
5893 let chopped = s
5894 .pop()
5895 .map(|c| StrykeValue::string(c.to_string()))
5896 .unwrap_or(StrykeValue::UNDEF);
5897 self.assign_value(target, StrykeValue::string(s))?;
5898 Ok(chopped)
5899 }
5900
5901 pub(crate) fn regex_match_execute(
5903 &mut self,
5904 s: String,
5905 pattern: &str,
5906 flags: &str,
5907 scalar_g: bool,
5908 pos_key: &str,
5909 line: usize,
5910 ) -> ExecResult {
5911 if !flags.contains('g') && !scalar_g {
5919 let memo_hit = {
5920 if let Some(ref mem) = self.regex_match_memo {
5921 mem.pattern == pattern
5922 && mem.flags == flags
5923 && mem.multiline == self.multiline_match
5924 && mem.haystack == s
5925 } else {
5926 false
5927 }
5928 };
5929 if memo_hit {
5930 if self.regex_capture_scope_fresh {
5931 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
5932 }
5933 let (memo_s, memo_result) = {
5936 let mem = self.regex_match_memo.as_ref().expect("memo");
5937 (mem.haystack.clone(), mem.result.clone())
5938 };
5939 let re = self.compile_regex(pattern, flags, line)?;
5940 if let Some(caps) = re.captures(&memo_s) {
5941 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
5942 }
5943 self.regex_capture_scope_fresh = true;
5944 return Ok(memo_result);
5945 }
5946 }
5947 let re = self.compile_regex(pattern, flags, line)?;
5948 if flags.contains('g') && scalar_g {
5949 let key = pos_key.to_string();
5950 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
5951 if start == 0 {
5952 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5953 }
5954 if start > s.len() {
5955 self.regex_pos.insert(key, None);
5956 return Ok(StrykeValue::integer(0));
5957 }
5958 let sub = s.get(start..).unwrap_or("");
5959 if let Some(caps) = re.captures(sub) {
5960 let overall = caps.get(0).expect("capture 0");
5961 let abs_end = start + overall.end;
5962 self.regex_pos.insert(key, Some(abs_end));
5963 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
5964 Ok(StrykeValue::integer(1))
5965 } else {
5966 self.regex_pos.insert(key, None);
5967 Ok(StrykeValue::integer(0))
5968 }
5969 } else if flags.contains('g') {
5970 let mut rows = Vec::new();
5971 let mut last_caps: Option<PerlCaptures<'_>> = None;
5972 let mut has_groups = false;
5973 let mut flat_captures: Vec<StrykeValue> = Vec::new();
5977 for caps in re.captures_iter(&s) {
5978 if caps.len() > 1 {
5979 has_groups = true;
5980 }
5981 let cap_row = crate::perl_regex::numbered_capture_flat(&caps);
5982 flat_captures.extend(cap_row.iter().cloned());
5983 rows.push(StrykeValue::array(cap_row));
5984 last_caps = Some(caps);
5985 }
5986 self.scope.set_array("^CAPTURE_ALL", rows)?;
5987 if has_groups {
5988 if flat_captures.is_empty() {
5989 return Ok(StrykeValue::integer(0));
5990 }
5991 if let Some(caps) = last_caps {
5992 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
5993 }
5994 return Ok(StrykeValue::array(flat_captures));
5995 }
5996 let matches: Vec<StrykeValue> = match &*re {
5997 PerlCompiledRegex::Rust(r) => r
5998 .find_iter(&s)
5999 .map(|m| StrykeValue::string(m.as_str().to_string()))
6000 .collect(),
6001 PerlCompiledRegex::Fancy(r) => r
6002 .find_iter(&s)
6003 .filter_map(|m| m.ok())
6004 .map(|m| StrykeValue::string(m.as_str().to_string()))
6005 .collect(),
6006 PerlCompiledRegex::Pcre2(r) => r
6007 .find_iter(s.as_bytes())
6008 .filter_map(|m| m.ok())
6009 .map(|m| {
6010 let t = s.get(m.start()..m.end()).unwrap_or("");
6011 StrykeValue::string(t.to_string())
6012 })
6013 .collect(),
6014 };
6015 if matches.is_empty() {
6016 Ok(StrykeValue::integer(0))
6017 } else {
6018 if let Some(caps) = last_caps {
6019 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
6020 }
6021 Ok(StrykeValue::array(matches))
6022 }
6023 } else if let Some(caps) = re.captures(&s) {
6024 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
6025 let list_context = self.wantarray_kind == WantarrayCtx::List;
6033 let has_groups = caps.len() > 1;
6034 let result = if list_context && has_groups {
6035 let cap_vec = crate::perl_regex::numbered_capture_flat(&caps);
6036 let any_defined = cap_vec.iter().any(|v| !v.is_undef());
6037 if any_defined {
6038 StrykeValue::array(cap_vec)
6039 } else {
6040 StrykeValue::integer(1)
6041 }
6042 } else {
6043 StrykeValue::integer(1)
6044 };
6045 if matches!(result.as_integer(), Some(1)) {
6048 self.regex_match_memo = Some(RegexMatchMemo {
6049 pattern: pattern.to_string(),
6050 flags: flags.to_string(),
6051 multiline: self.multiline_match,
6052 haystack: s,
6053 result: result.clone(),
6054 });
6055 }
6056 self.regex_capture_scope_fresh = true;
6057 Ok(result)
6058 } else {
6059 let list_context = self.wantarray_kind == WantarrayCtx::List;
6061 let result = if list_context {
6062 StrykeValue::array(Vec::new())
6063 } else {
6064 StrykeValue::integer(0)
6065 };
6066 if !list_context {
6068 self.regex_match_memo = Some(RegexMatchMemo {
6069 pattern: pattern.to_string(),
6070 flags: flags.to_string(),
6071 multiline: self.multiline_match,
6072 haystack: s,
6073 result: result.clone(),
6074 });
6075 }
6076 Ok(result)
6079 }
6080 }
6081
6082 pub(crate) fn expand_env_braces_in_subst(
6086 &mut self,
6087 raw: &str,
6088 line: usize,
6089 ) -> StrykeResult<String> {
6090 self.materialize_env_if_needed();
6091 let mut out = String::new();
6092 let mut rest = raw;
6093 while let Some(idx) = rest.find("$ENV{") {
6094 out.push_str(&rest[..idx]);
6095 let after = &rest[idx + 5..];
6096 let end = after
6097 .find('}')
6098 .ok_or_else(|| StrykeError::runtime("Unclosed $ENV{...} in s///", line))?;
6099 let key = &after[..end];
6100 let val = self.scope.get_hash_element("ENV", key);
6101 out.push_str(&val.to_string());
6102 rest = &after[end + 1..];
6103 }
6104 out.push_str(rest);
6105 Ok(out)
6106 }
6107
6108 pub(crate) fn regex_subst_execute(
6114 &mut self,
6115 s: String,
6116 pattern: &str,
6117 replacement: &str,
6118 flags: &str,
6119 target: &Expr,
6120 line: usize,
6121 ) -> ExecResult {
6122 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
6123 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
6124 let re = self.compile_regex(&pattern, &re_flags, line)?;
6125 if flags.contains('e') {
6126 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
6127 }
6128 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
6129 let replacement = self.interpolate_replacement_string(&replacement);
6130 let replacement = normalize_replacement_backrefs(&replacement);
6131 let last_caps = if flags.contains('g') {
6132 let mut rows = Vec::new();
6133 let mut last = None;
6134 for caps in re.captures_iter(&s) {
6135 rows.push(StrykeValue::array(
6136 crate::perl_regex::numbered_capture_flat(&caps),
6137 ));
6138 last = Some(caps);
6139 }
6140 self.scope.set_array("^CAPTURE_ALL", rows)?;
6141 last
6142 } else {
6143 re.captures(&s)
6144 };
6145 if let Some(caps) = last_caps {
6146 let mode = if flags.contains('g') {
6147 CaptureAllMode::Skip
6148 } else {
6149 CaptureAllMode::Empty
6150 };
6151 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
6152 }
6153 let (new_s, count) = if flags.contains('g') {
6154 let count = re.find_iter_count(&s);
6155 (re.replace_all(&s, replacement.as_str()), count)
6156 } else {
6157 let count = if re.is_match(&s) { 1 } else { 0 };
6158 (re.replace(&s, replacement.as_str()), count)
6159 };
6160 if flags.contains('r') {
6161 Ok(StrykeValue::string(new_s))
6163 } else {
6164 self.assign_value(target, StrykeValue::string(new_s))?;
6165 Ok(StrykeValue::integer(count as i64))
6166 }
6167 }
6168
6169 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
6172 let prep_source = |raw: &str| -> String {
6173 let mut code = raw.trim().to_string();
6174 if !code.ends_with(';') {
6175 code.push(';');
6176 }
6177 code
6178 };
6179 let mut cur = prep_source(replacement);
6180 let mut last = StrykeValue::UNDEF;
6181 for round in 0..e_count {
6182 last = crate::parse_and_run_string(&cur, self)?;
6183 if round + 1 < e_count {
6184 cur = prep_source(&last.to_string());
6185 }
6186 }
6187 Ok(last)
6188 }
6189
6190 fn regex_subst_execute_eval(
6191 &mut self,
6192 s: String,
6193 re: &PerlCompiledRegex,
6194 replacement: &str,
6195 flags: &str,
6196 target: &Expr,
6197 line: usize,
6198 ) -> ExecResult {
6199 let e_count = flags.chars().filter(|c| *c == 'e').count();
6200 if e_count == 0 {
6201 return Err(StrykeError::runtime("s///e: internal error (no e flag)", line).into());
6202 }
6203
6204 if flags.contains('g') {
6205 let mut rows = Vec::new();
6206 let mut out = String::new();
6207 let mut last = 0usize;
6208 let mut count = 0usize;
6209 for caps in re.captures_iter(&s) {
6210 let m0 = caps.get(0).expect("regex capture 0");
6211 out.push_str(&s[last..m0.start]);
6212 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
6213 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
6214 out.push_str(&repl_val.to_string());
6215 last = m0.end;
6216 count += 1;
6217 rows.push(StrykeValue::array(
6218 crate::perl_regex::numbered_capture_flat(&caps),
6219 ));
6220 }
6221 self.scope.set_array("^CAPTURE_ALL", rows)?;
6222 out.push_str(&s[last..]);
6223 if flags.contains('r') {
6224 return Ok(StrykeValue::string(out));
6225 }
6226 self.assign_value(target, StrykeValue::string(out))?;
6227 return Ok(StrykeValue::integer(count as i64));
6228 }
6229 if let Some(caps) = re.captures(&s) {
6230 let m0 = caps.get(0).expect("regex capture 0");
6231 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
6232 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
6233 let mut out = String::new();
6234 out.push_str(&s[..m0.start]);
6235 out.push_str(&repl_val.to_string());
6236 out.push_str(&s[m0.end..]);
6237 if flags.contains('r') {
6238 return Ok(StrykeValue::string(out));
6239 }
6240 self.assign_value(target, StrykeValue::string(out))?;
6241 return Ok(StrykeValue::integer(1));
6242 }
6243 if flags.contains('r') {
6244 return Ok(StrykeValue::string(s));
6245 }
6246 self.assign_value(target, StrykeValue::string(s))?;
6247 Ok(StrykeValue::integer(0))
6248 }
6249
6250 pub(crate) fn regex_transliterate_execute(
6252 &mut self,
6253 s: String,
6254 from: &str,
6255 to: &str,
6256 flags: &str,
6257 target: &Expr,
6258 line: usize,
6259 ) -> ExecResult {
6260 let _ = line;
6261 let from_chars = Self::tr_expand_ranges(from);
6262 let to_chars = Self::tr_expand_ranges(to);
6263 let delete_mode = flags.contains('d');
6264 let complement = flags.contains('c');
6265 let squash = flags.contains('s');
6266
6267 let mut count = 0i64;
6268 let mut new_s = String::with_capacity(s.len());
6269 let mut last_out: Option<char> = None;
6270 for c in s.chars() {
6271 let in_from = from_chars.iter().position(|&fc| fc == c);
6272 let matched = if complement {
6273 in_from.is_none()
6274 } else {
6275 in_from.is_some()
6276 };
6277 if !matched {
6278 new_s.push(c);
6279 last_out = Some(c);
6280 continue;
6281 }
6282 count += 1;
6283 let out_c = if complement {
6290 to_chars.last().copied()
6291 } else if let Some(pos) = in_from {
6292 if pos < to_chars.len() {
6293 Some(to_chars[pos])
6294 } else if delete_mode {
6295 None
6296 } else {
6297 to_chars.last().copied().or(Some(c))
6298 }
6299 } else {
6300 None
6301 };
6302 let out_c = match out_c {
6303 Some(c) => c,
6304 None => {
6305 if delete_mode {
6309 continue;
6310 }
6311 c
6312 }
6313 };
6314 if squash && last_out == Some(out_c) {
6315 continue;
6316 }
6317 new_s.push(out_c);
6318 last_out = Some(out_c);
6319 }
6320
6321 if flags.contains('r') {
6322 Ok(StrykeValue::string(new_s))
6324 } else {
6325 self.assign_value(target, StrykeValue::string(new_s))?;
6326 Ok(StrykeValue::integer(count))
6327 }
6328 }
6329
6330 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
6333 let raw: Vec<char> = spec.chars().collect();
6334 let mut out = Vec::with_capacity(raw.len());
6335 let mut i = 0;
6336 while i < raw.len() {
6337 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
6338 let start = raw[i] as u32;
6339 let end = raw[i + 2] as u32;
6340 for code in start..=end {
6341 if let Some(c) = char::from_u32(code) {
6342 out.push(c);
6343 }
6344 }
6345 i += 3;
6346 } else {
6347 out.push(raw[i]);
6348 i += 1;
6349 }
6350 }
6351 out
6352 }
6353
6354 pub(crate) fn splice_builtin_execute(
6356 &mut self,
6357 args: &[StrykeValue],
6358 line: usize,
6359 ) -> StrykeResult<StrykeValue> {
6360 if args.is_empty() {
6361 return Err(StrykeError::runtime("splice: missing array", line));
6362 }
6363 let arr_name = args[0].to_string();
6364 let arr_len = self.scope.array_len(&arr_name);
6365 let offset_val = args
6366 .get(1)
6367 .cloned()
6368 .unwrap_or_else(|| StrykeValue::integer(0));
6369 let length_val = match args.get(2) {
6370 None => StrykeValue::UNDEF,
6371 Some(v) => v.clone(),
6372 };
6373 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
6374 let mut rep_vals: Vec<StrykeValue> = Vec::new();
6378 for a in args.iter().skip(3) {
6379 if let Some(items) = a.as_array_vec() {
6380 rep_vals.extend(items);
6381 } else {
6382 rep_vals.push(a.clone());
6383 }
6384 }
6385 let removed = self.scope.splice_in_place(&arr_name, off, end, rep_vals)?;
6386 Ok(match self.wantarray_kind {
6387 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
6388 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
6389 })
6390 }
6391
6392 pub(crate) fn unshift_builtin_execute(
6394 &mut self,
6395 args: &[StrykeValue],
6396 line: usize,
6397 ) -> StrykeResult<StrykeValue> {
6398 if args.is_empty() {
6399 return Err(StrykeError::runtime("unshift: missing array", line));
6400 }
6401 let arr_name = args[0].to_string();
6402 let mut flat_vals: Vec<StrykeValue> = Vec::new();
6403 for a in args.iter().skip(1) {
6404 if let Some(items) = a.as_array_vec() {
6405 flat_vals.extend(items);
6406 } else {
6407 flat_vals.push(a.clone());
6408 }
6409 }
6410 let arr = self.scope.get_array_mut(&arr_name)?;
6411 for (i, v) in flat_vals.into_iter().enumerate() {
6412 arr.insert(i, v);
6413 }
6414 Ok(StrykeValue::integer(arr.len() as i64))
6415 }
6416
6417 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
6420 if upper == 0.0 {
6421 self.rand_rng.gen_range(0.0..1.0)
6422 } else if upper > 0.0 {
6423 self.rand_rng.gen_range(0.0..upper)
6424 } else {
6425 self.rand_rng.gen_range(upper..0.0)
6426 }
6427 }
6428
6429 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
6431 let n = if let Some(s) = seed {
6432 s as i64
6433 } else {
6434 std::time::SystemTime::now()
6435 .duration_since(std::time::UNIX_EPOCH)
6436 .map(|d| d.as_secs() as i64)
6437 .unwrap_or(1)
6438 };
6439 let mag = n.unsigned_abs();
6440 self.rand_rng = StdRng::seed_from_u64(mag);
6441 n.abs()
6442 }
6443
6444 pub fn set_file(&mut self, file: &str) {
6445 self.file = file.to_string();
6446 }
6447
6448 pub fn repl_completion_names(&self) -> Vec<String> {
6450 let mut v = self.scope.repl_binding_names();
6451 v.extend(self.subs.keys().cloned());
6452 v.sort();
6453 v.dedup();
6454 v
6455 }
6456
6457 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
6459 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
6460 subs.sort();
6461 let mut classes: HashSet<String> = HashSet::new();
6462 for k in &subs {
6463 if let Some((pkg, rest)) = k.split_once("::") {
6464 if !rest.contains("::") {
6465 classes.insert(pkg.to_string());
6466 }
6467 }
6468 }
6469 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
6470 for bn in self.scope.repl_binding_names() {
6471 if let Some(r) = bn.strip_prefix('$') {
6472 let v = self.scope.get_scalar(r);
6473 if let Some(b) = v.as_blessed_ref() {
6474 blessed_scalars.insert(r.to_string(), b.class.clone());
6475 classes.insert(b.class.clone());
6476 }
6477 }
6478 }
6479 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
6480 for c in classes {
6481 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
6482 }
6483 ReplCompletionSnapshot {
6484 subs,
6485 blessed_scalars,
6486 isa_for_class,
6487 }
6488 }
6489
6490 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
6491 if n == 0 {
6492 return Err(FlowOrError::Error(StrykeError::runtime(
6493 "bench: iteration count must be positive",
6494 line,
6495 )));
6496 }
6497 let mut samples = Vec::with_capacity(n);
6498 for _ in 0..n {
6499 let start = std::time::Instant::now();
6500 self.exec_block(body)?;
6501 samples.push(start.elapsed().as_secs_f64() * 1000.0);
6502 }
6503 let mut sorted = samples.clone();
6504 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6505 let min_ms = sorted[0];
6506 let mean = samples.iter().sum::<f64>() / n as f64;
6507 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
6508 .saturating_sub(1)
6509 .min(n - 1);
6510 let p99_ms = sorted[p99_idx];
6511 Ok(StrykeValue::string(format!(
6512 "bench: n={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
6513 n, min_ms, mean, p99_ms
6514 )))
6515 }
6516
6517 pub fn execute(&mut self, program: &Program) -> StrykeResult<StrykeValue> {
6518 crate::serialize_normalize::install_class_defs(self.class_defs.clone());
6524 if self.line_mode_skip_main {
6526 crate::compile_and_run_prelude(program, self)?;
6527 return Ok(StrykeValue::UNDEF);
6528 }
6529 crate::try_vm_execute(program, self)
6530 .expect("VM compilation must succeed — all execution is VM-only")
6531 }
6532
6533 pub fn run_end_blocks(&mut self) -> StrykeResult<()> {
6535 self.global_phase = "END".to_string();
6536 let ends = std::mem::take(&mut self.end_blocks);
6537 for block in &ends {
6538 self.exec_block(block).map_err(|e| match e {
6539 FlowOrError::Error(e) => e,
6540 FlowOrError::Flow(_) => StrykeError::runtime("Unexpected flow control in END", 0),
6541 })?;
6542 }
6543 Ok(())
6544 }
6545
6546 pub fn run_global_teardown(&mut self) -> StrykeResult<()> {
6549 self.global_phase = "DESTRUCT".to_string();
6550 self.drain_pending_destroys(0)
6551 }
6552
6553 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> StrykeResult<()> {
6555 loop {
6556 let batch = crate::pending_destroy::take_queue();
6557 if batch.is_empty() {
6558 break;
6559 }
6560 for (class, payload) in batch {
6561 let fq = format!("{}::DESTROY", class);
6562 let Some(sub) = self.subs.get(&fq).cloned() else {
6563 continue;
6564 };
6565 let inv = StrykeValue::blessed(Arc::new(
6566 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
6567 ));
6568 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
6569 Ok(_) => {}
6570 Err(FlowOrError::Error(e)) => return Err(e),
6571 Err(FlowOrError::Flow(Flow::Return(_))) => {}
6572 Err(FlowOrError::Flow(other)) => {
6573 return Err(StrykeError::runtime(
6574 format!("DESTROY: unexpected control flow ({other:?})"),
6575 line,
6576 ));
6577 }
6578 }
6579 }
6580 }
6581 Ok(())
6582 }
6583
6584 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
6585 self.exec_block_with_tail(block, WantarrayCtx::Void)
6586 }
6587
6588 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6591 let uses_goto = block
6592 .iter()
6593 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
6594 if uses_goto {
6595 self.scope_push_hook();
6596 let r = self.exec_block_with_goto_tail(block, tail);
6597 self.scope_pop_hook();
6598 r
6599 } else {
6600 self.scope_push_hook();
6601 let result = self.exec_block_no_scope_with_tail(block, tail);
6602 self.scope_pop_hook();
6603 result
6604 }
6605 }
6606
6607 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6608 let mut map: HashMap<String, usize> = HashMap::new();
6609 for (i, s) in block.iter().enumerate() {
6610 if let Some(l) = &s.label {
6611 map.insert(l.clone(), i);
6612 }
6613 }
6614 let mut pc = 0usize;
6615 let mut last = StrykeValue::UNDEF;
6616 let last_idx = block.len().saturating_sub(1);
6617 while pc < block.len() {
6618 if let StmtKind::Goto { target } = &block[pc].kind {
6619 let line = block[pc].line;
6620 let name = self.eval_expr(target)?.to_string();
6621 pc = *map.get(&name).ok_or_else(|| {
6622 FlowOrError::Error(StrykeError::runtime(
6623 format!("goto: unknown label {}", name),
6624 line,
6625 ))
6626 })?;
6627 continue;
6628 }
6629 let v = if pc == last_idx {
6630 match &block[pc].kind {
6631 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
6632 _ => self.exec_statement(&block[pc])?,
6633 }
6634 } else {
6635 self.exec_statement(&block[pc])?
6636 };
6637 last = v;
6638 pc += 1;
6639 }
6640 Ok(last)
6641 }
6642
6643 #[inline]
6646 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
6647 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
6648 }
6649
6650 pub(crate) fn exec_block_no_scope_with_tail(
6651 &mut self,
6652 block: &Block,
6653 tail: WantarrayCtx,
6654 ) -> ExecResult {
6655 if block.is_empty() {
6656 return Ok(StrykeValue::UNDEF);
6657 }
6658 let last_i = block.len() - 1;
6659 for (i, stmt) in block.iter().enumerate() {
6660 if i < last_i {
6661 self.exec_statement(stmt)?;
6662 } else {
6663 return match &stmt.kind {
6664 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
6665 _ => self.exec_statement(stmt),
6666 };
6667 }
6668 }
6669 Ok(StrykeValue::UNDEF)
6670 }
6671
6672 pub(crate) fn spawn_async_block(&self, block: &Block) -> StrykeValue {
6674 use parking_lot::Mutex as ParkMutex;
6675
6676 let block = block.clone();
6677 let subs = self.subs.clone();
6678 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6679 let result = Arc::new(ParkMutex::new(None));
6680 let join = Arc::new(ParkMutex::new(None));
6681 let result2 = result.clone();
6682 let h = std::thread::spawn(move || {
6683 let mut interp = VMHelper::new();
6684 interp.subs = subs;
6685 interp.scope.restore_capture(&scalars);
6686 interp.scope.restore_atomics(&aar, &ahash);
6687 interp.enable_parallel_guard();
6688 let r = match interp.exec_block(&block) {
6689 Ok(v) => Ok(v),
6690 Err(FlowOrError::Error(e)) => Err(e),
6691 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6692 Err(StrykeError::runtime("yield inside async/spawn block", 0))
6693 }
6694 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
6695 };
6696 *result2.lock() = Some(r);
6697 });
6698 *join.lock() = Some(h);
6699 StrykeValue::async_task(Arc::new(StrykeAsyncTask { result, join }))
6700 }
6701
6702 pub(crate) fn eval_timeout_block(
6704 &mut self,
6705 body: &Block,
6706 secs: f64,
6707 line: usize,
6708 ) -> ExecResult {
6709 use std::sync::mpsc::channel;
6710 use std::time::Duration;
6711
6712 let block = body.clone();
6713 let subs = self.subs.clone();
6714 let struct_defs = self.struct_defs.clone();
6715 let enum_defs = self.enum_defs.clone();
6716 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6717 self.materialize_env_if_needed();
6718 let env = self.env.clone();
6719 let argv = self.argv.clone();
6720 let inc = self.scope.get_array("INC");
6721 let (tx, rx) = channel::<StrykeResult<StrykeValue>>();
6722 let _handle = std::thread::spawn(move || {
6723 let mut interp = VMHelper::new();
6724 interp.subs = subs;
6725 interp.struct_defs = struct_defs;
6726 interp.enum_defs = enum_defs;
6727 interp.env = env.clone();
6728 interp.argv = argv.clone();
6729 interp.scope.declare_array(
6730 "ARGV",
6731 argv.iter()
6732 .map(|s| StrykeValue::string(s.clone()))
6733 .collect(),
6734 );
6735 for (k, v) in env {
6736 interp
6737 .scope
6738 .set_hash_element("ENV", &k, v)
6739 .expect("set ENV in timeout thread");
6740 }
6741 interp.scope.declare_array("INC", inc);
6742 interp.scope.restore_capture(&scalars);
6743 interp.scope.restore_atomics(&aar, &ahash);
6744 interp.enable_parallel_guard();
6745 let out: StrykeResult<StrykeValue> = match interp.exec_block(&block) {
6746 Ok(v) => Ok(v),
6747 Err(FlowOrError::Error(e)) => Err(e),
6748 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6749 Err(StrykeError::runtime("yield inside eval_timeout block", 0))
6750 }
6751 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
6752 };
6753 let _ = tx.send(out);
6754 });
6755 let dur = Duration::from_secs_f64(secs.max(0.0));
6756 match rx.recv_timeout(dur) {
6757 Ok(Ok(v)) => Ok(v),
6758 Ok(Err(e)) => Err(FlowOrError::Error(e)),
6759 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(StrykeError::runtime(
6760 format!(
6761 "eval_timeout: exceeded {} second(s) (worker continues in background)",
6762 secs
6763 ),
6764 line,
6765 )
6766 .into()),
6767 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(StrykeError::runtime(
6768 "eval_timeout: worker thread panicked or disconnected",
6769 line,
6770 )
6771 .into()),
6772 }
6773 }
6774
6775 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
6776 let mut last = StrykeValue::UNDEF;
6777 for stmt in body {
6778 match &stmt.kind {
6779 StmtKind::When { cond, body: wb } => {
6780 if self.when_matches(cond)? {
6781 return self.exec_block_smart(wb);
6782 }
6783 }
6784 StmtKind::DefaultCase { body: db } => {
6785 return self.exec_block_smart(db);
6786 }
6787 _ => {
6788 last = self.exec_statement(stmt)?;
6789 }
6790 }
6791 }
6792 Ok(last)
6793 }
6794
6795 pub(crate) fn exec_given_with_topic_value(
6797 &mut self,
6798 topic: StrykeValue,
6799 body: &Block,
6800 ) -> ExecResult {
6801 self.scope_push_hook();
6802 self.scope.declare_scalar("_", topic);
6803 self.english_note_lexical_scalar("_");
6804 let r = self.exec_given_body(body);
6805 self.scope_pop_hook();
6806 r
6807 }
6808
6809 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
6810 let t = self.eval_expr(topic)?;
6811 self.exec_given_with_topic_value(t, body)
6812 }
6813
6814 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6816 let topic = self.scope.get_scalar("_");
6817 let line = cond.line;
6818 match &cond.kind {
6819 ExprKind::Regex(pattern, flags) => {
6820 let re = self.compile_regex(pattern, flags, line)?;
6821 let s = topic.to_string();
6822 Ok(re.is_match(&s))
6823 }
6824 ExprKind::String(s) => Ok(topic.to_string() == *s),
6825 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
6826 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
6827 _ => {
6828 let c = self.eval_expr(cond)?;
6829 Ok(self.smartmatch_when(&topic, &c))
6830 }
6831 }
6832 }
6833
6834 fn smartmatch_when(&self, topic: &StrykeValue, c: &StrykeValue) -> bool {
6835 if let Some(re) = c.as_regex() {
6836 return re.is_match(&topic.to_string());
6837 }
6838 if let Some(arr) = c.as_array_ref() {
6845 let arr = arr.read();
6846 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
6847 }
6848 if let Some(arr) = c.as_array_vec() {
6849 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
6850 }
6851 if let Some(href) = c.as_hash_ref() {
6853 return href.read().contains_key(&topic.to_string());
6854 }
6855 if let Some(h) = c.as_hash_map() {
6856 return h.contains_key(&topic.to_string());
6857 }
6858 if let Some(sub) = c.as_code_ref() {
6860 let _ = sub;
6864 }
6865 if let (Some(a), Some(b)) = (topic.as_integer(), c.as_integer()) {
6867 return a == b;
6868 }
6869 topic.to_string() == c.to_string()
6870 }
6871
6872 pub(crate) fn eval_boolean_rvalue_condition(
6874 &mut self,
6875 cond: &Expr,
6876 ) -> Result<bool, FlowOrError> {
6877 match &cond.kind {
6878 ExprKind::Regex(pattern, flags) => {
6879 let topic = self.scope.get_scalar("_");
6880 let line = cond.line;
6881 let s = topic.to_string();
6882 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
6883 Ok(v.is_true())
6884 }
6885 ExprKind::ReadLine(_) => {
6887 let v = self.eval_expr(cond)?;
6888 self.scope.set_topic(v.clone());
6889 Ok(!v.is_undef())
6890 }
6891 _ => {
6892 let v = self.eval_expr(cond)?;
6893 Ok(v.is_true())
6894 }
6895 }
6896 }
6897
6898 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6900 self.eval_boolean_rvalue_condition(cond)
6901 }
6902
6903 pub(crate) fn eval_algebraic_match(
6904 &mut self,
6905 subject: &Expr,
6906 arms: &[MatchArm],
6907 line: usize,
6908 ) -> ExecResult {
6909 let val = self.eval_algebraic_match_subject(subject, line)?;
6910 self.eval_algebraic_match_with_subject_value(val, arms, line)
6911 }
6912
6913 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
6915 match &subject.kind {
6916 ExprKind::ArrayVar(name) => {
6917 self.check_strict_array_var(name, line)?;
6918 let aname = self.stash_array_name_for_package(name);
6919 Ok(StrykeValue::array_binding_ref(aname))
6920 }
6921 ExprKind::HashVar(name) => {
6922 self.check_strict_hash_var(name, line)?;
6923 self.touch_env_hash(name);
6924 Ok(StrykeValue::hash_binding_ref(name.clone()))
6925 }
6926 _ => self.eval_expr(subject),
6927 }
6928 }
6929
6930 pub(crate) fn eval_algebraic_match_with_subject_value(
6932 &mut self,
6933 val: StrykeValue,
6934 arms: &[MatchArm],
6935 line: usize,
6936 ) -> ExecResult {
6937 if let Some(e) = val.as_enum_inst() {
6939 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
6940 if !has_catchall {
6941 let covered: Vec<String> = arms
6942 .iter()
6943 .filter_map(|a| {
6944 if let MatchPattern::Value(expr) = &a.pattern {
6945 if let ExprKind::FuncCall { name, .. } = &expr.kind {
6946 return name.rsplit_once("::").map(|(_, v)| v.to_string());
6947 }
6948 }
6949 None
6950 })
6951 .collect();
6952 let missing: Vec<&str> = e
6953 .def
6954 .variants
6955 .iter()
6956 .filter(|v| !covered.contains(&v.name))
6957 .map(|v| v.name.as_str())
6958 .collect();
6959 if !missing.is_empty() {
6960 return Err(StrykeError::runtime(
6961 format!(
6962 "non-exhaustive match on enum `{}`: missing variant(s) {}",
6963 e.def.name,
6964 missing.join(", ")
6965 ),
6966 line,
6967 )
6968 .into());
6969 }
6970 }
6971 }
6972 for arm in arms {
6973 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
6974 let re = self.compile_regex(pattern, flags, line)?;
6975 let s = val.to_string();
6976 if let Some(caps) = re.captures(&s) {
6977 self.scope_push_hook();
6978 self.scope.declare_scalar("_", val.clone());
6979 self.english_note_lexical_scalar("_");
6980 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
6981 let guard_ok = if let Some(g) = &arm.guard {
6982 self.eval_expr(g)?.is_true()
6983 } else {
6984 true
6985 };
6986 if !guard_ok {
6987 self.scope_pop_hook();
6988 continue;
6989 }
6990 let out = self.eval_expr(&arm.body);
6991 self.scope_pop_hook();
6992 return out;
6993 }
6994 continue;
6995 }
6996 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
6997 self.scope_push_hook();
6998 self.scope.declare_scalar("_", val.clone());
6999 self.english_note_lexical_scalar("_");
7000 for b in bindings {
7001 match b {
7002 PatternBinding::Scalar(name, v) => {
7003 self.scope.declare_scalar(&name, v);
7004 self.english_note_lexical_scalar(&name);
7005 }
7006 PatternBinding::Array(name, elems) => {
7007 self.scope.declare_array(&name, elems);
7008 }
7009 }
7010 }
7011 let guard_ok = if let Some(g) = &arm.guard {
7012 self.eval_expr(g)?.is_true()
7013 } else {
7014 true
7015 };
7016 if !guard_ok {
7017 self.scope_pop_hook();
7018 continue;
7019 }
7020 let out = self.eval_expr(&arm.body);
7021 self.scope_pop_hook();
7022 return out;
7023 }
7024 }
7025 Err(StrykeError::runtime(
7026 "match: no arm matched the value (add a `_` catch-all)",
7027 line,
7028 )
7029 .into())
7030 }
7031
7032 fn parse_duration_seconds(pv: &StrykeValue) -> Option<f64> {
7033 let s = pv.to_string();
7034 let s = s.trim();
7035 if let Some(rest) = s.strip_suffix("ms") {
7036 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
7037 }
7038 if let Some(rest) = s.strip_suffix('s') {
7039 return rest.trim().parse::<f64>().ok();
7040 }
7041 if let Some(rest) = s.strip_suffix('m') {
7042 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
7043 }
7044 s.parse::<f64>().ok()
7045 }
7046
7047 fn eval_retry_block(
7048 &mut self,
7049 body: &Block,
7050 times: &Expr,
7051 backoff: RetryBackoff,
7052 _line: usize,
7053 ) -> ExecResult {
7054 let max = self.eval_expr(times)?.to_int().max(1) as usize;
7055 let base_ms: u64 = 10;
7056 let mut attempt = 0usize;
7057 loop {
7058 attempt += 1;
7059 match self.exec_block(body) {
7060 Ok(v) => return Ok(v),
7061 Err(FlowOrError::Error(e)) => {
7062 if attempt >= max {
7063 return Err(FlowOrError::Error(e));
7064 }
7065 let delay_ms = match backoff {
7066 RetryBackoff::None => 0,
7067 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
7068 RetryBackoff::Exponential => {
7069 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
7070 }
7071 };
7072 if delay_ms > 0 {
7073 std::thread::sleep(Duration::from_millis(delay_ms));
7074 }
7075 }
7076 Err(e) => return Err(e),
7077 }
7078 }
7079 }
7080
7081 fn eval_rate_limit_block(
7082 &mut self,
7083 slot: u32,
7084 max: &Expr,
7085 window: &Expr,
7086 body: &Block,
7087 _line: usize,
7088 ) -> ExecResult {
7089 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
7090 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
7091 .filter(|s| *s > 0.0)
7092 .unwrap_or(1.0);
7093 let window_d = Duration::from_secs_f64(window_sec);
7094 let slot = slot as usize;
7095 while self.rate_limit_slots.len() <= slot {
7096 self.rate_limit_slots.push(VecDeque::new());
7097 }
7098 {
7099 let dq = &mut self.rate_limit_slots[slot];
7100 loop {
7101 let now = Instant::now();
7102 while let Some(t0) = dq.front().copied() {
7103 if now.duration_since(t0) >= window_d {
7104 dq.pop_front();
7105 } else {
7106 break;
7107 }
7108 }
7109 if dq.len() < max_n || max_n == 0 {
7110 break;
7111 }
7112 let t0 = dq.front().copied().unwrap();
7113 let wait = window_d.saturating_sub(now.duration_since(t0));
7114 if wait.is_zero() {
7115 dq.pop_front();
7116 continue;
7117 }
7118 std::thread::sleep(wait);
7119 }
7120 dq.push_back(Instant::now());
7121 }
7122 self.exec_block(body)
7123 }
7124
7125 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
7126 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
7127 .filter(|s| *s > 0.0)
7128 .unwrap_or(1.0);
7129 loop {
7130 match self.exec_block(body) {
7131 Ok(_) => {}
7132 Err(e) => return Err(e),
7133 }
7134 std::thread::sleep(Duration::from_secs_f64(sec));
7135 }
7136 }
7137
7138 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> StrykeResult<StrykeValue> {
7140 let pair = |value: StrykeValue, more: i64| {
7141 StrykeValue::array_ref(Arc::new(RwLock::new(vec![
7142 value,
7143 StrykeValue::integer(more),
7144 ])))
7145 };
7146 let mut exhausted = gen.exhausted.lock();
7147 if *exhausted {
7148 return Ok(pair(StrykeValue::UNDEF, 0));
7149 }
7150 let mut pc = gen.pc.lock();
7151 let mut scope_started = gen.scope_started.lock();
7152 if *pc >= gen.block.len() {
7153 if *scope_started {
7154 self.scope_pop_hook();
7155 *scope_started = false;
7156 }
7157 *exhausted = true;
7158 return Ok(pair(StrykeValue::UNDEF, 0));
7159 }
7160 if !*scope_started {
7161 self.scope_push_hook();
7162 *scope_started = true;
7163 }
7164 self.in_generator = true;
7165 while *pc < gen.block.len() {
7166 let stmt = &gen.block[*pc];
7167 match self.exec_statement(stmt) {
7168 Ok(_) => {
7169 *pc += 1;
7170 }
7171 Err(FlowOrError::Flow(Flow::Yield(v))) => {
7172 *pc += 1;
7173 self.in_generator = false;
7174 if *scope_started {
7177 self.scope_pop_hook();
7178 *scope_started = false;
7179 }
7180 return Ok(pair(v, 1));
7181 }
7182 Err(e) => {
7183 self.in_generator = false;
7184 if *scope_started {
7185 self.scope_pop_hook();
7186 *scope_started = false;
7187 }
7188 return Err(match e {
7189 FlowOrError::Error(ee) => ee,
7190 FlowOrError::Flow(Flow::Yield(_)) => {
7191 unreachable!("yield handled above")
7192 }
7193 FlowOrError::Flow(flow) => StrykeError::runtime(
7194 format!("unexpected control flow in generator: {:?}", flow),
7195 0,
7196 ),
7197 });
7198 }
7199 }
7200 }
7201 self.in_generator = false;
7202 if *scope_started {
7203 self.scope_pop_hook();
7204 *scope_started = false;
7205 }
7206 *exhausted = true;
7207 Ok(pair(StrykeValue::UNDEF, 0))
7208 }
7209
7210 fn match_pattern_try(
7211 &mut self,
7212 subject: &StrykeValue,
7213 pattern: &MatchPattern,
7214 line: usize,
7215 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7216 match pattern {
7217 MatchPattern::Any => Ok(Some(vec![])),
7218 MatchPattern::Regex { .. } => {
7219 unreachable!("regex arms are handled in eval_algebraic_match")
7220 }
7221 MatchPattern::Value(expr) => {
7222 if self.match_pattern_value_alternation(subject, expr, line)? {
7223 Ok(Some(vec![]))
7224 } else {
7225 Ok(None)
7226 }
7227 }
7228 MatchPattern::Array(elems) => {
7229 let Some(arr) = self.match_subject_as_array(subject) else {
7230 return Ok(None);
7231 };
7232 self.match_array_pattern_elems(&arr, elems, line)
7233 }
7234 MatchPattern::Hash(pairs) => {
7235 let Some(h) = self.match_subject_as_hash(subject) else {
7236 return Ok(None);
7237 };
7238 self.match_hash_pattern_pairs(&h, pairs, line)
7239 }
7240 MatchPattern::OptionSome(name) => {
7241 let Some(arr) = self.match_subject_as_array(subject) else {
7242 return Ok(None);
7243 };
7244 if arr.len() < 2 {
7245 return Ok(None);
7246 }
7247 if !arr[1].is_true() {
7248 return Ok(None);
7249 }
7250 Ok(Some(vec![PatternBinding::Scalar(
7251 name.clone(),
7252 arr[0].clone(),
7253 )]))
7254 }
7255 }
7256 }
7257
7258 fn match_pattern_value_alternation(
7261 &mut self,
7262 subject: &StrykeValue,
7263 expr: &Expr,
7264 _line: usize,
7265 ) -> Result<bool, FlowOrError> {
7266 if let ExprKind::BinOp {
7267 left,
7268 op: BinOp::BitOr,
7269 right,
7270 } = &expr.kind
7271 {
7272 if self.match_pattern_value_alternation(subject, left, _line)? {
7273 return Ok(true);
7274 }
7275 return self.match_pattern_value_alternation(subject, right, _line);
7276 }
7277 let pv = self.eval_expr(expr)?;
7278 Ok(self.smartmatch_when(subject, &pv))
7279 }
7280
7281 fn match_subject_as_array(&self, v: &StrykeValue) -> Option<Vec<StrykeValue>> {
7283 if let Some(a) = v.as_array_vec() {
7284 return Some(a);
7285 }
7286 if let Some(r) = v.as_array_ref() {
7287 return Some(r.read().clone());
7288 }
7289 if let Some(name) = v.as_array_binding_name() {
7290 return Some(self.scope.get_array(&name));
7291 }
7292 None
7293 }
7294
7295 fn match_subject_as_hash(&mut self, v: &StrykeValue) -> Option<IndexMap<String, StrykeValue>> {
7296 if let Some(h) = v.as_hash_map() {
7297 return Some(h);
7298 }
7299 if let Some(r) = v.as_hash_ref() {
7300 return Some(r.read().clone());
7301 }
7302 if let Some(name) = v.as_hash_binding_name() {
7303 self.touch_env_hash(&name);
7304 return Some(self.scope.get_hash(&name));
7305 }
7306 None
7307 }
7308
7309 pub(crate) fn hash_slice_deref_values(
7312 &mut self,
7313 container: &StrykeValue,
7314 key_values: &[StrykeValue],
7315 line: usize,
7316 ) -> Result<StrykeValue, FlowOrError> {
7317 let h = if let Some(m) = self.match_subject_as_hash(container) {
7318 m
7319 } else {
7320 return Err(StrykeError::runtime(
7321 "Hash slice dereference needs a hash or hash reference value",
7322 line,
7323 )
7324 .into());
7325 };
7326 let mut result = Vec::new();
7327 for kv in key_values {
7328 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7329 vv.iter().map(|x| x.to_string()).collect()
7330 } else {
7331 vec![kv.to_string()]
7332 };
7333 for k in key_strings {
7334 result.push(h.get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
7335 }
7336 }
7337 Ok(StrykeValue::array(result))
7338 }
7339
7340 pub(crate) fn assign_hash_slice_one_key(
7343 &mut self,
7344 container: StrykeValue,
7345 key: &str,
7346 val: StrykeValue,
7347 line: usize,
7348 ) -> Result<StrykeValue, FlowOrError> {
7349 if let Some(r) = container.as_hash_ref() {
7350 r.write().insert(key.to_string(), val);
7351 return Ok(StrykeValue::UNDEF);
7352 }
7353 if let Some(name) = container.as_hash_binding_name() {
7354 self.touch_env_hash(&name);
7355 self.scope
7356 .set_hash_element(&name, key, val)
7357 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7358 return Ok(StrykeValue::UNDEF);
7359 }
7360 if let Some(s) = container.as_str() {
7361 self.touch_env_hash(&s);
7362 if self.strict_refs {
7363 return Err(StrykeError::runtime(
7364 format!(
7365 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7366 s
7367 ),
7368 line,
7369 )
7370 .into());
7371 }
7372 self.scope
7373 .set_hash_element(&s, key, val)
7374 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7375 return Ok(StrykeValue::UNDEF);
7376 }
7377 Err(StrykeError::runtime(
7378 "Hash slice assignment needs a hash or hash reference value",
7379 line,
7380 )
7381 .into())
7382 }
7383
7384 pub(crate) fn assign_named_hash_slice(
7387 &mut self,
7388 hash: &str,
7389 key_values: Vec<StrykeValue>,
7390 val: StrykeValue,
7391 line: usize,
7392 ) -> Result<StrykeValue, FlowOrError> {
7393 self.touch_env_hash(hash);
7394 let mut ks: Vec<String> = Vec::new();
7395 for kv in key_values {
7396 if let Some(vv) = kv.as_array_vec() {
7397 ks.extend(vv.iter().map(|x| x.to_string()));
7398 } else {
7399 ks.push(kv.to_string());
7400 }
7401 }
7402 if ks.is_empty() {
7403 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7404 }
7405 let items = val.to_list();
7406 for (i, k) in ks.iter().enumerate() {
7407 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7408 self.scope
7409 .set_hash_element(hash, k, v)
7410 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7411 }
7412 Ok(StrykeValue::UNDEF)
7413 }
7414
7415 pub(crate) fn assign_hash_slice_deref(
7417 &mut self,
7418 container: StrykeValue,
7419 key_values: Vec<StrykeValue>,
7420 val: StrykeValue,
7421 line: usize,
7422 ) -> Result<StrykeValue, FlowOrError> {
7423 let mut ks: Vec<String> = Vec::new();
7424 for kv in key_values {
7425 if let Some(vv) = kv.as_array_vec() {
7426 ks.extend(vv.iter().map(|x| x.to_string()));
7427 } else {
7428 ks.push(kv.to_string());
7429 }
7430 }
7431 if ks.is_empty() {
7432 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7433 }
7434 let items = val.to_list();
7435 if let Some(r) = container.as_hash_ref() {
7436 let mut h = r.write();
7437 for (i, k) in ks.iter().enumerate() {
7438 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7439 h.insert(k.clone(), v);
7440 }
7441 return Ok(StrykeValue::UNDEF);
7442 }
7443 if let Some(name) = container.as_hash_binding_name() {
7444 self.touch_env_hash(&name);
7445 for (i, k) in ks.iter().enumerate() {
7446 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7447 self.scope
7448 .set_hash_element(&name, k, v)
7449 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7450 }
7451 return Ok(StrykeValue::UNDEF);
7452 }
7453 if let Some(s) = container.as_str() {
7454 if self.strict_refs {
7455 return Err(StrykeError::runtime(
7456 format!(
7457 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7458 s
7459 ),
7460 line,
7461 )
7462 .into());
7463 }
7464 self.touch_env_hash(&s);
7465 for (i, k) in ks.iter().enumerate() {
7466 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7467 self.scope
7468 .set_hash_element(&s, k, v)
7469 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7470 }
7471 return Ok(StrykeValue::UNDEF);
7472 }
7473 Err(StrykeError::runtime(
7474 "Hash slice assignment needs a hash or hash reference value",
7475 line,
7476 )
7477 .into())
7478 }
7479
7480 pub(crate) fn compound_assign_hash_slice_deref(
7483 &mut self,
7484 container: StrykeValue,
7485 key_values: Vec<StrykeValue>,
7486 op: BinOp,
7487 rhs: StrykeValue,
7488 line: usize,
7489 ) -> Result<StrykeValue, FlowOrError> {
7490 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7491 let last_old = old_list
7492 .to_list()
7493 .last()
7494 .cloned()
7495 .unwrap_or(StrykeValue::UNDEF);
7496 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7497 let mut ks: Vec<String> = Vec::new();
7498 for kv in &key_values {
7499 if let Some(vv) = kv.as_array_vec() {
7500 ks.extend(vv.iter().map(|x| x.to_string()));
7501 } else {
7502 ks.push(kv.to_string());
7503 }
7504 }
7505 if ks.is_empty() {
7506 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7507 }
7508 let last_key = ks.last().expect("non-empty ks");
7509 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7510 Ok(new_val)
7511 }
7512
7513 pub(crate) fn hash_slice_deref_inc_dec(
7519 &mut self,
7520 container: StrykeValue,
7521 key_values: Vec<StrykeValue>,
7522 kind: u8,
7523 line: usize,
7524 ) -> Result<StrykeValue, FlowOrError> {
7525 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7526 let last_old = old_list
7527 .to_list()
7528 .last()
7529 .cloned()
7530 .unwrap_or(StrykeValue::UNDEF);
7531 let new_val = if kind & 1 == 0 {
7532 StrykeValue::integer(last_old.to_int() + 1)
7533 } else {
7534 StrykeValue::integer(last_old.to_int() - 1)
7535 };
7536 let mut ks: Vec<String> = Vec::new();
7537 for kv in &key_values {
7538 if let Some(vv) = kv.as_array_vec() {
7539 ks.extend(vv.iter().map(|x| x.to_string()));
7540 } else {
7541 ks.push(kv.to_string());
7542 }
7543 }
7544 let last_key = ks.last().ok_or_else(|| {
7545 StrykeError::runtime("Hash slice increment needs at least one key", line)
7546 })?;
7547 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7548 Ok(if kind < 2 { new_val } else { last_old })
7549 }
7550
7551 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[StrykeValue]) -> StrykeValue {
7552 self.touch_env_hash(hash);
7553 let h = self.scope.get_hash(hash);
7554 let mut result = Vec::new();
7555 for kv in key_values {
7556 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7557 vv.iter().map(|x| x.to_string()).collect()
7558 } else {
7559 vec![kv.to_string()]
7560 };
7561 for k in key_strings {
7562 result.push(h.get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
7563 }
7564 }
7565 StrykeValue::array(result)
7566 }
7567
7568 pub(crate) fn compound_assign_named_hash_slice(
7570 &mut self,
7571 hash: &str,
7572 key_values: Vec<StrykeValue>,
7573 op: BinOp,
7574 rhs: StrykeValue,
7575 line: usize,
7576 ) -> Result<StrykeValue, FlowOrError> {
7577 let old_list = self.hash_slice_named_values(hash, &key_values);
7578 let last_old = old_list
7579 .to_list()
7580 .last()
7581 .cloned()
7582 .unwrap_or(StrykeValue::UNDEF);
7583 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7584 let mut ks: Vec<String> = Vec::new();
7585 for kv in &key_values {
7586 if let Some(vv) = kv.as_array_vec() {
7587 ks.extend(vv.iter().map(|x| x.to_string()));
7588 } else {
7589 ks.push(kv.to_string());
7590 }
7591 }
7592 if ks.is_empty() {
7593 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7594 }
7595 let last_key = ks.last().expect("non-empty ks");
7596 let container = StrykeValue::string(hash.to_string());
7597 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7598 Ok(new_val)
7599 }
7600
7601 pub(crate) fn named_hash_slice_inc_dec(
7603 &mut self,
7604 hash: &str,
7605 key_values: Vec<StrykeValue>,
7606 kind: u8,
7607 line: usize,
7608 ) -> Result<StrykeValue, FlowOrError> {
7609 let old_list = self.hash_slice_named_values(hash, &key_values);
7610 let last_old = old_list
7611 .to_list()
7612 .last()
7613 .cloned()
7614 .unwrap_or(StrykeValue::UNDEF);
7615 let new_val = if kind & 1 == 0 {
7616 StrykeValue::integer(last_old.to_int() + 1)
7617 } else {
7618 StrykeValue::integer(last_old.to_int() - 1)
7619 };
7620 let mut ks: Vec<String> = Vec::new();
7621 for kv in &key_values {
7622 if let Some(vv) = kv.as_array_vec() {
7623 ks.extend(vv.iter().map(|x| x.to_string()));
7624 } else {
7625 ks.push(kv.to_string());
7626 }
7627 }
7628 let last_key = ks.last().ok_or_else(|| {
7629 StrykeError::runtime("Hash slice increment needs at least one key", line)
7630 })?;
7631 let container = StrykeValue::string(hash.to_string());
7632 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7633 Ok(if kind < 2 { new_val } else { last_old })
7634 }
7635
7636 fn match_array_pattern_elems(
7637 &mut self,
7638 arr: &[StrykeValue],
7639 elems: &[MatchArrayElem],
7640 line: usize,
7641 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7642 let has_rest = elems
7643 .iter()
7644 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
7645 let mut binds: Vec<PatternBinding> = Vec::new();
7646 let mut idx = 0usize;
7647 for (i, elem) in elems.iter().enumerate() {
7648 match elem {
7649 MatchArrayElem::Rest => {
7650 if i != elems.len() - 1 {
7651 return Err(StrykeError::runtime(
7652 "internal: `*` must be last in array match pattern",
7653 line,
7654 )
7655 .into());
7656 }
7657 return Ok(Some(binds));
7658 }
7659 MatchArrayElem::RestBind(name) => {
7660 if i != elems.len() - 1 {
7661 return Err(StrykeError::runtime(
7662 "internal: `@name` rest bind must be last in array match pattern",
7663 line,
7664 )
7665 .into());
7666 }
7667 let tail = arr[idx..].to_vec();
7668 binds.push(PatternBinding::Array(name.clone(), tail));
7669 return Ok(Some(binds));
7670 }
7671 MatchArrayElem::CaptureScalar(name) => {
7672 if idx >= arr.len() {
7673 return Ok(None);
7674 }
7675 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
7676 idx += 1;
7677 }
7678 MatchArrayElem::Expr(e) => {
7679 if idx >= arr.len() {
7680 return Ok(None);
7681 }
7682 let expected = self.eval_expr(e)?;
7683 if !self.smartmatch_when(&arr[idx], &expected) {
7684 return Ok(None);
7685 }
7686 idx += 1;
7687 }
7688 }
7689 }
7690 if !has_rest && idx != arr.len() {
7691 return Ok(None);
7692 }
7693 Ok(Some(binds))
7694 }
7695
7696 fn match_hash_pattern_pairs(
7697 &mut self,
7698 h: &IndexMap<String, StrykeValue>,
7699 pairs: &[MatchHashPair],
7700 _line: usize,
7701 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7702 let mut binds = Vec::new();
7703 for pair in pairs {
7704 match pair {
7705 MatchHashPair::KeyOnly { key } => {
7706 let ks = self.eval_expr(key)?.to_string();
7707 if !h.contains_key(&ks) {
7708 return Ok(None);
7709 }
7710 }
7711 MatchHashPair::Capture { key, name } => {
7712 let ks = self.eval_expr(key)?.to_string();
7713 let Some(v) = h.get(&ks) else {
7714 return Ok(None);
7715 };
7716 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
7717 }
7718 }
7719 }
7720 Ok(Some(binds))
7721 }
7722
7723 #[inline]
7725 fn block_needs_scope(block: &Block) -> bool {
7726 block.iter().any(|s| match &s.kind {
7727 StmtKind::My(_)
7728 | StmtKind::Our(_)
7729 | StmtKind::Local(_)
7730 | StmtKind::State(_)
7731 | StmtKind::LocalExpr { .. } => true,
7732 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
7733 _ => false,
7734 })
7735 }
7736
7737 #[inline]
7739 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
7740 if Self::block_needs_scope(block) {
7741 self.exec_block(block)
7742 } else {
7743 self.exec_block_no_scope(block)
7744 }
7745 }
7746
7747 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
7748 let t0 = self.profiler.is_some().then(std::time::Instant::now);
7749 let r = self.exec_statement_inner(stmt);
7750 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
7751 prof.on_line(&self.file, stmt.line, t0.elapsed());
7752 }
7753 r
7754 }
7755
7756 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
7757 if let Err(e) = crate::perl_signal::poll(self) {
7758 return Err(FlowOrError::Error(e));
7759 }
7760 if let Err(e) = self.drain_pending_destroys(stmt.line) {
7761 return Err(FlowOrError::Error(e));
7762 }
7763 match &stmt.kind {
7764 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
7765 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
7766 StmtKind::If {
7767 condition,
7768 body,
7769 elsifs,
7770 else_block,
7771 } => {
7772 if self.eval_boolean_rvalue_condition(condition)? {
7773 return self.exec_block(body);
7774 }
7775 for (c, b) in elsifs {
7776 if self.eval_boolean_rvalue_condition(c)? {
7777 return self.exec_block(b);
7778 }
7779 }
7780 if let Some(eb) = else_block {
7781 return self.exec_block(eb);
7782 }
7783 Ok(StrykeValue::UNDEF)
7784 }
7785 StmtKind::Unless {
7786 condition,
7787 body,
7788 else_block,
7789 } => {
7790 if !self.eval_boolean_rvalue_condition(condition)? {
7791 return self.exec_block(body);
7792 }
7793 if let Some(eb) = else_block {
7794 return self.exec_block(eb);
7795 }
7796 Ok(StrykeValue::UNDEF)
7797 }
7798 StmtKind::While {
7799 condition,
7800 body,
7801 label,
7802 continue_block,
7803 } => {
7804 'outer: loop {
7805 if !self.eval_boolean_rvalue_condition(condition)? {
7806 break;
7807 }
7808 'inner: loop {
7809 match self.exec_block_smart(body) {
7810 Ok(_) => break 'inner,
7811 Err(FlowOrError::Flow(Flow::Last(ref l)))
7812 if l == label || l.is_none() =>
7813 {
7814 break 'outer;
7815 }
7816 Err(FlowOrError::Flow(Flow::Next(ref l)))
7817 if l == label || l.is_none() =>
7818 {
7819 if let Some(cb) = continue_block {
7820 let _ = self.exec_block_smart(cb);
7821 }
7822 continue 'outer;
7823 }
7824 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7825 if l == label || l.is_none() =>
7826 {
7827 continue 'inner;
7828 }
7829 Err(e) => return Err(e),
7830 }
7831 }
7832 if let Some(cb) = continue_block {
7833 let _ = self.exec_block_smart(cb);
7834 }
7835 }
7836 Ok(StrykeValue::UNDEF)
7837 }
7838 StmtKind::Until {
7839 condition,
7840 body,
7841 label,
7842 continue_block,
7843 } => {
7844 'outer: loop {
7845 if self.eval_boolean_rvalue_condition(condition)? {
7846 break;
7847 }
7848 'inner: loop {
7849 match self.exec_block(body) {
7850 Ok(_) => break 'inner,
7851 Err(FlowOrError::Flow(Flow::Last(ref l)))
7852 if l == label || l.is_none() =>
7853 {
7854 break 'outer;
7855 }
7856 Err(FlowOrError::Flow(Flow::Next(ref l)))
7857 if l == label || l.is_none() =>
7858 {
7859 if let Some(cb) = continue_block {
7860 let _ = self.exec_block_smart(cb);
7861 }
7862 continue 'outer;
7863 }
7864 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7865 if l == label || l.is_none() =>
7866 {
7867 continue 'inner;
7868 }
7869 Err(e) => return Err(e),
7870 }
7871 }
7872 if let Some(cb) = continue_block {
7873 let _ = self.exec_block_smart(cb);
7874 }
7875 }
7876 Ok(StrykeValue::UNDEF)
7877 }
7878 StmtKind::DoWhile { body, condition } => {
7879 loop {
7880 self.exec_block(body)?;
7881 if !self.eval_boolean_rvalue_condition(condition)? {
7882 break;
7883 }
7884 }
7885 Ok(StrykeValue::UNDEF)
7886 }
7887 StmtKind::For {
7888 init,
7889 condition,
7890 step,
7891 body,
7892 label,
7893 continue_block,
7894 } => {
7895 self.scope_push_hook();
7896 if let Some(init) = init {
7897 self.exec_statement(init)?;
7898 }
7899 'outer: loop {
7900 if let Some(cond) = condition {
7901 if !self.eval_boolean_rvalue_condition(cond)? {
7902 break;
7903 }
7904 }
7905 'inner: loop {
7906 match self.exec_block_smart(body) {
7907 Ok(_) => break 'inner,
7908 Err(FlowOrError::Flow(Flow::Last(ref l)))
7909 if l == label || l.is_none() =>
7910 {
7911 break 'outer;
7912 }
7913 Err(FlowOrError::Flow(Flow::Next(ref l)))
7914 if l == label || l.is_none() =>
7915 {
7916 if let Some(cb) = continue_block {
7917 let _ = self.exec_block_smart(cb);
7918 }
7919 if let Some(step) = step {
7920 self.eval_expr(step)?;
7921 }
7922 continue 'outer;
7923 }
7924 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7925 if l == label || l.is_none() =>
7926 {
7927 continue 'inner;
7928 }
7929 Err(e) => {
7930 self.scope_pop_hook();
7931 return Err(e);
7932 }
7933 }
7934 }
7935 if let Some(cb) = continue_block {
7936 let _ = self.exec_block_smart(cb);
7937 }
7938 if let Some(step) = step {
7939 self.eval_expr(step)?;
7940 }
7941 }
7942 self.scope_pop_hook();
7943 Ok(StrykeValue::UNDEF)
7944 }
7945 StmtKind::Foreach {
7946 var,
7947 list,
7948 body,
7949 label,
7950 continue_block,
7951 } => {
7952 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
7953 let items = list_val.to_list();
7954 self.scope_push_hook();
7955 self.scope.declare_scalar(var, StrykeValue::UNDEF);
7956 self.english_note_lexical_scalar(var);
7957 let mut i = 0usize;
7958 'outer: while i < items.len() {
7959 if var == "_" {
7966 self.scope.set_topic(items[i].clone());
7967 } else {
7968 self.scope
7969 .set_scalar(var, items[i].clone())
7970 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
7971 }
7972 'inner: loop {
7973 match self.exec_block_smart(body) {
7974 Ok(_) => break 'inner,
7975 Err(FlowOrError::Flow(Flow::Last(ref l)))
7976 if l == label || l.is_none() =>
7977 {
7978 break 'outer;
7979 }
7980 Err(FlowOrError::Flow(Flow::Next(ref l)))
7981 if l == label || l.is_none() =>
7982 {
7983 if let Some(cb) = continue_block {
7984 let _ = self.exec_block_smart(cb);
7985 }
7986 i += 1;
7987 continue 'outer;
7988 }
7989 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7990 if l == label || l.is_none() =>
7991 {
7992 continue 'inner;
7993 }
7994 Err(e) => {
7995 self.scope_pop_hook();
7996 return Err(e);
7997 }
7998 }
7999 }
8000 if let Some(cb) = continue_block {
8001 let _ = self.exec_block_smart(cb);
8002 }
8003 i += 1;
8004 }
8005 self.scope_pop_hook();
8006 Ok(StrykeValue::UNDEF)
8007 }
8008 StmtKind::SubDecl {
8009 name,
8010 params,
8011 body,
8012 prototype,
8013 } => {
8014 let key = self.qualify_sub_key(name);
8015 let captured = self.scope.capture();
8016 let closure_env = if captured.is_empty() {
8017 None
8018 } else {
8019 Some(captured)
8020 };
8021 let mut sub = StrykeSub {
8022 name: name.clone(),
8023 params: params.clone(),
8024 body: body.clone(),
8025 closure_env,
8026 prototype: prototype.clone(),
8027 fib_like: None,
8028 };
8029 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
8030 self.subs.insert(key, Arc::new(sub));
8031 Ok(StrykeValue::UNDEF)
8032 }
8033 StmtKind::StructDecl { def } => {
8034 if self.struct_defs.contains_key(&def.name) {
8035 return Err(StrykeError::runtime(
8036 format!("duplicate struct `{}`", def.name),
8037 stmt.line,
8038 )
8039 .into());
8040 }
8041 self.struct_defs
8042 .insert(def.name.clone(), Arc::new(def.clone()));
8043 Ok(StrykeValue::UNDEF)
8044 }
8045 StmtKind::EnumDecl { def } => {
8046 if self.enum_defs.contains_key(&def.name) {
8047 return Err(StrykeError::runtime(
8048 format!("duplicate enum `{}`", def.name),
8049 stmt.line,
8050 )
8051 .into());
8052 }
8053 self.enum_defs
8054 .insert(def.name.clone(), Arc::new(def.clone()));
8055 Ok(StrykeValue::UNDEF)
8056 }
8057 StmtKind::ClassDecl { def } => {
8058 if self.class_defs.contains_key(&def.name) {
8059 return Err(StrykeError::runtime(
8060 format!("duplicate class `{}`", def.name),
8061 stmt.line,
8062 )
8063 .into());
8064 }
8065 for parent_name in &def.extends {
8067 if let Some(parent_def) = self.class_defs.get(parent_name) {
8068 if parent_def.is_final {
8069 return Err(StrykeError::runtime(
8070 format!("cannot extend final class `{}`", parent_name),
8071 stmt.line,
8072 )
8073 .into());
8074 }
8075 for m in &def.methods {
8077 if let Some(parent_method) = parent_def.method(&m.name) {
8078 if parent_method.is_final {
8079 return Err(StrykeError::runtime(
8080 format!(
8081 "cannot override final method `{}` from class `{}`",
8082 m.name, parent_name
8083 ),
8084 stmt.line,
8085 )
8086 .into());
8087 }
8088 }
8089 }
8090 }
8091 }
8092 let mut def = def.clone();
8094 for trait_name in &def.implements.clone() {
8095 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
8096 for required in trait_def.required_methods() {
8097 let has_method = def.methods.iter().any(|m| m.name == required.name);
8098 if !has_method {
8099 return Err(StrykeError::runtime(
8100 format!(
8101 "class `{}` implements trait `{}` but does not define required method `{}`",
8102 def.name, trait_name, required.name
8103 ),
8104 stmt.line,
8105 )
8106 .into());
8107 }
8108 }
8109 for tm in &trait_def.methods {
8111 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
8112 def.methods.push(tm.clone());
8113 }
8114 }
8115 }
8116 }
8117 if !def.is_abstract {
8120 for parent_name in &def.extends.clone() {
8121 if let Some(parent_def) = self.class_defs.get(parent_name) {
8122 if parent_def.is_abstract {
8123 for m in &parent_def.methods {
8124 if m.body.is_none()
8125 && !def.methods.iter().any(|dm| dm.name == m.name)
8126 {
8127 return Err(StrykeError::runtime(
8128 format!(
8129 "class `{}` must implement abstract method `{}` from `{}`",
8130 def.name, m.name, parent_name
8131 ),
8132 stmt.line,
8133 )
8134 .into());
8135 }
8136 }
8137 }
8138 }
8139 }
8140 }
8141 for sf in &def.static_fields {
8143 let val = if let Some(ref expr) = sf.default {
8144 self.eval_expr(expr)?
8145 } else {
8146 StrykeValue::UNDEF
8147 };
8148 let key = format!("{}::{}", def.name, sf.name);
8149 self.scope.declare_scalar(&key, val);
8150 }
8151 for m in &def.methods {
8153 if let Some(ref body) = m.body {
8154 let fq = format!("{}::{}", def.name, m.name);
8155 let sub = Arc::new(StrykeSub {
8156 name: fq.clone(),
8157 params: m.params.clone(),
8158 body: body.clone(),
8159 closure_env: None,
8160 prototype: None,
8161 fib_like: None,
8162 });
8163 self.subs.insert(fq, sub);
8164 }
8165 }
8166 if !def.extends.is_empty() {
8168 let isa_key = format!("{}::ISA", def.name);
8169 let parents: Vec<StrykeValue> = def
8170 .extends
8171 .iter()
8172 .map(|p| StrykeValue::string(p.clone()))
8173 .collect();
8174 self.scope.declare_array(&isa_key, parents);
8175 }
8176 let arc_def = Arc::new(def);
8177 self.class_defs
8178 .insert(arc_def.name.clone(), Arc::clone(&arc_def));
8179 crate::serialize_normalize::register_class_def(arc_def);
8183 Ok(StrykeValue::UNDEF)
8184 }
8185 StmtKind::TraitDecl { def } => {
8186 if self.trait_defs.contains_key(&def.name) {
8187 return Err(StrykeError::runtime(
8188 format!("duplicate trait `{}`", def.name),
8189 stmt.line,
8190 )
8191 .into());
8192 }
8193 self.trait_defs
8194 .insert(def.name.clone(), Arc::new(def.clone()));
8195 Ok(StrykeValue::UNDEF)
8196 }
8197 StmtKind::My(decls) | StmtKind::Our(decls) => {
8198 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
8199 if decls.len() > 1 && decls[0].initializer.is_some() {
8202 let val = self.eval_expr_ctx(
8203 decls[0].initializer.as_ref().unwrap(),
8204 WantarrayCtx::List,
8205 )?;
8206 let items = val.to_list();
8207 let mut idx = 0;
8208 for decl in decls {
8209 match decl.sigil {
8210 Sigil::Scalar => {
8211 let v = items.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
8212 let skey = if is_our {
8213 self.stash_scalar_name_for_package(&decl.name)
8214 } else {
8215 decl.name.clone()
8216 };
8217 self.scope.declare_scalar_frozen(
8218 &skey,
8219 v,
8220 decl.frozen,
8221 decl.type_annotation.clone(),
8222 )?;
8223 self.english_note_lexical_scalar(&decl.name);
8224 if is_our {
8225 self.note_our_scalar(&decl.name);
8226 }
8227 idx += 1;
8228 }
8229 Sigil::Array => {
8230 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8232 idx = items.len();
8233 if is_our {
8234 self.record_exporter_our_array_name(&decl.name, &rest);
8235 }
8236 let aname = if is_our {
8237 self.stash_array_full_name_for_package(&decl.name)
8238 } else {
8239 self.stash_array_name_for_package(&decl.name)
8240 };
8241 self.scope.declare_array(&aname, rest);
8242 if is_our {
8243 self.note_our_array(&decl.name);
8244 }
8245 }
8246 Sigil::Hash => {
8247 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8248 idx = items.len();
8249 let mut map = IndexMap::new();
8250 let mut i = 0;
8251 while i + 1 < rest.len() {
8252 map.insert(rest[i].to_string(), rest[i + 1].clone());
8253 i += 2;
8254 }
8255 let hname = if is_our {
8256 self.stash_hash_full_name_for_package(&decl.name)
8257 } else {
8258 decl.name.clone()
8259 };
8260 self.scope.declare_hash(&hname, map);
8261 if is_our {
8262 self.note_our_hash(&decl.name);
8263 }
8264 }
8265 Sigil::Typeglob => {
8266 return Err(StrykeError::runtime(
8267 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
8268 stmt.line,
8269 )
8270 .into());
8271 }
8272 }
8273 }
8274 } else {
8275 for decl in decls {
8277 let compound_init = decl
8281 .initializer
8282 .as_ref()
8283 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
8284
8285 if compound_init {
8286 match decl.sigil {
8287 Sigil::Typeglob => {
8288 return Err(StrykeError::runtime(
8289 "compound assignment on typeglob declaration is not supported",
8290 stmt.line,
8291 )
8292 .into());
8293 }
8294 Sigil::Scalar => {
8295 let skey = if is_our {
8296 self.stash_scalar_name_for_package(&decl.name)
8297 } else {
8298 decl.name.clone()
8299 };
8300 self.scope.declare_scalar_frozen(
8301 &skey,
8302 StrykeValue::UNDEF,
8303 decl.frozen,
8304 decl.type_annotation.clone(),
8305 )?;
8306 self.english_note_lexical_scalar(&decl.name);
8307 if is_our {
8308 self.note_our_scalar(&decl.name);
8309 }
8310 let init = decl.initializer.as_ref().unwrap();
8311 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8312 }
8313 Sigil::Array => {
8314 let aname = if is_our {
8315 self.stash_array_full_name_for_package(&decl.name)
8316 } else {
8317 self.stash_array_name_for_package(&decl.name)
8318 };
8319 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
8320 if is_our {
8321 self.note_our_array(&decl.name);
8322 }
8323 let init = decl.initializer.as_ref().unwrap();
8324 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8325 if is_our {
8326 let items = self.scope.get_array(&aname);
8327 self.record_exporter_our_array_name(&decl.name, &items);
8328 }
8329 }
8330 Sigil::Hash => {
8331 let hname = if is_our {
8332 self.stash_hash_full_name_for_package(&decl.name)
8333 } else {
8334 decl.name.clone()
8335 };
8336 self.scope.declare_hash_frozen(
8337 &hname,
8338 IndexMap::new(),
8339 decl.frozen,
8340 );
8341 if is_our {
8342 self.note_our_hash(&decl.name);
8343 }
8344 let init = decl.initializer.as_ref().unwrap();
8345 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8346 }
8347 }
8348 continue;
8349 }
8350
8351 let val = if let Some(init) = &decl.initializer {
8352 let ctx = match decl.sigil {
8353 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8354 Sigil::Scalar if decl.list_context => WantarrayCtx::List,
8355 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8356 };
8357 let v = self.eval_expr_ctx(init, ctx)?;
8358 if decl.sigil == Sigil::Scalar && decl.list_context {
8360 v.to_list().first().cloned().unwrap_or(StrykeValue::UNDEF)
8361 } else {
8362 v
8363 }
8364 } else {
8365 StrykeValue::UNDEF
8366 };
8367 match decl.sigil {
8368 Sigil::Typeglob => {
8369 return Err(StrykeError::runtime(
8370 "`my *FH` / typeglob declaration is not supported",
8371 stmt.line,
8372 )
8373 .into());
8374 }
8375 Sigil::Scalar => {
8376 let skey = if is_our {
8377 self.stash_scalar_name_for_package(&decl.name)
8378 } else {
8379 decl.name.clone()
8380 };
8381 self.scope.declare_scalar_frozen(
8382 &skey,
8383 val,
8384 decl.frozen,
8385 decl.type_annotation.clone(),
8386 )?;
8387 self.english_note_lexical_scalar(&decl.name);
8388 if is_our {
8389 self.note_our_scalar(&decl.name);
8390 }
8391 }
8392 Sigil::Array => {
8393 let items = val.to_list();
8394 if is_our {
8395 self.record_exporter_our_array_name(&decl.name, &items);
8396 }
8397 let aname = if is_our {
8398 self.stash_array_full_name_for_package(&decl.name)
8399 } else {
8400 self.stash_array_name_for_package(&decl.name)
8401 };
8402 self.scope.declare_array_frozen(&aname, items, decl.frozen);
8403 if is_our {
8404 self.note_our_array(&decl.name);
8405 }
8406 }
8407 Sigil::Hash => {
8408 let items = val.to_list();
8409 let mut map = IndexMap::new();
8410 let mut i = 0;
8411 while i + 1 < items.len() {
8412 let k = items[i].to_string();
8413 let v = items[i + 1].clone();
8414 map.insert(k, v);
8415 i += 2;
8416 }
8417 let hname = if is_our {
8418 self.stash_hash_full_name_for_package(&decl.name)
8419 } else {
8420 decl.name.clone()
8421 };
8422 self.scope.declare_hash_frozen(&hname, map, decl.frozen);
8423 if is_our {
8424 self.note_our_hash(&decl.name);
8425 }
8426 }
8427 }
8428 }
8429 }
8430 Ok(StrykeValue::UNDEF)
8431 }
8432 StmtKind::State(decls) => {
8433 for decl in decls {
8436 let state_key = format!("{}:{}", stmt.line, decl.name);
8437 match decl.sigil {
8438 Sigil::Scalar => {
8439 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
8440 self.scope.declare_scalar(&decl.name, prev);
8442 } else {
8443 let val = if let Some(init) = &decl.initializer {
8445 self.eval_expr(init)?
8446 } else {
8447 StrykeValue::UNDEF
8448 };
8449 self.state_vars.insert(state_key.clone(), val.clone());
8450 self.scope.declare_scalar(&decl.name, val);
8451 }
8452 if let Some(frame) = self.state_bindings_stack.last_mut() {
8454 frame.push((decl.name.clone(), state_key));
8455 }
8456 }
8457 _ => {
8458 let val = if let Some(init) = &decl.initializer {
8460 self.eval_expr(init)?
8461 } else {
8462 StrykeValue::UNDEF
8463 };
8464 match decl.sigil {
8465 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
8466 Sigil::Hash => {
8467 let items = val.to_list();
8468 let mut map = IndexMap::new();
8469 let mut i = 0;
8470 while i + 1 < items.len() {
8471 map.insert(items[i].to_string(), items[i + 1].clone());
8472 i += 2;
8473 }
8474 self.scope.declare_hash(&decl.name, map);
8475 }
8476 _ => {}
8477 }
8478 }
8479 }
8480 }
8481 Ok(StrykeValue::UNDEF)
8482 }
8483 StmtKind::Local(decls) => {
8484 if decls.len() > 1 && decls[0].initializer.is_some() {
8485 let val = self.eval_expr_ctx(
8486 decls[0].initializer.as_ref().unwrap(),
8487 WantarrayCtx::List,
8488 )?;
8489 let items = val.to_list();
8490 let mut idx = 0;
8491 for decl in decls {
8492 match decl.sigil {
8493 Sigil::Scalar => {
8494 let v = items.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
8495 idx += 1;
8496 self.scope.local_set_scalar(&decl.name, v)?;
8497 }
8498 Sigil::Array => {
8499 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8500 idx = items.len();
8501 self.scope.local_set_array(&decl.name, rest)?;
8502 }
8503 Sigil::Hash => {
8504 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8505 idx = items.len();
8506 if decl.name == "ENV" {
8507 self.materialize_env_if_needed();
8508 }
8509 let mut map = IndexMap::new();
8510 let mut i = 0;
8511 while i + 1 < rest.len() {
8512 map.insert(rest[i].to_string(), rest[i + 1].clone());
8513 i += 2;
8514 }
8515 self.scope.local_set_hash(&decl.name, map)?;
8516 }
8517 Sigil::Typeglob => {
8518 return Err(StrykeError::runtime(
8519 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
8520 stmt.line,
8521 )
8522 .into());
8523 }
8524 }
8525 }
8526 Ok(val)
8527 } else {
8528 let mut last_val = StrykeValue::UNDEF;
8529 for decl in decls {
8530 let val = if let Some(init) = &decl.initializer {
8531 let ctx = match decl.sigil {
8532 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8533 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8534 };
8535 self.eval_expr_ctx(init, ctx)?
8536 } else {
8537 StrykeValue::UNDEF
8538 };
8539 last_val = val.clone();
8540 match decl.sigil {
8541 Sigil::Typeglob => {
8542 let old = self.glob_handle_alias.remove(&decl.name);
8543 if let Some(frame) = self.glob_restore_frames.last_mut() {
8544 frame.push((decl.name.clone(), old));
8545 }
8546 if let Some(init) = &decl.initializer {
8547 if let ExprKind::Typeglob(rhs) = &init.kind {
8548 self.glob_handle_alias
8549 .insert(decl.name.clone(), rhs.clone());
8550 } else {
8551 return Err(StrykeError::runtime(
8552 "local *GLOB = *OTHER — right side must be a typeglob",
8553 stmt.line,
8554 )
8555 .into());
8556 }
8557 }
8558 }
8559 Sigil::Scalar => {
8560 if Self::is_special_scalar_name_for_set(&decl.name) {
8566 let old = self.get_special_var(&decl.name);
8567 if let Some(frame) = self.special_var_restore_frames.last_mut()
8568 {
8569 frame.push((decl.name.clone(), old));
8570 }
8571 self.set_special_var(&decl.name, &val)
8572 .map_err(|e| e.at_line(stmt.line))?;
8573 }
8574 self.scope.local_set_scalar(&decl.name, val)?;
8575 }
8576 Sigil::Array => {
8577 self.scope.local_set_array(&decl.name, val.to_list())?;
8578 }
8579 Sigil::Hash => {
8580 if decl.name == "ENV" {
8581 self.materialize_env_if_needed();
8582 }
8583 let items = val.to_list();
8584 let mut map = IndexMap::new();
8585 let mut i = 0;
8586 while i + 1 < items.len() {
8587 let k = items[i].to_string();
8588 let v = items[i + 1].clone();
8589 map.insert(k, v);
8590 i += 2;
8591 }
8592 self.scope.local_set_hash(&decl.name, map)?;
8593 }
8594 }
8595 }
8596 Ok(last_val)
8597 }
8598 }
8599 StmtKind::LocalExpr {
8600 target,
8601 initializer,
8602 } => {
8603 let rhs_name = |init: &Expr| -> StrykeResult<Option<String>> {
8604 match &init.kind {
8605 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
8606 _ => Err(StrykeError::runtime(
8607 "local *GLOB = *OTHER — right side must be a typeglob",
8608 stmt.line,
8609 )),
8610 }
8611 };
8612 match &target.kind {
8613 ExprKind::Typeglob(name) => {
8614 let rhs = if let Some(init) = initializer {
8615 rhs_name(init)?
8616 } else {
8617 None
8618 };
8619 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
8620 return Ok(StrykeValue::UNDEF);
8621 }
8622 ExprKind::Deref {
8623 expr,
8624 kind: Sigil::Typeglob,
8625 } => {
8626 let lhs = self.eval_expr(expr)?.to_string();
8627 let rhs = if let Some(init) = initializer {
8628 rhs_name(init)?
8629 } else {
8630 None
8631 };
8632 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8633 return Ok(StrykeValue::UNDEF);
8634 }
8635 ExprKind::TypeglobExpr(e) => {
8636 let lhs = self.eval_expr(e)?.to_string();
8637 let rhs = if let Some(init) = initializer {
8638 rhs_name(init)?
8639 } else {
8640 None
8641 };
8642 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8643 return Ok(StrykeValue::UNDEF);
8644 }
8645 _ => {}
8646 }
8647 let val = if let Some(init) = initializer {
8648 let ctx = match &target.kind {
8649 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
8650 _ => WantarrayCtx::Scalar,
8651 };
8652 self.eval_expr_ctx(init, ctx)?
8653 } else {
8654 StrykeValue::UNDEF
8655 };
8656 match &target.kind {
8657 ExprKind::ScalarVar(name) => {
8658 if Self::is_special_scalar_name_for_set(name) {
8661 let old = self.get_special_var(name);
8662 if let Some(frame) = self.special_var_restore_frames.last_mut() {
8663 frame.push((name.clone(), old));
8664 }
8665 self.set_special_var(name, &val)
8666 .map_err(|e| e.at_line(stmt.line))?;
8667 }
8668 self.scope.local_set_scalar(name, val.clone())?;
8669 }
8670 ExprKind::ArrayVar(name) => {
8671 self.scope.local_set_array(name, val.to_list())?;
8672 }
8673 ExprKind::HashVar(name) => {
8674 if name == "ENV" {
8675 self.materialize_env_if_needed();
8676 }
8677 let items = val.to_list();
8678 let mut map = IndexMap::new();
8679 let mut i = 0;
8680 while i + 1 < items.len() {
8681 map.insert(items[i].to_string(), items[i + 1].clone());
8682 i += 2;
8683 }
8684 self.scope.local_set_hash(name, map)?;
8685 }
8686 ExprKind::HashElement { hash, key } => {
8687 let ks = self.eval_expr(key)?.to_string();
8688 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
8689 }
8690 ExprKind::ArrayElement { array, index } => {
8691 self.check_strict_array_var(array, stmt.line)?;
8692 let aname = self.stash_array_name_for_package(array);
8693 let idx = self.eval_expr(index)?.to_int();
8694 self.scope
8695 .local_set_array_element(&aname, idx, val.clone())?;
8696 }
8697 _ => {
8698 return Err(StrykeError::runtime(
8699 format!(
8700 "local on this lvalue is not supported yet ({:?})",
8701 target.kind
8702 ),
8703 stmt.line,
8704 )
8705 .into());
8706 }
8707 }
8708 Ok(val)
8709 }
8710 StmtKind::MySync(decls) => {
8711 for decl in decls {
8712 let val = if let Some(init) = &decl.initializer {
8713 self.eval_expr(init)?
8714 } else {
8715 StrykeValue::UNDEF
8716 };
8717 match decl.sigil {
8718 Sigil::Typeglob => {
8719 return Err(StrykeError::runtime(
8720 "`mysync` does not support typeglob variables",
8721 stmt.line,
8722 )
8723 .into());
8724 }
8725 Sigil::Scalar => {
8726 let stored = if val.is_mysync_deque_or_heap() {
8729 val
8730 } else {
8731 StrykeValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(
8732 val,
8733 )))
8734 };
8735 self.scope.declare_scalar(&decl.name, stored);
8736 }
8737 Sigil::Array => {
8738 self.scope.declare_atomic_array(&decl.name, val.to_list());
8739 }
8740 Sigil::Hash => {
8741 let items = val.to_list();
8742 let mut map = IndexMap::new();
8743 let mut i = 0;
8744 while i + 1 < items.len() {
8745 map.insert(items[i].to_string(), items[i + 1].clone());
8746 i += 2;
8747 }
8748 self.scope.declare_atomic_hash(&decl.name, map);
8749 }
8750 }
8751 }
8752 Ok(StrykeValue::UNDEF)
8753 }
8754 StmtKind::OurSync(decls) => {
8755 for decl in decls {
8762 let val = if let Some(init) = &decl.initializer {
8763 self.eval_expr(init)?
8764 } else {
8765 StrykeValue::UNDEF
8766 };
8767 match decl.sigil {
8768 Sigil::Typeglob => {
8769 return Err(StrykeError::runtime(
8770 "`oursync` does not support typeglob variables",
8771 stmt.line,
8772 )
8773 .into());
8774 }
8775 Sigil::Scalar => {
8776 let stash = self.stash_scalar_name_for_package(&decl.name);
8777 let stored = if val.is_mysync_deque_or_heap() {
8778 val
8779 } else {
8780 StrykeValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(
8781 val,
8782 )))
8783 };
8784 self.scope.declare_scalar(&stash, stored);
8785 self.english_note_lexical_scalar(&decl.name);
8786 self.note_our_scalar(&decl.name);
8787 }
8788 Sigil::Array => {
8789 let stash = self.stash_array_name_for_package(&decl.name);
8790 self.scope.declare_atomic_array(&stash, val.to_list());
8791 self.english_note_lexical_scalar(&decl.name);
8792 self.note_our_scalar(&decl.name);
8793 }
8794 Sigil::Hash => {
8795 let items = val.to_list();
8796 let mut map = IndexMap::new();
8797 let mut i = 0;
8798 while i + 1 < items.len() {
8799 map.insert(items[i].to_string(), items[i + 1].clone());
8800 i += 2;
8801 }
8802 self.scope.declare_atomic_hash(&decl.name, map);
8805 self.english_note_lexical_scalar(&decl.name);
8806 self.note_our_scalar(&decl.name);
8807 }
8808 }
8809 }
8810 Ok(StrykeValue::UNDEF)
8811 }
8812 StmtKind::Package { name } => {
8813 let _ = self
8815 .scope
8816 .set_scalar("__PACKAGE__", StrykeValue::string(name.clone()));
8817 Ok(StrykeValue::UNDEF)
8818 }
8819 StmtKind::UsePerlVersion { .. } => Ok(StrykeValue::UNDEF),
8820 StmtKind::Use { .. } => {
8821 Ok(StrykeValue::UNDEF)
8823 }
8824 StmtKind::UseOverload { pairs } => {
8825 self.install_use_overload_pairs(pairs);
8826 Ok(StrykeValue::UNDEF)
8827 }
8828 StmtKind::No { .. } => {
8829 Ok(StrykeValue::UNDEF)
8831 }
8832 StmtKind::Return(val) => {
8833 let v = if let Some(e) = val {
8834 self.eval_expr_ctx(e, self.wantarray_kind)?
8838 } else {
8839 StrykeValue::UNDEF
8840 };
8841 Err(Flow::Return(v).into())
8842 }
8843 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
8844 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
8845 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
8846 StmtKind::Block(block) => self.exec_block(block),
8847 StmtKind::Begin(_)
8848 | StmtKind::UnitCheck(_)
8849 | StmtKind::Check(_)
8850 | StmtKind::Init(_)
8851 | StmtKind::End(_) => Ok(StrykeValue::UNDEF),
8852 StmtKind::Empty => Ok(StrykeValue::UNDEF),
8853 StmtKind::Goto { target } => {
8854 if let ExprKind::SubroutineRef(name) = &target.kind {
8856 return Err(Flow::GotoSub(name.clone()).into());
8857 }
8858 Err(StrykeError::runtime("goto reached outside goto-aware block", stmt.line).into())
8859 }
8860 StmtKind::EvalTimeout { timeout, body } => {
8861 let secs = self.eval_expr(timeout)?.to_number();
8862 self.eval_timeout_block(body, secs, stmt.line)
8863 }
8864 StmtKind::Tie {
8865 target,
8866 class,
8867 args,
8868 } => {
8869 let kind = match &target {
8870 TieTarget::Scalar(_) => 0u8,
8871 TieTarget::Array(_) => 1u8,
8872 TieTarget::Hash(_) => 2u8,
8873 };
8874 let name = match &target {
8875 TieTarget::Scalar(s) => s.as_str(),
8876 TieTarget::Array(a) => a.as_str(),
8877 TieTarget::Hash(h) => h.as_str(),
8878 };
8879 let mut vals = vec![self.eval_expr(class)?];
8880 for a in args {
8881 vals.push(self.eval_expr(a)?);
8882 }
8883 self.tie_execute(kind, name, vals, stmt.line)
8884 .map_err(Into::into)
8885 }
8886 StmtKind::TryCatch {
8887 try_block,
8888 catch_var,
8889 catch_block,
8890 finally_block,
8891 } => match self.exec_block(try_block) {
8892 Ok(v) => {
8893 if let Some(fb) = finally_block {
8894 self.exec_block(fb)?;
8895 }
8896 Ok(v)
8897 }
8898 Err(FlowOrError::Error(e)) => {
8899 if matches!(e.kind, ErrorKind::Exit(_)) {
8900 return Err(FlowOrError::Error(e));
8901 }
8902 self.scope_push_hook();
8903 self.scope
8904 .declare_scalar(catch_var, StrykeValue::string(e.to_string()));
8905 self.english_note_lexical_scalar(catch_var);
8906 let r = self.exec_block(catch_block);
8907 self.scope_pop_hook();
8908 if let Some(fb) = finally_block {
8909 self.exec_block(fb)?;
8910 }
8911 r
8912 }
8913 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
8914 },
8915 StmtKind::Given { topic, body } => self.exec_given(topic, body),
8916 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(StrykeError::runtime(
8917 "when/default may only appear inside a given block",
8918 stmt.line,
8919 )
8920 .into()),
8921 StmtKind::FormatDecl { .. } => {
8922 Ok(StrykeValue::UNDEF)
8924 }
8925 StmtKind::AdviceDecl {
8926 kind,
8927 pattern,
8928 body,
8929 } => {
8930 let id = self.next_intercept_id;
8936 self.next_intercept_id = id.saturating_add(1);
8937 self.intercepts.push(crate::aop::Intercept {
8938 id,
8939 kind: *kind,
8940 pattern: pattern.clone(),
8941 body: body.clone(),
8942 body_block_idx: u16::MAX,
8943 });
8944 Ok(StrykeValue::UNDEF)
8945 }
8946 StmtKind::Continue(block) => self.exec_block_smart(block),
8947 }
8948 }
8949
8950 #[inline]
8951 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
8952 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
8953 }
8954
8955 pub(crate) fn scalar_compound_assign_scalar_target(
8959 &mut self,
8960 name: &str,
8961 op: BinOp,
8962 rhs: StrykeValue,
8963 ) -> Result<StrykeValue, StrykeError> {
8964 if op == BinOp::Concat {
8965 return self.scope.scalar_concat_inplace(name, &rhs);
8966 }
8967 self.scope
8968 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs))
8969 }
8970
8971 fn compound_scalar_binop(old: &StrykeValue, op: BinOp, rhs: &StrykeValue) -> StrykeValue {
8972 match op {
8973 BinOp::Add => {
8974 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8975 StrykeValue::integer(a.wrapping_add(b))
8976 } else {
8977 StrykeValue::float(old.to_number() + rhs.to_number())
8978 }
8979 }
8980 BinOp::Sub => {
8981 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8982 StrykeValue::integer(a.wrapping_sub(b))
8983 } else {
8984 StrykeValue::float(old.to_number() - rhs.to_number())
8985 }
8986 }
8987 BinOp::Mul => {
8988 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
8989 StrykeValue::integer(a.wrapping_mul(b))
8990 } else {
8991 StrykeValue::float(old.to_number() * rhs.to_number())
8992 }
8993 }
8994 BinOp::BitAnd => {
8995 if let Some(s) = crate::value::set_intersection(old, rhs) {
8996 s
8997 } else {
8998 StrykeValue::integer(old.to_int() & rhs.to_int())
8999 }
9000 }
9001 BinOp::BitOr => {
9002 if let Some(s) = crate::value::set_union(old, rhs) {
9003 s
9004 } else {
9005 StrykeValue::integer(old.to_int() | rhs.to_int())
9006 }
9007 }
9008 BinOp::BitXor => StrykeValue::integer(old.to_int() ^ rhs.to_int()),
9009 BinOp::ShiftLeft => StrykeValue::integer(perl_shl_i64(old.to_int(), rhs.to_int())),
9010 BinOp::ShiftRight => StrykeValue::integer(perl_shr_i64(old.to_int(), rhs.to_int())),
9011 BinOp::Div => StrykeValue::float(old.to_number() / rhs.to_number()),
9012 BinOp::Mod => {
9013 let b = rhs.to_int();
9018 if b == 0 {
9019 StrykeValue::integer(0)
9020 } else {
9021 StrykeValue::integer(crate::value::perl_mod_i64(old.to_int(), b))
9022 }
9023 }
9024 BinOp::Pow => StrykeValue::float(old.to_number().powf(rhs.to_number())),
9025 BinOp::LogOr => {
9026 if old.is_true() {
9027 old.clone()
9028 } else {
9029 rhs.clone()
9030 }
9031 }
9032 BinOp::DefinedOr => {
9033 if !old.is_undef() {
9034 old.clone()
9035 } else {
9036 rhs.clone()
9037 }
9038 }
9039 BinOp::LogAnd => {
9040 if old.is_true() {
9041 rhs.clone()
9042 } else {
9043 old.clone()
9044 }
9045 }
9046 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
9047 }
9048 }
9049
9050 fn eval_hash_slice_key_components(
9054 &mut self,
9055 key_expr: &Expr,
9056 ) -> Result<Vec<String>, FlowOrError> {
9057 let v = self.eval_expr_ctx(key_expr, WantarrayCtx::List)?;
9062 if let Some(vv) = v.as_array_vec() {
9063 return Ok(vv.iter().map(|x| x.to_string()).collect());
9064 }
9065 if let Some(r) = v.as_array_ref() {
9066 return Ok(r.read().iter().map(|x| x.to_string()).collect());
9067 }
9068 if v.is_iterator() {
9069 return Ok(v
9070 .into_iterator()
9071 .collect_all()
9072 .iter()
9073 .map(|x| x.to_string())
9074 .collect());
9075 }
9076 Ok(vec![v.to_string()])
9077 }
9078
9079 pub(crate) fn symbolic_deref(
9081 &mut self,
9082 val: StrykeValue,
9083 kind: Sigil,
9084 line: usize,
9085 ) -> ExecResult {
9086 match kind {
9087 Sigil::Scalar => {
9088 if let Some(name) = val.as_scalar_binding_name() {
9089 return Ok(self.get_special_var(&name));
9090 }
9091 if let Some(r) = val.as_scalar_ref() {
9092 return Ok(r.read().clone());
9093 }
9094 if let Some(r) = val.as_array_ref() {
9096 return Ok(StrykeValue::array(r.read().clone()));
9097 }
9098 if let Some(name) = val.as_array_binding_name() {
9099 return Ok(StrykeValue::array(self.scope.get_array(&name)));
9100 }
9101 if let Some(r) = val.as_hash_ref() {
9102 return Ok(StrykeValue::hash(r.read().clone()));
9103 }
9104 if let Some(name) = val.as_hash_binding_name() {
9105 self.touch_env_hash(&name);
9106 return Ok(StrykeValue::hash(self.scope.get_hash(&name)));
9107 }
9108 if let Some(s) = val.as_str() {
9109 if self.strict_refs {
9110 return Err(StrykeError::runtime(
9111 format!(
9112 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
9113 s
9114 ),
9115 line,
9116 )
9117 .into());
9118 }
9119 return Ok(self.get_special_var(&s));
9120 }
9121 Err(StrykeError::runtime("Can't dereference non-reference as scalar", line).into())
9122 }
9123 Sigil::Array => {
9124 if let Some(r) = val.as_array_ref() {
9125 return Ok(StrykeValue::array(r.read().clone()));
9126 }
9127 if let Some(name) = val.as_array_binding_name() {
9128 return Ok(StrykeValue::array(self.scope.get_array(&name)));
9129 }
9130 if val.is_undef() {
9131 if self.strict_refs {
9132 return Err(StrykeError::runtime(
9133 "Can't use an undefined value as an ARRAY reference",
9134 line,
9135 )
9136 .into());
9137 }
9138 return Ok(StrykeValue::array(vec![]));
9139 }
9140 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
9146 let s = val.to_string();
9147 if self.strict_refs {
9148 return Err(StrykeError::runtime(
9149 format!(
9150 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
9151 s
9152 ),
9153 line,
9154 )
9155 .into());
9156 }
9157 return Ok(StrykeValue::array(self.scope.get_array(&s)));
9158 }
9159 Err(StrykeError::runtime("Can't dereference non-reference as array", line).into())
9160 }
9161 Sigil::Hash => {
9162 if let Some(r) = val.as_hash_ref() {
9163 return Ok(StrykeValue::hash(r.read().clone()));
9164 }
9165 if let Some(name) = val.as_hash_binding_name() {
9166 self.touch_env_hash(&name);
9167 return Ok(StrykeValue::hash(self.scope.get_hash(&name)));
9168 }
9169 if let Some(c) = val.as_class_inst() {
9177 let all_fields = self.collect_class_fields_full(&c.def);
9178 let values = c.get_values();
9179 let mut map = IndexMap::new();
9180 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
9181 if let Some(v) = values.get(i) {
9182 map.insert(name.clone(), v.clone());
9183 }
9184 }
9185 return Ok(StrykeValue::hash(map));
9186 }
9187 if let Some(s) = val.as_struct_inst() {
9190 let values = s.get_values();
9191 let mut map = IndexMap::new();
9192 for (i, field) in s.def.fields.iter().enumerate() {
9193 if let Some(v) = values.get(i) {
9194 map.insert(field.name.clone(), v.clone());
9195 }
9196 }
9197 return Ok(StrykeValue::hash(map));
9198 }
9199 if let Some(b) = val.as_blessed_ref() {
9204 let inner = b.data.read().clone();
9205 if let Some(r) = inner.as_hash_ref() {
9206 return Ok(StrykeValue::hash(r.read().clone()));
9207 }
9208 if let Some(h) = inner.as_hash_map() {
9209 return Ok(StrykeValue::hash(h));
9210 }
9211 }
9212 if val.is_undef() {
9213 if self.strict_refs {
9214 return Err(StrykeError::runtime(
9215 "Can't use an undefined value as a HASH reference",
9216 line,
9217 )
9218 .into());
9219 }
9220 return Ok(StrykeValue::hash(IndexMap::new()));
9221 }
9222 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
9223 let s = val.to_string();
9224 if self.strict_refs {
9225 return Err(StrykeError::runtime(
9226 format!(
9227 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
9228 s
9229 ),
9230 line,
9231 )
9232 .into());
9233 }
9234 self.touch_env_hash(&s);
9235 return Ok(StrykeValue::hash(self.scope.get_hash(&s)));
9236 }
9237 Err(StrykeError::runtime("Can't dereference non-reference as hash", line).into())
9238 }
9239 Sigil::Typeglob => {
9240 if let Some(s) = val.as_str() {
9241 return Ok(StrykeValue::string(self.resolve_io_handle_name(&s)));
9242 }
9243 Err(
9244 StrykeError::runtime("Can't dereference non-reference as typeglob", line)
9245 .into(),
9246 )
9247 }
9248 }
9249 }
9250
9251 #[inline]
9254 pub(crate) fn peel_array_ref_for_list_join(&self, v: StrykeValue) -> StrykeValue {
9255 if let Some(r) = v.as_array_ref() {
9256 return StrykeValue::array(r.read().clone());
9257 }
9258 v
9259 }
9260
9261 pub(crate) fn make_array_ref_alias(&self, val: StrykeValue, line: usize) -> ExecResult {
9263 if let Some(a) = val.as_array_ref() {
9264 return Ok(StrykeValue::array_ref(Arc::clone(&a)));
9265 }
9266 if let Some(name) = val.as_array_binding_name() {
9267 return Ok(StrykeValue::array_binding_ref(name));
9268 }
9269 if let Some(s) = val.as_str() {
9270 if self.strict_refs {
9271 return Err(StrykeError::runtime(
9272 format!(
9273 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
9274 s
9275 ),
9276 line,
9277 )
9278 .into());
9279 }
9280 return Ok(StrykeValue::array_binding_ref(s.to_string()));
9281 }
9282 if let Some(r) = val.as_scalar_ref() {
9283 let inner = r.read().clone();
9284 return self.make_array_ref_alias(inner, line);
9285 }
9286 Err(StrykeError::runtime("Can't make array reference from value", line).into())
9287 }
9288
9289 pub(crate) fn make_hash_ref_alias(&self, val: StrykeValue, line: usize) -> ExecResult {
9291 if let Some(h) = val.as_hash_ref() {
9292 return Ok(StrykeValue::hash_ref(Arc::clone(&h)));
9293 }
9294 if let Some(name) = val.as_hash_binding_name() {
9295 return Ok(StrykeValue::hash_binding_ref(name));
9296 }
9297 if let Some(s) = val.as_str() {
9298 if self.strict_refs {
9299 return Err(StrykeError::runtime(
9300 format!(
9301 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
9302 s
9303 ),
9304 line,
9305 )
9306 .into());
9307 }
9308 return Ok(StrykeValue::hash_binding_ref(s.to_string()));
9309 }
9310 if let Some(r) = val.as_scalar_ref() {
9311 let inner = r.read().clone();
9312 return self.make_hash_ref_alias(inner, line);
9313 }
9314 Err(StrykeError::runtime("Can't make hash reference from value", line).into())
9315 }
9316
9317 pub(crate) fn process_case_escapes(s: &str) -> String {
9320 if !s.contains('\\') {
9322 return s.to_string();
9323 }
9324 let mut result = String::with_capacity(s.len());
9325 let mut chars = s.chars().peekable();
9326 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
9330 if c == '\\' {
9331 match chars.peek() {
9332 Some(&'U') => {
9333 chars.next();
9334 mode = Some('U');
9335 continue;
9336 }
9337 Some(&'L') => {
9338 chars.next();
9339 mode = Some('L');
9340 continue;
9341 }
9342 Some(&'Q') => {
9343 chars.next();
9344 mode = Some('Q');
9345 continue;
9346 }
9347 Some(&'E') => {
9348 chars.next();
9349 mode = None;
9350 next_char_mod = None;
9351 continue;
9352 }
9353 Some(&'u') => {
9354 chars.next();
9355 next_char_mod = Some('u');
9356 continue;
9357 }
9358 Some(&'l') => {
9359 chars.next();
9360 next_char_mod = Some('l');
9361 continue;
9362 }
9363 _ => {}
9364 }
9365 }
9366
9367 let ch = c;
9368
9369 if let Some(m) = next_char_mod.take() {
9371 let transformed = match m {
9372 'u' => ch.to_uppercase().next().unwrap_or(ch),
9373 'l' => ch.to_lowercase().next().unwrap_or(ch),
9374 _ => ch,
9375 };
9376 result.push(transformed);
9377 } else {
9378 match mode {
9380 Some('U') => {
9381 for uc in ch.to_uppercase() {
9382 result.push(uc);
9383 }
9384 }
9385 Some('L') => {
9386 for lc in ch.to_lowercase() {
9387 result.push(lc);
9388 }
9389 }
9390 Some('Q') => {
9391 if !ch.is_ascii_alphanumeric() && ch != '_' {
9392 result.push('\\');
9393 }
9394 result.push(ch);
9395 }
9396 None | Some(_) => {
9397 result.push(ch);
9398 }
9399 }
9400 }
9401 }
9402 result
9403 }
9404
9405 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
9406 let line = expr.line;
9407 match &expr.kind {
9408 ExprKind::Integer(n) => Ok(StrykeValue::integer(*n)),
9409 ExprKind::Float(f) => Ok(StrykeValue::float(*f)),
9410 ExprKind::String(s) => {
9411 let processed = Self::process_case_escapes(s);
9412 Ok(StrykeValue::string(processed))
9413 }
9414 ExprKind::Bareword(s) => {
9415 if s == "__PACKAGE__" {
9416 return Ok(StrykeValue::string(self.current_package()));
9417 }
9418 if let Some(sub) = self.resolve_sub_by_name(s) {
9419 return self.call_sub(&sub, vec![], ctx, line);
9420 }
9421 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
9423 return r.map_err(Into::into);
9424 }
9425 Ok(StrykeValue::string(s.clone()))
9426 }
9427 ExprKind::Undef => Ok(StrykeValue::UNDEF),
9428 ExprKind::MagicConst(MagicConstKind::File) => {
9429 Ok(StrykeValue::string(self.file.clone()))
9430 }
9431 ExprKind::MagicConst(MagicConstKind::Line) => {
9432 Ok(StrykeValue::integer(expr.line as i64))
9433 }
9434 ExprKind::MagicConst(MagicConstKind::Sub) => {
9435 if let Some(sub) = self.current_sub_stack.last().cloned() {
9436 Ok(StrykeValue::code_ref(sub))
9437 } else {
9438 Ok(StrykeValue::UNDEF)
9439 }
9440 }
9441 ExprKind::Regex(pattern, flags) => {
9442 if ctx == WantarrayCtx::Void {
9443 let topic = self.scope.get_scalar("_");
9445 let s = topic.to_string();
9446 self.regex_match_execute(s, pattern, flags, false, "_", line)
9447 } else {
9448 let re = self.compile_regex(pattern, flags, line)?;
9449 Ok(StrykeValue::regex(re, pattern.clone(), flags.clone()))
9450 }
9451 }
9452 ExprKind::QW(words) => Ok(StrykeValue::array(
9453 words
9454 .iter()
9455 .map(|w| StrykeValue::string(w.clone()))
9456 .collect(),
9457 )),
9458
9459 ExprKind::InterpolatedString(parts) => {
9461 let mut raw_result = String::new();
9462 for part in parts {
9463 match part {
9464 StringPart::Literal(s) => raw_result.push_str(s),
9465 StringPart::ScalarVar(name) => {
9466 self.check_strict_scalar_var(name, line)?;
9467 let val = self.get_special_var(name);
9468 let s = self.stringify_value(val, line)?;
9469 raw_result.push_str(&s);
9470 }
9471 StringPart::ArrayVar(name) => {
9472 self.check_strict_array_var(name, line)?;
9473 let aname = self.stash_array_name_for_package(name);
9474 let arr = self.scope.get_array(&aname);
9475 let mut parts = Vec::with_capacity(arr.len());
9476 for v in &arr {
9477 parts.push(self.stringify_value(v.clone(), line)?);
9478 }
9479 let sep = self.list_separator.clone();
9480 raw_result.push_str(&parts.join(&sep));
9481 }
9482 StringPart::Expr(e) => {
9483 if let ExprKind::ArraySlice { array, .. } = &e.kind {
9484 self.check_strict_array_var(array, line)?;
9485 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9486 let val = self.peel_array_ref_for_list_join(val);
9487 let list = val.to_list();
9488 let sep = self.list_separator.clone();
9489 let mut parts = Vec::with_capacity(list.len());
9490 for v in list {
9491 parts.push(self.stringify_value(v, line)?);
9492 }
9493 raw_result.push_str(&parts.join(&sep));
9494 } else if let ExprKind::Deref {
9495 kind: Sigil::Array, ..
9496 } = &e.kind
9497 {
9498 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9499 let val = self.peel_array_ref_for_list_join(val);
9500 let list = val.to_list();
9501 let sep = self.list_separator.clone();
9502 let mut parts = Vec::with_capacity(list.len());
9503 for v in list {
9504 parts.push(self.stringify_value(v, line)?);
9505 }
9506 raw_result.push_str(&parts.join(&sep));
9507 } else {
9508 let val = self.eval_expr(e)?;
9509 let s = self.stringify_value(val, line)?;
9510 raw_result.push_str(&s);
9511 }
9512 }
9513 }
9514 }
9515 let result = Self::process_case_escapes(&raw_result);
9516 Ok(StrykeValue::string(result))
9517 }
9518
9519 ExprKind::ScalarVar(name) => {
9521 self.check_strict_scalar_var(name, line)?;
9522 let stor = self.tree_scalar_storage_name(name);
9523 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
9524 let class = obj
9525 .as_blessed_ref()
9526 .map(|b| b.class.clone())
9527 .unwrap_or_default();
9528 let full = format!("{}::FETCH", class);
9529 if let Some(sub) = self.subs.get(&full).cloned() {
9530 return self.call_sub(&sub, vec![obj], ctx, line);
9531 }
9532 }
9533 Ok(self.get_special_var(&stor))
9534 }
9535 ExprKind::ArrayVar(name) => {
9536 self.check_strict_array_var(name, line)?;
9537 let qualified = self.tree_array_storage_name(name);
9538 let aname = self.stash_array_name_for_package(&qualified);
9539 let arr = self.scope.get_array(&aname);
9540 if ctx == WantarrayCtx::List {
9541 Ok(StrykeValue::array(arr))
9542 } else {
9543 Ok(StrykeValue::integer(arr.len() as i64))
9544 }
9545 }
9546 ExprKind::HashVar(name) => {
9547 self.check_strict_hash_var(name, line)?;
9548 self.touch_env_hash(name);
9549 let hname = self.tree_hash_storage_name(name);
9550 let h = self.scope.get_hash(&hname);
9551 let pv = StrykeValue::hash(h);
9552 if ctx == WantarrayCtx::List {
9553 Ok(pv)
9554 } else {
9555 Ok(pv.scalar_context())
9556 }
9557 }
9558 ExprKind::Typeglob(name) => {
9559 let n = self.resolve_io_handle_name(name);
9560 Ok(StrykeValue::string(n))
9561 }
9562 ExprKind::TypeglobExpr(e) => {
9563 let name = self.eval_expr(e)?.to_string();
9564 let n = self.resolve_io_handle_name(&name);
9565 Ok(StrykeValue::string(n))
9566 }
9567 ExprKind::ArrayElement { array, index } => {
9568 if let Some(real) = array.strip_prefix("__topicstr__") {
9573 let s = self.scope.get_scalar(real).to_string();
9574 if let ExprKind::Range {
9575 from,
9576 to,
9577 exclusive,
9578 step,
9579 } = &index.kind
9580 {
9581 let n = s.chars().count() as i64;
9582 let mut from_i = self.eval_expr(from)?.to_int();
9583 let mut to_i = self.eval_expr(to)?.to_int();
9584 let step_i = match step {
9585 Some(e) => self.eval_expr(e)?.to_int(),
9586 None => 1,
9587 };
9588 if from_i < 0 {
9589 from_i += n
9590 }
9591 if to_i < 0 {
9592 to_i += n
9593 }
9594 if *exclusive {
9595 to_i -= 1
9596 }
9597 let chars: Vec<char> = s.chars().collect();
9598 let mut out = String::new();
9599 if step_i > 0 {
9600 let mut i = from_i;
9601 while i <= to_i && i < n {
9602 if i >= 0 {
9603 out.push(chars[i as usize]);
9604 }
9605 i += step_i;
9606 }
9607 } else if step_i < 0 {
9608 let mut i = from_i;
9609 while i >= to_i && i >= 0 {
9610 if i < n {
9611 out.push(chars[i as usize]);
9612 }
9613 i += step_i;
9614 }
9615 }
9616 return Ok(StrykeValue::string(out));
9617 }
9618 let idx = self.eval_expr(index)?.to_int();
9619 let n = s.chars().count() as i64;
9620 let i = if idx < 0 { idx + n } else { idx };
9621 return Ok(if i >= 0 && i < n {
9622 s.chars()
9623 .nth(i as usize)
9624 .map(|c| StrykeValue::string(c.to_string()))
9625 .unwrap_or(StrykeValue::UNDEF)
9626 } else {
9627 StrykeValue::UNDEF
9628 });
9629 }
9630 self.check_strict_array_var(array, line)?;
9631 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9636 if let ExprKind::Range {
9637 from,
9638 to,
9639 exclusive,
9640 step,
9641 } = &index.kind
9642 {
9643 let aname_check = self.stash_array_name_for_package(array);
9644 let prefer_scalar =
9645 array == "_" || self.scope.get_array(&aname_check).is_empty();
9646 if prefer_scalar {
9647 let s = self.scope.get_scalar(array).to_string();
9648 if !s.is_empty() {
9649 let n = s.chars().count() as i64;
9650 let mut from_i = self.eval_expr(from)?.to_int();
9651 let mut to_i = self.eval_expr(to)?.to_int();
9652 let step_i = match step {
9653 Some(e) => self.eval_expr(e)?.to_int(),
9654 None => 1,
9655 };
9656 if from_i < 0 {
9657 from_i += n
9658 }
9659 if to_i < 0 {
9660 to_i += n
9661 }
9662 if *exclusive {
9663 to_i -= 1
9664 }
9665 let chars: Vec<char> = s.chars().collect();
9666 let mut out = String::new();
9667 if step_i > 0 {
9668 let mut i = from_i;
9669 while i <= to_i && i < n {
9670 if i >= 0 {
9671 out.push(chars[i as usize]);
9672 }
9673 i += step_i;
9674 }
9675 } else if step_i < 0 {
9676 let mut i = from_i;
9677 while i >= to_i && i >= 0 {
9678 if i < n {
9679 out.push(chars[i as usize]);
9680 }
9681 i += step_i;
9682 }
9683 }
9684 return Ok(StrykeValue::string(out));
9685 }
9686 }
9687 }
9688 }
9689 let idx = self.eval_expr(index)?.to_int();
9690 let aname = self.stash_array_name_for_package(array);
9691 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
9692 let class = obj
9693 .as_blessed_ref()
9694 .map(|b| b.class.clone())
9695 .unwrap_or_default();
9696 let full = format!("{}::FETCH", class);
9697 if let Some(sub) = self.subs.get(&full).cloned() {
9698 let arg_vals = vec![obj, StrykeValue::integer(idx)];
9699 return self.call_sub(&sub, arg_vals, ctx, line);
9700 }
9701 }
9702 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9711 let prefer_scalar = self.scope.get_array(&aname).is_empty();
9712 if prefer_scalar {
9713 let s = self.scope.get_scalar(array).to_string();
9714 if !s.is_empty() {
9715 let n = s.chars().count() as i64;
9716 let i = if idx < 0 { idx + n } else { idx };
9717 if i >= 0 && i < n {
9718 if let Some(c) = s.chars().nth(i as usize) {
9719 return Ok(StrykeValue::string(c.to_string()));
9720 }
9721 }
9722 return Ok(StrykeValue::UNDEF);
9723 }
9724 }
9725 }
9726 Ok(self.scope.get_array_element(&aname, idx))
9727 }
9728 ExprKind::HashElement { hash, key } => {
9729 self.check_strict_hash_var(hash, line)?;
9730 let k = self.eval_expr(key)?.to_string();
9731 self.touch_env_hash(hash);
9732 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
9733 let class = obj
9734 .as_blessed_ref()
9735 .map(|b| b.class.clone())
9736 .unwrap_or_default();
9737 let full = format!("{}::FETCH", class);
9738 if let Some(sub) = self.subs.get(&full).cloned() {
9739 let arg_vals = vec![obj, StrykeValue::string(k)];
9740 return self.call_sub(&sub, arg_vals, ctx, line);
9741 }
9742 }
9743 let hname = self.tree_hash_storage_name(hash);
9744 Ok(self.scope.get_hash_element(&hname, &k))
9745 }
9746 ExprKind::ArraySlice { array, indices } => {
9747 self.check_strict_array_var(array, line)?;
9748 let aname = self.stash_array_name_for_package(array);
9749 let flat = self.flatten_array_slice_index_specs(indices)?;
9750 let mut result = Vec::with_capacity(flat.len());
9751 for idx in flat {
9752 result.push(self.scope.get_array_element(&aname, idx));
9753 }
9754 Ok(StrykeValue::array(result))
9755 }
9756 ExprKind::HashSlice { hash, keys } => {
9757 self.check_strict_hash_var(hash, line)?;
9758 self.touch_env_hash(hash);
9759 let mut result = Vec::new();
9760 for key_expr in keys {
9761 for k in self.eval_hash_slice_key_components(key_expr)? {
9762 result.push(self.scope.get_hash_element(hash, &k));
9763 }
9764 }
9765 Ok(StrykeValue::array(result))
9766 }
9767 ExprKind::HashKvSlice { hash, keys } => {
9768 self.check_strict_hash_var(hash, line)?;
9771 self.touch_env_hash(hash);
9772 let mut result = Vec::new();
9773 for key_expr in keys {
9774 for k in self.eval_hash_slice_key_components(key_expr)? {
9775 let v = self.scope.get_hash_element(hash, &k);
9776 result.push(StrykeValue::string(k));
9777 result.push(v);
9778 }
9779 }
9780 Ok(StrykeValue::array(result))
9781 }
9782 ExprKind::HashSliceDeref { container, keys } => {
9783 let hv = self.eval_expr(container)?;
9784 let mut key_vals = Vec::with_capacity(keys.len());
9785 for key_expr in keys {
9786 let v = if matches!(
9787 key_expr.kind,
9788 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
9789 ) {
9790 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
9791 } else {
9792 self.eval_expr(key_expr)?
9793 };
9794 key_vals.push(v);
9795 }
9796 self.hash_slice_deref_values(&hv, &key_vals, line)
9797 }
9798 ExprKind::AnonymousListSlice { source, indices } => {
9799 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
9800 let items = list_val.to_list();
9801 let flat = self.flatten_array_slice_index_specs(indices)?;
9802 let mut out = Vec::with_capacity(flat.len());
9803 for idx in flat {
9804 let i = if idx < 0 {
9805 (items.len() as i64 + idx) as usize
9806 } else {
9807 idx as usize
9808 };
9809 out.push(items.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
9810 }
9811 let arr = StrykeValue::array(out);
9812 if ctx != WantarrayCtx::List {
9813 let v = arr.to_list();
9814 Ok(v.last().cloned().unwrap_or(StrykeValue::UNDEF))
9815 } else {
9816 Ok(arr)
9817 }
9818 }
9819
9820 ExprKind::ScalarRef(inner) => match &inner.kind {
9822 ExprKind::ScalarVar(name) => Ok(StrykeValue::scalar_binding_ref(name.clone())),
9823 ExprKind::ArrayVar(name) => {
9824 self.check_strict_array_var(name, line)?;
9825 let aname = self.stash_array_name_for_package(name);
9826 let arc = self.scope.promote_array_to_shared(&aname);
9829 Ok(StrykeValue::array_ref(arc))
9830 }
9831 ExprKind::HashVar(name) => {
9832 self.check_strict_hash_var(name, line)?;
9833 let arc = self.scope.promote_hash_to_shared(name);
9834 Ok(StrykeValue::hash_ref(arc))
9835 }
9836 ExprKind::Deref {
9837 expr: e,
9838 kind: Sigil::Array,
9839 } => {
9840 let v = self.eval_expr(e)?;
9841 self.make_array_ref_alias(v, line)
9842 }
9843 ExprKind::Deref {
9844 expr: e,
9845 kind: Sigil::Hash,
9846 } => {
9847 let v = self.eval_expr(e)?;
9848 self.make_hash_ref_alias(v, line)
9849 }
9850 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
9851 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
9852 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(
9853 list.to_list(),
9854 ))))
9855 }
9856 ExprKind::HashSliceDeref { .. } => {
9857 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
9858 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(
9859 list.to_list(),
9860 ))))
9861 }
9862 _ => {
9863 let val = self.eval_expr(inner)?;
9864 Ok(StrykeValue::scalar_ref(Arc::new(RwLock::new(val))))
9865 }
9866 },
9867 ExprKind::ArrayRef(elems) => {
9868 let mut arr = Vec::with_capacity(elems.len());
9872 for e in elems {
9873 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9874 let v = self.scope.resolve_container_binding_ref(v);
9875 if let Some(vec) = v.as_array_vec() {
9876 arr.extend(vec);
9877 } else {
9878 arr.push(v);
9879 }
9880 }
9881 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(arr))))
9882 }
9883 ExprKind::HashRef(pairs) => {
9884 let mut map = IndexMap::new();
9887 for (k, v) in pairs {
9888 let key_str = self.eval_expr(k)?.to_string();
9889 if key_str == "__HASH_SPREAD__" {
9890 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
9892 let items = spread.to_list();
9893 let mut i = 0;
9894 while i + 1 < items.len() {
9895 map.insert(items[i].to_string(), items[i + 1].clone());
9896 i += 2;
9897 }
9898 } else {
9899 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
9900 map.insert(key_str, val);
9901 }
9902 }
9903 Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map))))
9904 }
9905 ExprKind::CodeRef { params, body } => {
9906 let captured = self.scope.capture();
9907 Ok(StrykeValue::code_ref(Arc::new(StrykeSub {
9908 name: "__ANON__".to_string(),
9909 params: params.clone(),
9910 body: body.clone(),
9911 closure_env: Some(captured),
9912 prototype: None,
9913 fib_like: None,
9914 })))
9915 }
9916 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
9917 ExprKind::SubroutineCodeRef(name) => {
9918 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
9919 StrykeError::runtime(self.undefined_subroutine_resolve_message(name), line)
9920 })?;
9921 Ok(StrykeValue::code_ref(sub))
9922 }
9923 ExprKind::DynamicSubCodeRef(expr) => {
9924 let name = self.eval_expr(expr)?.to_string();
9925 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
9926 StrykeError::runtime(self.undefined_subroutine_resolve_message(&name), line)
9927 })?;
9928 Ok(StrykeValue::code_ref(sub))
9929 }
9930 ExprKind::Deref { expr, kind } => {
9931 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
9932 let val = self.eval_expr(expr)?;
9933 let n = self.array_deref_len(val, line)?;
9934 return Ok(StrykeValue::integer(n));
9935 }
9936 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
9937 let val = self.eval_expr(expr)?;
9938 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
9939 return Ok(h.scalar_context());
9940 }
9941 let val = self.eval_expr(expr)?;
9942 self.symbolic_deref(val, *kind, line)
9943 }
9944 ExprKind::ArrowDeref { expr, index, kind } => {
9945 match kind {
9946 DerefKind::Array => {
9947 let container = self.eval_arrow_array_base(expr, line)?;
9948 if let ExprKind::List(indices) = &index.kind {
9949 let mut out = Vec::with_capacity(indices.len());
9950 for ix in indices {
9951 let idx = self.eval_expr(ix)?.to_int();
9952 out.push(self.read_arrow_array_element(
9953 container.clone(),
9954 idx,
9955 line,
9956 )?);
9957 }
9958 let arr = StrykeValue::array(out);
9959 if ctx != WantarrayCtx::List {
9960 let v = arr.to_list();
9961 return Ok(v.last().cloned().unwrap_or(StrykeValue::UNDEF));
9962 }
9963 return Ok(arr);
9964 }
9965 let idx = self.eval_expr(index)?.to_int();
9966 self.read_arrow_array_element(container, idx, line)
9967 }
9968 DerefKind::Hash => {
9969 let val = self.eval_arrow_hash_base(expr, line)?;
9970 let key = self.eval_expr(index)?.to_string();
9971 self.read_arrow_hash_element(val, key.as_str(), line)
9972 }
9973 DerefKind::Call => {
9974 let val = self.eval_expr(expr)?;
9983 if let ExprKind::List(ref arg_exprs) = index.kind {
9984 let mut args = Vec::with_capacity(arg_exprs.len());
9985 for a in arg_exprs {
9986 if matches!(a.kind, ExprKind::ArrayVar(_) | ExprKind::HashVar(_)) {
9987 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
9988 if let Some(items) = v.as_array_vec() {
9989 args.extend(items);
9990 } else {
9991 args.push(v);
9992 }
9993 } else {
9994 args.push(self.eval_expr(a)?);
9995 }
9996 }
9997 let callable = if let Some(inner) = val.as_scalar_ref() {
9999 inner.read().clone()
10000 } else {
10001 val
10002 };
10003 if let Some(sub) = callable.as_code_ref() {
10004 return self.call_sub(&sub, args, ctx, line);
10005 }
10006 Err(StrykeError::runtime("Not a code reference", line).into())
10007 } else {
10008 Err(StrykeError::runtime("Invalid call deref", line).into())
10009 }
10010 }
10011 }
10012 }
10013
10014 ExprKind::BinOp { left, op, right } => {
10016 match op {
10018 BinOp::BindMatch => {
10019 let lv = self.eval_expr(left)?;
10020 let rv = self.eval_expr(right)?;
10021 let s = lv.to_string();
10022 let pat = rv.to_string();
10023 return self.regex_match_execute(s, &pat, "", false, "_", line);
10024 }
10025 BinOp::BindNotMatch => {
10026 let lv = self.eval_expr(left)?;
10027 let rv = self.eval_expr(right)?;
10028 let s = lv.to_string();
10029 let pat = rv.to_string();
10030 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
10031 return Ok(StrykeValue::integer(if m.is_true() { 0 } else { 1 }));
10032 }
10033 BinOp::LogAnd | BinOp::LogAndWord => {
10034 match &left.kind {
10035 ExprKind::Regex(_, _) => {
10036 if !self.eval_boolean_rvalue_condition(left)? {
10037 return Ok(StrykeValue::string(String::new()));
10038 }
10039 }
10040 _ => {
10041 let lv = self.eval_expr(left)?;
10042 if !lv.is_true() {
10043 return Ok(lv);
10044 }
10045 }
10046 }
10047 return match &right.kind {
10048 ExprKind::Regex(_, _) => Ok(StrykeValue::integer(
10049 if self.eval_boolean_rvalue_condition(right)? {
10050 1
10051 } else {
10052 0
10053 },
10054 )),
10055 _ => self.eval_expr(right),
10056 };
10057 }
10058 BinOp::LogOr | BinOp::LogOrWord => {
10059 match &left.kind {
10060 ExprKind::Regex(_, _) => {
10061 if self.eval_boolean_rvalue_condition(left)? {
10062 return Ok(StrykeValue::integer(1));
10063 }
10064 }
10065 _ => {
10066 let lv = self.eval_expr(left)?;
10067 if lv.is_true() {
10068 return Ok(lv);
10069 }
10070 }
10071 }
10072 return match &right.kind {
10073 ExprKind::Regex(_, _) => Ok(StrykeValue::integer(
10074 if self.eval_boolean_rvalue_condition(right)? {
10075 1
10076 } else {
10077 0
10078 },
10079 )),
10080 _ => self.eval_expr(right),
10081 };
10082 }
10083 BinOp::DefinedOr => {
10084 let lv = self.eval_expr(left)?;
10085 if !lv.is_undef() {
10086 return Ok(lv);
10087 }
10088 return self.eval_expr(right);
10089 }
10090 _ => {}
10091 }
10092 let lv = self.eval_expr(left)?;
10093 let rv = self.eval_expr(right)?;
10094 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
10095 return r;
10096 }
10097 self.eval_binop(*op, &lv, &rv, line)
10098 }
10099
10100 ExprKind::UnaryOp { op, expr } => match op {
10102 UnaryOp::PreIncrement => {
10103 if let ExprKind::ScalarVar(name) = &expr.kind {
10104 self.check_strict_scalar_var(name, line)?;
10105 let n = self.resolved_scalar_storage_name(name);
10106 return Ok(self
10107 .scope
10108 .atomic_mutate(&n, perl_inc)
10109 .map_err(|e| e.at_line(line))?);
10110 }
10111 if let ExprKind::Deref { kind, .. } = &expr.kind {
10112 if matches!(kind, Sigil::Array | Sigil::Hash) {
10113 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
10114 *kind, true, true, line,
10115 ));
10116 }
10117 }
10118 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
10119 let href = self.eval_expr(container)?;
10120 let mut key_vals = Vec::with_capacity(keys.len());
10121 for key_expr in keys {
10122 key_vals.push(self.eval_expr(key_expr)?);
10123 }
10124 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
10125 }
10126 if let ExprKind::ArrowDeref {
10127 expr: arr_expr,
10128 index,
10129 kind: DerefKind::Array,
10130 } = &expr.kind
10131 {
10132 if let ExprKind::List(indices) = &index.kind {
10133 let container = self.eval_arrow_array_base(arr_expr, line)?;
10134 let mut idxs = Vec::with_capacity(indices.len());
10135 for ix in indices {
10136 idxs.push(self.eval_expr(ix)?.to_int());
10137 }
10138 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
10139 }
10140 }
10141 let val = self.eval_expr(expr)?;
10142 let new_val = perl_inc(&val);
10143 self.assign_value(expr, new_val.clone())?;
10144 Ok(new_val)
10145 }
10146 UnaryOp::PreDecrement => {
10147 if let ExprKind::ScalarVar(name) = &expr.kind {
10148 self.check_strict_scalar_var(name, line)?;
10149 let n = self.resolved_scalar_storage_name(name);
10150 return Ok(self
10151 .scope
10152 .atomic_mutate(&n, |v| StrykeValue::integer(v.to_int() - 1))
10153 .map_err(|e| e.at_line(line))?);
10154 }
10155 if let ExprKind::Deref { kind, .. } = &expr.kind {
10156 if matches!(kind, Sigil::Array | Sigil::Hash) {
10157 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
10158 *kind, true, false, line,
10159 ));
10160 }
10161 }
10162 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
10163 let href = self.eval_expr(container)?;
10164 let mut key_vals = Vec::with_capacity(keys.len());
10165 for key_expr in keys {
10166 key_vals.push(self.eval_expr(key_expr)?);
10167 }
10168 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
10169 }
10170 if let ExprKind::ArrowDeref {
10171 expr: arr_expr,
10172 index,
10173 kind: DerefKind::Array,
10174 } = &expr.kind
10175 {
10176 if let ExprKind::List(indices) = &index.kind {
10177 let container = self.eval_arrow_array_base(arr_expr, line)?;
10178 let mut idxs = Vec::with_capacity(indices.len());
10179 for ix in indices {
10180 idxs.push(self.eval_expr(ix)?.to_int());
10181 }
10182 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
10183 }
10184 }
10185 let val = self.eval_expr(expr)?;
10186 let new_val = StrykeValue::integer(val.to_int() - 1);
10187 self.assign_value(expr, new_val.clone())?;
10188 Ok(new_val)
10189 }
10190 _ => {
10191 match op {
10192 UnaryOp::LogNot | UnaryOp::LogNotWord => {
10193 if let ExprKind::Regex(pattern, flags) = &expr.kind {
10194 let topic = self.scope.get_scalar("_");
10195 let rl = expr.line;
10196 let s = topic.to_string();
10197 let v =
10198 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
10199 return Ok(StrykeValue::integer(if v.is_true() { 0 } else { 1 }));
10200 }
10201 }
10202 _ => {}
10203 }
10204 let val = self.eval_expr(expr)?;
10205 match op {
10206 UnaryOp::Negate => {
10207 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
10208 return r;
10209 }
10210 if let Some(n) = val.as_integer() {
10211 Ok(StrykeValue::integer(-n))
10212 } else {
10213 Ok(StrykeValue::float(-val.to_number()))
10214 }
10215 }
10216 UnaryOp::LogNot => {
10217 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
10218 let pv = r?;
10219 return Ok(StrykeValue::integer(if pv.is_true() { 0 } else { 1 }));
10220 }
10221 Ok(StrykeValue::integer(if val.is_true() { 0 } else { 1 }))
10222 }
10223 UnaryOp::BitNot => Ok(StrykeValue::integer(!val.to_int())),
10224 UnaryOp::LogNotWord => {
10225 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
10226 let pv = r?;
10227 return Ok(StrykeValue::integer(if pv.is_true() { 0 } else { 1 }));
10228 }
10229 Ok(StrykeValue::integer(if val.is_true() { 0 } else { 1 }))
10230 }
10231 UnaryOp::Ref => {
10232 if let ExprKind::ScalarVar(name) = &expr.kind {
10233 return Ok(StrykeValue::scalar_binding_ref(name.clone()));
10234 }
10235 Ok(StrykeValue::scalar_ref(Arc::new(RwLock::new(val))))
10236 }
10237 _ => unreachable!(),
10238 }
10239 }
10240 },
10241
10242 ExprKind::PostfixOp { expr, op } => {
10243 if let ExprKind::ScalarVar(name) = &expr.kind {
10246 self.check_strict_scalar_var(name, line)?;
10247 let n = self.resolved_scalar_storage_name(name);
10248 let f: fn(&StrykeValue) -> StrykeValue = match op {
10249 PostfixOp::Increment => |v| perl_inc(v),
10250 PostfixOp::Decrement => |v| StrykeValue::integer(v.to_int() - 1),
10251 };
10252 return Ok(self
10253 .scope
10254 .atomic_mutate_post(&n, f)
10255 .map_err(|e| e.at_line(line))?);
10256 }
10257 if let ExprKind::Deref { kind, .. } = &expr.kind {
10258 if matches!(kind, Sigil::Array | Sigil::Hash) {
10259 let is_inc = matches!(op, PostfixOp::Increment);
10260 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
10261 *kind, false, is_inc, line,
10262 ));
10263 }
10264 }
10265 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
10266 let href = self.eval_expr(container)?;
10267 let mut key_vals = Vec::with_capacity(keys.len());
10268 for key_expr in keys {
10269 key_vals.push(self.eval_expr(key_expr)?);
10270 }
10271 let kind_byte = match op {
10272 PostfixOp::Increment => 2u8,
10273 PostfixOp::Decrement => 3u8,
10274 };
10275 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
10276 }
10277 if let ExprKind::ArrowDeref {
10278 expr: arr_expr,
10279 index,
10280 kind: DerefKind::Array,
10281 } = &expr.kind
10282 {
10283 if let ExprKind::List(indices) = &index.kind {
10284 let container = self.eval_arrow_array_base(arr_expr, line)?;
10285 let mut idxs = Vec::with_capacity(indices.len());
10286 for ix in indices {
10287 idxs.push(self.eval_expr(ix)?.to_int());
10288 }
10289 let kind_byte = match op {
10290 PostfixOp::Increment => 2u8,
10291 PostfixOp::Decrement => 3u8,
10292 };
10293 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
10294 }
10295 }
10296 let val = self.eval_expr(expr)?;
10297 let old = val.clone();
10298 let new_val = match op {
10299 PostfixOp::Increment => perl_inc(&val),
10300 PostfixOp::Decrement => StrykeValue::integer(val.to_int() - 1),
10301 };
10302 self.assign_value(expr, new_val)?;
10303 Ok(old)
10304 }
10305
10306 ExprKind::Assign { target, value } => {
10308 if let ExprKind::Typeglob(lhs) = &target.kind {
10309 if let ExprKind::Typeglob(rhs) = &value.kind {
10310 self.copy_typeglob_slots(lhs, rhs, line)?;
10311 return self.eval_expr(value);
10312 }
10313 }
10314 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
10315 self.assign_value(target, val.clone())?;
10316 Ok(val)
10317 }
10318 ExprKind::CompoundAssign { target, op, value } => {
10319 if let ExprKind::ScalarVar(name) = &target.kind {
10322 self.check_strict_scalar_var(name, line)?;
10323 let n = self.resolved_scalar_storage_name(name);
10324 let op = *op;
10325 let rhs = match op {
10326 BinOp::LogOr => {
10327 let old = self.scope.get_scalar(&n);
10328 if old.is_true() {
10329 return Ok(old);
10330 }
10331 self.eval_expr(value)?
10332 }
10333 BinOp::DefinedOr => {
10334 let old = self.scope.get_scalar(&n);
10335 if !old.is_undef() {
10336 return Ok(old);
10337 }
10338 self.eval_expr(value)?
10339 }
10340 BinOp::LogAnd => {
10341 let old = self.scope.get_scalar(&n);
10342 if !old.is_true() {
10343 return Ok(old);
10344 }
10345 self.eval_expr(value)?
10346 }
10347 _ => self.eval_expr(value)?,
10348 };
10349 return Ok(self.scalar_compound_assign_scalar_target(&n, op, rhs)?);
10350 }
10351 let rhs = self.eval_expr(value)?;
10352 if let ExprKind::HashElement { hash, key } = &target.kind {
10354 self.check_strict_hash_var(hash, line)?;
10355 let k = self.eval_expr(key)?.to_string();
10356 let op = *op;
10357 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
10358 BinOp::Add => {
10359 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10360 StrykeValue::integer(a.wrapping_add(b))
10361 } else {
10362 StrykeValue::float(old.to_number() + rhs.to_number())
10363 }
10364 }
10365 BinOp::Sub => {
10366 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10367 StrykeValue::integer(a.wrapping_sub(b))
10368 } else {
10369 StrykeValue::float(old.to_number() - rhs.to_number())
10370 }
10371 }
10372 BinOp::Concat => {
10373 let mut s = old.to_string();
10374 rhs.append_to(&mut s);
10375 StrykeValue::string(s)
10376 }
10377 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
10378 })?);
10379 }
10380 if let ExprKind::ArrayElement { array, index } = &target.kind {
10382 self.check_strict_array_var(array, line)?;
10383 let idx = self.eval_expr(index)?.to_int();
10384 let op = *op;
10385 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
10386 BinOp::Add => {
10387 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10388 StrykeValue::integer(a.wrapping_add(b))
10389 } else {
10390 StrykeValue::float(old.to_number() + rhs.to_number())
10391 }
10392 }
10393 BinOp::Sub => {
10394 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10395 StrykeValue::integer(a.wrapping_sub(b))
10396 } else {
10397 StrykeValue::float(old.to_number() - rhs.to_number())
10398 }
10399 }
10400 BinOp::Mul => {
10401 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10402 StrykeValue::integer(a.wrapping_mul(b))
10403 } else {
10404 StrykeValue::float(old.to_number() * rhs.to_number())
10405 }
10406 }
10407 BinOp::Div => StrykeValue::float(old.to_number() / rhs.to_number()),
10408 BinOp::Mod => {
10409 let a = old.to_int();
10414 let b = rhs.to_int();
10415 if b == 0 {
10416 StrykeValue::integer(0)
10417 } else {
10418 StrykeValue::integer(crate::value::perl_mod_i64(a, b))
10419 }
10420 }
10421 BinOp::Concat => {
10422 let mut s = old.to_string();
10423 rhs.append_to(&mut s);
10424 StrykeValue::string(s)
10425 }
10426 BinOp::Pow => StrykeValue::float(old.to_number().powf(rhs.to_number())),
10427 BinOp::BitAnd => StrykeValue::integer(old.to_int() & rhs.to_int()),
10428 BinOp::BitOr => StrykeValue::integer(old.to_int() | rhs.to_int()),
10429 BinOp::BitXor => StrykeValue::integer(old.to_int() ^ rhs.to_int()),
10430 BinOp::ShiftLeft => {
10431 StrykeValue::integer(perl_shl_i64(old.to_int(), rhs.to_int()))
10432 }
10433 BinOp::ShiftRight => {
10434 StrykeValue::integer(perl_shr_i64(old.to_int(), rhs.to_int()))
10435 }
10436 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
10437 })?);
10438 }
10439 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
10440 let href = self.eval_expr(container)?;
10441 let mut key_vals = Vec::with_capacity(keys.len());
10442 for key_expr in keys {
10443 key_vals.push(self.eval_expr(key_expr)?);
10444 }
10445 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
10446 }
10447 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
10448 if let ExprKind::Deref {
10449 expr: inner,
10450 kind: Sigil::Array,
10451 } = &source.kind
10452 {
10453 let container = self.eval_arrow_array_base(inner, line)?;
10454 let idxs = self.flatten_array_slice_index_specs(indices)?;
10455 return self
10456 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
10457 }
10458 }
10459 if let ExprKind::ArrowDeref {
10460 expr: arr_expr,
10461 index,
10462 kind: DerefKind::Array,
10463 } = &target.kind
10464 {
10465 if let ExprKind::List(indices) = &index.kind {
10466 let container = self.eval_arrow_array_base(arr_expr, line)?;
10467 let mut idxs = Vec::with_capacity(indices.len());
10468 for ix in indices {
10469 idxs.push(self.eval_expr(ix)?.to_int());
10470 }
10471 return self
10472 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
10473 }
10474 }
10475 let old = self.eval_expr(target)?;
10476 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
10477 self.assign_value(target, new_val.clone())?;
10478 Ok(new_val)
10479 }
10480
10481 ExprKind::Ternary {
10485 condition,
10486 then_expr,
10487 else_expr,
10488 } => {
10489 if self.eval_boolean_rvalue_condition(condition)? {
10490 self.eval_expr_ctx(then_expr, ctx)
10491 } else {
10492 self.eval_expr_ctx(else_expr, ctx)
10493 }
10494 }
10495
10496 ExprKind::Range {
10498 from,
10499 to,
10500 exclusive,
10501 step,
10502 } => {
10503 if ctx == WantarrayCtx::List {
10504 let f = self.eval_expr(from)?;
10505 let t = self.eval_expr(to)?;
10506 if let Some(s) = step {
10507 let step_val = self.eval_expr(s)?.to_int();
10508 let from_i = f.to_int();
10509 let to_i = t.to_int();
10510 let list = if step_val == 0 {
10511 vec![]
10512 } else if step_val > 0 {
10513 (from_i..=to_i)
10514 .step_by(step_val as usize)
10515 .map(StrykeValue::integer)
10516 .collect()
10517 } else {
10518 std::iter::successors(Some(from_i), |&x| {
10519 let next = x - step_val.abs();
10520 if next >= to_i {
10521 Some(next)
10522 } else {
10523 None
10524 }
10525 })
10526 .map(StrykeValue::integer)
10527 .collect()
10528 };
10529 Ok(StrykeValue::array(list))
10530 } else {
10531 let list = perl_list_range_expand(f, t);
10532 Ok(StrykeValue::array(list))
10533 }
10534 } else {
10535 let key = std::ptr::from_ref(expr) as usize;
10536 match (&from.kind, &to.kind) {
10537 (
10538 ExprKind::Regex(left_pat, left_flags),
10539 ExprKind::Regex(right_pat, right_flags),
10540 ) => {
10541 let dot = self.scalar_flipflop_dot_line();
10542 let subject = self.scope.get_scalar("_").to_string();
10543 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10544 |e| match e {
10545 FlowOrError::Error(err) => err,
10546 FlowOrError::Flow(_) => StrykeError::runtime(
10547 "unexpected flow in regex flip-flop",
10548 line,
10549 ),
10550 },
10551 )?;
10552 let right_re = self
10553 .compile_regex(right_pat, right_flags, line)
10554 .map_err(|e| match e {
10555 FlowOrError::Error(err) => err,
10556 FlowOrError::Flow(_) => StrykeError::runtime(
10557 "unexpected flow in regex flip-flop",
10558 line,
10559 ),
10560 })?;
10561 let left_m = left_re.is_match(&subject);
10562 let right_m = right_re.is_match(&subject);
10563 let st = self.flip_flop_tree.entry(key).or_default();
10564 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10565 &mut st.active,
10566 &mut st.exclusive_left_line,
10567 *exclusive,
10568 dot,
10569 left_m,
10570 right_m,
10571 )))
10572 }
10573 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
10574 let dot = self.scalar_flipflop_dot_line();
10575 let subject = self.scope.get_scalar("_").to_string();
10576 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10577 |e| match e {
10578 FlowOrError::Error(err) => err,
10579 FlowOrError::Flow(_) => StrykeError::runtime(
10580 "unexpected flow in regex/eof flip-flop",
10581 line,
10582 ),
10583 },
10584 )?;
10585 let left_m = left_re.is_match(&subject);
10586 let right_m = self.eof_without_arg_is_true();
10587 let st = self.flip_flop_tree.entry(key).or_default();
10588 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10589 &mut st.active,
10590 &mut st.exclusive_left_line,
10591 *exclusive,
10592 dot,
10593 left_m,
10594 right_m,
10595 )))
10596 }
10597 (
10598 ExprKind::Regex(left_pat, left_flags),
10599 ExprKind::Integer(_) | ExprKind::Float(_),
10600 ) => {
10601 let dot = self.scalar_flipflop_dot_line();
10602 let right = self.eval_expr(to)?.to_int();
10603 let subject = self.scope.get_scalar("_").to_string();
10604 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10605 |e| match e {
10606 FlowOrError::Error(err) => err,
10607 FlowOrError::Flow(_) => StrykeError::runtime(
10608 "unexpected flow in regex flip-flop",
10609 line,
10610 ),
10611 },
10612 )?;
10613 let left_m = left_re.is_match(&subject);
10614 let right_m = dot == right;
10615 let st = self.flip_flop_tree.entry(key).or_default();
10616 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10617 &mut st.active,
10618 &mut st.exclusive_left_line,
10619 *exclusive,
10620 dot,
10621 left_m,
10622 right_m,
10623 )))
10624 }
10625 (ExprKind::Regex(left_pat, left_flags), _) => {
10626 if let ExprKind::Eof(Some(_)) = &to.kind {
10627 return Err(FlowOrError::Error(StrykeError::runtime(
10628 "regex flip-flop with eof(HANDLE) is not supported",
10629 line,
10630 )));
10631 }
10632 let dot = self.scalar_flipflop_dot_line();
10633 let subject = self.scope.get_scalar("_").to_string();
10634 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10635 |e| match e {
10636 FlowOrError::Error(err) => err,
10637 FlowOrError::Flow(_) => StrykeError::runtime(
10638 "unexpected flow in regex flip-flop",
10639 line,
10640 ),
10641 },
10642 )?;
10643 let left_m = left_re.is_match(&subject);
10644 let right_m = self.eval_boolean_rvalue_condition(to)?;
10645 let st = self.flip_flop_tree.entry(key).or_default();
10646 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10647 &mut st.active,
10648 &mut st.exclusive_left_line,
10649 *exclusive,
10650 dot,
10651 left_m,
10652 right_m,
10653 )))
10654 }
10655 _ => {
10656 let left = self.eval_expr(from)?.to_int();
10657 let right = self.eval_expr(to)?.to_int();
10658 let dot = self.scalar_flipflop_dot_line();
10659 let st = self.flip_flop_tree.entry(key).or_default();
10660 if !st.active {
10661 if dot == left {
10662 st.active = true;
10663 if *exclusive {
10664 st.exclusive_left_line = Some(dot);
10665 } else {
10666 st.exclusive_left_line = None;
10667 if dot == right {
10668 st.active = false;
10669 }
10670 }
10671 return Ok(StrykeValue::integer(1));
10672 }
10673 return Ok(StrykeValue::integer(0));
10674 }
10675 if let Some(ll) = st.exclusive_left_line {
10676 if dot == right && dot > ll {
10677 st.active = false;
10678 st.exclusive_left_line = None;
10679 }
10680 } else if dot == right {
10681 st.active = false;
10682 }
10683 Ok(StrykeValue::integer(1))
10684 }
10685 }
10686 }
10687 }
10688
10689 ExprKind::SliceRange { from, to, step } => {
10695 let f = match from {
10696 Some(e) => self.eval_expr(e)?,
10697 None => {
10698 return Err(StrykeError::runtime(
10699 "open-ended slice range cannot be evaluated outside slice subscript",
10700 line,
10701 )
10702 .into());
10703 }
10704 };
10705 let t = match to {
10706 Some(e) => self.eval_expr(e)?,
10707 None => {
10708 return Err(StrykeError::runtime(
10709 "open-ended slice range cannot be evaluated outside slice subscript",
10710 line,
10711 )
10712 .into());
10713 }
10714 };
10715 let list = if let Some(s) = step {
10716 let sv = self.eval_expr(s)?;
10717 crate::value::perl_list_range_expand_stepped(f, t, sv)
10718 } else {
10719 perl_list_range_expand(f, t)
10720 };
10721 Ok(StrykeValue::array(list))
10722 }
10723
10724 ExprKind::Repeat {
10726 expr,
10727 count,
10728 list_repeat,
10729 } => {
10730 let n = self.eval_expr(count)?.to_int().max(0) as usize;
10731 if *list_repeat {
10732 let saved = self.wantarray_kind;
10734 self.wantarray_kind = WantarrayCtx::List;
10735 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10736 self.wantarray_kind = saved;
10737 let items: Vec<StrykeValue> = val.as_array_vec().unwrap_or_else(|| vec![val]);
10738 let mut result = Vec::with_capacity(items.len() * n);
10739 for _ in 0..n {
10740 result.extend(items.iter().cloned());
10741 }
10742 Ok(StrykeValue::array(result))
10743 } else {
10744 let val = self.eval_expr(expr)?;
10746 Ok(StrykeValue::string(val.to_string().repeat(n)))
10747 }
10748 }
10749
10750 ExprKind::MyExpr { keyword, decls } => {
10755 let stmt_kind = match keyword.as_str() {
10758 "my" => StmtKind::My(decls.clone()),
10759 "our" => StmtKind::Our(decls.clone()),
10760 "state" => StmtKind::State(decls.clone()),
10761 "local" => StmtKind::Local(decls.clone()),
10762 _ => StmtKind::My(decls.clone()),
10763 };
10764 let stmt = Statement {
10765 label: None,
10766 kind: stmt_kind,
10767 line,
10768 };
10769 self.exec_statement(&stmt)?;
10770 let first = decls.first().ok_or_else(|| {
10774 FlowOrError::Error(StrykeError::runtime("MyExpr: empty decl list", line))
10775 })?;
10776 Ok(match first.sigil {
10777 Sigil::Scalar => self.scope.get_scalar(&first.name),
10778 Sigil::Array => StrykeValue::array(self.scope.get_array(&first.name)),
10779 Sigil::Hash => {
10780 let h = self.scope.get_hash(&first.name);
10781 let mut flat: Vec<StrykeValue> = Vec::with_capacity(h.len() * 2);
10782 for (k, v) in h {
10783 flat.push(StrykeValue::string(k));
10784 flat.push(v);
10785 }
10786 StrykeValue::array(flat)
10787 }
10788 Sigil::Typeglob => StrykeValue::UNDEF,
10789 })
10790 }
10791
10792 ExprKind::FuncCall { name, args } => {
10794 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
10797 if matches!(dispatch_name, "read") && args.len() >= 3 {
10799 let fh_val = self.eval_expr(&args[0])?;
10800 let fh = fh_val
10801 .as_io_handle_name()
10802 .unwrap_or_else(|| fh_val.to_string());
10803 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
10804 let offset = if args.len() > 3 {
10805 self.eval_expr(&args[3])?.to_int().max(0) as usize
10806 } else {
10807 0
10808 };
10809 let var_name = match &args[1].kind {
10811 ExprKind::ScalarVar(n) => n.clone(),
10812 _ => self.eval_expr(&args[1])?.to_string(),
10813 };
10814 let mut buf = vec![0u8; len];
10815 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
10816 slot.lock().read(&mut buf).unwrap_or(0)
10817 } else if fh == "STDIN" {
10818 std::io::stdin().read(&mut buf).unwrap_or(0)
10819 } else {
10820 return Err(StrykeError::runtime(
10821 format!("read: unopened handle {}", fh),
10822 line,
10823 )
10824 .into());
10825 };
10826 buf.truncate(n);
10827 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
10828 if offset > 0 {
10829 let mut existing = self.scope.get_scalar(&var_name).to_string();
10830 while existing.len() < offset {
10831 existing.push('\0');
10832 }
10833 existing.push_str(&read_str);
10834 let _ = self
10835 .scope
10836 .set_scalar(&var_name, StrykeValue::string(existing));
10837 } else {
10838 let _ = self
10839 .scope
10840 .set_scalar(&var_name, StrykeValue::string(read_str));
10841 }
10842 return Ok(StrykeValue::integer(n as i64));
10843 }
10844 if matches!(dispatch_name, "group_by" | "chunk_by") {
10845 if args.len() != 2 {
10846 return Err(StrykeError::runtime(
10847 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
10848 line,
10849 )
10850 .into());
10851 }
10852 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
10853 }
10854 if matches!(dispatch_name, "puniq" | "pfirst" | "pany") {
10855 let mut arg_vals = Vec::with_capacity(args.len());
10856 for a in args {
10857 arg_vals.push(self.eval_expr(a)?);
10858 }
10859 let saved_wa = self.wantarray_kind;
10860 self.wantarray_kind = ctx;
10861 let r = self.eval_par_list_call(dispatch_name, &arg_vals, ctx, line);
10862 self.wantarray_kind = saved_wa;
10863 return r.map_err(Into::into);
10864 }
10865 let arg_vals = if matches!(dispatch_name, "any" | "all" | "none" | "first")
10866 || matches!(
10867 dispatch_name,
10868 "take_while"
10869 | "drop_while"
10870 | "skip_while"
10871 | "reject"
10872 | "grepv"
10873 | "tap"
10874 | "peek"
10875 )
10876 || matches!(
10877 dispatch_name,
10878 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
10879 ) {
10880 if args.len() != 2 {
10881 return Err(StrykeError::runtime(
10882 format!("{}: expected BLOCK, LIST", name),
10883 line,
10884 )
10885 .into());
10886 }
10887 let cr = self.eval_expr(&args[0])?;
10888 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
10889 let mut v = vec![cr];
10890 v.extend(list_src.to_list());
10891 v
10892 } else if matches!(
10893 dispatch_name,
10894 "zip"
10895 | "zip_longest"
10896 | "zip_shortest"
10897 | "mesh"
10898 | "mesh_longest"
10899 | "mesh_shortest"
10900 ) {
10901 let mut v = Vec::with_capacity(args.len());
10902 for a in args {
10903 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
10904 }
10905 v
10906 } else if matches!(
10907 dispatch_name,
10908 "count" | "size" | "cnt" | "len" | "list_count" | "list_size"
10909 ) {
10910 let mut list_out = Vec::new();
10917 if args.len() == 1 {
10918 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10919 } else {
10920 for a in args {
10921 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
10922 }
10923 }
10924 list_out
10925 } else if matches!(
10926 dispatch_name,
10927 "uniq"
10928 | "distinct"
10929 | "uniqstr"
10930 | "uniqint"
10931 | "uniqnum"
10932 | "flatten"
10933 | "set"
10934 | "with_index"
10935 | "shuffle"
10936 | "sum"
10937 | "sum0"
10938 | "product"
10939 | "min"
10940 | "max"
10941 | "minstr"
10942 | "maxstr"
10943 | "mean"
10944 | "median"
10945 | "mode"
10946 | "stddev"
10947 | "variance"
10948 | "pairs"
10949 | "unpairs"
10950 | "pairkeys"
10951 | "pairvalues"
10952 ) {
10953 let mut list_out = Vec::new();
10957 if args.len() == 1 {
10958 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
10959 } else {
10960 for a in args {
10961 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
10962 }
10963 }
10964 list_out
10965 } else if matches!(dispatch_name, "take" | "head" | "tail" | "drop") {
10966 if args.is_empty() {
10967 return Err(StrykeError::runtime(
10968 "take/head/tail/drop: need LIST..., N or unary N",
10969 line,
10970 )
10971 .into());
10972 }
10973 let mut arg_vals = Vec::with_capacity(args.len());
10974 if args.len() == 1 {
10975 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10977 } else {
10978 for a in &args[..args.len() - 1] {
10979 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
10980 }
10981 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
10982 }
10983 arg_vals
10984 } else if matches!(dispatch_name, "chunked" | "windowed") {
10985 let mut list_out = Vec::new();
10986 match args.len() {
10987 0 => {
10988 return Err(StrykeError::runtime(
10989 format!("{name}: expected (LIST, N) or unary N after |>"),
10990 line,
10991 )
10992 .into());
10993 }
10994 1 => {
10995 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
10997 }
10998 2 => {
10999 list_out.extend(
11000 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
11001 );
11002 list_out.push(self.eval_expr(&args[1])?);
11003 }
11004 _ => {
11005 return Err(StrykeError::runtime(
11006 format!(
11007 "{name}: expected exactly (LIST, N); use one list expression then size"
11008 ),
11009 line,
11010 )
11011 .into());
11012 }
11013 }
11014 list_out
11015 } else {
11016 let mut arg_vals = Vec::with_capacity(args.len());
11019 for a in args {
11020 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
11021 if let Some(items) = v.as_array_vec() {
11022 arg_vals.extend(items);
11023 } else {
11024 arg_vals.push(v);
11025 }
11026 }
11027 arg_vals
11028 };
11029 let saved_wa = self.wantarray_kind;
11031 self.wantarray_kind = ctx;
11032 if !crate::compat_mode() {
11035 if matches!(
11036 dispatch_name,
11037 "take_while"
11038 | "drop_while"
11039 | "skip_while"
11040 | "reject"
11041 | "grepv"
11042 | "tap"
11043 | "peek"
11044 ) {
11045 let r =
11046 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
11047 self.wantarray_kind = saved_wa;
11048 return r.map_err(Into::into);
11049 }
11050 if let Some(r) =
11051 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
11052 {
11053 self.wantarray_kind = saved_wa;
11054 return r.map_err(Into::into);
11055 }
11056 }
11057 if let Some(sub) = self.resolve_sub_by_name(name) {
11058 self.wantarray_kind = saved_wa;
11059 let args = self.with_topic_default_args(arg_vals);
11060 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
11061 return self.call_sub_with_package(&sub, args, ctx, line, pkg);
11062 }
11063 if crate::compat_mode() {
11065 if matches!(
11066 dispatch_name,
11067 "take_while"
11068 | "drop_while"
11069 | "skip_while"
11070 | "reject"
11071 | "grepv"
11072 | "tap"
11073 | "peek"
11074 ) {
11075 let r =
11076 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
11077 self.wantarray_kind = saved_wa;
11078 return r.map_err(Into::into);
11079 }
11080 if let Some(r) =
11081 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
11082 {
11083 self.wantarray_kind = saved_wa;
11084 return r.map_err(Into::into);
11085 }
11086 }
11087 self.wantarray_kind = saved_wa;
11088 self.call_named_sub(name, arg_vals, line, ctx)
11089 }
11090 ExprKind::IndirectCall {
11091 target,
11092 args,
11093 ampersand: _,
11094 pass_caller_arglist,
11095 } => {
11096 let tval = self.eval_expr(target)?;
11097 let arg_vals = if *pass_caller_arglist {
11098 self.scope.get_array("_")
11099 } else {
11100 let mut v = Vec::with_capacity(args.len());
11108 for a in args {
11109 if matches!(a.kind, ExprKind::ArrayVar(_) | ExprKind::HashVar(_)) {
11110 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
11111 if let Some(items) = val.as_array_vec() {
11112 v.extend(items);
11113 } else {
11114 v.push(val);
11115 }
11116 } else {
11117 v.push(self.eval_expr(a)?);
11118 }
11119 }
11120 v
11121 };
11122 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
11123 }
11124 ExprKind::MethodCall {
11125 object,
11126 method,
11127 args,
11128 super_call,
11129 } => {
11130 let obj = self.eval_expr(object)?;
11131 let mut arg_vals = vec![obj.clone()];
11132 for a in args {
11133 arg_vals.push(self.eval_expr(a)?);
11134 }
11135 if let Some(r) =
11136 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
11137 {
11138 return r.map_err(Into::into);
11139 }
11140 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
11141 return r.map_err(Into::into);
11142 }
11143 let class = if let Some(b) = obj.as_blessed_ref() {
11145 b.class.clone()
11146 } else if let Some(s) = obj.as_str() {
11147 s } else {
11149 return Err(
11150 StrykeError::runtime("Can't call method on non-object", line).into(),
11151 );
11152 };
11153 if method == "VERSION" && !*super_call {
11154 if let Some(ver) = self.package_version_scalar(class.as_str())? {
11155 return Ok(ver);
11156 }
11157 }
11158 if !*super_call {
11160 match method.as_str() {
11161 "isa" => {
11162 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
11163 let mro = self.mro_linearize(&class);
11164 let result = mro.iter().any(|c| c == &target);
11165 return Ok(StrykeValue::integer(if result { 1 } else { 0 }));
11166 }
11167 "can" => {
11168 let target_method =
11169 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
11170 let found = self
11171 .resolve_method_full_name(&class, &target_method, false)
11172 .and_then(|fq| self.subs.get(&fq))
11173 .is_some();
11174 if found {
11175 return Ok(StrykeValue::code_ref(Arc::new(StrykeSub {
11176 name: target_method,
11177 params: vec![],
11178 body: vec![],
11179 closure_env: None,
11180 prototype: None,
11181 fib_like: None,
11182 })));
11183 } else {
11184 return Ok(StrykeValue::UNDEF);
11185 }
11186 }
11187 "DOES" => {
11188 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
11189 let mro = self.mro_linearize(&class);
11190 let result = mro.iter().any(|c| c == &target);
11191 return Ok(StrykeValue::integer(if result { 1 } else { 0 }));
11192 }
11193 _ => {}
11194 }
11195 }
11196 let full_name = self
11197 .resolve_method_full_name(&class, method, *super_call)
11198 .ok_or_else(|| {
11199 StrykeError::runtime(
11200 format!(
11201 "Can't locate method \"{}\" for invocant \"{}\"",
11202 method, class
11203 ),
11204 line,
11205 )
11206 })?;
11207 if let Some(sub) = self.subs.get(&full_name).cloned() {
11208 self.call_sub(&sub, arg_vals, ctx, line)
11209 } else if method == "new" && !*super_call {
11210 self.builtin_new(&class, arg_vals, line)
11212 } else if let Some(r) =
11213 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
11214 {
11215 r
11216 } else {
11217 Err(StrykeError::runtime(
11218 format!(
11219 "Can't locate method \"{}\" in package \"{}\"",
11220 method, class
11221 ),
11222 line,
11223 )
11224 .into())
11225 }
11226 }
11227
11228 ExprKind::Print { handle, args } => {
11230 self.exec_print(handle.as_deref(), args, false, line)
11231 }
11232 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
11233 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
11234 ExprKind::Die(args) => {
11235 if args.is_empty() {
11236 let current = self.scope.get_scalar("@");
11238 let msg = if current.is_undef() || current.to_string().is_empty() {
11239 let mut m = "Died".to_string();
11240 m.push_str(&self.die_warn_at_suffix(line));
11241 m.push('\n');
11242 m
11243 } else {
11244 current.to_string()
11245 };
11246 self.fire_pseudosig_die(&msg, line)?;
11247 return Err(StrykeError::die(msg, line).into());
11248 }
11249 if args.len() == 1 {
11251 let v = self.eval_expr(&args[0])?;
11252 if v.as_hash_ref().is_some()
11253 || v.as_blessed_ref().is_some()
11254 || v.as_array_ref().is_some()
11255 || v.as_code_ref().is_some()
11256 {
11257 let msg = v.to_string();
11258 self.fire_pseudosig_die(&msg, line)?;
11259 return Err(StrykeError::die_with_value(v, msg, line).into());
11260 }
11261 }
11262 let mut msg = String::new();
11263 for a in args {
11264 let v = self.eval_expr(a)?;
11265 msg.push_str(&v.to_string());
11266 }
11267 if msg.is_empty() {
11268 msg = "Died".to_string();
11269 }
11270 if !msg.ends_with('\n') {
11271 msg.push_str(&self.die_warn_at_suffix(line));
11272 msg.push('\n');
11273 }
11274 self.fire_pseudosig_die(&msg, line)?;
11275 Err(StrykeError::die(msg, line).into())
11276 }
11277 ExprKind::Warn(args) => {
11278 let mut msg = String::new();
11279 for a in args {
11280 let v = self.eval_expr(a)?;
11281 msg.push_str(&v.to_string());
11282 }
11283 if msg.is_empty() {
11284 msg = "Warning: something's wrong".to_string();
11285 }
11286 if !msg.ends_with('\n') {
11287 msg.push_str(&self.die_warn_at_suffix(line));
11288 msg.push('\n');
11289 }
11290 self.fire_pseudosig_warn(&msg, line)?;
11291 Ok(StrykeValue::integer(1))
11292 }
11293
11294 ExprKind::Match {
11296 expr,
11297 pattern,
11298 flags,
11299 scalar_g,
11300 delim: _,
11301 } => {
11302 let val = self.eval_expr(expr)?;
11303 if val.is_iterator() {
11304 let source = crate::map_stream::into_pull_iter(val);
11305 let re = self.compile_regex(pattern, flags, line)?;
11306 let global = flags.contains('g');
11307 if global {
11308 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11309 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
11310 )));
11311 } else {
11312 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11313 crate::map_stream::MatchStreamIterator::new(source, re),
11314 )));
11315 }
11316 }
11317 let s = val.to_string();
11318 let pos_key = match &expr.kind {
11319 ExprKind::ScalarVar(n) => n.as_str(),
11320 _ => "_",
11321 };
11322 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
11323 }
11324 ExprKind::Substitution {
11325 expr,
11326 pattern,
11327 replacement,
11328 flags,
11329 delim: _,
11330 } => {
11331 let val = self.eval_expr(expr)?;
11332 if val.is_iterator() {
11333 let source = crate::map_stream::into_pull_iter(val);
11334 let re = self.compile_regex(pattern, flags, line)?;
11335 let global = flags.contains('g');
11336 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11337 crate::map_stream::SubstStreamIterator::new(
11338 source,
11339 re,
11340 normalize_replacement_backrefs(replacement),
11341 global,
11342 ),
11343 )));
11344 }
11345 let s = val.to_string();
11346 self.regex_subst_execute(
11347 s,
11348 pattern,
11349 replacement.as_str(),
11350 flags.as_str(),
11351 expr,
11352 line,
11353 )
11354 }
11355 ExprKind::Transliterate {
11356 expr,
11357 from,
11358 to,
11359 flags,
11360 delim: _,
11361 } => {
11362 let val = self.eval_expr(expr)?;
11363 if val.is_iterator() {
11364 let source = crate::map_stream::into_pull_iter(val);
11365 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11366 crate::map_stream::TransliterateStreamIterator::new(
11367 source, from, to, flags,
11368 ),
11369 )));
11370 }
11371 let s = val.to_string();
11372 self.regex_transliterate_execute(
11373 s,
11374 from.as_str(),
11375 to.as_str(),
11376 flags.as_str(),
11377 expr,
11378 line,
11379 )
11380 }
11381
11382 ExprKind::MapExpr {
11384 block,
11385 list,
11386 flatten_array_refs,
11387 stream,
11388 } => {
11389 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11390 if *stream {
11391 let out =
11392 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
11393 if ctx == WantarrayCtx::List {
11394 return Ok(out);
11395 }
11396 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11397 }
11398 let items = list_val.to_list();
11399 if items.len() == 1 {
11400 if let Some(p) = items[0].as_pipeline() {
11401 if *flatten_array_refs {
11402 return Err(StrykeError::runtime(
11403 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
11404 line,
11405 )
11406 .into());
11407 }
11408 let sub = self.anon_coderef_from_block(block);
11409 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
11410 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
11411 }
11412 }
11413 let mut result = Vec::new();
11418 for item in items {
11419 self.scope.set_topic(item);
11420 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
11421 result.extend(val.map_flatten_outputs(*flatten_array_refs));
11422 }
11423 if ctx == WantarrayCtx::List {
11424 Ok(StrykeValue::array(result))
11425 } else {
11426 Ok(StrykeValue::integer(result.len() as i64))
11427 }
11428 }
11429 ExprKind::ForEachExpr { block, list } => {
11430 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11431 if list_val.is_iterator() {
11433 let iter = list_val.into_iterator();
11434 let mut count = 0i64;
11435 while let Some(item) = iter.next_item() {
11436 count += 1;
11437 self.scope.set_topic(item);
11438 self.exec_block(block)?;
11439 }
11440 return Ok(StrykeValue::integer(count));
11441 }
11442 let items = list_val.to_list();
11443 let count = items.len();
11444 for item in items {
11445 self.scope.set_topic(item);
11446 self.exec_block(block)?;
11447 }
11448 Ok(StrykeValue::integer(count as i64))
11449 }
11450 ExprKind::MapExprComma {
11451 expr,
11452 list,
11453 flatten_array_refs,
11454 stream,
11455 } => {
11456 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11457 if *stream {
11458 let out =
11459 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
11460 if ctx == WantarrayCtx::List {
11461 return Ok(out);
11462 }
11463 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11464 }
11465 let items = list_val.to_list();
11466 let mut result = Vec::new();
11467 for item in items {
11468 self.scope.set_topic_local(item.clone());
11474 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11475 let val = if !crate::compat_mode() {
11479 if let Some(sub) = val.as_code_ref() {
11480 let sub = sub.clone();
11481 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::List, line)?
11482 } else {
11483 val
11484 }
11485 } else {
11486 val
11487 };
11488 result.extend(val.map_flatten_outputs(*flatten_array_refs));
11489 }
11490 if ctx == WantarrayCtx::List {
11491 Ok(StrykeValue::array(result))
11492 } else {
11493 Ok(StrykeValue::integer(result.len() as i64))
11494 }
11495 }
11496 ExprKind::GrepExpr {
11497 block,
11498 list,
11499 keyword,
11500 } => {
11501 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11502 if keyword.is_stream() {
11503 let out = self.filter_stream_block_output(list_val, block, line)?;
11504 if ctx == WantarrayCtx::List {
11505 return Ok(out);
11506 }
11507 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11508 }
11509 let items = list_val.to_list();
11510 if items.len() == 1 {
11511 if let Some(p) = items[0].as_pipeline() {
11512 let sub = self.anon_coderef_from_block(block);
11513 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
11514 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
11515 }
11516 }
11517 let mut result = Vec::new();
11518 for item in items {
11519 self.scope.set_topic(item.clone());
11520 let val = self.exec_block(block)?;
11521 let keep = if let Some(re) = val.as_regex() {
11524 re.is_match(&item.to_string())
11525 } else {
11526 val.is_true()
11527 };
11528 if keep {
11529 result.push(item);
11530 }
11531 }
11532 if ctx == WantarrayCtx::List {
11533 Ok(StrykeValue::array(result))
11534 } else {
11535 Ok(StrykeValue::integer(result.len() as i64))
11536 }
11537 }
11538 ExprKind::GrepExprComma {
11539 expr,
11540 list,
11541 keyword,
11542 } => {
11543 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11544 if keyword.is_stream() {
11545 let out = self.filter_stream_expr_output(list_val, expr, line)?;
11546 if ctx == WantarrayCtx::List {
11547 return Ok(out);
11548 }
11549 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11550 }
11551 let items = list_val.to_list();
11552 let mut result = Vec::new();
11553 for item in items {
11554 self.scope.set_topic_local(item.clone());
11558 let val = self.eval_expr(expr)?;
11559 let val = if !crate::compat_mode() {
11563 if let Some(sub) = val.as_code_ref() {
11564 let sub = sub.clone();
11565 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?
11566 } else {
11567 val
11568 }
11569 } else {
11570 val
11571 };
11572 let keep = if let Some(re) = val.as_regex() {
11573 re.is_match(&item.to_string())
11574 } else {
11575 val.is_true()
11576 };
11577 if keep {
11578 result.push(item);
11579 }
11580 }
11581 if ctx == WantarrayCtx::List {
11582 Ok(StrykeValue::array(result))
11583 } else {
11584 Ok(StrykeValue::integer(result.len() as i64))
11585 }
11586 }
11587 ExprKind::SortExpr { cmp, list } => {
11588 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11589 let mut items = list_val.to_list();
11590 match cmp {
11591 Some(SortComparator::Code(code_expr)) => {
11592 let sub = self.eval_expr(code_expr)?;
11593 let Some(sub) = sub.as_code_ref() else {
11594 return Err(StrykeError::runtime(
11595 "sort: comparator must be a code reference",
11596 line,
11597 )
11598 .into());
11599 };
11600 let saved_topic = self.scope.save_topic_chain();
11603 let sub = sub.clone();
11604 items.sort_by(|a, b| {
11605 self.scope.set_sort_pair(a.clone(), b.clone());
11610 match self.call_sub(&sub, vec![a.clone(), b.clone()], ctx, line) {
11611 Ok(v) => {
11612 let n = v.to_int();
11613 if n < 0 {
11614 Ordering::Less
11615 } else if n > 0 {
11616 Ordering::Greater
11617 } else {
11618 Ordering::Equal
11619 }
11620 }
11621 Err(_) => Ordering::Equal,
11622 }
11623 });
11624 self.scope.restore_topic_chain(saved_topic);
11625 }
11626 Some(SortComparator::Block(cmp_block)) => {
11627 if let Some(mode) = detect_sort_block_fast(cmp_block) {
11628 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
11629 } else {
11630 let saved_topic = self.scope.save_topic_chain();
11633 let cmp_block = cmp_block.clone();
11634 items.sort_by(|a, b| {
11635 self.scope.set_sort_pair(a.clone(), b.clone());
11636 match self.exec_block(&cmp_block) {
11637 Ok(v) => {
11638 let n = v.to_int();
11639 if n < 0 {
11640 Ordering::Less
11641 } else if n > 0 {
11642 Ordering::Greater
11643 } else {
11644 Ordering::Equal
11645 }
11646 }
11647 Err(_) => Ordering::Equal,
11648 }
11649 });
11650 self.scope.restore_topic_chain(saved_topic);
11651 }
11652 }
11653 None => {
11654 items.sort_by_key(|a| a.to_string());
11655 }
11656 }
11657 Ok(StrykeValue::array(items))
11658 }
11659 ExprKind::Rev(expr) => {
11660 let val = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11662 if val.is_iterator() {
11663 return Ok(StrykeValue::iterator(Arc::new(
11664 crate::value::RevIterator::new(val.into_iterator()),
11665 )));
11666 }
11667 if let Some(s) = crate::value::set_payload(&val) {
11668 let mut out = crate::value::PerlSet::new();
11669 for (k, v) in s.iter().rev() {
11670 out.insert(k.clone(), v.clone());
11671 }
11672 return Ok(StrykeValue::set(Arc::new(out)));
11673 }
11674 if let Some(ar) = val.as_array_ref() {
11675 let items: Vec<_> = ar.read().iter().rev().cloned().collect();
11676 return Ok(StrykeValue::array_ref(Arc::new(parking_lot::RwLock::new(
11677 items,
11678 ))));
11679 }
11680 if let Some(hr) = val.as_hash_ref() {
11681 let mut out: indexmap::IndexMap<String, StrykeValue> =
11682 indexmap::IndexMap::new();
11683 for (k, v) in hr.read().iter() {
11684 out.insert(v.to_string(), StrykeValue::string(k.clone()));
11685 }
11686 return Ok(StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(
11687 out,
11688 ))));
11689 }
11690 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11692 if let Some(hm) = val.as_hash_map() {
11693 let mut out: indexmap::IndexMap<String, StrykeValue> =
11694 indexmap::IndexMap::new();
11695 for (k, v) in hm.iter() {
11696 out.insert(v.to_string(), StrykeValue::string(k.clone()));
11697 }
11698 return Ok(StrykeValue::hash(out));
11699 }
11700 if val.as_array_vec().is_some() {
11701 let mut items = val.to_list();
11702 items.reverse();
11703 Ok(StrykeValue::array(items))
11704 } else {
11705 let items = val.to_list();
11706 if items.len() > 1 {
11707 let mut items = items;
11708 items.reverse();
11709 Ok(StrykeValue::array(items))
11710 } else {
11711 let s = val.to_string();
11712 Ok(StrykeValue::string(s.chars().rev().collect()))
11713 }
11714 }
11715 }
11716 ExprKind::ReverseExpr(list) => {
11717 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11718 match ctx {
11719 WantarrayCtx::List => {
11720 let mut items = val.to_list();
11721 items.reverse();
11722 Ok(StrykeValue::array(items))
11723 }
11724 _ => {
11725 let items = val.to_list();
11726 let s: String = items.iter().map(|v| v.to_string()).collect();
11727 Ok(StrykeValue::string(s.chars().rev().collect()))
11728 }
11729 }
11730 }
11731
11732 ExprKind::ParLinesExpr {
11734 path,
11735 callback,
11736 progress,
11737 } => self.eval_par_lines_expr(
11738 path.as_ref(),
11739 callback.as_ref(),
11740 progress.as_deref(),
11741 line,
11742 ),
11743 ExprKind::ParWalkExpr {
11744 path,
11745 callback,
11746 progress,
11747 } => {
11748 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
11749 }
11750 ExprKind::PwatchExpr { path, callback } => {
11751 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
11752 }
11753 ExprKind::PMapExpr {
11754 block,
11755 list,
11756 progress,
11757 flat_outputs,
11758 on_cluster,
11759 stream,
11760 } => {
11761 let show_progress = progress
11762 .as_ref()
11763 .map(|p| self.eval_expr(p))
11764 .transpose()?
11765 .map(|v| v.is_true())
11766 .unwrap_or(false);
11767 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11774 if let Some(cluster_e) = on_cluster {
11775 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
11776 return self.eval_pmap_remote(
11777 cluster_val,
11778 list_val,
11779 show_progress,
11780 block,
11781 *flat_outputs,
11782 line,
11783 );
11784 }
11785 if *stream {
11786 let source = crate::map_stream::into_pull_iter(list_val);
11787 let sub = self.anon_coderef_from_block(block);
11788 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
11789 return Ok(StrykeValue::iterator(Arc::new(
11790 crate::map_stream::PMapStreamIterator::new(
11791 source,
11792 sub,
11793 self.subs.clone(),
11794 capture,
11795 atomic_arrays,
11796 atomic_hashes,
11797 *flat_outputs,
11798 ),
11799 )));
11800 }
11801 let items = list_val.to_list();
11802 let block = block.clone();
11803 let subs = self.subs.clone();
11804 let (scope_capture, atomic_arrays, atomic_hashes) =
11805 self.scope.capture_with_atomics();
11806 let pmap_progress = PmapProgress::new(show_progress, items.len());
11807
11808 if *flat_outputs {
11809 let mut indexed: Vec<(usize, Vec<StrykeValue>)> = items
11810 .into_par_iter()
11811 .enumerate()
11812 .map(|(i, item)| {
11813 let mut local_interp = VMHelper::new();
11814 local_interp.subs = subs.clone();
11815 local_interp.scope.restore_capture(&scope_capture);
11816 local_interp
11817 .scope
11818 .restore_atomics(&atomic_arrays, &atomic_hashes);
11819 local_interp.enable_parallel_guard();
11820 local_interp.scope.set_topic(item);
11821 let val = match local_interp.exec_block(&block) {
11822 Ok(val) => val,
11823 Err(_) => StrykeValue::UNDEF,
11824 };
11825 let chunk = val.map_flatten_outputs(true);
11826 pmap_progress.tick();
11827 (i, chunk)
11828 })
11829 .collect();
11830 pmap_progress.finish();
11831 indexed.sort_by_key(|(i, _)| *i);
11832 let results: Vec<StrykeValue> =
11833 indexed.into_iter().flat_map(|(_, v)| v).collect();
11834 Ok(StrykeValue::array(results))
11835 } else {
11836 let results: Vec<StrykeValue> = items
11837 .into_par_iter()
11838 .map(|item| {
11839 let mut local_interp = VMHelper::new();
11840 local_interp.subs = subs.clone();
11841 local_interp.scope.restore_capture(&scope_capture);
11842 local_interp
11843 .scope
11844 .restore_atomics(&atomic_arrays, &atomic_hashes);
11845 local_interp.enable_parallel_guard();
11846 local_interp.scope.set_topic(item);
11847 let val = match local_interp.exec_block(&block) {
11848 Ok(val) => val,
11849 Err(_) => StrykeValue::UNDEF,
11850 };
11851 pmap_progress.tick();
11852 val
11853 })
11854 .collect();
11855 pmap_progress.finish();
11856 Ok(StrykeValue::array(results))
11857 }
11858 }
11859 ExprKind::PMapChunkedExpr {
11860 chunk_size,
11861 block,
11862 list,
11863 progress,
11864 } => {
11865 let show_progress = progress
11866 .as_ref()
11867 .map(|p| self.eval_expr(p))
11868 .transpose()?
11869 .map(|v| v.is_true())
11870 .unwrap_or(false);
11871 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
11872 let list_val = self.eval_expr(list)?;
11873 let items = list_val.to_list();
11874 let block = block.clone();
11875 let subs = self.subs.clone();
11876 let (scope_capture, atomic_arrays, atomic_hashes) =
11877 self.scope.capture_with_atomics();
11878
11879 let indexed_chunks: Vec<(usize, Vec<StrykeValue>)> = items
11880 .chunks(chunk_n)
11881 .enumerate()
11882 .map(|(i, c)| (i, c.to_vec()))
11883 .collect();
11884
11885 let n_chunks = indexed_chunks.len();
11886 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
11887
11888 let mut chunk_results: Vec<(usize, Vec<StrykeValue>)> = indexed_chunks
11889 .into_par_iter()
11890 .map(|(chunk_idx, chunk)| {
11891 let mut local_interp = VMHelper::new();
11892 local_interp.subs = subs.clone();
11893 local_interp.scope.restore_capture(&scope_capture);
11894 local_interp
11895 .scope
11896 .restore_atomics(&atomic_arrays, &atomic_hashes);
11897 local_interp.enable_parallel_guard();
11898 let mut out = Vec::with_capacity(chunk.len());
11899 for item in chunk {
11900 local_interp.scope.set_topic(item);
11901 match local_interp.exec_block(&block) {
11902 Ok(val) => out.push(val),
11903 Err(_) => out.push(StrykeValue::UNDEF),
11904 }
11905 }
11906 pmap_progress.tick();
11907 (chunk_idx, out)
11908 })
11909 .collect();
11910
11911 pmap_progress.finish();
11912 chunk_results.sort_by_key(|(i, _)| *i);
11913 let results: Vec<StrykeValue> =
11914 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
11915 Ok(StrykeValue::array(results))
11916 }
11917 ExprKind::PGrepExpr {
11918 block,
11919 list,
11920 progress,
11921 stream,
11922 } => {
11923 let show_progress = progress
11924 .as_ref()
11925 .map(|p| self.eval_expr(p))
11926 .transpose()?
11927 .map(|v| v.is_true())
11928 .unwrap_or(false);
11929 let list_val = self.eval_expr(list)?;
11930 if *stream {
11931 let source = crate::map_stream::into_pull_iter(list_val);
11932 let sub = self.anon_coderef_from_block(block);
11933 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
11934 return Ok(StrykeValue::iterator(Arc::new(
11935 crate::map_stream::PGrepStreamIterator::new(
11936 source,
11937 sub,
11938 self.subs.clone(),
11939 capture,
11940 atomic_arrays,
11941 atomic_hashes,
11942 ),
11943 )));
11944 }
11945 let items = list_val.to_list();
11946 let block = block.clone();
11947 let subs = self.subs.clone();
11948 let (scope_capture, atomic_arrays, atomic_hashes) =
11949 self.scope.capture_with_atomics();
11950 let pmap_progress = PmapProgress::new(show_progress, items.len());
11951
11952 let results: Vec<StrykeValue> = items
11953 .into_par_iter()
11954 .filter_map(|item| {
11955 let mut local_interp = VMHelper::new();
11956 local_interp.subs = subs.clone();
11957 local_interp.scope.restore_capture(&scope_capture);
11958 local_interp
11959 .scope
11960 .restore_atomics(&atomic_arrays, &atomic_hashes);
11961 local_interp.enable_parallel_guard();
11962 local_interp.scope.set_topic(item.clone());
11963 let keep = match local_interp.exec_block(&block) {
11964 Ok(val) => val.is_true(),
11965 Err(_) => false,
11966 };
11967 pmap_progress.tick();
11968 if keep {
11969 Some(item)
11970 } else {
11971 None
11972 }
11973 })
11974 .collect();
11975 pmap_progress.finish();
11976 Ok(StrykeValue::array(results))
11977 }
11978 ExprKind::ParExpr { block, list } => {
11979 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11994 let n_threads = rayon::current_num_threads().clamp(1, 8);
11995 let chunks = par_chunk_value(&list_val, n_threads);
11996 if chunks.len() < 2 {
11997 self.scope.set_topic(list_val);
12000 let v = self.exec_block(block)?;
12001 return Ok(v);
12002 }
12003 let block_clone = block.clone();
12004 let subs = self.subs.clone();
12005 let (scope_capture, atomic_arrays, atomic_hashes) =
12006 self.scope.capture_with_atomics();
12007 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
12008 let err_w = Arc::clone(&first_err);
12009 let per_chunk: Vec<Vec<StrykeValue>> = chunks
12010 .into_par_iter()
12011 .map(|chunk| {
12012 if err_w.lock().is_some() {
12013 return Vec::new();
12014 }
12015 let mut local_interp = VMHelper::new();
12016 local_interp.subs = subs.clone();
12017 local_interp.scope.restore_capture(&scope_capture);
12018 local_interp
12019 .scope
12020 .restore_atomics(&atomic_arrays, &atomic_hashes);
12021 local_interp.enable_parallel_guard();
12022 local_interp.scope.set_topic(chunk);
12023 match local_interp.exec_block(&block_clone) {
12024 Ok(v) => v.map_flatten_outputs(true),
12025 Err(e) => {
12026 let mut g = err_w.lock();
12027 if g.is_none() {
12028 *g = Some(format!("par: {:?}", e));
12029 }
12030 Vec::new()
12031 }
12032 }
12033 })
12034 .collect();
12035 if let Some(msg) = first_err.lock().take() {
12036 return Err(FlowOrError::Error(StrykeError::runtime(msg, line)));
12037 }
12038 let total: usize = per_chunk.iter().map(|v| v.len()).sum();
12039 let mut out = Vec::with_capacity(total);
12040 for v in per_chunk {
12041 out.extend(v);
12042 }
12043 Ok(StrykeValue::array(out))
12044 }
12045 ExprKind::ParReduceExpr {
12046 extract_block,
12047 reduce_block,
12048 list,
12049 } => {
12050 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12061 let n_threads = rayon::current_num_threads().clamp(1, 8);
12062 let chunks = par_chunk_value(&list_val, n_threads);
12063 if chunks.len() < 2 {
12064 let chunk_arr = match list_val.as_array_vec() {
12072 Some(arr) => arr,
12073 None => vec![list_val.clone()],
12074 };
12075 let first = chunk_arr.first().cloned().unwrap_or(StrykeValue::UNDEF);
12076 self.scope.declare_array("_", chunk_arr);
12077 self.scope.set_topic(first);
12078 return self.exec_block_with_tail(extract_block, WantarrayCtx::List);
12079 }
12080 let extract = extract_block.clone();
12081 let subs = self.subs.clone();
12082 let (scope_capture, atomic_arrays, atomic_hashes) =
12083 self.scope.capture_with_atomics();
12084 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
12085 let err_w = Arc::clone(&first_err);
12086 let per_chunk: Vec<StrykeValue> = chunks
12087 .into_par_iter()
12088 .map(|chunk| {
12089 if err_w.lock().is_some() {
12090 return StrykeValue::UNDEF;
12091 }
12092 let mut local = VMHelper::new();
12093 local.subs = subs.clone();
12094 local.scope.restore_capture(&scope_capture);
12095 local.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
12096 local.enable_parallel_guard();
12097 let chunk_arr = match chunk.as_array_vec() {
12101 Some(arr) => arr,
12102 None => vec![chunk.clone()],
12103 };
12104 let first = chunk_arr.first().cloned().unwrap_or(StrykeValue::UNDEF);
12105 local.scope.declare_array("_", chunk_arr);
12106 local.scope.set_topic(first);
12107 match local.exec_block_with_tail(&extract, WantarrayCtx::List) {
12108 Ok(v) => v,
12109 Err(e) => {
12110 let mut g = err_w.lock();
12111 if g.is_none() {
12112 *g = Some(format!("par_reduce: {:?}", e));
12113 }
12114 StrykeValue::UNDEF
12115 }
12116 }
12117 })
12118 .collect();
12119 if let Some(msg) = first_err.lock().take() {
12120 return Err(FlowOrError::Error(StrykeError::runtime(msg, line)));
12121 }
12122 if per_chunk.is_empty() {
12123 return Ok(StrykeValue::UNDEF);
12124 }
12125 if let Some(rb) = reduce_block {
12127 let mut acc = per_chunk[0].clone();
12128 for v in per_chunk.into_iter().skip(1) {
12129 self.scope.declare_scalar("a", acc.clone());
12130 self.scope.declare_scalar("b", v);
12131 acc = self.exec_block(rb)?;
12132 }
12133 return Ok(acc);
12134 }
12135 Ok(par_reduce_auto_merge(per_chunk))
12137 }
12138 ExprKind::DistReduceExpr {
12139 cluster,
12140 extract_block,
12141 list,
12142 } => {
12143 let cluster_pv = self.eval_expr(cluster)?;
12155 let Some(remote_cluster) = cluster_pv.as_remote_cluster() else {
12156 return Err(StrykeError::runtime(
12157 "~d>: expected cluster(...) value after `on`",
12158 line,
12159 )
12160 .into());
12161 };
12162 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12163 let items_flat = list_val.to_list();
12164 if items_flat.is_empty() {
12165 return Ok(StrykeValue::array(vec![]));
12166 }
12167 let (scope_capture, atomic_arrays, atomic_hashes) =
12168 self.scope.capture_with_atomics();
12169 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
12170 return Err(StrykeError::runtime(
12171 "~d>: mysync/atomic capture is not supported for remote workers",
12172 line,
12173 )
12174 .into());
12175 }
12176 let n_slots = remote_cluster.slots.len().max(1);
12181 let target_chunks = (n_slots * 4).min(items_flat.len()).max(1);
12182 let chunk_size = items_flat.len().div_ceil(target_chunks);
12183 let mut chunk_items: Vec<serde_json::Value> = Vec::new();
12184 let mut chunk_jsons_buf: Vec<StrykeValue> = Vec::new();
12185 for item in items_flat.into_iter() {
12186 chunk_jsons_buf.push(item);
12187 if chunk_jsons_buf.len() >= chunk_size {
12188 let drained: Vec<StrykeValue> = std::mem::take(&mut chunk_jsons_buf);
12189 let as_array = StrykeValue::array(drained);
12190 chunk_items.push(
12191 crate::remote_wire::perl_to_json_value(&as_array)
12192 .map_err(|e| StrykeError::runtime(e, line))?,
12193 );
12194 }
12195 }
12196 if !chunk_jsons_buf.is_empty() {
12197 let as_array = StrykeValue::array(chunk_jsons_buf);
12198 chunk_items.push(
12199 crate::remote_wire::perl_to_json_value(&as_array)
12200 .map_err(|e| StrykeError::runtime(e, line))?,
12201 );
12202 }
12203 let cap_json: Vec<(String, serde_json::Value)> = scope_capture
12211 .iter()
12212 .filter_map(|(k, v)| {
12213 crate::remote_wire::perl_to_json_value(v)
12214 .ok()
12215 .map(|j| (k.clone(), j))
12216 })
12217 .collect();
12218 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
12219 let user_block_src = crate::fmt::format_block(extract_block);
12231 let block_src = format!("@_ = $_;\n[ {user_block_src} ]");
12232 let result_values = crate::cluster::run_cluster(
12233 &remote_cluster,
12234 subs_prelude,
12235 block_src,
12236 cap_json,
12237 chunk_items,
12238 )
12239 .map_err(|e| StrykeError::runtime(format!("~d> remote: {e}"), line))?;
12240 let mut merged: Vec<StrykeValue> = Vec::new();
12244 for v in result_values {
12245 if let Some(items) = v.as_array_vec() {
12246 merged.extend(items);
12247 } else if let Some(ar) = v.as_array_ref() {
12248 merged.extend(ar.read().iter().cloned());
12249 } else {
12250 merged.push(v);
12251 }
12252 }
12253 Ok(StrykeValue::array(merged))
12254 }
12255 ExprKind::PForExpr {
12256 block,
12257 list,
12258 progress,
12259 } => {
12260 let show_progress = progress
12261 .as_ref()
12262 .map(|p| self.eval_expr(p))
12263 .transpose()?
12264 .map(|v| v.is_true())
12265 .unwrap_or(false);
12266 let list_val = self.eval_expr(list)?;
12267 let items = list_val.to_list();
12268 let block = block.clone();
12269 let subs = self.subs.clone();
12270 let (scope_capture, atomic_arrays, atomic_hashes) =
12271 self.scope.capture_with_atomics();
12272
12273 let pmap_progress = PmapProgress::new(show_progress, items.len());
12274 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
12275 items.into_par_iter().for_each(|item| {
12276 if first_err.lock().is_some() {
12277 return;
12278 }
12279 let mut local_interp = VMHelper::new();
12280 local_interp.subs = subs.clone();
12281 local_interp.scope.restore_capture(&scope_capture);
12282 local_interp
12283 .scope
12284 .restore_atomics(&atomic_arrays, &atomic_hashes);
12285 local_interp.enable_parallel_guard();
12286 local_interp.scope.set_topic(item);
12287 match local_interp.exec_block(&block) {
12288 Ok(_) => {}
12289 Err(e) => {
12290 let stryke = match e {
12291 FlowOrError::Error(stryke) => stryke,
12292 FlowOrError::Flow(_) => StrykeError::runtime(
12293 "return/last/next/redo not supported inside pfor block",
12294 line,
12295 ),
12296 };
12297 let mut g = first_err.lock();
12298 if g.is_none() {
12299 *g = Some(stryke);
12300 }
12301 }
12302 }
12303 pmap_progress.tick();
12304 });
12305 pmap_progress.finish();
12306 if let Some(e) = first_err.lock().take() {
12307 return Err(FlowOrError::Error(e));
12308 }
12309 Ok(StrykeValue::UNDEF)
12310 }
12311 ExprKind::FanExpr {
12312 count,
12313 block,
12314 progress,
12315 capture,
12316 } => {
12317 let show_progress = progress
12318 .as_ref()
12319 .map(|p| self.eval_expr(p))
12320 .transpose()?
12321 .map(|v| v.is_true())
12322 .unwrap_or(false);
12323 let n = match count {
12324 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
12325 None => self.parallel_thread_count(),
12326 };
12327 let block = block.clone();
12328 let subs = self.subs.clone();
12329 let (scope_capture, atomic_arrays, atomic_hashes) =
12330 self.scope.capture_with_atomics();
12331
12332 let fan_progress = FanProgress::new(show_progress, n);
12333 if *capture {
12334 if n == 0 {
12335 return Ok(StrykeValue::array(Vec::new()));
12336 }
12337 let pairs: Vec<(usize, ExecResult)> = (0..n)
12338 .into_par_iter()
12339 .map(|i| {
12340 fan_progress.start_worker(i);
12341 let mut local_interp = VMHelper::new();
12342 local_interp.subs = subs.clone();
12343 local_interp.suppress_stdout = show_progress;
12344 local_interp.scope.restore_capture(&scope_capture);
12345 local_interp
12346 .scope
12347 .restore_atomics(&atomic_arrays, &atomic_hashes);
12348 local_interp.enable_parallel_guard();
12349 local_interp.scope.set_topic(StrykeValue::integer(i as i64));
12350 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
12351 let res = local_interp.exec_block(&block);
12352 crate::parallel_trace::fan_worker_set_index(None);
12353 fan_progress.finish_worker(i);
12354 (i, res)
12355 })
12356 .collect();
12357 fan_progress.finish();
12358 let mut pairs = pairs;
12359 pairs.sort_by_key(|(i, _)| *i);
12360 let mut out = Vec::with_capacity(n);
12361 for (_, r) in pairs {
12362 match r {
12363 Ok(v) => out.push(v),
12364 Err(e) => return Err(e),
12365 }
12366 }
12367 return Ok(StrykeValue::array(out));
12368 }
12369 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
12370 (0..n).into_par_iter().for_each(|i| {
12371 if first_err.lock().is_some() {
12372 return;
12373 }
12374 fan_progress.start_worker(i);
12375 let mut local_interp = VMHelper::new();
12376 local_interp.subs = subs.clone();
12377 local_interp.suppress_stdout = show_progress;
12378 local_interp.scope.restore_capture(&scope_capture);
12379 local_interp
12380 .scope
12381 .restore_atomics(&atomic_arrays, &atomic_hashes);
12382 local_interp.enable_parallel_guard();
12383 local_interp.scope.set_topic(StrykeValue::integer(i as i64));
12384 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
12385 match local_interp.exec_block(&block) {
12386 Ok(_) => {}
12387 Err(e) => {
12388 let stryke = match e {
12389 FlowOrError::Error(stryke) => stryke,
12390 FlowOrError::Flow(_) => StrykeError::runtime(
12391 "return/last/next/redo not supported inside fan block",
12392 line,
12393 ),
12394 };
12395 let mut g = first_err.lock();
12396 if g.is_none() {
12397 *g = Some(stryke);
12398 }
12399 }
12400 }
12401 crate::parallel_trace::fan_worker_set_index(None);
12402 fan_progress.finish_worker(i);
12403 });
12404 fan_progress.finish();
12405 if let Some(e) = first_err.lock().take() {
12406 return Err(FlowOrError::Error(e));
12407 }
12408 Ok(StrykeValue::UNDEF)
12409 }
12410 ExprKind::RetryBlock {
12411 body,
12412 times,
12413 backoff,
12414 } => self.eval_retry_block(body, times, *backoff, line),
12415 ExprKind::RateLimitBlock {
12416 slot,
12417 max,
12418 window,
12419 body,
12420 } => self.eval_rate_limit_block(*slot, max, window, body, line),
12421 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
12422 ExprKind::GenBlock { body } => {
12423 let g = Arc::new(PerlGenerator {
12424 block: body.clone(),
12425 pc: Mutex::new(0),
12426 scope_started: Mutex::new(false),
12427 exhausted: Mutex::new(false),
12428 });
12429 Ok(StrykeValue::generator(g))
12430 }
12431 ExprKind::Yield(e) => {
12432 if !self.in_generator {
12433 return Err(StrykeError::runtime("yield outside gen block", line).into());
12434 }
12435 let v = self.eval_expr(e)?;
12436 Err(FlowOrError::Flow(Flow::Yield(v)))
12437 }
12438 ExprKind::AlgebraicMatch { subject, arms } => {
12439 self.eval_algebraic_match(subject, arms, line)
12440 }
12441 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
12442 Ok(self.spawn_async_block(body))
12443 }
12444 ExprKind::Trace { body } => {
12445 crate::parallel_trace::trace_enter();
12446 let out = self.exec_block(body);
12447 crate::parallel_trace::trace_leave();
12448 out
12449 }
12450 ExprKind::Spinner { message, body } => {
12451 use std::io::Write as _;
12452 let msg = self.eval_expr(message)?.to_string();
12453 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
12454 let done2 = done.clone();
12455 let handle = std::thread::spawn(move || {
12456 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12457 let mut i = 0;
12458 let stderr = std::io::stderr();
12459 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
12460 {
12461 let stdout = std::io::stdout();
12462 let _stdout_lock = stdout.lock();
12463 let mut err = stderr.lock();
12464 let _ = write!(
12465 err,
12466 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
12467 frames[i % frames.len()],
12468 msg
12469 );
12470 let _ = err.flush();
12471 }
12472 std::thread::sleep(std::time::Duration::from_millis(80));
12473 i += 1;
12474 }
12475 let mut err = stderr.lock();
12476 let _ = write!(err, "\r\x1b[2K");
12477 let _ = err.flush();
12478 });
12479 let result = self.exec_block(body);
12480 done.store(true, std::sync::atomic::Ordering::Relaxed);
12481 let _ = handle.join();
12482 result
12483 }
12484 ExprKind::Timer { body } => {
12485 let start = std::time::Instant::now();
12486 self.exec_block(body)?;
12487 let ms = start.elapsed().as_secs_f64() * 1000.0;
12488 Ok(StrykeValue::float(ms))
12489 }
12490 ExprKind::Bench { body, times } => {
12491 let n = self.eval_expr(times)?.to_int();
12492 if n < 0 {
12493 return Err(StrykeError::runtime(
12494 "bench: iteration count must be non-negative",
12495 line,
12496 )
12497 .into());
12498 }
12499 self.run_bench_block(body, n as usize, line)
12500 }
12501 ExprKind::Await(expr) => {
12502 let v = self.eval_expr(expr)?;
12503 if let Some(t) = v.as_async_task() {
12504 t.await_result().map_err(FlowOrError::from)
12505 } else {
12506 Ok(v)
12507 }
12508 }
12509 ExprKind::Slurp(e) => {
12510 let path = self.eval_expr(e)?.to_string();
12511 let path = self.resolve_stryke_path_string(&path);
12512 crate::perl_fs::read_file_text_or_glob(&path)
12513 .map(StrykeValue::string)
12514 .map_err(|e| {
12515 FlowOrError::Error(StrykeError::runtime(format!("slurp: {}", e), line))
12516 })
12517 }
12518 ExprKind::Capture(e) => {
12519 let cmd = self.eval_expr(e)?.to_string();
12520 let output = Command::new("sh")
12521 .arg("-c")
12522 .arg(&cmd)
12523 .output()
12524 .map_err(|e| {
12525 FlowOrError::Error(StrykeError::runtime(format!("capture: {}", e), line))
12526 })?;
12527 self.record_child_exit_status(output.status);
12528 let exitcode = output.status.code().unwrap_or(-1) as i64;
12529 let stdout = decode_utf8_or_latin1(&output.stdout);
12530 let stderr = decode_utf8_or_latin1(&output.stderr);
12531 Ok(StrykeValue::capture(Arc::new(CaptureResult {
12532 stdout,
12533 stderr,
12534 exitcode,
12535 })))
12536 }
12537 ExprKind::Qx(e) => {
12538 let cmd = self.eval_expr(e)?.to_string();
12539 let raw =
12540 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)?;
12541 if ctx == WantarrayCtx::List {
12542 let s = raw.to_string();
12546 if s.is_empty() {
12547 return Ok(StrykeValue::array(Vec::new()));
12548 }
12549 let mut lines = Vec::new();
12550 let mut buf = String::new();
12551 for c in s.chars() {
12552 buf.push(c);
12553 if c == '\n' {
12554 lines.push(StrykeValue::string(std::mem::take(&mut buf)));
12555 }
12556 }
12557 if !buf.is_empty() {
12558 lines.push(StrykeValue::string(buf));
12559 }
12560 Ok(StrykeValue::array(lines))
12561 } else {
12562 Ok(raw)
12563 }
12564 }
12565 ExprKind::FetchUrl(e) => {
12566 let url = self.eval_expr(e)?.to_string();
12567 ureq::get(&url)
12568 .call()
12569 .map_err(|e| {
12570 FlowOrError::Error(StrykeError::runtime(format!("fetch_url: {}", e), line))
12571 })
12572 .and_then(|r| {
12573 r.into_string().map(StrykeValue::string).map_err(|e| {
12574 FlowOrError::Error(StrykeError::runtime(
12575 format!("fetch_url: {}", e),
12576 line,
12577 ))
12578 })
12579 })
12580 }
12581 ExprKind::Pchannel { capacity } => {
12582 if let Some(c) = capacity {
12583 let n = self.eval_expr(c)?.to_int().max(1) as usize;
12584 Ok(crate::pchannel::create_bounded_pair(n))
12585 } else {
12586 Ok(crate::pchannel::create_pair())
12587 }
12588 }
12589 ExprKind::PSortExpr {
12590 cmp,
12591 list,
12592 progress,
12593 } => {
12594 let show_progress = progress
12595 .as_ref()
12596 .map(|p| self.eval_expr(p))
12597 .transpose()?
12598 .map(|v| v.is_true())
12599 .unwrap_or(false);
12600 let list_val = self.eval_expr(list)?;
12601 let mut items = list_val.to_list();
12602 let pmap_progress = PmapProgress::new(show_progress, 2);
12603 pmap_progress.tick();
12604 if let Some(cmp_block) = cmp {
12605 if let Some(mode) = detect_sort_block_fast(cmp_block) {
12606 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
12607 } else {
12608 let cmp_block = cmp_block.clone();
12609 let subs = self.subs.clone();
12610 let scope_capture = self.scope.capture();
12611 items.par_sort_by(|a, b| {
12612 let mut local_interp = VMHelper::new();
12613 local_interp.subs = subs.clone();
12614 local_interp.scope.restore_capture(&scope_capture);
12615 local_interp.scope.set_sort_pair(a.clone(), b.clone());
12616 match local_interp.exec_block(&cmp_block) {
12617 Ok(v) => {
12618 let n = v.to_int();
12619 if n < 0 {
12620 std::cmp::Ordering::Less
12621 } else if n > 0 {
12622 std::cmp::Ordering::Greater
12623 } else {
12624 std::cmp::Ordering::Equal
12625 }
12626 }
12627 Err(_) => std::cmp::Ordering::Equal,
12628 }
12629 });
12630 }
12631 } else {
12632 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
12633 }
12634 pmap_progress.tick();
12635 pmap_progress.finish();
12636 Ok(StrykeValue::array(items))
12637 }
12638
12639 ExprKind::ReduceExpr { block, list } => {
12640 let list_val = self.eval_expr(list)?;
12641 let items = list_val.to_list();
12642 if items.is_empty() {
12643 return Ok(StrykeValue::UNDEF);
12644 }
12645 if items.len() == 1 {
12646 return Ok(items.into_iter().next().unwrap());
12647 }
12648 let block = block.clone();
12649 let subs = self.subs.clone();
12650 let scope_capture = self.scope.capture();
12651 let mut acc = items[0].clone();
12652 for b in items.into_iter().skip(1) {
12653 let mut local_interp = VMHelper::new();
12654 local_interp.subs = subs.clone();
12655 local_interp.scope.restore_capture(&scope_capture);
12656 local_interp.scope.set_sort_pair(acc, b);
12657 acc = match local_interp.exec_block(&block) {
12658 Ok(val) => val,
12659 Err(_) => StrykeValue::UNDEF,
12660 };
12661 }
12662 Ok(acc)
12663 }
12664
12665 ExprKind::PReduceExpr {
12666 block,
12667 list,
12668 progress,
12669 } => {
12670 let show_progress = progress
12671 .as_ref()
12672 .map(|p| self.eval_expr(p))
12673 .transpose()?
12674 .map(|v| v.is_true())
12675 .unwrap_or(false);
12676 let list_val = self.eval_expr(list)?;
12677 let items = list_val.to_list();
12678 if items.is_empty() {
12679 return Ok(StrykeValue::UNDEF);
12680 }
12681 if items.len() == 1 {
12682 return Ok(items.into_iter().next().unwrap());
12683 }
12684 let block = block.clone();
12685 let subs = self.subs.clone();
12686 let scope_capture = self.scope.capture();
12687 let pmap_progress = PmapProgress::new(show_progress, items.len());
12688
12689 let result = items
12690 .into_par_iter()
12691 .map(|x| {
12692 pmap_progress.tick();
12693 x
12694 })
12695 .reduce_with(|a, b| {
12696 let mut local_interp = VMHelper::new();
12697 local_interp.subs = subs.clone();
12698 local_interp.scope.restore_capture(&scope_capture);
12699 local_interp.scope.set_sort_pair(a, b);
12700 match local_interp.exec_block(&block) {
12701 Ok(val) => val,
12702 Err(_) => StrykeValue::UNDEF,
12703 }
12704 });
12705 pmap_progress.finish();
12706 Ok(result.unwrap_or(StrykeValue::UNDEF))
12707 }
12708
12709 ExprKind::PReduceInitExpr {
12710 init,
12711 block,
12712 list,
12713 progress,
12714 } => {
12715 let show_progress = progress
12716 .as_ref()
12717 .map(|p| self.eval_expr(p))
12718 .transpose()?
12719 .map(|v| v.is_true())
12720 .unwrap_or(false);
12721 let init_val = self.eval_expr(init)?;
12722 let list_val = self.eval_expr(list)?;
12723 let items = list_val.to_list();
12724 if items.is_empty() {
12725 return Ok(init_val);
12726 }
12727 let block = block.clone();
12728 let subs = self.subs.clone();
12729 let scope_capture = self.scope.capture();
12730 let cap: &[(String, StrykeValue)] = scope_capture.as_slice();
12731 if items.len() == 1 {
12732 return Ok(fold_preduce_init_step(
12733 &subs,
12734 cap,
12735 &block,
12736 preduce_init_fold_identity(&init_val),
12737 items.into_iter().next().unwrap(),
12738 ));
12739 }
12740 let pmap_progress = PmapProgress::new(show_progress, items.len());
12741 let result = items
12742 .into_par_iter()
12743 .fold(
12744 || preduce_init_fold_identity(&init_val),
12745 |acc, item| {
12746 pmap_progress.tick();
12747 fold_preduce_init_step(&subs, cap, &block, acc, item)
12748 },
12749 )
12750 .reduce(
12751 || preduce_init_fold_identity(&init_val),
12752 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
12753 );
12754 pmap_progress.finish();
12755 Ok(result)
12756 }
12757
12758 ExprKind::PMapReduceExpr {
12759 map_block,
12760 reduce_block,
12761 list,
12762 progress,
12763 } => {
12764 let show_progress = progress
12765 .as_ref()
12766 .map(|p| self.eval_expr(p))
12767 .transpose()?
12768 .map(|v| v.is_true())
12769 .unwrap_or(false);
12770 let list_val = self.eval_expr(list)?;
12771 let items = list_val.to_list();
12772 if items.is_empty() {
12773 return Ok(StrykeValue::UNDEF);
12774 }
12775 let map_block = map_block.clone();
12776 let reduce_block = reduce_block.clone();
12777 let subs = self.subs.clone();
12778 let scope_capture = self.scope.capture();
12779 if items.len() == 1 {
12780 let mut local_interp = VMHelper::new();
12781 local_interp.subs = subs.clone();
12782 local_interp.scope.restore_capture(&scope_capture);
12783 local_interp.scope.set_topic(items[0].clone());
12784 return match local_interp.exec_block_no_scope(&map_block) {
12785 Ok(v) => Ok(v),
12786 Err(_) => Ok(StrykeValue::UNDEF),
12787 };
12788 }
12789 let pmap_progress = PmapProgress::new(show_progress, items.len());
12790 let result = items
12791 .into_par_iter()
12792 .map(|item| {
12793 let mut local_interp = VMHelper::new();
12794 local_interp.subs = subs.clone();
12795 local_interp.scope.restore_capture(&scope_capture);
12796 local_interp.scope.set_topic(item);
12797 let val = match local_interp.exec_block_no_scope(&map_block) {
12798 Ok(val) => val,
12799 Err(_) => StrykeValue::UNDEF,
12800 };
12801 pmap_progress.tick();
12802 val
12803 })
12804 .reduce_with(|a, b| {
12805 let mut local_interp = VMHelper::new();
12806 local_interp.subs = subs.clone();
12807 local_interp.scope.restore_capture(&scope_capture);
12808 local_interp.scope.set_sort_pair(a, b);
12809 match local_interp.exec_block_no_scope(&reduce_block) {
12810 Ok(val) => val,
12811 Err(_) => StrykeValue::UNDEF,
12812 }
12813 });
12814 pmap_progress.finish();
12815 Ok(result.unwrap_or(StrykeValue::UNDEF))
12816 }
12817
12818 ExprKind::PcacheExpr {
12819 block,
12820 list,
12821 progress,
12822 } => {
12823 let show_progress = progress
12824 .as_ref()
12825 .map(|p| self.eval_expr(p))
12826 .transpose()?
12827 .map(|v| v.is_true())
12828 .unwrap_or(false);
12829 let list_val = self.eval_expr(list)?;
12830 let items = list_val.to_list();
12831 let block = block.clone();
12832 let subs = self.subs.clone();
12833 let scope_capture = self.scope.capture();
12834 let cache = &*crate::pcache::GLOBAL_PCACHE;
12835 let pmap_progress = PmapProgress::new(show_progress, items.len());
12836 let results: Vec<StrykeValue> = items
12837 .into_par_iter()
12838 .map(|item| {
12839 let k = crate::pcache::cache_key(&item);
12840 if let Some(v) = cache.get(&k) {
12841 pmap_progress.tick();
12842 return v.clone();
12843 }
12844 let mut local_interp = VMHelper::new();
12845 local_interp.subs = subs.clone();
12846 local_interp.scope.restore_capture(&scope_capture);
12847 local_interp.scope.set_topic(item.clone());
12848 let val = match local_interp.exec_block_no_scope(&block) {
12849 Ok(v) => v,
12850 Err(_) => StrykeValue::UNDEF,
12851 };
12852 cache.insert(k, val.clone());
12853 pmap_progress.tick();
12854 val
12855 })
12856 .collect();
12857 pmap_progress.finish();
12858 Ok(StrykeValue::array(results))
12859 }
12860
12861 ExprKind::PselectExpr { receivers, timeout } => {
12862 let mut rx_vals = Vec::with_capacity(receivers.len());
12863 for r in receivers {
12864 rx_vals.push(self.eval_expr(r)?);
12865 }
12866 let dur = if let Some(t) = timeout.as_ref() {
12867 Some(std::time::Duration::from_secs_f64(
12868 self.eval_expr(t)?.to_number().max(0.0),
12869 ))
12870 } else {
12871 None
12872 };
12873 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
12874 &rx_vals, dur, line,
12875 )?)
12876 }
12877
12878 ExprKind::Push { array, values } => {
12880 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
12881 }
12882 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
12883 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
12884 ExprKind::Unshift { array, values } => {
12885 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
12886 }
12887 ExprKind::Splice {
12888 array,
12889 offset,
12890 length,
12891 replacement,
12892 } => self.eval_splice_expr(
12893 array.as_ref(),
12894 offset.as_deref(),
12895 length.as_deref(),
12896 replacement.as_slice(),
12897 ctx,
12898 line,
12899 ),
12900 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
12901 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
12902 ExprKind::Keys(expr) => {
12903 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
12904 let keys = Self::keys_from_value(val, line)?;
12905 if ctx == WantarrayCtx::List {
12906 Ok(keys)
12907 } else {
12908 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
12909 Ok(StrykeValue::integer(n as i64))
12910 }
12911 }
12912 ExprKind::Values(expr) => {
12913 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
12914 let vals = Self::values_from_value(val, line)?;
12915 if ctx == WantarrayCtx::List {
12916 Ok(vals)
12917 } else {
12918 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
12919 Ok(StrykeValue::integer(n as i64))
12920 }
12921 }
12922 ExprKind::Each(_) => {
12923 Ok(StrykeValue::array(vec![]))
12925 }
12926
12927 ExprKind::Chomp(expr) => {
12929 let val = self.eval_expr(expr)?;
12930 self.chomp_inplace_execute(val, expr)
12931 }
12932 ExprKind::Chop(expr) => {
12933 let val = self.eval_expr(expr)?;
12934 self.chop_inplace_execute(val, expr)
12935 }
12936 ExprKind::Length(expr) => {
12937 let val = self.eval_expr(expr)?;
12938 Ok(if let Some(a) = val.as_array_vec() {
12939 StrykeValue::integer(a.len() as i64)
12940 } else if let Some(h) = val.as_hash_map() {
12941 StrykeValue::integer(h.len() as i64)
12942 } else if let Some(b) = val.as_bytes_arc() {
12943 StrykeValue::integer(b.len() as i64)
12945 } else {
12946 let s = val.to_string();
12947 let n = if self.utf8_pragma {
12948 s.chars().count()
12949 } else {
12950 s.len()
12951 };
12952 StrykeValue::integer(n as i64)
12953 })
12954 }
12955 ExprKind::Substr {
12956 string,
12957 offset,
12958 length,
12959 replacement,
12960 } => self.eval_substr_expr(
12961 string.as_ref(),
12962 offset.as_ref(),
12963 length.as_deref(),
12964 replacement.as_deref(),
12965 line,
12966 ),
12967 ExprKind::Index {
12968 string,
12969 substr,
12970 position,
12971 } => {
12972 let s = self.eval_expr(string)?.to_string();
12973 let sub = self.eval_expr(substr)?.to_string();
12974 let pos = if let Some(p) = position {
12977 let raw = self.eval_expr(p)?.to_int();
12978 if raw < 0 {
12979 0usize
12980 } else {
12981 (raw as usize).min(s.len())
12982 }
12983 } else {
12984 0
12985 };
12986 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
12987 Ok(StrykeValue::integer(result))
12988 }
12989 ExprKind::Rindex {
12990 string,
12991 substr,
12992 position,
12993 } => {
12994 let s = self.eval_expr(string)?.to_string();
12995 let sub = self.eval_expr(substr)?.to_string();
12996 let result = if let Some(p) = position {
12999 let raw = self.eval_expr(p)?.to_int();
13000 if raw < 0 {
13001 -1
13002 } else {
13003 let end = (raw as usize).saturating_add(sub.len()).min(s.len());
13004 s[..end].rfind(&sub).map(|i| i as i64).unwrap_or(-1)
13005 }
13006 } else {
13007 s.rfind(&sub).map(|i| i as i64).unwrap_or(-1)
13008 };
13009 Ok(StrykeValue::integer(result))
13010 }
13011 ExprKind::Sprintf { format, args } => {
13012 let fmt = self.eval_expr(format)?.to_string();
13013 let mut arg_vals = Vec::new();
13016 for a in args {
13017 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
13018 if let Some(items) = v.as_array_vec() {
13019 arg_vals.extend(items);
13020 } else {
13021 arg_vals.push(v);
13022 }
13023 }
13024 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
13025 Ok(StrykeValue::string(s))
13026 }
13027 ExprKind::JoinExpr { separator, list } => {
13028 let sep = self.eval_expr(separator)?.to_string();
13029 let items = if let ExprKind::List(exprs) = &list.kind {
13033 let saved = self.wantarray_kind;
13034 self.wantarray_kind = WantarrayCtx::List;
13035 let mut vals = Vec::new();
13036 for e in exprs {
13037 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
13038 if let Some(items) = v.as_array_vec() {
13039 vals.extend(items);
13040 } else if v.is_iterator() {
13041 vals.extend(v.into_iterator().collect_all());
13045 } else {
13046 vals.push(v);
13047 }
13048 }
13049 self.wantarray_kind = saved;
13050 vals
13051 } else {
13052 let saved = self.wantarray_kind;
13053 self.wantarray_kind = WantarrayCtx::List;
13054 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
13055 self.wantarray_kind = saved;
13056 if let Some(items) = v.as_array_vec() {
13057 items
13058 } else if v.is_iterator() {
13059 v.into_iterator().collect_all()
13063 } else {
13064 vec![v]
13065 }
13066 };
13067 let mut strs = Vec::with_capacity(items.len());
13068 for v in &items {
13069 strs.push(self.stringify_value(v.clone(), line)?);
13070 }
13071 Ok(StrykeValue::string(strs.join(&sep)))
13072 }
13073 ExprKind::SplitExpr {
13074 pattern,
13075 string,
13076 limit,
13077 } => {
13078 let pat_val = self.eval_expr(pattern)?;
13079 let pat = pat_val
13083 .regex_src_and_flags()
13084 .map(|(s, _)| s)
13085 .unwrap_or_else(|| pat_val.to_string());
13086 let s = self.eval_expr(string)?.to_string();
13087 if s.is_empty() {
13088 return Ok(StrykeValue::array(vec![]));
13089 }
13090 let lim_opt: Option<i64> = limit
13099 .as_ref()
13100 .map(|l| self.eval_expr(l).map(|v| v.to_int()))
13101 .transpose()?;
13102 let re = self.compile_regex(&pat, "", line)?;
13103 let mut parts: Vec<String> = match lim_opt {
13104 Some(l) if l > 0 => re.splitn_strings(&s, l as usize),
13105 _ => re.split_strings(&s),
13106 };
13107
13108 if pat.is_empty() && parts.first().is_some_and(|p| p.is_empty()) {
13114 parts.remove(0);
13115 }
13116 let strip_trailing = matches!(lim_opt, None | Some(0));
13120 if strip_trailing {
13121 while parts.last().is_some_and(|p| p.is_empty()) {
13122 parts.pop();
13123 }
13124 }
13125
13126 Ok(StrykeValue::array(
13127 parts.into_iter().map(StrykeValue::string).collect(),
13128 ))
13129 }
13130
13131 ExprKind::Abs(expr) => {
13133 let val = self.eval_expr(expr)?;
13134 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
13135 return r;
13136 }
13137 Ok(StrykeValue::float(val.to_number().abs()))
13138 }
13139 ExprKind::Int(expr) => {
13140 let val = self.eval_expr(expr)?;
13141 Ok(StrykeValue::integer(val.to_number() as i64))
13142 }
13143 ExprKind::Sqrt(expr) => {
13144 let val = self.eval_expr(expr)?;
13145 Ok(StrykeValue::float(val.to_number().sqrt()))
13146 }
13147 ExprKind::Sin(expr) => {
13148 let val = self.eval_expr(expr)?;
13149 Ok(StrykeValue::float(val.to_number().sin()))
13150 }
13151 ExprKind::Cos(expr) => {
13152 let val = self.eval_expr(expr)?;
13153 Ok(StrykeValue::float(val.to_number().cos()))
13154 }
13155 ExprKind::Atan2 { y, x } => {
13156 let yv = self.eval_expr(y)?.to_number();
13157 let xv = self.eval_expr(x)?.to_number();
13158 Ok(StrykeValue::float(yv.atan2(xv)))
13159 }
13160 ExprKind::Exp(expr) => {
13161 let val = self.eval_expr(expr)?;
13162 Ok(StrykeValue::float(val.to_number().exp()))
13163 }
13164 ExprKind::Log(expr) => {
13165 let val = self.eval_expr(expr)?;
13166 Ok(StrykeValue::float(val.to_number().ln()))
13167 }
13168 ExprKind::Rand(upper) => {
13169 let u = match upper {
13170 Some(e) => self.eval_expr(e)?.to_number(),
13171 None => 1.0,
13172 };
13173 Ok(StrykeValue::float(self.perl_rand(u)))
13174 }
13175 ExprKind::Srand(seed) => {
13176 let s = match seed {
13177 Some(e) => Some(self.eval_expr(e)?.to_number()),
13178 None => None,
13179 };
13180 Ok(StrykeValue::integer(self.perl_srand(s)))
13181 }
13182 ExprKind::Hex(expr) => {
13183 let val = self.eval_expr(expr)?.to_string();
13184 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
13185 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
13186 Ok(StrykeValue::integer(n))
13187 }
13188 ExprKind::Oct(expr) => {
13189 let val = self.eval_expr(expr)?.to_string();
13190 let s = val.trim();
13191 let n = if s.starts_with("0x") || s.starts_with("0X") {
13192 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
13193 } else if s.starts_with("0b") || s.starts_with("0B") {
13194 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
13195 } else if s.starts_with("0o") || s.starts_with("0O") {
13196 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
13197 } else {
13198 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
13199 };
13200 Ok(StrykeValue::integer(n))
13201 }
13202
13203 ExprKind::Lc(expr) => Ok(StrykeValue::string(
13205 self.eval_expr(expr)?.to_string().to_lowercase(),
13206 )),
13207 ExprKind::Uc(expr) => Ok(StrykeValue::string(
13208 self.eval_expr(expr)?.to_string().to_uppercase(),
13209 )),
13210 ExprKind::Lcfirst(expr) => {
13211 let s = self.eval_expr(expr)?.to_string();
13212 let mut chars = s.chars();
13213 let result = match chars.next() {
13214 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
13215 None => String::new(),
13216 };
13217 Ok(StrykeValue::string(result))
13218 }
13219 ExprKind::Ucfirst(expr) => {
13220 let s = self.eval_expr(expr)?.to_string();
13221 let mut chars = s.chars();
13222 let result = match chars.next() {
13223 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
13224 None => String::new(),
13225 };
13226 Ok(StrykeValue::string(result))
13227 }
13228 ExprKind::Fc(expr) => Ok(StrykeValue::string(default_case_fold_str(
13229 &self.eval_expr(expr)?.to_string(),
13230 ))),
13231 ExprKind::Crypt { plaintext, salt } => {
13232 let p = self.eval_expr(plaintext)?.to_string();
13233 let sl = self.eval_expr(salt)?.to_string();
13234 Ok(StrykeValue::string(perl_crypt(&p, &sl)))
13235 }
13236 ExprKind::Pos(e) => {
13237 let key = match e {
13238 None => "_".to_string(),
13239 Some(expr) => match &expr.kind {
13240 ExprKind::ScalarVar(n) => n.clone(),
13241 _ => self.eval_expr(expr)?.to_string(),
13242 },
13243 };
13244 Ok(self
13245 .regex_pos
13246 .get(&key)
13247 .copied()
13248 .flatten()
13249 .map(|p| StrykeValue::integer(p as i64))
13250 .unwrap_or(StrykeValue::UNDEF))
13251 }
13252 ExprKind::Study(expr) => {
13253 let s = self.eval_expr(expr)?.to_string();
13254 Ok(Self::study_return_value(&s))
13255 }
13256
13257 ExprKind::Defined(expr) => {
13259 if let ExprKind::SubroutineRef(name) = &expr.kind {
13261 let exists = self.resolve_sub_by_name(name).is_some();
13262 return Ok(StrykeValue::integer(if exists { 1 } else { 0 }));
13263 }
13264 let val = self.eval_expr(expr)?;
13265 Ok(StrykeValue::integer(if val.is_undef() { 0 } else { 1 }))
13266 }
13267 ExprKind::Ref(expr) => {
13268 let val = self.eval_expr(expr)?;
13269 Ok(val.ref_type())
13270 }
13271 ExprKind::ScalarContext(expr) => {
13272 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
13273 Ok(v.scalar_context())
13274 }
13275
13276 ExprKind::Chr(expr) => {
13278 let n = self.eval_expr(expr)?.to_int() as u32;
13279 Ok(StrykeValue::string(
13280 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
13281 ))
13282 }
13283 ExprKind::Ord(expr) => {
13284 let s = self.eval_expr(expr)?.to_string();
13285 Ok(StrykeValue::integer(
13286 s.chars().next().map(|c| c as i64).unwrap_or(0),
13287 ))
13288 }
13289
13290 ExprKind::OpenMyHandle { .. } => Err(StrykeError::runtime(
13292 "internal: `open my $fh` handle used outside open()",
13293 line,
13294 )
13295 .into()),
13296 ExprKind::Open { handle, mode, file } => {
13297 if let ExprKind::OpenMyHandle { name } = &handle.kind {
13298 self.scope
13299 .declare_scalar_frozen(name, StrykeValue::UNDEF, false, None)?;
13300 self.english_note_lexical_scalar(name);
13301 let mode_s = self.eval_expr(mode)?.to_string();
13302 let file_opt = if let Some(f) = file {
13303 Some(self.eval_expr(f)?.to_string())
13304 } else {
13305 None
13306 };
13307 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
13308 self.scope.set_scalar(name, ret.clone())?;
13309 return Ok(ret);
13310 }
13311 let handle_s = self.eval_expr(handle)?.to_string();
13312 let handle_name = self.resolve_io_handle_name(&handle_s);
13313 let mode_s = self.eval_expr(mode)?.to_string();
13314 let file_opt = if let Some(f) = file {
13315 Some(self.eval_expr(f)?.to_string())
13316 } else {
13317 None
13318 };
13319 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
13320 .map_err(Into::into)
13321 }
13322 ExprKind::Close(expr) => {
13323 let s = self.eval_expr(expr)?.to_string();
13324 let name = self.resolve_io_handle_name(&s);
13325 self.close_builtin_execute(name).map_err(Into::into)
13326 }
13327 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
13328 self.readline_builtin_execute_list(handle.as_deref())
13329 } else {
13330 self.readline_builtin_execute(handle.as_deref())
13331 }
13332 .map_err(Into::into),
13333 ExprKind::Eof(expr) => match expr {
13334 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
13335 Some(e) => {
13336 let name = self.eval_expr(e)?;
13337 self.eof_builtin_execute(&[name], line).map_err(Into::into)
13338 }
13339 },
13340
13341 ExprKind::Opendir { handle, path } => {
13342 let h = self.eval_expr(handle)?.to_string();
13343 let p = self.eval_expr(path)?.to_string();
13344 Ok(self.opendir_handle(&h, &p))
13345 }
13346 ExprKind::Readdir(e) => {
13347 let h = self.eval_expr(e)?.to_string();
13348 Ok(if ctx == WantarrayCtx::List {
13349 self.readdir_handle_list(&h)
13350 } else {
13351 self.readdir_handle(&h)
13352 })
13353 }
13354 ExprKind::Closedir(e) => {
13355 let h = self.eval_expr(e)?.to_string();
13356 Ok(self.closedir_handle(&h))
13357 }
13358 ExprKind::Rewinddir(e) => {
13359 let h = self.eval_expr(e)?.to_string();
13360 Ok(self.rewinddir_handle(&h))
13361 }
13362 ExprKind::Telldir(e) => {
13363 let h = self.eval_expr(e)?.to_string();
13364 Ok(self.telldir_handle(&h))
13365 }
13366 ExprKind::Seekdir { handle, position } => {
13367 let h = self.eval_expr(handle)?.to_string();
13368 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
13369 Ok(self.seekdir_handle(&h, pos))
13370 }
13371
13372 ExprKind::FileTest { op, expr } => {
13374 let raw = self.eval_expr(expr)?.to_string();
13375 let path = self.resolve_stryke_path_string(&raw);
13376 if matches!(op, 'M' | 'A' | 'C') {
13378 #[cfg(unix)]
13379 {
13380 return match crate::perl_fs::filetest_age_days(&path, *op) {
13381 Some(days) => Ok(StrykeValue::float(days)),
13382 None => Ok(StrykeValue::UNDEF),
13383 };
13384 }
13385 #[cfg(not(unix))]
13386 return Ok(StrykeValue::UNDEF);
13387 }
13388 if *op == 's' {
13390 return match std::fs::metadata(&path) {
13391 Ok(m) => Ok(StrykeValue::integer(m.len() as i64)),
13392 Err(_) => Ok(StrykeValue::UNDEF),
13393 };
13394 }
13395 let result = match op {
13396 'e' => std::path::Path::new(&path).exists(),
13397 'f' => std::path::Path::new(&path).is_file(),
13398 'd' => std::path::Path::new(&path).is_dir(),
13399 'l' => std::path::Path::new(&path).is_symlink(),
13400 #[cfg(unix)]
13401 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
13402 #[cfg(not(unix))]
13403 'r' => std::fs::metadata(&path).is_ok(),
13404 #[cfg(unix)]
13405 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
13406 #[cfg(not(unix))]
13407 'w' => std::fs::metadata(&path).is_ok(),
13408 #[cfg(unix)]
13409 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
13410 #[cfg(not(unix))]
13411 'x' => false,
13412 #[cfg(unix)]
13413 'o' => crate::perl_fs::filetest_owned_effective(&path),
13414 #[cfg(not(unix))]
13415 'o' => false,
13416 #[cfg(unix)]
13417 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
13418 #[cfg(not(unix))]
13419 'R' => false,
13420 #[cfg(unix)]
13421 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
13422 #[cfg(not(unix))]
13423 'W' => false,
13424 #[cfg(unix)]
13425 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
13426 #[cfg(not(unix))]
13427 'X' => false,
13428 #[cfg(unix)]
13429 'O' => crate::perl_fs::filetest_owned_real(&path),
13430 #[cfg(not(unix))]
13431 'O' => false,
13432 'z' => std::fs::metadata(&path)
13433 .map(|m| m.len() == 0)
13434 .unwrap_or(true),
13435 't' => crate::perl_fs::filetest_is_tty(&path),
13436 #[cfg(unix)]
13437 'p' => crate::perl_fs::filetest_is_pipe(&path),
13438 #[cfg(not(unix))]
13439 'p' => false,
13440 #[cfg(unix)]
13441 'S' => crate::perl_fs::filetest_is_socket(&path),
13442 #[cfg(not(unix))]
13443 'S' => false,
13444 #[cfg(unix)]
13445 'b' => crate::perl_fs::filetest_is_block_device(&path),
13446 #[cfg(not(unix))]
13447 'b' => false,
13448 #[cfg(unix)]
13449 'c' => crate::perl_fs::filetest_is_char_device(&path),
13450 #[cfg(not(unix))]
13451 'c' => false,
13452 #[cfg(unix)]
13453 'u' => crate::perl_fs::filetest_is_setuid(&path),
13454 #[cfg(not(unix))]
13455 'u' => false,
13456 #[cfg(unix)]
13457 'g' => crate::perl_fs::filetest_is_setgid(&path),
13458 #[cfg(not(unix))]
13459 'g' => false,
13460 #[cfg(unix)]
13461 'k' => crate::perl_fs::filetest_is_sticky(&path),
13462 #[cfg(not(unix))]
13463 'k' => false,
13464 'T' => crate::perl_fs::filetest_is_text(&path),
13465 'B' => crate::perl_fs::filetest_is_binary(&path),
13466 _ => false,
13467 };
13468 Ok(StrykeValue::integer(if result { 1 } else { 0 }))
13469 }
13470
13471 ExprKind::System(args) => {
13473 let mut cmd_args = Vec::new();
13480 for a in args {
13481 cmd_args.push(self.eval_expr(a)?.to_string());
13482 }
13483 if cmd_args.is_empty() {
13484 self.child_exit_status = -1;
13485 return Ok(StrykeValue::integer(-1));
13486 }
13487 let status = if cmd_args.len() == 1 {
13488 Command::new("sh").arg("-c").arg(&cmd_args[0]).status()
13489 } else {
13490 Command::new(&cmd_args[0]).args(&cmd_args[1..]).status()
13491 };
13492 match status {
13493 Ok(s) => {
13494 self.record_child_exit_status(s);
13495 Ok(StrykeValue::integer(self.child_exit_status))
13496 }
13497 Err(e) => {
13498 self.apply_io_error_to_errno(&e);
13499 self.child_exit_status = -1;
13500 Ok(StrykeValue::integer(-1))
13501 }
13502 }
13503 }
13504 ExprKind::Exec(args) => {
13505 let mut cmd_args = Vec::new();
13506 for a in args {
13507 cmd_args.push(self.eval_expr(a)?.to_string());
13508 }
13509 if cmd_args.is_empty() {
13510 return Ok(StrykeValue::integer(-1));
13511 }
13512 let status = Command::new("sh")
13513 .arg("-c")
13514 .arg(cmd_args.join(" "))
13515 .status();
13516 match status {
13517 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
13518 Err(e) => {
13519 self.apply_io_error_to_errno(&e);
13520 Ok(StrykeValue::integer(-1))
13521 }
13522 }
13523 }
13524 ExprKind::Eval(expr) => {
13525 self.eval_nesting += 1;
13526 let out = match &expr.kind {
13527 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
13528 Ok(v) => {
13529 self.clear_eval_error();
13530 Ok(v)
13531 }
13532 Err(FlowOrError::Error(e)) => {
13533 self.set_eval_error_from_perl_error(&e);
13534 Ok(StrykeValue::UNDEF)
13535 }
13536 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
13537 },
13538 _ => {
13539 let code = self.eval_expr(expr)?.to_string();
13540 match crate::parse_and_run_string(&code, self) {
13542 Ok(v) => {
13543 self.clear_eval_error();
13544 Ok(v)
13545 }
13546 Err(e) => {
13547 self.set_eval_error(e.to_string());
13548 Ok(StrykeValue::UNDEF)
13549 }
13550 }
13551 }
13552 };
13553 self.eval_nesting -= 1;
13554 out
13555 }
13556 ExprKind::Do(expr) => match &expr.kind {
13557 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
13558 _ => {
13559 let val = self.eval_expr(expr)?;
13560 let filename = val.to_string();
13561 match read_file_text_perl_compat(&filename) {
13562 Ok(code) => {
13563 let code = crate::data_section::strip_perl_end_marker(&code);
13564 match crate::parse_and_run_string_in_file(code, self, &filename) {
13565 Ok(v) => Ok(v),
13566 Err(e) => {
13567 self.set_eval_error(e.to_string());
13568 Ok(StrykeValue::UNDEF)
13569 }
13570 }
13571 }
13572 Err(e) => {
13573 self.apply_io_error_to_errno(&e);
13574 Ok(StrykeValue::UNDEF)
13575 }
13576 }
13577 }
13578 },
13579 ExprKind::Require(expr) => {
13580 let spec = self.eval_expr(expr)?.to_string();
13581 self.require_execute(&spec, line)
13582 .map_err(FlowOrError::Error)
13583 }
13584 ExprKind::Exit(code) => {
13585 let c = if let Some(e) = code {
13586 self.eval_expr(e)?.to_int() as i32
13587 } else {
13588 0
13589 };
13590 Err(StrykeError::new(ErrorKind::Exit(c), "", line, &self.file).into())
13591 }
13592 ExprKind::Chdir(expr) => {
13593 let path = self.eval_expr(expr)?.to_string();
13594 match std::env::set_current_dir(&path) {
13595 Ok(_) => {
13596 if let Ok(c) = std::env::current_dir() {
13597 self.stryke_pwd = std::fs::canonicalize(&c).unwrap_or(c);
13598 }
13599 Ok(StrykeValue::integer(1))
13600 }
13601 Err(e) => {
13602 self.apply_io_error_to_errno(&e);
13603 Ok(StrykeValue::integer(0))
13604 }
13605 }
13606 }
13607 ExprKind::Mkdir { path, mode: _ } => {
13608 let raw = self.eval_expr(path)?.to_string();
13609 let p = self.resolve_stryke_path_string(&raw);
13610 match std::fs::create_dir(&p) {
13611 Ok(_) => Ok(StrykeValue::integer(1)),
13612 Err(e) => {
13613 self.apply_io_error_to_errno(&e);
13614 Ok(StrykeValue::integer(0))
13615 }
13616 }
13617 }
13618 ExprKind::Unlink(args) => {
13619 let mut count = 0i64;
13620 for a in args {
13621 let raw = self.eval_expr(a)?.to_string();
13622 let path = self.resolve_stryke_path_string(&raw);
13623 if std::fs::remove_file(&path).is_ok() {
13624 count += 1;
13625 }
13626 }
13627 Ok(StrykeValue::integer(count))
13628 }
13629 ExprKind::Rename { old, new } => {
13630 let o_raw = self.eval_expr(old)?.to_string();
13631 let n_raw = self.eval_expr(new)?.to_string();
13632 let o = self.resolve_stryke_path_string(&o_raw);
13633 let n = self.resolve_stryke_path_string(&n_raw);
13634 Ok(crate::perl_fs::rename_paths(&o, &n))
13635 }
13636 ExprKind::Chmod(args) => {
13637 let mode = self.eval_expr(&args[0])?.to_int();
13638 let mut paths = Vec::new();
13639 for a in &args[1..] {
13640 let raw = self.eval_expr(a)?.to_string();
13641 paths.push(self.resolve_stryke_path_string(&raw));
13642 }
13643 Ok(StrykeValue::integer(crate::perl_fs::chmod_paths(
13644 &paths, mode,
13645 )))
13646 }
13647 ExprKind::Chown(args) => {
13648 let uid = self.eval_expr(&args[0])?.to_int();
13649 let gid = self.eval_expr(&args[1])?.to_int();
13650 let mut paths = Vec::new();
13651 for a in &args[2..] {
13652 let raw = self.eval_expr(a)?.to_string();
13653 paths.push(self.resolve_stryke_path_string(&raw));
13654 }
13655 Ok(StrykeValue::integer(crate::perl_fs::chown_paths(
13656 &paths, uid, gid,
13657 )))
13658 }
13659 ExprKind::Stat(e) => {
13660 let raw = self.eval_expr(e)?.to_string();
13661 let path = self.resolve_stryke_path_string(&raw);
13662 Ok(crate::perl_fs::stat_path(&path, false))
13663 }
13664 ExprKind::Lstat(e) => {
13665 let raw = self.eval_expr(e)?.to_string();
13666 let path = self.resolve_stryke_path_string(&raw);
13667 Ok(crate::perl_fs::stat_path(&path, true))
13668 }
13669 ExprKind::Link { old, new } => {
13670 let o_raw = self.eval_expr(old)?.to_string();
13671 let n_raw = self.eval_expr(new)?.to_string();
13672 let o = self.resolve_stryke_path_string(&o_raw);
13673 let n = self.resolve_stryke_path_string(&n_raw);
13674 Ok(crate::perl_fs::link_hard(&o, &n))
13675 }
13676 ExprKind::Symlink { old, new } => {
13677 let o = self.eval_expr(old)?.to_string();
13678 let n_raw = self.eval_expr(new)?.to_string();
13679 let n = self.resolve_stryke_path_string(&n_raw);
13680 Ok(crate::perl_fs::link_sym(&o, &n))
13681 }
13682 ExprKind::Readlink(e) => {
13683 let raw = self.eval_expr(e)?.to_string();
13684 let path = self.resolve_stryke_path_string(&raw);
13685 Ok(crate::perl_fs::read_link(&path))
13686 }
13687 ExprKind::Files(args) => {
13688 let dir_raw = if args.is_empty() {
13689 ".".to_string()
13690 } else {
13691 self.eval_expr(&args[0])?.to_string()
13692 };
13693 let dir = self.resolve_stryke_path_string(&dir_raw);
13694 Ok(crate::perl_fs::list_files(&dir))
13695 }
13696 ExprKind::Filesf(args) => {
13697 let dir_raw = if args.is_empty() {
13698 ".".to_string()
13699 } else {
13700 self.eval_expr(&args[0])?.to_string()
13701 };
13702 let dir = self.resolve_stryke_path_string(&dir_raw);
13703 Ok(crate::perl_fs::list_filesf(&dir))
13704 }
13705 ExprKind::FilesfRecursive(args) => {
13706 let dir_raw = if args.is_empty() {
13707 ".".to_string()
13708 } else {
13709 self.eval_expr(&args[0])?.to_string()
13710 };
13711 let dir = self.resolve_stryke_path_string(&dir_raw);
13712 Ok(StrykeValue::iterator(Arc::new(
13713 crate::value::FsWalkIterator::new(&dir, true),
13714 )))
13715 }
13716 ExprKind::Dirs(args) => {
13717 let dir_raw = if args.is_empty() {
13718 ".".to_string()
13719 } else {
13720 self.eval_expr(&args[0])?.to_string()
13721 };
13722 let dir = self.resolve_stryke_path_string(&dir_raw);
13723 Ok(crate::perl_fs::list_dirs(&dir))
13724 }
13725 ExprKind::DirsRecursive(args) => {
13726 let dir_raw = if args.is_empty() {
13727 ".".to_string()
13728 } else {
13729 self.eval_expr(&args[0])?.to_string()
13730 };
13731 let dir = self.resolve_stryke_path_string(&dir_raw);
13732 Ok(StrykeValue::iterator(Arc::new(
13733 crate::value::FsWalkIterator::new(&dir, false),
13734 )))
13735 }
13736 ExprKind::SymLinks(args) => {
13737 let dir_raw = if args.is_empty() {
13738 ".".to_string()
13739 } else {
13740 self.eval_expr(&args[0])?.to_string()
13741 };
13742 let dir = self.resolve_stryke_path_string(&dir_raw);
13743 Ok(crate::perl_fs::list_sym_links(&dir))
13744 }
13745 ExprKind::Sockets(args) => {
13746 let dir_raw = if args.is_empty() {
13747 ".".to_string()
13748 } else {
13749 self.eval_expr(&args[0])?.to_string()
13750 };
13751 let dir = self.resolve_stryke_path_string(&dir_raw);
13752 Ok(crate::perl_fs::list_sockets(&dir))
13753 }
13754 ExprKind::Pipes(args) => {
13755 let dir_raw = if args.is_empty() {
13756 ".".to_string()
13757 } else {
13758 self.eval_expr(&args[0])?.to_string()
13759 };
13760 let dir = self.resolve_stryke_path_string(&dir_raw);
13761 Ok(crate::perl_fs::list_pipes(&dir))
13762 }
13763 ExprKind::BlockDevices(args) => {
13764 let dir_raw = if args.is_empty() {
13765 ".".to_string()
13766 } else {
13767 self.eval_expr(&args[0])?.to_string()
13768 };
13769 let dir = self.resolve_stryke_path_string(&dir_raw);
13770 Ok(crate::perl_fs::list_block_devices(&dir))
13771 }
13772 ExprKind::CharDevices(args) => {
13773 let dir_raw = if args.is_empty() {
13774 ".".to_string()
13775 } else {
13776 self.eval_expr(&args[0])?.to_string()
13777 };
13778 let dir = self.resolve_stryke_path_string(&dir_raw);
13779 Ok(crate::perl_fs::list_char_devices(&dir))
13780 }
13781 ExprKind::Executables(args) => {
13782 let dir_raw = if args.is_empty() {
13783 ".".to_string()
13784 } else {
13785 self.eval_expr(&args[0])?.to_string()
13786 };
13787 let dir = self.resolve_stryke_path_string(&dir_raw);
13788 Ok(crate::perl_fs::list_executables(&dir))
13789 }
13790 ExprKind::Glob(args) => {
13791 let mut pats = Vec::new();
13798 for a in args {
13799 pats.push(self.eval_expr(a)?.to_string());
13800 }
13801 Ok(crate::perl_fs::glob_patterns(&pats))
13802 }
13803 ExprKind::GlobPar { args, progress } => {
13804 let mut pats = Vec::new();
13805 for a in args {
13806 pats.push(self.eval_expr(a)?.to_string());
13807 }
13808 let show_progress = progress
13809 .as_ref()
13810 .map(|p| self.eval_expr(p))
13811 .transpose()?
13812 .map(|v| v.is_true())
13813 .unwrap_or(false);
13814 if show_progress {
13815 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
13816 } else {
13817 Ok(crate::perl_fs::glob_par_patterns(&pats))
13818 }
13819 }
13820 ExprKind::ParSed { args, progress } => {
13821 let has_progress = progress.is_some();
13822 let mut vals: Vec<StrykeValue> = Vec::new();
13823 for a in args {
13824 vals.push(self.eval_expr(a)?);
13825 }
13826 if let Some(p) = progress {
13827 vals.push(self.eval_expr(p.as_ref())?);
13828 }
13829 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
13830 }
13831 ExprKind::Bless { ref_expr, class } => {
13832 let val = self.eval_expr(ref_expr)?;
13833 let class_name = if let Some(c) = class {
13834 self.eval_expr(c)?.to_string()
13835 } else {
13836 self.scope.get_scalar("__PACKAGE__").to_string()
13837 };
13838 Ok(StrykeValue::blessed(Arc::new(
13839 crate::value::BlessedRef::new_blessed(class_name, val),
13840 )))
13841 }
13842 ExprKind::Caller(_) => {
13843 let sub_name = self
13849 .current_sub_stack
13850 .last()
13851 .map(|s| StrykeValue::string(s.name.clone()))
13852 .unwrap_or(StrykeValue::UNDEF);
13853 let pkg = self.current_package();
13854 Ok(StrykeValue::array(vec![
13855 StrykeValue::string(pkg),
13856 StrykeValue::string(self.file.clone()),
13857 StrykeValue::integer(line as i64),
13858 sub_name,
13859 ]))
13860 }
13861 ExprKind::Wantarray => Ok(match self.wantarray_kind {
13862 WantarrayCtx::Void => StrykeValue::UNDEF,
13863 WantarrayCtx::Scalar => StrykeValue::integer(0),
13864 WantarrayCtx::List => StrykeValue::integer(1),
13865 }),
13866
13867 ExprKind::List(exprs) => {
13868 if ctx == WantarrayCtx::Scalar {
13870 if let Some(last) = exprs.last() {
13871 for e in &exprs[..exprs.len() - 1] {
13873 self.eval_expr(e)?;
13874 }
13875 return self.eval_expr(last);
13876 } else {
13877 return Ok(StrykeValue::UNDEF);
13878 }
13879 }
13880 let mut vals = Vec::new();
13881 for e in exprs {
13882 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
13883 if let Some(items) = v.as_array_vec() {
13884 vals.extend(items);
13885 } else {
13886 vals.push(v);
13887 }
13888 }
13889 if vals.len() == 1 {
13890 Ok(vals.pop().unwrap())
13891 } else {
13892 Ok(StrykeValue::array(vals))
13893 }
13894 }
13895
13896 ExprKind::PostfixIf { expr, condition } => {
13898 if self.eval_postfix_condition(condition)? {
13899 self.eval_expr(expr)
13900 } else {
13901 Ok(StrykeValue::UNDEF)
13902 }
13903 }
13904 ExprKind::PostfixUnless { expr, condition } => {
13905 if !self.eval_postfix_condition(condition)? {
13906 self.eval_expr(expr)
13907 } else {
13908 Ok(StrykeValue::UNDEF)
13909 }
13910 }
13911 ExprKind::PostfixWhile { expr, condition } => {
13912 let is_do_block = matches!(
13915 &expr.kind,
13916 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
13917 );
13918 let mut last = StrykeValue::UNDEF;
13919 if is_do_block {
13920 loop {
13921 last = self.eval_expr(expr)?;
13922 if !self.eval_postfix_condition(condition)? {
13923 break;
13924 }
13925 }
13926 } else {
13927 loop {
13928 if !self.eval_postfix_condition(condition)? {
13929 break;
13930 }
13931 last = self.eval_expr(expr)?;
13932 }
13933 }
13934 Ok(last)
13935 }
13936 ExprKind::PostfixUntil { expr, condition } => {
13937 let is_do_block = matches!(
13938 &expr.kind,
13939 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
13940 );
13941 let mut last = StrykeValue::UNDEF;
13942 if is_do_block {
13943 loop {
13944 last = self.eval_expr(expr)?;
13945 if self.eval_postfix_condition(condition)? {
13946 break;
13947 }
13948 }
13949 } else {
13950 loop {
13951 if self.eval_postfix_condition(condition)? {
13952 break;
13953 }
13954 last = self.eval_expr(expr)?;
13955 }
13956 }
13957 Ok(last)
13958 }
13959 ExprKind::PostfixForeach { expr, list } => {
13960 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
13961 let mut last = StrykeValue::UNDEF;
13962 for item in items {
13963 self.scope.set_topic(item);
13964 last = self.eval_expr(expr)?;
13965 }
13966 Ok(last)
13967 }
13968 }
13969 }
13970
13971 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
13974 match op {
13975 BinOp::Add => Some("+"),
13976 BinOp::Sub => Some("-"),
13977 BinOp::Mul => Some("*"),
13978 BinOp::Div => Some("/"),
13979 BinOp::Mod => Some("%"),
13980 BinOp::Pow => Some("**"),
13981 BinOp::Concat => Some("."),
13982 BinOp::StrEq => Some("eq"),
13983 BinOp::NumEq => Some("=="),
13984 BinOp::StrNe => Some("ne"),
13985 BinOp::NumNe => Some("!="),
13986 BinOp::StrLt => Some("lt"),
13987 BinOp::StrGt => Some("gt"),
13988 BinOp::StrLe => Some("le"),
13989 BinOp::StrGe => Some("ge"),
13990 BinOp::NumLt => Some("<"),
13991 BinOp::NumGt => Some(">"),
13992 BinOp::NumLe => Some("<="),
13993 BinOp::NumGe => Some(">="),
13994 BinOp::Spaceship => Some("<=>"),
13995 BinOp::StrCmp => Some("cmp"),
13996 _ => None,
13997 }
13998 }
13999
14000 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
14002 map.get("").or_else(|| map.get("\"\""))
14003 }
14004
14005 pub(crate) fn stringify_value(
14007 &mut self,
14008 v: StrykeValue,
14009 line: usize,
14010 ) -> Result<String, FlowOrError> {
14011 if let Some(r) = self.try_overload_stringify(&v, line) {
14012 let pv = r?;
14013 return Ok(pv.to_string());
14014 }
14015 Ok(v.to_string())
14016 }
14017
14018 pub(crate) fn perl_sprintf_stringify(
14020 &mut self,
14021 fmt: &str,
14022 args: &[StrykeValue],
14023 line: usize,
14024 ) -> Result<String, FlowOrError> {
14025 let (out, pending_n) = {
14027 let mut stringify = |v: &StrykeValue| -> Result<String, FlowOrError> {
14028 self.stringify_value(v.clone(), line)
14029 };
14030 perl_sprintf_format_full(fmt, args, &mut stringify)?
14031 };
14032 for (target, count) in pending_n {
14034 self.assign_scalar_ref_deref(target, StrykeValue::integer(count), line)?;
14035 }
14036 Ok(out)
14037 }
14038
14039 pub(crate) fn render_format_template(
14041 &mut self,
14042 tmpl: &crate::format::FormatTemplate,
14043 line: usize,
14044 ) -> Result<String, FlowOrError> {
14045 use crate::format::{FormatRecord, PictureSegment};
14046 let mut buf = String::new();
14047 for rec in &tmpl.records {
14048 match rec {
14049 FormatRecord::Literal(s) => {
14050 buf.push_str(s);
14051 buf.push('\n');
14052 }
14053 FormatRecord::Picture { segments, exprs } => {
14054 let mut vals: Vec<String> = Vec::new();
14055 for e in exprs {
14056 let v = self.eval_expr(e)?;
14057 vals.push(self.stringify_value(v, line)?);
14058 }
14059 let mut vi = 0usize;
14060 let mut line_out = String::new();
14061 for seg in segments {
14062 match seg {
14063 PictureSegment::Literal(t) => line_out.push_str(t),
14064 PictureSegment::Field {
14065 width,
14066 align,
14067 kind: _,
14068 } => {
14069 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
14070 vi += 1;
14071 line_out.push_str(&crate::format::pad_field(s, *width, *align));
14072 }
14073 }
14074 }
14075 buf.push_str(line_out.trim_end());
14076 buf.push('\n');
14077 }
14078 }
14079 }
14080 Ok(buf)
14081 }
14082
14083 pub(crate) fn resolve_write_output_handle(
14085 &self,
14086 v: &StrykeValue,
14087 line: usize,
14088 ) -> StrykeResult<String> {
14089 if let Some(n) = v.as_io_handle_name() {
14090 let n = self.resolve_io_handle_name(&n);
14091 if self.is_bound_handle(&n) {
14092 return Ok(n);
14093 }
14094 }
14095 if let Some(s) = v.as_str() {
14096 if self.is_bound_handle(&s) {
14097 return Ok(self.resolve_io_handle_name(&s));
14098 }
14099 }
14100 let s = v.to_string();
14101 if self.is_bound_handle(&s) {
14102 return Ok(self.resolve_io_handle_name(&s));
14103 }
14104 Err(StrykeError::runtime(
14105 format!("write: invalid or unopened filehandle {}", s),
14106 line,
14107 ))
14108 }
14109
14110 pub(crate) fn write_format_execute(
14114 &mut self,
14115 args: &[StrykeValue],
14116 line: usize,
14117 ) -> StrykeResult<StrykeValue> {
14118 let handle_name = match args.len() {
14119 0 => self.default_print_handle.clone(),
14120 1 => self.resolve_write_output_handle(&args[0], line)?,
14121 _ => {
14122 return Err(StrykeError::runtime("write: too many arguments", line));
14123 }
14124 };
14125 let pkg = self.current_package();
14126 let mut fmt_name = self.scope.get_scalar("~").to_string();
14127 if fmt_name.is_empty() {
14128 fmt_name = "STDOUT".to_string();
14129 }
14130 let key = format!("{}::{}", pkg, fmt_name);
14131 let tmpl = self
14132 .format_templates
14133 .get(&key)
14134 .map(Arc::clone)
14135 .ok_or_else(|| {
14136 StrykeError::runtime(
14137 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
14138 line,
14139 )
14140 })?;
14141 let out = self
14142 .render_format_template(&tmpl, line)
14143 .map_err(|e| match e {
14144 FlowOrError::Error(e) => e,
14145 FlowOrError::Flow(_) => {
14146 StrykeError::runtime("write: unexpected control flow", line)
14147 }
14148 })?;
14149 self.write_formatted_print(handle_name.as_str(), &out, line)?;
14150 Ok(StrykeValue::integer(1))
14151 }
14152
14153 pub(crate) fn try_overload_stringify(
14154 &mut self,
14155 v: &StrykeValue,
14156 line: usize,
14157 ) -> Option<ExecResult> {
14158 if let Some(c) = v.as_class_inst() {
14160 let method_name = c
14161 .def
14162 .method("stringify")
14163 .or_else(|| c.def.method("\"\""))
14164 .filter(|m| m.body.is_some())?;
14165 let body = method_name.body.clone().unwrap();
14166 let params = method_name.params.clone();
14167 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
14168 }
14169 let br = v.as_blessed_ref()?;
14170 let class = br.class.clone();
14171 let map = self.overload_table.get(&class)?;
14172 let sub_short = Self::overload_stringify_method(map)?;
14173 let fq = format!("{}::{}", class, sub_short);
14174 let sub = self.subs.get(&fq)?.clone();
14175 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
14176 }
14177
14178 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
14180 match key {
14181 "+" => Some("op_add"),
14182 "-" => Some("op_sub"),
14183 "*" => Some("op_mul"),
14184 "/" => Some("op_div"),
14185 "%" => Some("op_mod"),
14186 "**" => Some("op_pow"),
14187 "." => Some("op_concat"),
14188 "==" => Some("op_eq"),
14189 "!=" => Some("op_ne"),
14190 "<" => Some("op_lt"),
14191 ">" => Some("op_gt"),
14192 "<=" => Some("op_le"),
14193 ">=" => Some("op_ge"),
14194 "<=>" => Some("op_spaceship"),
14195 "eq" => Some("op_str_eq"),
14196 "ne" => Some("op_str_ne"),
14197 "lt" => Some("op_str_lt"),
14198 "gt" => Some("op_str_gt"),
14199 "le" => Some("op_str_le"),
14200 "ge" => Some("op_str_ge"),
14201 "cmp" => Some("op_cmp"),
14202 _ => None,
14203 }
14204 }
14205
14206 pub(crate) fn try_overload_binop(
14207 &mut self,
14208 op: BinOp,
14209 lv: &StrykeValue,
14210 rv: &StrykeValue,
14211 line: usize,
14212 ) -> Option<ExecResult> {
14213 let key = Self::overload_key_for_binop(op)?;
14214 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
14216 (Some(c.def.clone()), lv.clone(), rv.clone())
14217 } else if let Some(c) = rv.as_class_inst() {
14218 (Some(c.def.clone()), rv.clone(), lv.clone())
14219 } else {
14220 (None, lv.clone(), rv.clone())
14221 };
14222 if let Some(ref def) = ci_def {
14223 if let Some(method_name) = Self::overload_method_name_for_key(key) {
14224 if let Some((m, _)) = self.find_class_method(def, method_name) {
14225 if let Some(ref body) = m.body {
14226 let params = m.params.clone();
14227 return Some(self.call_class_method(
14228 body,
14229 ¶ms,
14230 vec![invocant, other],
14231 line,
14232 ));
14233 }
14234 }
14235 }
14236 }
14237 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
14239 (br.class.clone(), lv.clone(), rv.clone())
14240 } else if let Some(br) = rv.as_blessed_ref() {
14241 (br.class.clone(), rv.clone(), lv.clone())
14242 } else {
14243 return None;
14244 };
14245 let map = self.overload_table.get(&class)?;
14246 let sub_short = if let Some(s) = map.get(key) {
14247 s.clone()
14248 } else if let Some(nm) = map.get("nomethod") {
14249 let fq = format!("{}::{}", class, nm);
14250 let sub = self.subs.get(&fq)?.clone();
14251 return Some(self.call_sub(
14252 &sub,
14253 vec![invocant, other, StrykeValue::string(key.to_string())],
14254 WantarrayCtx::Scalar,
14255 line,
14256 ));
14257 } else {
14258 return None;
14259 };
14260 let fq = format!("{}::{}", class, sub_short);
14261 let sub = self.subs.get(&fq)?.clone();
14262 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
14263 }
14264
14265 pub(crate) fn try_overload_unary_dispatch(
14267 &mut self,
14268 op_key: &str,
14269 val: &StrykeValue,
14270 line: usize,
14271 ) -> Option<ExecResult> {
14272 if let Some(c) = val.as_class_inst() {
14274 let method_name = match op_key {
14275 "neg" => "op_neg",
14276 "bool" => "op_bool",
14277 "abs" => "op_abs",
14278 "0+" => "op_numify",
14279 _ => return None,
14280 };
14281 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
14282 if let Some(ref body) = m.body {
14283 let params = m.params.clone();
14284 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
14285 }
14286 }
14287 return None;
14288 }
14289 let br = val.as_blessed_ref()?;
14291 let class = br.class.clone();
14292 let map = self.overload_table.get(&class)?;
14293 if let Some(s) = map.get(op_key) {
14294 let fq = format!("{}::{}", class, s);
14295 let sub = self.subs.get(&fq)?.clone();
14296 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
14297 }
14298 if let Some(nm) = map.get("nomethod") {
14299 let fq = format!("{}::{}", class, nm);
14300 let sub = self.subs.get(&fq)?.clone();
14301 return Some(self.call_sub(
14302 &sub,
14303 vec![val.clone(), StrykeValue::string(op_key.to_string())],
14304 WantarrayCtx::Scalar,
14305 line,
14306 ));
14307 }
14308 None
14309 }
14310
14311 #[inline]
14312 fn eval_binop(
14313 &mut self,
14314 op: BinOp,
14315 lv: &StrykeValue,
14316 rv: &StrykeValue,
14317 _line: usize,
14318 ) -> ExecResult {
14319 Ok(match op {
14320 BinOp::Add => {
14323 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14324 StrykeValue::integer(a.wrapping_add(b))
14325 } else {
14326 StrykeValue::float(lv.to_number() + rv.to_number())
14327 }
14328 }
14329 BinOp::Sub => {
14330 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14331 StrykeValue::integer(a.wrapping_sub(b))
14332 } else {
14333 StrykeValue::float(lv.to_number() - rv.to_number())
14334 }
14335 }
14336 BinOp::Mul => {
14337 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14338 StrykeValue::integer(a.wrapping_mul(b))
14339 } else {
14340 StrykeValue::float(lv.to_number() * rv.to_number())
14341 }
14342 }
14343 BinOp::Div => {
14344 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14345 if b == 0 {
14346 return Err(StrykeError::division_by_zero(
14347 "Illegal division by zero",
14348 _line,
14349 )
14350 .into());
14351 }
14352 if a % b == 0 {
14353 StrykeValue::integer(a / b)
14354 } else {
14355 StrykeValue::float(a as f64 / b as f64)
14356 }
14357 } else {
14358 let d = rv.to_number();
14359 if d == 0.0 {
14360 return Err(StrykeError::division_by_zero(
14361 "Illegal division by zero",
14362 _line,
14363 )
14364 .into());
14365 }
14366 StrykeValue::float(lv.to_number() / d)
14367 }
14368 }
14369 BinOp::Mod => {
14370 let d = rv.to_int();
14371 if d == 0 {
14372 return Err(StrykeError::division_by_zero("Illegal modulus zero", _line).into());
14373 }
14374 StrykeValue::integer(crate::value::perl_mod_i64(lv.to_int(), d))
14375 }
14376 BinOp::Pow => {
14377 if crate::compat_mode() || crate::bigint_pragma() {
14381 crate::value::compat_pow(lv, rv)
14382 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14383 let int_pow = (b >= 0)
14384 .then(|| u32::try_from(b).ok())
14385 .flatten()
14386 .and_then(|bu| a.checked_pow(bu))
14387 .map(StrykeValue::integer);
14388 int_pow
14389 .unwrap_or_else(|| StrykeValue::float(lv.to_number().powf(rv.to_number())))
14390 } else {
14391 StrykeValue::float(lv.to_number().powf(rv.to_number()))
14392 }
14393 }
14394 BinOp::Concat => {
14395 let mut s = String::new();
14396 lv.append_to(&mut s);
14397 rv.append_to(&mut s);
14398 StrykeValue::string(s)
14399 }
14400 BinOp::NumEq => {
14401 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
14403 if a.def.name != b.def.name {
14404 StrykeValue::integer(0)
14405 } else {
14406 let av = a.get_values();
14407 let bv = b.get_values();
14408 let eq = av.len() == bv.len()
14409 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
14410 StrykeValue::integer(if eq { 1 } else { 0 })
14411 }
14412 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14413 StrykeValue::integer(if a == b { 1 } else { 0 })
14414 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
14415 StrykeValue::integer(if lv.to_string() == rv.to_string() {
14421 1
14422 } else {
14423 0
14424 })
14425 } else {
14426 StrykeValue::integer(if lv.to_number() == rv.to_number() {
14427 1
14428 } else {
14429 0
14430 })
14431 }
14432 }
14433 BinOp::NumNe => {
14434 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14435 StrykeValue::integer(if a != b { 1 } else { 0 })
14436 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
14437 StrykeValue::integer(if lv.to_string() != rv.to_string() {
14438 1
14439 } else {
14440 0
14441 })
14442 } else {
14443 StrykeValue::integer(if lv.to_number() != rv.to_number() {
14444 1
14445 } else {
14446 0
14447 })
14448 }
14449 }
14450 BinOp::NumLt => {
14451 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14452 StrykeValue::integer(if a < b { 1 } else { 0 })
14453 } else {
14454 StrykeValue::integer(if lv.to_number() < rv.to_number() {
14455 1
14456 } else {
14457 0
14458 })
14459 }
14460 }
14461 BinOp::NumGt => {
14462 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14463 StrykeValue::integer(if a > b { 1 } else { 0 })
14464 } else {
14465 StrykeValue::integer(if lv.to_number() > rv.to_number() {
14466 1
14467 } else {
14468 0
14469 })
14470 }
14471 }
14472 BinOp::NumLe => {
14473 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14474 StrykeValue::integer(if a <= b { 1 } else { 0 })
14475 } else {
14476 StrykeValue::integer(if lv.to_number() <= rv.to_number() {
14477 1
14478 } else {
14479 0
14480 })
14481 }
14482 }
14483 BinOp::NumGe => {
14484 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14485 StrykeValue::integer(if a >= b { 1 } else { 0 })
14486 } else {
14487 StrykeValue::integer(if lv.to_number() >= rv.to_number() {
14488 1
14489 } else {
14490 0
14491 })
14492 }
14493 }
14494 BinOp::Spaceship => {
14495 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14496 StrykeValue::integer(if a < b {
14497 -1
14498 } else if a > b {
14499 1
14500 } else {
14501 0
14502 })
14503 } else {
14504 let a = lv.to_number();
14505 let b = rv.to_number();
14506 StrykeValue::integer(if a < b {
14507 -1
14508 } else if a > b {
14509 1
14510 } else {
14511 0
14512 })
14513 }
14514 }
14515 BinOp::StrEq => StrykeValue::integer(if lv.to_string() == rv.to_string() {
14516 1
14517 } else {
14518 0
14519 }),
14520 BinOp::StrNe => StrykeValue::integer(if lv.to_string() != rv.to_string() {
14521 1
14522 } else {
14523 0
14524 }),
14525 BinOp::StrLt => StrykeValue::integer(if lv.to_string() < rv.to_string() {
14526 1
14527 } else {
14528 0
14529 }),
14530 BinOp::StrGt => StrykeValue::integer(if lv.to_string() > rv.to_string() {
14531 1
14532 } else {
14533 0
14534 }),
14535 BinOp::StrLe => StrykeValue::integer(if lv.to_string() <= rv.to_string() {
14536 1
14537 } else {
14538 0
14539 }),
14540 BinOp::StrGe => StrykeValue::integer(if lv.to_string() >= rv.to_string() {
14541 1
14542 } else {
14543 0
14544 }),
14545 BinOp::StrCmp => {
14546 let cmp = lv.to_string().cmp(&rv.to_string());
14547 StrykeValue::integer(match cmp {
14548 std::cmp::Ordering::Less => -1,
14549 std::cmp::Ordering::Greater => 1,
14550 std::cmp::Ordering::Equal => 0,
14551 })
14552 }
14553 BinOp::BitAnd => {
14554 if let Some(s) = crate::value::set_intersection(lv, rv) {
14555 s
14556 } else {
14557 StrykeValue::integer(lv.to_int() & rv.to_int())
14558 }
14559 }
14560 BinOp::BitOr => {
14561 if let Some(s) = crate::value::set_union(lv, rv) {
14562 s
14563 } else {
14564 StrykeValue::integer(lv.to_int() | rv.to_int())
14565 }
14566 }
14567 BinOp::BitXor => StrykeValue::integer(lv.to_int() ^ rv.to_int()),
14568 BinOp::ShiftLeft => StrykeValue::integer(perl_shl_i64(lv.to_int(), rv.to_int())),
14569 BinOp::ShiftRight => StrykeValue::integer(perl_shr_i64(lv.to_int(), rv.to_int())),
14570 BinOp::LogAnd
14572 | BinOp::LogOr
14573 | BinOp::DefinedOr
14574 | BinOp::LogAndWord
14575 | BinOp::LogOrWord => unreachable!(),
14576 BinOp::BindMatch | BinOp::BindNotMatch => {
14577 unreachable!("regex bind handled in eval_expr BinOp arm")
14578 }
14579 })
14580 }
14581
14582 fn err_modify_symbolic_aggregate_deref_inc_dec(
14586 kind: Sigil,
14587 is_pre: bool,
14588 is_inc: bool,
14589 line: usize,
14590 ) -> FlowOrError {
14591 let agg = match kind {
14592 Sigil::Array => "array",
14593 Sigil::Hash => "hash",
14594 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
14595 };
14596 let op = match (is_pre, is_inc) {
14597 (true, true) => "preincrement (++)",
14598 (true, false) => "predecrement (--)",
14599 (false, true) => "postincrement (++)",
14600 (false, false) => "postdecrement (--)",
14601 };
14602 FlowOrError::Error(StrykeError::runtime(
14603 format!("Can't modify {agg} dereference in {op}"),
14604 line,
14605 ))
14606 }
14607
14608 pub(crate) fn symbolic_scalar_ref_postfix(
14610 &mut self,
14611 ref_val: StrykeValue,
14612 decrement: bool,
14613 line: usize,
14614 ) -> Result<StrykeValue, FlowOrError> {
14615 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
14616 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14617 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
14618 Ok(old)
14619 }
14620
14621 pub(crate) fn assign_scalar_ref_deref(
14624 &mut self,
14625 ref_val: StrykeValue,
14626 val: StrykeValue,
14627 line: usize,
14628 ) -> ExecResult {
14629 if let Some(name) = ref_val.as_scalar_binding_name() {
14630 self.set_special_var(&name, &val)
14631 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14632 return Ok(StrykeValue::UNDEF);
14633 }
14634 if let Some(r) = ref_val.as_scalar_ref() {
14635 *r.write() = val;
14636 return Ok(StrykeValue::UNDEF);
14637 }
14638 if ref_val.is_integer_like() || ref_val.is_float_like() || ref_val.is_string_like() {
14641 let s = ref_val.to_string();
14642 if self.strict_refs {
14643 return Err(StrykeError::runtime(
14644 format!(
14645 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
14646 s
14647 ),
14648 line,
14649 )
14650 .into());
14651 }
14652 self.set_special_var(&s, &val)
14653 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14654 return Ok(StrykeValue::UNDEF);
14655 }
14656 Err(StrykeError::runtime("Can't assign to non-scalar reference", line).into())
14657 }
14658
14659 pub(crate) fn assign_symbolic_array_ref_deref(
14661 &mut self,
14662 ref_val: StrykeValue,
14663 val: StrykeValue,
14664 line: usize,
14665 ) -> ExecResult {
14666 if let Some(a) = ref_val.as_array_ref() {
14667 *a.write() = val.to_list();
14668 return Ok(StrykeValue::UNDEF);
14669 }
14670 if let Some(name) = ref_val.as_array_binding_name() {
14671 self.scope
14672 .set_array(&name, val.to_list())
14673 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14674 return Ok(StrykeValue::UNDEF);
14675 }
14676 if let Some(s) = ref_val.as_str() {
14677 if self.strict_refs {
14678 return Err(StrykeError::runtime(
14679 format!(
14680 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
14681 s
14682 ),
14683 line,
14684 )
14685 .into());
14686 }
14687 self.scope
14688 .set_array(&s, val.to_list())
14689 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14690 return Ok(StrykeValue::UNDEF);
14691 }
14692 Err(StrykeError::runtime("Can't assign to non-array reference", line).into())
14693 }
14694
14695 pub(crate) fn assign_symbolic_typeglob_ref_deref(
14698 &mut self,
14699 ref_val: StrykeValue,
14700 val: StrykeValue,
14701 line: usize,
14702 ) -> ExecResult {
14703 let lhs_name = if let Some(s) = ref_val.as_str() {
14704 if self.strict_refs {
14705 return Err(StrykeError::runtime(
14706 format!(
14707 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
14708 s
14709 ),
14710 line,
14711 )
14712 .into());
14713 }
14714 s.to_string()
14715 } else {
14716 return Err(
14717 StrykeError::runtime("Can't assign to non-glob symbolic reference", line).into(),
14718 );
14719 };
14720 let is_coderef = val.as_code_ref().is_some()
14721 || val
14722 .as_scalar_ref()
14723 .map(|r| r.read().as_code_ref().is_some())
14724 .unwrap_or(false);
14725 if is_coderef {
14726 return self.assign_typeglob_value(&lhs_name, val, line);
14727 }
14728 let rhs_key = val.to_string();
14729 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
14730 .map_err(FlowOrError::Error)?;
14731 Ok(StrykeValue::UNDEF)
14732 }
14733
14734 pub(crate) fn assign_symbolic_hash_ref_deref(
14736 &mut self,
14737 ref_val: StrykeValue,
14738 val: StrykeValue,
14739 line: usize,
14740 ) -> ExecResult {
14741 let items = val.to_list();
14742 let mut map = IndexMap::new();
14743 let mut i = 0;
14744 while i + 1 < items.len() {
14745 map.insert(items[i].to_string(), items[i + 1].clone());
14746 i += 2;
14747 }
14748 if let Some(h) = ref_val.as_hash_ref() {
14749 *h.write() = map;
14750 return Ok(StrykeValue::UNDEF);
14751 }
14752 if let Some(name) = ref_val.as_hash_binding_name() {
14753 self.touch_env_hash(&name);
14754 self.scope
14755 .set_hash(&name, map)
14756 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14757 return Ok(StrykeValue::UNDEF);
14758 }
14759 if let Some(s) = ref_val.as_str() {
14760 if self.strict_refs {
14761 return Err(StrykeError::runtime(
14762 format!(
14763 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
14764 s
14765 ),
14766 line,
14767 )
14768 .into());
14769 }
14770 self.touch_env_hash(&s);
14771 self.scope
14772 .set_hash(&s, map)
14773 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14774 return Ok(StrykeValue::UNDEF);
14775 }
14776 Err(StrykeError::runtime("Can't assign to non-hash reference", line).into())
14777 }
14778
14779 pub(crate) fn assign_arrow_hash_deref(
14781 &mut self,
14782 container: StrykeValue,
14783 key: String,
14784 val: StrykeValue,
14785 line: usize,
14786 ) -> ExecResult {
14787 if let Some(b) = container.as_blessed_ref() {
14788 let mut data = b.data.write();
14789 if let Some(r) = data.as_hash_ref() {
14790 r.write().insert(key, val);
14791 return Ok(StrykeValue::UNDEF);
14792 }
14793 if let Some(mut map) = data.as_hash_map() {
14794 map.insert(key, val);
14795 *data = StrykeValue::hash(map);
14796 return Ok(StrykeValue::UNDEF);
14797 }
14798 return Err(
14799 StrykeError::runtime("Can't assign into non-hash blessed ref", line).into(),
14800 );
14801 }
14802 if let Some(r) = container.as_hash_ref() {
14803 r.write().insert(key, val);
14804 return Ok(StrykeValue::UNDEF);
14805 }
14806 if let Some(name) = container.as_hash_binding_name() {
14807 self.touch_env_hash(&name);
14808 self.scope
14809 .set_hash_element(&name, &key, val)
14810 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14811 return Ok(StrykeValue::UNDEF);
14812 }
14813 Err(StrykeError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
14814 }
14815
14816 pub(crate) fn eval_arrow_array_base(
14819 &mut self,
14820 expr: &Expr,
14821 _line: usize,
14822 ) -> Result<StrykeValue, FlowOrError> {
14823 match &expr.kind {
14824 ExprKind::Deref {
14825 expr: inner,
14826 kind: Sigil::Array | Sigil::Scalar,
14827 } => self.eval_expr(inner),
14828 _ => self.eval_expr(expr),
14829 }
14830 }
14831
14832 pub(crate) fn eval_arrow_hash_base(
14834 &mut self,
14835 expr: &Expr,
14836 _line: usize,
14837 ) -> Result<StrykeValue, FlowOrError> {
14838 match &expr.kind {
14839 ExprKind::Deref {
14840 expr: inner,
14841 kind: Sigil::Scalar,
14842 } => self.eval_expr(inner),
14843 _ => self.eval_expr(expr),
14844 }
14845 }
14846
14847 pub(crate) fn read_arrow_array_element(
14849 &self,
14850 container: StrykeValue,
14851 idx: i64,
14852 line: usize,
14853 ) -> Result<StrykeValue, FlowOrError> {
14854 if let Some(a) = container.as_array_ref() {
14855 let arr = a.read();
14856 let i = if idx < 0 {
14857 (arr.len() as i64 + idx) as usize
14858 } else {
14859 idx as usize
14860 };
14861 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
14862 }
14863 if let Some(name) = container.as_array_binding_name() {
14864 return Ok(self.scope.get_array_element(&name, idx));
14865 }
14866 if let Some(arr) = container.as_array_vec() {
14867 let i = if idx < 0 {
14868 (arr.len() as i64 + idx) as usize
14869 } else {
14870 idx as usize
14871 };
14872 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
14873 }
14874 if let Some(b) = container.as_blessed_ref() {
14877 let inner = b.data.read().clone();
14878 if let Some(a) = inner.as_array_ref() {
14879 let arr = a.read();
14880 let i = if idx < 0 {
14881 (arr.len() as i64 + idx) as usize
14882 } else {
14883 idx as usize
14884 };
14885 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
14886 }
14887 }
14888 Err(StrykeError::runtime("Can't use arrow deref on non-array-ref", line).into())
14889 }
14890
14891 pub(crate) fn read_arrow_hash_element(
14893 &mut self,
14894 container: StrykeValue,
14895 key: &str,
14896 line: usize,
14897 ) -> Result<StrykeValue, FlowOrError> {
14898 if let Some(r) = container.as_hash_ref() {
14899 let h = r.read();
14900 return Ok(h.get(key).cloned().unwrap_or(StrykeValue::UNDEF));
14901 }
14902 if let Some(name) = container.as_hash_binding_name() {
14903 self.touch_env_hash(&name);
14904 return Ok(self.scope.get_hash_element(&name, key));
14905 }
14906 if let Some(b) = container.as_blessed_ref() {
14907 let data = b.data.read();
14908 if let Some(v) = data.hash_get(key) {
14909 return Ok(v);
14910 }
14911 if let Some(r) = data.as_hash_ref() {
14912 let h = r.read();
14913 return Ok(h.get(key).cloned().unwrap_or(StrykeValue::UNDEF));
14914 }
14915 return Err(StrykeError::runtime(
14916 "Can't access hash field on non-hash blessed ref",
14917 line,
14918 )
14919 .into());
14920 }
14921 if let Some(s) = container.as_struct_inst() {
14923 if let Some(idx) = s.def.field_index(key) {
14924 return Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF));
14925 }
14926 return Err(StrykeError::runtime(
14927 format!("struct {} has no field `{}`", s.def.name, key),
14928 line,
14929 )
14930 .into());
14931 }
14932 if let Some(c) = container.as_class_inst() {
14934 if let Some(idx) = c.def.field_index(key) {
14935 return Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF));
14936 }
14937 return Err(StrykeError::runtime(
14938 format!("class {} has no field `{}`", c.def.name, key),
14939 line,
14940 )
14941 .into());
14942 }
14943 Err(StrykeError::runtime("Can't use arrow deref on non-hash-ref", line).into())
14944 }
14945
14946 pub(crate) fn arrow_array_postfix(
14948 &mut self,
14949 container: StrykeValue,
14950 idx: i64,
14951 decrement: bool,
14952 line: usize,
14953 ) -> Result<StrykeValue, FlowOrError> {
14954 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
14955 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14956 self.assign_arrow_array_deref(container, idx, new_val, line)?;
14957 Ok(old)
14958 }
14959
14960 pub(crate) fn arrow_hash_postfix(
14962 &mut self,
14963 container: StrykeValue,
14964 key: String,
14965 decrement: bool,
14966 line: usize,
14967 ) -> Result<StrykeValue, FlowOrError> {
14968 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
14969 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14970 self.assign_arrow_hash_deref(container, key, new_val, line)?;
14971 Ok(old)
14972 }
14973
14974 pub(crate) fn resolve_bareword_rvalue(
14982 &mut self,
14983 name: &str,
14984 want: WantarrayCtx,
14985 line: usize,
14986 ) -> Result<StrykeValue, FlowOrError> {
14987 if name == "__PACKAGE__" {
14988 return Ok(StrykeValue::string(self.current_package()));
14989 }
14990 if let Some(sub) = self.resolve_sub_by_name(name) {
14991 return self.call_sub(&sub, vec![], want, line);
14992 }
14993 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
14995 return r.map_err(Into::into);
14996 }
14997 Ok(StrykeValue::string(name.to_string()))
14998 }
14999
15000 pub(crate) fn arrow_array_slice_values(
15004 &mut self,
15005 container: StrykeValue,
15006 indices: &[i64],
15007 line: usize,
15008 ) -> Result<StrykeValue, FlowOrError> {
15009 let mut out = Vec::with_capacity(indices.len());
15010 for &idx in indices {
15011 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
15012 out.push(v);
15013 }
15014 Ok(StrykeValue::array(out))
15015 }
15016
15017 pub(crate) fn assign_arrow_array_slice(
15021 &mut self,
15022 container: StrykeValue,
15023 indices: Vec<i64>,
15024 val: StrykeValue,
15025 line: usize,
15026 ) -> Result<StrykeValue, FlowOrError> {
15027 if indices.is_empty() {
15028 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15029 }
15030 let vals = val.to_list();
15031 for (i, idx) in indices.iter().enumerate() {
15032 let v = vals.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15033 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
15034 }
15035 Ok(StrykeValue::UNDEF)
15036 }
15037
15038 pub(crate) fn flatten_array_slice_index_specs(
15040 &mut self,
15041 indices: &[Expr],
15042 ) -> Result<Vec<i64>, FlowOrError> {
15043 let mut out = Vec::new();
15044 for idx_expr in indices {
15045 let v = if matches!(
15046 idx_expr.kind,
15047 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
15048 ) {
15049 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
15050 } else {
15051 self.eval_expr(idx_expr)?
15052 };
15053 if let Some(list) = v.as_array_vec() {
15054 for idx in list {
15055 out.push(idx.to_int());
15056 }
15057 } else {
15058 out.push(v.to_int());
15059 }
15060 }
15061 Ok(out)
15062 }
15063
15064 pub(crate) fn assign_named_array_slice(
15066 &mut self,
15067 stash_array_name: &str,
15068 indices: Vec<i64>,
15069 val: StrykeValue,
15070 line: usize,
15071 ) -> Result<StrykeValue, FlowOrError> {
15072 if indices.is_empty() {
15073 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15074 }
15075 let vals = val.to_list();
15076 for (i, idx) in indices.iter().enumerate() {
15077 let v = vals.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15078 self.scope
15079 .set_array_element(stash_array_name, *idx, v)
15080 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15081 }
15082 Ok(StrykeValue::UNDEF)
15083 }
15084
15085 pub(crate) fn compound_assign_arrow_array_slice(
15088 &mut self,
15089 container: StrykeValue,
15090 indices: Vec<i64>,
15091 op: BinOp,
15092 rhs: StrykeValue,
15093 line: usize,
15094 ) -> Result<StrykeValue, FlowOrError> {
15095 if indices.is_empty() {
15096 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15097 }
15098 let last_idx = *indices.last().expect("non-empty indices");
15099 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
15100 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
15101 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
15102 Ok(new_val)
15103 }
15104
15105 pub(crate) fn arrow_array_slice_inc_dec(
15110 &mut self,
15111 container: StrykeValue,
15112 indices: Vec<i64>,
15113 kind: u8,
15114 line: usize,
15115 ) -> Result<StrykeValue, FlowOrError> {
15116 if indices.is_empty() {
15117 return Err(StrykeError::runtime(
15118 "array slice increment needs at least one index",
15119 line,
15120 )
15121 .into());
15122 }
15123 let last_idx = *indices.last().expect("non-empty indices");
15124 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
15125 let new_val = if kind & 1 == 0 {
15126 StrykeValue::integer(last_old.to_int() + 1)
15127 } else {
15128 StrykeValue::integer(last_old.to_int() - 1)
15129 };
15130 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
15131 Ok(if kind < 2 { new_val } else { last_old })
15132 }
15133
15134 pub(crate) fn named_array_slice_inc_dec(
15137 &mut self,
15138 stash_array_name: &str,
15139 indices: Vec<i64>,
15140 kind: u8,
15141 line: usize,
15142 ) -> Result<StrykeValue, FlowOrError> {
15143 let last_idx = *indices.last().ok_or_else(|| {
15144 StrykeError::runtime("array slice increment needs at least one index", line)
15145 })?;
15146 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
15147 let new_val = if kind & 1 == 0 {
15148 StrykeValue::integer(last_old.to_int() + 1)
15149 } else {
15150 StrykeValue::integer(last_old.to_int() - 1)
15151 };
15152 self.scope
15153 .set_array_element(stash_array_name, last_idx, new_val.clone())
15154 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15155 Ok(if kind < 2 { new_val } else { last_old })
15156 }
15157
15158 pub(crate) fn compound_assign_named_array_slice(
15160 &mut self,
15161 stash_array_name: &str,
15162 indices: Vec<i64>,
15163 op: BinOp,
15164 rhs: StrykeValue,
15165 line: usize,
15166 ) -> Result<StrykeValue, FlowOrError> {
15167 if indices.is_empty() {
15168 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15169 }
15170 let last_idx = *indices.last().expect("non-empty indices");
15171 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
15172 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
15173 self.scope
15174 .set_array_element(stash_array_name, last_idx, new_val.clone())
15175 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15176 Ok(new_val)
15177 }
15178
15179 pub(crate) fn assign_arrow_array_deref(
15181 &mut self,
15182 container: StrykeValue,
15183 idx: i64,
15184 val: StrykeValue,
15185 line: usize,
15186 ) -> ExecResult {
15187 if let Some(a) = container.as_array_ref() {
15188 let mut arr = a.write();
15189 let i = if idx < 0 {
15190 (arr.len() as i64 + idx) as usize
15191 } else {
15192 idx as usize
15193 };
15194 if i >= arr.len() {
15195 arr.resize(i + 1, StrykeValue::UNDEF);
15196 }
15197 arr[i] = val;
15198 return Ok(StrykeValue::UNDEF);
15199 }
15200 if let Some(name) = container.as_array_binding_name() {
15201 self.scope
15202 .set_array_element(&name, idx, val)
15203 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15204 return Ok(StrykeValue::UNDEF);
15205 }
15206 Err(StrykeError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
15207 }
15208
15209 pub(crate) fn assign_typeglob_value(
15211 &mut self,
15212 name: &str,
15213 val: StrykeValue,
15214 line: usize,
15215 ) -> ExecResult {
15216 let sub = if let Some(c) = val.as_code_ref() {
15217 Some(c)
15218 } else if let Some(r) = val.as_scalar_ref() {
15219 r.read().as_code_ref().map(|c| Arc::clone(&c))
15220 } else {
15221 None
15222 };
15223 if let Some(sub) = sub {
15224 let lhs_sub = self.qualify_typeglob_sub_key(name);
15225 self.subs.insert(lhs_sub, sub);
15226 return Ok(StrykeValue::UNDEF);
15227 }
15228 Err(StrykeError::runtime(
15229 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
15230 line,
15231 )
15232 .into())
15233 }
15234
15235 fn assign_value(&mut self, target: &Expr, val: StrykeValue) -> ExecResult {
15236 match &target.kind {
15237 ExprKind::Substr {
15243 string,
15244 offset,
15245 length,
15246 replacement: None,
15247 } => {
15248 let s = self.eval_expr(string)?.to_string();
15249 let off = self.eval_expr(offset)?.to_int();
15250 let start = if off < 0 {
15251 (s.len() as i64 + off).max(0) as usize
15252 } else {
15253 (off as usize).min(s.len())
15254 };
15255 let len = if let Some(l) = length {
15256 let lv = self.eval_expr(l)?.to_int();
15257 if lv < 0 {
15258 let remaining = s.len().saturating_sub(start) as i64;
15259 (remaining + lv).max(0) as usize
15260 } else {
15261 lv as usize
15262 }
15263 } else {
15264 s.len().saturating_sub(start)
15265 };
15266 let end = start.saturating_add(len).min(s.len());
15267 let mut new_s = String::with_capacity(s.len());
15268 new_s.push_str(&s[..start]);
15269 new_s.push_str(&val.to_string());
15270 new_s.push_str(&s[end..]);
15271 self.assign_value(string, StrykeValue::string(new_s))?;
15272 Ok(StrykeValue::UNDEF)
15273 }
15274 ExprKind::MyExpr { decls, .. } => {
15281 let first = decls.first().ok_or_else(|| {
15282 FlowOrError::Error(StrykeError::runtime(
15283 "assign_value: empty MyExpr decl list",
15284 target.line,
15285 ))
15286 })?;
15287 match first.sigil {
15288 Sigil::Scalar => {
15289 let stor = self.tree_scalar_storage_name(&first.name);
15290 self.set_special_var(&stor, &val)
15291 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
15292 Ok(StrykeValue::UNDEF)
15293 }
15294 Sigil::Array => {
15295 self.scope.set_array(&first.name, val.to_list())?;
15296 Ok(StrykeValue::UNDEF)
15297 }
15298 Sigil::Hash => {
15299 let items = val.to_list();
15300 let mut map = IndexMap::new();
15301 let mut i = 0;
15302 while i + 1 < items.len() {
15303 map.insert(items[i].to_string(), items[i + 1].clone());
15304 i += 2;
15305 }
15306 self.scope.set_hash(&first.name, map)?;
15307 Ok(StrykeValue::UNDEF)
15308 }
15309 Sigil::Typeglob => Ok(StrykeValue::UNDEF),
15310 }
15311 }
15312 ExprKind::ScalarVar(name) => {
15313 let stor = self.tree_scalar_storage_name(name);
15314 if self.scope.is_scalar_frozen(&stor) {
15315 return Err(FlowOrError::Error(StrykeError::runtime(
15316 format!("Modification of a frozen value: ${}", name),
15317 target.line,
15318 )));
15319 }
15320 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
15321 let class = obj
15322 .as_blessed_ref()
15323 .map(|b| b.class.clone())
15324 .unwrap_or_default();
15325 let full = format!("{}::STORE", class);
15326 if let Some(sub) = self.subs.get(&full).cloned() {
15327 let arg_vals = vec![obj, val];
15328 return match self.call_sub(
15329 &sub,
15330 arg_vals,
15331 WantarrayCtx::Scalar,
15332 target.line,
15333 ) {
15334 Ok(_) => Ok(StrykeValue::UNDEF),
15335 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15336 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15337 };
15338 }
15339 }
15340 self.set_special_var(&stor, &val)
15341 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
15342 Ok(StrykeValue::UNDEF)
15343 }
15344 ExprKind::ArrayVar(name) => {
15345 if self.scope.is_array_frozen(name) {
15346 return Err(StrykeError::runtime(
15347 format!("Modification of a frozen value: @{}", name),
15348 target.line,
15349 )
15350 .into());
15351 }
15352 if self.strict_vars
15353 && !name.contains("::")
15354 && !self.scope.array_binding_exists(name)
15355 {
15356 return Err(StrykeError::runtime(
15357 format!(
15358 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
15359 name, name
15360 ),
15361 target.line,
15362 )
15363 .into());
15364 }
15365 self.scope.set_array(name, val.to_list())?;
15366 Ok(StrykeValue::UNDEF)
15367 }
15368 ExprKind::HashVar(name) => {
15369 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
15370 {
15371 return Err(StrykeError::runtime(
15372 format!(
15373 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15374 name, name
15375 ),
15376 target.line,
15377 )
15378 .into());
15379 }
15380 let items = val.to_list();
15381 let mut map = IndexMap::new();
15382 let mut i = 0;
15383 while i + 1 < items.len() {
15384 map.insert(items[i].to_string(), items[i + 1].clone());
15385 i += 2;
15386 }
15387 self.scope.set_hash(name, map)?;
15388 Ok(StrykeValue::UNDEF)
15389 }
15390 ExprKind::ArrayElement { array, index } => {
15391 if self.strict_vars
15392 && !array.contains("::")
15393 && !self.scope.array_binding_exists(array)
15394 {
15395 return Err(StrykeError::runtime(
15396 format!(
15397 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
15398 array, array
15399 ),
15400 target.line,
15401 )
15402 .into());
15403 }
15404 if self.scope.is_array_frozen(array) {
15405 return Err(StrykeError::runtime(
15406 format!("Modification of a frozen value: @{}", array),
15407 target.line,
15408 )
15409 .into());
15410 }
15411 let idx = self.eval_expr(index)?.to_int();
15412 let aname = self.stash_array_name_for_package(array);
15413 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
15414 let class = obj
15415 .as_blessed_ref()
15416 .map(|b| b.class.clone())
15417 .unwrap_or_default();
15418 let full = format!("{}::STORE", class);
15419 if let Some(sub) = self.subs.get(&full).cloned() {
15420 let arg_vals = vec![obj, StrykeValue::integer(idx), val];
15421 return match self.call_sub(
15422 &sub,
15423 arg_vals,
15424 WantarrayCtx::Scalar,
15425 target.line,
15426 ) {
15427 Ok(_) => Ok(StrykeValue::UNDEF),
15428 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15429 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15430 };
15431 }
15432 }
15433 self.scope.set_array_element(&aname, idx, val)?;
15434 Ok(StrykeValue::UNDEF)
15435 }
15436 ExprKind::ArraySlice { array, indices } => {
15437 if indices.is_empty() {
15438 return Err(
15439 StrykeError::runtime("assign to empty array slice", target.line).into(),
15440 );
15441 }
15442 self.check_strict_array_var(array, target.line)?;
15443 if self.scope.is_array_frozen(array) {
15444 return Err(StrykeError::runtime(
15445 format!("Modification of a frozen value: @{}", array),
15446 target.line,
15447 )
15448 .into());
15449 }
15450 let aname = self.stash_array_name_for_package(array);
15451 let flat = self.flatten_array_slice_index_specs(indices)?;
15452 self.assign_named_array_slice(&aname, flat, val, target.line)
15453 }
15454 ExprKind::HashElement { hash, key } => {
15455 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
15456 {
15457 return Err(StrykeError::runtime(
15458 format!(
15459 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15460 hash, hash
15461 ),
15462 target.line,
15463 )
15464 .into());
15465 }
15466 if self.scope.is_hash_frozen(hash) {
15467 return Err(StrykeError::runtime(
15468 format!("Modification of a frozen value: %%{}", hash),
15469 target.line,
15470 )
15471 .into());
15472 }
15473 let k = self.eval_expr(key)?.to_string();
15474 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
15475 let class = obj
15476 .as_blessed_ref()
15477 .map(|b| b.class.clone())
15478 .unwrap_or_default();
15479 let full = format!("{}::STORE", class);
15480 if let Some(sub) = self.subs.get(&full).cloned() {
15481 let arg_vals = vec![obj, StrykeValue::string(k), val];
15482 return match self.call_sub(
15483 &sub,
15484 arg_vals,
15485 WantarrayCtx::Scalar,
15486 target.line,
15487 ) {
15488 Ok(_) => Ok(StrykeValue::UNDEF),
15489 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15490 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15491 };
15492 }
15493 }
15494 let hname = self.tree_hash_storage_name(hash);
15495 self.scope.set_hash_element(&hname, &k, val)?;
15496 Ok(StrykeValue::UNDEF)
15497 }
15498 ExprKind::HashSlice { hash, keys } => {
15499 if keys.is_empty() {
15500 return Err(
15501 StrykeError::runtime("assign to empty hash slice", target.line).into(),
15502 );
15503 }
15504 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
15505 {
15506 return Err(StrykeError::runtime(
15507 format!(
15508 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15509 hash, hash
15510 ),
15511 target.line,
15512 )
15513 .into());
15514 }
15515 if self.scope.is_hash_frozen(hash) {
15516 return Err(StrykeError::runtime(
15517 format!("Modification of a frozen value: %%{}", hash),
15518 target.line,
15519 )
15520 .into());
15521 }
15522 let mut key_vals = Vec::with_capacity(keys.len());
15523 for key_expr in keys {
15524 let v = if matches!(
15525 key_expr.kind,
15526 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
15527 ) {
15528 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
15529 } else {
15530 self.eval_expr(key_expr)?
15531 };
15532 key_vals.push(v);
15533 }
15534 self.assign_named_hash_slice(hash, key_vals, val, target.line)
15535 }
15536 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
15537 ExprKind::TypeglobExpr(e) => {
15538 let name = self.eval_expr(e)?.to_string();
15539 let synthetic = Expr {
15540 kind: ExprKind::Typeglob(name),
15541 line: target.line,
15542 };
15543 self.assign_value(&synthetic, val)
15544 }
15545 ExprKind::AnonymousListSlice { source, indices } => {
15546 if let ExprKind::Deref {
15547 expr: inner,
15548 kind: Sigil::Array,
15549 } = &source.kind
15550 {
15551 let container = self.eval_arrow_array_base(inner, target.line)?;
15552 let vals = val.to_list();
15553 let n = indices.len().min(vals.len());
15554 for i in 0..n {
15555 let idx = self.eval_expr(&indices[i])?.to_int();
15556 self.assign_arrow_array_deref(
15557 container.clone(),
15558 idx,
15559 vals[i].clone(),
15560 target.line,
15561 )?;
15562 }
15563 return Ok(StrykeValue::UNDEF);
15564 }
15565 Err(
15566 StrykeError::runtime("assign to list slice: unsupported base", target.line)
15567 .into(),
15568 )
15569 }
15570 ExprKind::ArrowDeref {
15571 expr,
15572 index,
15573 kind: DerefKind::Hash,
15574 } => {
15575 let key = self.eval_expr(index)?.to_string();
15576 let container = self.eval_expr(expr)?;
15577 self.assign_arrow_hash_deref(container, key, val, target.line)
15578 }
15579 ExprKind::ArrowDeref {
15580 expr,
15581 index,
15582 kind: DerefKind::Array,
15583 } => {
15584 let container = self.eval_arrow_array_base(expr, target.line)?;
15585 if let ExprKind::List(indices) = &index.kind {
15586 let vals = val.to_list();
15587 let n = indices.len().min(vals.len());
15588 for i in 0..n {
15589 let idx = self.eval_expr(&indices[i])?.to_int();
15590 self.assign_arrow_array_deref(
15591 container.clone(),
15592 idx,
15593 vals[i].clone(),
15594 target.line,
15595 )?;
15596 }
15597 return Ok(StrykeValue::UNDEF);
15598 }
15599 let idx = self.eval_expr(index)?.to_int();
15600 self.assign_arrow_array_deref(container, idx, val, target.line)
15601 }
15602 ExprKind::HashSliceDeref { container, keys } => {
15603 let href = self.eval_expr(container)?;
15604 let mut key_vals = Vec::with_capacity(keys.len());
15605 for key_expr in keys {
15606 key_vals.push(self.eval_expr(key_expr)?);
15607 }
15608 self.assign_hash_slice_deref(href, key_vals, val, target.line)
15609 }
15610 ExprKind::Deref {
15611 expr,
15612 kind: Sigil::Scalar,
15613 } => {
15614 let ref_val = self.eval_expr(expr)?;
15615 self.assign_scalar_ref_deref(ref_val, val, target.line)
15616 }
15617 ExprKind::Deref {
15618 expr,
15619 kind: Sigil::Array,
15620 } => {
15621 let ref_val = self.eval_expr(expr)?;
15622 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
15623 }
15624 ExprKind::Deref {
15625 expr,
15626 kind: Sigil::Hash,
15627 } => {
15628 let ref_val = self.eval_expr(expr)?;
15629 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
15630 }
15631 ExprKind::Deref {
15632 expr,
15633 kind: Sigil::Typeglob,
15634 } => {
15635 let ref_val = self.eval_expr(expr)?;
15636 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
15637 }
15638 ExprKind::Pos(inner) => {
15639 let key = match inner {
15640 None => "_".to_string(),
15641 Some(expr) => match &expr.kind {
15642 ExprKind::ScalarVar(n) => n.clone(),
15643 _ => self.eval_expr(expr)?.to_string(),
15644 },
15645 };
15646 if val.is_undef() {
15647 self.regex_pos.insert(key, None);
15648 } else {
15649 let u = val.to_int().max(0) as usize;
15650 self.regex_pos.insert(key, Some(u));
15651 }
15652 Ok(StrykeValue::UNDEF)
15653 }
15654 ExprKind::List(targets) => {
15657 let items = val.to_list();
15658 for (i, t) in targets.iter().enumerate() {
15659 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15660 self.assign_value(t, v)?;
15661 }
15662 Ok(StrykeValue::UNDEF)
15663 }
15664 ExprKind::Assign { target, .. } => self.assign_value(target, val),
15667 _ => Ok(StrykeValue::UNDEF),
15668 }
15669 }
15670
15671 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
15673 (name.starts_with('#') && name.len() > 1)
15674 || name.starts_with('^')
15675 || matches!(
15676 name,
15677 "$$" | "0"
15678 | "!"
15679 | "@"
15680 | "/"
15681 | "\\"
15682 | ","
15683 | "."
15684 | "]"
15685 | ";"
15686 | "ARGV"
15687 | "^I"
15688 | "^D"
15689 | "^P"
15690 | "^S"
15691 | "^W"
15692 | "^O"
15693 | "^T"
15694 | "^V"
15695 | "^E"
15696 | "^H"
15697 | "^WARNING_BITS"
15698 | "^GLOBAL_PHASE"
15699 | "^MATCH"
15700 | "^PREMATCH"
15701 | "^POSTMATCH"
15702 | "^LAST_SUBMATCH_RESULT"
15703 | "<"
15704 | ">"
15705 | "("
15706 | ")"
15707 | "?"
15708 | "|"
15709 | "\""
15710 | "+"
15711 | "%"
15712 | "="
15713 | "-"
15714 | ":"
15715 | "*"
15716 | "INC"
15717 )
15718 || crate::english::is_known_alias(name)
15719 }
15720
15721 #[inline]
15726 pub(crate) fn resolved_scalar_storage_name(&self, name: &str) -> String {
15731 self.tree_scalar_storage_name(self.english_scalar_name(name))
15732 }
15733
15734 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
15735 if !self.english_enabled {
15736 return name;
15737 }
15738 if self
15739 .english_lexical_scalars
15740 .iter()
15741 .any(|s| s.contains(name))
15742 {
15743 return name;
15744 }
15745 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
15746 return short;
15747 }
15748 name
15749 }
15750
15751 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
15753 (name.starts_with('#') && name.len() > 1)
15758 || name.starts_with('^')
15759 || matches!(
15760 name,
15761 "0" | "/"
15762 | "\\"
15763 | ","
15764 | ";"
15765 | "\""
15766 | "%"
15767 | "="
15768 | "-"
15769 | ":"
15770 | "*"
15771 | "INC"
15772 | "^I"
15773 | "^D"
15774 | "^P"
15775 | "^W"
15776 | "^H"
15777 | "^WARNING_BITS"
15778 | "$$"
15779 | "]"
15780 | "^S"
15781 | "ARGV"
15782 | "|"
15783 | "+"
15784 | "?"
15785 | "!"
15786 | "@"
15787 | "."
15788 )
15789 || crate::english::is_known_alias(name)
15790 }
15791
15792 pub(crate) fn get_special_var(&self, name: &str) -> StrykeValue {
15793 let name = if !crate::compat_mode() {
15795 match name {
15796 "NR" => ".",
15797 "RS" => "/",
15798 "OFS" => ",",
15799 "ORS" => "\\",
15800 "NF" => {
15801 let len = self.scope.array_len("F");
15802 return StrykeValue::integer(len as i64);
15803 }
15804 _ => self.english_scalar_name(name),
15805 }
15806 } else {
15807 self.english_scalar_name(name)
15808 };
15809 match name {
15810 "$$" => StrykeValue::integer(std::process::id() as i64),
15811 "_" => self.scope.get_scalar("_"),
15812 "^MATCH" => StrykeValue::string(self.last_match.clone()),
15813 "^PREMATCH" => StrykeValue::string(self.prematch.clone()),
15814 "^POSTMATCH" => StrykeValue::string(self.postmatch.clone()),
15815 "^LAST_SUBMATCH_RESULT" => StrykeValue::string(self.last_paren_match.clone()),
15816 "0" => StrykeValue::string(self.program_name.clone()),
15817 "!" => StrykeValue::errno_dual(self.errno_code, self.errno.clone()),
15818 "@" => {
15819 if let Some(ref v) = self.eval_error_value {
15820 v.clone()
15821 } else {
15822 StrykeValue::errno_dual(self.eval_error_code, self.eval_error.clone())
15823 }
15824 }
15825 "/" => match &self.irs {
15826 Some(s) => StrykeValue::string(s.clone()),
15827 None => StrykeValue::UNDEF,
15828 },
15829 "\\" => StrykeValue::string(self.ors.clone()),
15830 "," => StrykeValue::string(self.ofs.clone()),
15831 "." => {
15832 if self.last_readline_handle.is_empty() {
15834 if self.line_number == 0 {
15835 StrykeValue::UNDEF
15836 } else {
15837 StrykeValue::integer(self.line_number)
15838 }
15839 } else {
15840 StrykeValue::integer(
15841 *self
15842 .handle_line_numbers
15843 .get(&self.last_readline_handle)
15844 .unwrap_or(&0),
15845 )
15846 }
15847 }
15848 "]" => StrykeValue::float(perl_bracket_version()),
15849 ";" => StrykeValue::string(self.subscript_sep.clone()),
15850 "ARGV" => StrykeValue::string(self.argv_current_file.clone()),
15851 "^I" => StrykeValue::string(self.inplace_edit.clone()),
15852 "^D" => StrykeValue::integer(self.debug_flags),
15853 "^P" => StrykeValue::integer(self.perl_debug_flags),
15854 "^S" => StrykeValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
15855 "^W" => StrykeValue::integer(if self.warnings { 1 } else { 0 }),
15856 "^O" => StrykeValue::string(perl_osname()),
15857 "^T" => StrykeValue::integer(self.script_start_time),
15858 "^V" => StrykeValue::string(perl_version_v_string()),
15859 "^E" => StrykeValue::string(extended_os_error_string()),
15860 "^H" => StrykeValue::integer(self.compile_hints),
15861 "^WARNING_BITS" => StrykeValue::integer(self.warning_bits),
15862 "^GLOBAL_PHASE" => StrykeValue::string(self.global_phase.clone()),
15863 "<" | ">" => StrykeValue::integer(unix_id_for_special(name)),
15864 "(" | ")" => StrykeValue::string(unix_group_list_for_special(name)),
15865 "?" => StrykeValue::integer(self.child_exit_status),
15866 "|" => StrykeValue::integer(if self.output_autoflush { 1 } else { 0 }),
15867 "\"" => StrykeValue::string(self.list_separator.clone()),
15868 "+" => StrykeValue::string(self.last_paren_match.clone()),
15869 "%" => StrykeValue::integer(self.format_page_number),
15870 "=" => StrykeValue::integer(self.format_lines_per_page),
15871 "-" => StrykeValue::integer(self.format_lines_left),
15872 ":" => StrykeValue::string(self.format_line_break_chars.clone()),
15873 "*" => StrykeValue::integer(if self.multiline_match { 1 } else { 0 }),
15874 "^" => StrykeValue::string(self.format_top_name.clone()),
15875 "INC" => StrykeValue::integer(self.inc_hook_index),
15876 "^A" => StrykeValue::string(self.accumulator_format.clone()),
15877 "^C" => StrykeValue::integer(if self.sigint_pending_caret.replace(false) {
15878 1
15879 } else {
15880 0
15881 }),
15882 "^F" => StrykeValue::integer(self.max_system_fd),
15883 "^L" => StrykeValue::string(self.formfeed_string.clone()),
15884 "^M" => StrykeValue::string(self.emergency_memory.clone()),
15885 "^N" => StrykeValue::string(self.last_subpattern_name.clone()),
15886 "^X" => StrykeValue::string(self.executable_path.clone()),
15887 "^TAINT" | "^TAINTED" => StrykeValue::integer(0),
15889 "^UNICODE" => StrykeValue::integer(if self.utf8_pragma { 1 } else { 0 }),
15890 "^OPEN" => StrykeValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
15891 "^UTF8LOCALE" => StrykeValue::integer(0),
15892 "^UTF8CACHE" => StrykeValue::integer(-1),
15893 _ if name.starts_with('^') && name.len() > 1 => self
15894 .special_caret_scalars
15895 .get(name)
15896 .cloned()
15897 .unwrap_or(StrykeValue::UNDEF),
15898 _ if name.starts_with('#') && name.len() > 1 => {
15899 let arr = &name[1..];
15900 let aname = self.stash_array_name_for_package(arr);
15901 let len = self.scope.array_len(&aname);
15902 StrykeValue::integer(len as i64 - 1)
15903 }
15904 _ => self.scope.get_scalar(name),
15905 }
15906 }
15907
15908 pub(crate) fn set_special_var(
15909 &mut self,
15910 name: &str,
15911 val: &StrykeValue,
15912 ) -> Result<(), StrykeError> {
15913 let name = self.english_scalar_name(name);
15914 match name {
15915 "!" => {
15916 let code = val.to_int() as i32;
15917 self.errno_code = code;
15918 self.errno = if code == 0 {
15919 String::new()
15920 } else {
15921 std::io::Error::from_raw_os_error(code).to_string()
15922 };
15923 }
15924 "@" => {
15925 if let Some((code, msg)) = val.errno_dual_parts() {
15926 self.eval_error_code = code;
15927 self.eval_error = msg;
15928 } else {
15929 self.eval_error = val.to_string();
15930 let mut code = val.to_int() as i32;
15931 if code == 0 && !self.eval_error.is_empty() {
15932 code = 1;
15933 }
15934 self.eval_error_code = code;
15935 }
15936 }
15937 "." => {
15938 let n = val.to_int();
15941 if self.last_readline_handle.is_empty() {
15942 self.line_number = n;
15943 } else {
15944 self.handle_line_numbers
15945 .insert(self.last_readline_handle.clone(), n);
15946 }
15947 }
15948 "0" => self.program_name = val.to_string(),
15949 "/" => {
15950 self.irs = if val.is_undef() {
15951 None
15952 } else {
15953 Some(val.to_string())
15954 }
15955 }
15956 "\\" => self.ors = val.to_string(),
15957 "," => self.ofs = val.to_string(),
15958 ";" => self.subscript_sep = val.to_string(),
15959 "\"" => self.list_separator = val.to_string(),
15960 "%" => self.format_page_number = val.to_int(),
15961 "=" => self.format_lines_per_page = val.to_int(),
15962 "-" => self.format_lines_left = val.to_int(),
15963 ":" => self.format_line_break_chars = val.to_string(),
15964 "*" => self.multiline_match = val.to_int() != 0,
15965 "^" => self.format_top_name = val.to_string(),
15966 "INC" => self.inc_hook_index = val.to_int(),
15967 "^A" => self.accumulator_format = val.to_string(),
15968 "^F" => self.max_system_fd = val.to_int(),
15969 "^L" => self.formfeed_string = val.to_string(),
15970 "^M" => self.emergency_memory = val.to_string(),
15971 "^I" => self.inplace_edit = val.to_string(),
15972 "^D" => self.debug_flags = val.to_int(),
15973 "^P" => self.perl_debug_flags = val.to_int(),
15974 "^W" => self.warnings = val.to_int() != 0,
15975 "^H" => self.compile_hints = val.to_int(),
15976 "^WARNING_BITS" => self.warning_bits = val.to_int(),
15977 "|" => {
15978 self.output_autoflush = val.to_int() != 0;
15979 if self.output_autoflush {
15980 let _ = io::stdout().flush();
15981 }
15982 }
15983 "$$"
15985 | "]"
15986 | "^S"
15987 | "ARGV"
15988 | "?"
15989 | "^O"
15990 | "^T"
15991 | "^V"
15992 | "^E"
15993 | "^GLOBAL_PHASE"
15994 | "^MATCH"
15995 | "^PREMATCH"
15996 | "^POSTMATCH"
15997 | "^LAST_SUBMATCH_RESULT"
15998 | "^C"
15999 | "^N"
16000 | "^X"
16001 | "^TAINT"
16002 | "^TAINTED"
16003 | "^UNICODE"
16004 | "^UTF8LOCALE"
16005 | "^UTF8CACHE"
16006 | "+"
16007 | "<"
16008 | ">"
16009 | "("
16010 | ")" => {}
16011 _ if name.starts_with('^') && name.len() > 1 => {
16012 self.special_caret_scalars
16013 .insert(name.to_string(), val.clone());
16014 }
16015 _ if name.starts_with('#') && name.len() > 1 => {
16016 let arr = &name[1..];
16019 let aname = self.stash_array_name_for_package(arr);
16020 let new_last = val.to_int();
16021 let new_len = if new_last < 0 {
16022 0
16023 } else {
16024 (new_last as usize) + 1
16025 };
16026 let mut current = self.scope.get_array(&aname);
16027 current.resize(new_len, StrykeValue::UNDEF);
16028 self.scope.set_array(&aname, current)?;
16029 }
16030 _ => self.scope.set_scalar(name, val.clone())?,
16031 }
16032 Ok(())
16033 }
16034
16035 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
16036 match &expr.kind {
16037 ExprKind::ArrayVar(name) => Ok(self.tree_array_storage_name(name)),
16041 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(StrykeError::runtime("Expected array", expr.line).into()),
16043 }
16044 }
16045
16046 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
16048 match &expr.kind {
16049 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
16050 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
16051 _ => expr,
16052 }
16053 }
16054
16055 fn try_eval_array_deref_container(
16057 &mut self,
16058 expr: &Expr,
16059 ) -> Result<Option<StrykeValue>, FlowOrError> {
16060 let e = Self::peel_array_builtin_operand(expr);
16061 if let ExprKind::Deref {
16062 expr: inner,
16063 kind: Sigil::Array,
16064 } = &e.kind
16065 {
16066 return Ok(Some(self.eval_or_autoviv_array_ref(inner)?));
16067 }
16068 Ok(None)
16069 }
16070
16071 fn eval_or_autoviv_array_ref(&mut self, inner: &Expr) -> Result<StrykeValue, FlowOrError> {
16075 let line = inner.line;
16076 let val = self.eval_expr(inner)?;
16077 if !val.is_undef() {
16078 return Ok(val);
16079 }
16080 let new_ref = StrykeValue::array_ref(Arc::new(RwLock::new(Vec::new())));
16081 match &inner.kind {
16082 ExprKind::ScalarVar(name) => {
16083 self.scope
16084 .set_scalar(name, new_ref.clone())
16085 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
16086 Ok(new_ref)
16087 }
16088 ExprKind::HashElement { hash, key } => {
16089 let k = self.eval_expr(key)?.to_string();
16090 self.scope
16091 .set_hash_element(hash, &k, new_ref.clone())
16092 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
16093 Ok(new_ref)
16094 }
16095 ExprKind::ArrayElement { array, index } => {
16096 let i = self.eval_expr(index)?.to_int();
16097 self.scope
16098 .set_array_element(array, i, new_ref.clone())
16099 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
16100 Ok(new_ref)
16101 }
16102 _ => Ok(val),
16103 }
16104 }
16105
16106 pub(crate) fn current_package(&self) -> String {
16108 let s = self.scope.get_scalar("__PACKAGE__").to_string();
16109 if s.is_empty() {
16110 "main".to_string()
16111 } else {
16112 s
16113 }
16114 }
16115
16116 pub(crate) fn package_version_scalar(
16119 &mut self,
16120 package: &str,
16121 ) -> StrykeResult<Option<StrykeValue>> {
16122 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
16123 let _ = self
16124 .scope
16125 .set_scalar("__PACKAGE__", StrykeValue::string(package.to_string()));
16126 let ver = self.get_special_var("VERSION");
16127 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
16128 Ok(if ver.is_undef() { None } else { Some(ver) })
16129 }
16130
16131 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<StrykeSub>> {
16133 let root = if start_package.is_empty() {
16134 "main"
16135 } else {
16136 start_package
16137 };
16138 for pkg in self.mro_linearize(root) {
16139 let key = if pkg == "main" {
16140 "AUTOLOAD".to_string()
16141 } else {
16142 format!("{}::AUTOLOAD", pkg)
16143 };
16144 if let Some(s) = self.subs.get(&key) {
16145 return Some(s.clone());
16146 }
16147 }
16148 None
16149 }
16150
16151 pub(crate) fn try_autoload_call(
16156 &mut self,
16157 missing_name: &str,
16158 args: Vec<StrykeValue>,
16159 line: usize,
16160 want: WantarrayCtx,
16161 method_invocant_class: Option<&str>,
16162 ) -> Option<ExecResult> {
16163 let pkg = self.current_package();
16164 let full = if missing_name.contains("::") {
16165 missing_name.to_string()
16166 } else {
16167 format!("{}::{}", pkg, missing_name)
16168 };
16169 let start_pkg = method_invocant_class.unwrap_or_else(|| {
16170 full.rsplit_once("::")
16171 .map(|(p, _)| p)
16172 .filter(|p| !p.is_empty())
16173 .unwrap_or("main")
16174 });
16175 let sub = self.resolve_autoload_sub(start_pkg)?;
16176 if let Err(e) = self
16177 .scope
16178 .set_scalar("AUTOLOAD", StrykeValue::string(full.clone()))
16179 {
16180 return Some(Err(e.into()));
16181 }
16182 Some(self.call_sub(&sub, args, want, line))
16183 }
16184
16185 pub(crate) fn with_topic_default_args(&self, args: Vec<StrykeValue>) -> Vec<StrykeValue> {
16186 if args.is_empty() {
16187 vec![self.scope.get_scalar("_").clone()]
16188 } else {
16189 args
16190 }
16191 }
16192
16193 pub(crate) fn dispatch_indirect_call(
16196 &mut self,
16197 target: StrykeValue,
16198 arg_vals: Vec<StrykeValue>,
16199 want: WantarrayCtx,
16200 line: usize,
16201 ) -> ExecResult {
16202 if let Some(sub) = target.as_code_ref() {
16203 return self.call_sub(&sub, arg_vals, want, line);
16204 }
16205 if let Some(name) = target.as_str() {
16206 return self.call_named_sub(&name, arg_vals, line, want);
16207 }
16208 Err(StrykeError::runtime("Can't use non-code reference as a subroutine", line).into())
16209 }
16210
16211 pub(crate) fn call_bare_list_builtin(
16216 &mut self,
16217 name: &str,
16218 args: Vec<StrykeValue>,
16219 line: usize,
16220 want: WantarrayCtx,
16221 ) -> ExecResult {
16222 let canonical = match name {
16223 "distinct" | "uq" => "uniq",
16224 "shuf" => "shuffle",
16225 "chk" => "chunked",
16226 "win" => "windowed",
16227 "zp" => "zip",
16228 "fst" => "first",
16229 "rd" => "reduce",
16230 "med" => "median",
16231 "std" => "stddev",
16232 "var" => "variance",
16233 other => other,
16234 };
16235 match crate::list_builtins::dispatch_by_name(self, canonical, &args, want) {
16240 Some(r) => r,
16241 None => Err(StrykeError::runtime(
16242 format!("internal: not a stryke list builtin: {name}"),
16243 line,
16244 )
16245 .into()),
16246 }
16247 }
16248
16249 fn call_named_sub(
16250 &mut self,
16251 name: &str,
16252 args: Vec<StrykeValue>,
16253 line: usize,
16254 want: WantarrayCtx,
16255 ) -> ExecResult {
16256 if let Some(sub) = self.resolve_sub_by_name(name) {
16257 let args = self.with_topic_default_args(args);
16258 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
16262 return self.call_sub_with_package(&sub, args, want, line, pkg);
16263 }
16264 match name {
16265 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
16266 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
16267 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
16268 | "none" | "notall" | "first" | "fst" | "find_index" | "firstidx" | "first_index"
16269 | "reduce" | "rd" | "reductions" | "sum" | "sum0" | "product" | "min" | "max"
16270 | "minstr" | "maxstr" | "mean" | "median" | "med" | "mode" | "stddev" | "std"
16271 | "variance" | "var" | "pairs" | "unpairs" | "pairkeys" | "pairvalues" | "pairgrep"
16272 | "pairmap" | "pairfirst" | "blessed" | "refaddr" | "reftype" | "looks_like_number"
16273 | "weaken" | "unweaken" | "isweak" | "set_subname" | "subname"
16274 | "unicode_to_native" => self.call_bare_list_builtin(name, args, line, want),
16275 "deque" => {
16276 if !args.is_empty() {
16277 return Err(StrykeError::runtime("deque() takes no arguments", line).into());
16278 }
16279 Ok(StrykeValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
16280 }
16281 "defer__internal" => {
16282 if args.len() != 1 {
16283 return Err(StrykeError::runtime(
16284 "defer__internal expects one coderef argument",
16285 line,
16286 )
16287 .into());
16288 }
16289 self.scope.push_defer(args[0].clone());
16290 Ok(StrykeValue::UNDEF)
16291 }
16292 "heap" => {
16293 if args.len() != 1 {
16294 return Err(
16295 StrykeError::runtime("heap() expects one comparator sub", line).into(),
16296 );
16297 }
16298 if let Some(sub) = args[0].as_code_ref() {
16299 Ok(StrykeValue::heap(Arc::new(Mutex::new(PerlHeap {
16300 items: Vec::new(),
16301 cmp: Arc::clone(&sub),
16302 }))))
16303 } else {
16304 Err(StrykeError::runtime("heap() requires a code reference", line).into())
16305 }
16306 }
16307 "pipeline" => {
16308 let mut items = Vec::new();
16309 for v in args {
16310 if let Some(a) = v.as_array_vec() {
16311 items.extend(a);
16312 } else {
16313 items.push(v);
16314 }
16315 }
16316 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
16317 source: items,
16318 ops: Vec::new(),
16319 has_scalar_terminal: false,
16320 par_stream: false,
16321 streaming: false,
16322 streaming_workers: 0,
16323 streaming_buffer: 256,
16324 }))))
16325 }
16326 "par_pipeline" => {
16327 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
16328 return crate::par_pipeline::run_par_pipeline(self, &args, line)
16329 .map_err(Into::into);
16330 }
16331 Ok(self.builtin_par_pipeline_stream(&args, line)?)
16332 }
16333 "par_pipeline_stream" => {
16334 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
16335 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
16336 .map_err(Into::into);
16337 }
16338 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
16339 }
16340 "ppool" => {
16341 if args.len() != 1 {
16342 return Err(StrykeError::runtime(
16343 "ppool() expects one argument (worker count)",
16344 line,
16345 )
16346 .into());
16347 }
16348 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
16349 }
16350 "barrier" => {
16351 if args.len() != 1 {
16352 return Err(StrykeError::runtime(
16353 "barrier() expects one argument (party count)",
16354 line,
16355 )
16356 .into());
16357 }
16358 let n = args[0].to_int().max(1) as usize;
16359 Ok(StrykeValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
16360 }
16361 "cluster" => {
16362 let items = if args.len() == 1 {
16363 args[0].to_list()
16364 } else {
16365 args.to_vec()
16366 };
16367 let c = RemoteCluster::from_list_args(&items)
16368 .map_err(|msg| StrykeError::runtime(msg, line))?;
16369 Ok(StrykeValue::remote_cluster(Arc::new(c)))
16370 }
16371 _ => {
16372 if let Some(method_name) = name.strip_prefix("static::") {
16374 let self_val = self.scope.get_scalar("self");
16375 if let Some(c) = self_val.as_class_inst() {
16376 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
16377 if let Some(ref body) = m.body {
16378 let params = m.params.clone();
16379 let mut call_args = vec![self_val.clone()];
16380 call_args.extend(args);
16381 return match self.call_class_method(body, ¶ms, call_args, line)
16382 {
16383 Ok(v) => Ok(v),
16384 Err(FlowOrError::Error(e)) => Err(e.into()),
16385 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16386 Err(e) => Err(e),
16387 };
16388 }
16389 }
16390 return Err(StrykeError::runtime(
16391 format!(
16392 "static::{} — method not found on class {}",
16393 method_name, c.def.name
16394 ),
16395 line,
16396 )
16397 .into());
16398 }
16399 return Err(StrykeError::runtime(
16400 "static:: can only be used inside a class method",
16401 line,
16402 )
16403 .into());
16404 }
16405 if let Some(def) = self.struct_defs.get(name).cloned() {
16407 return self.struct_construct(&def, args, line);
16408 }
16409 if let Some(def) = self.class_defs.get(name).cloned() {
16411 return self.class_construct(&def, args, line);
16412 }
16413 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
16415 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
16416 return self.enum_construct(&def, variant_name, args, line);
16417 }
16418 }
16419 if let Some((class_name, member_name)) = name.rsplit_once("::") {
16421 if let Some(def) = self.class_defs.get(class_name).cloned() {
16422 if let Some(m) = def.method(member_name) {
16424 if m.is_static {
16425 if let Some(ref body) = m.body {
16426 let params = m.params.clone();
16427 return match self.call_static_class_method(
16428 body,
16429 ¶ms,
16430 args.clone(),
16431 line,
16432 ) {
16433 Ok(v) => Ok(v),
16434 Err(FlowOrError::Error(e)) => Err(e.into()),
16435 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16436 Err(e) => Err(e),
16437 };
16438 }
16439 }
16440 }
16441 if def.static_fields.iter().any(|sf| sf.name == member_name) {
16443 let key = format!("{}::{}", class_name, member_name);
16444 match args.len() {
16445 0 => {
16446 let val = self.scope.get_scalar(&key);
16447 return Ok(val);
16448 }
16449 1 => {
16450 let _ = self.scope.set_scalar(&key, args[0].clone());
16451 return Ok(args[0].clone());
16452 }
16453 _ => {
16454 return Err(StrykeError::runtime(
16455 format!(
16456 "static field `{}::{}` takes 0 or 1 arguments",
16457 class_name, member_name
16458 ),
16459 line,
16460 )
16461 .into());
16462 }
16463 }
16464 }
16465 }
16466 }
16467 let args = self.with_topic_default_args(args);
16468 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
16469 return r;
16470 }
16471 Err(StrykeError::runtime(self.undefined_subroutine_call_message(name), line).into())
16472 }
16473 }
16474 }
16475
16476 pub(crate) fn struct_construct(
16478 &mut self,
16479 def: &Arc<StructDef>,
16480 args: Vec<StrykeValue>,
16481 line: usize,
16482 ) -> ExecResult {
16483 let is_named = args.len() >= 2
16486 && args.len().is_multiple_of(2)
16487 && args.iter().step_by(2).all(|v| {
16488 let s = v.to_string();
16489 def.field_index(&s).is_some()
16490 });
16491
16492 let provided = if is_named {
16493 let mut pairs = Vec::new();
16495 let mut i = 0;
16496 while i + 1 < args.len() {
16497 let k = args[i].to_string();
16498 let v = args[i + 1].clone();
16499 pairs.push((k, v));
16500 i += 2;
16501 }
16502 pairs
16503 } else {
16504 def.fields
16506 .iter()
16507 .zip(args.iter())
16508 .map(|(f, v)| (f.name.clone(), v.clone()))
16509 .collect()
16510 };
16511
16512 let mut defaults = Vec::with_capacity(def.fields.len());
16514 for field in &def.fields {
16515 if let Some(ref expr) = field.default {
16516 let val = self.eval_expr(expr)?;
16517 defaults.push(Some(val));
16518 } else {
16519 defaults.push(None);
16520 }
16521 }
16522
16523 Ok(crate::native_data::struct_new_with_defaults(
16524 def, &provided, &defaults, line,
16525 )?)
16526 }
16527
16528 pub(crate) fn class_construct(
16530 &mut self,
16531 def: &Arc<ClassDef>,
16532 args: Vec<StrykeValue>,
16533 _line: usize,
16534 ) -> ExecResult {
16535 use crate::value::ClassInstance;
16536
16537 if def.is_abstract {
16539 return Err(StrykeError::runtime(
16540 format!("cannot instantiate abstract class `{}`", def.name),
16541 _line,
16542 )
16543 .into());
16544 }
16545
16546 let all_fields = self.collect_class_fields(def);
16548
16549 let is_named = args.len() >= 2
16551 && args.len().is_multiple_of(2)
16552 && args.iter().step_by(2).all(|v| {
16553 let s = v.to_string();
16554 all_fields.iter().any(|(name, _, _)| name == &s)
16555 });
16556
16557 let provided: Vec<(String, StrykeValue)> = if is_named {
16558 let mut pairs = Vec::new();
16559 let mut i = 0;
16560 while i + 1 < args.len() {
16561 let k = args[i].to_string();
16562 let v = args[i + 1].clone();
16563 pairs.push((k, v));
16564 i += 2;
16565 }
16566 pairs
16567 } else {
16568 all_fields
16569 .iter()
16570 .zip(args.iter())
16571 .map(|((name, _, _), v)| (name.clone(), v.clone()))
16572 .collect()
16573 };
16574
16575 let mut values = Vec::with_capacity(all_fields.len());
16577 for (name, default, ty) in &all_fields {
16578 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
16579 val.clone()
16580 } else if let Some(ref expr) = default {
16581 self.eval_expr(expr)?
16582 } else {
16583 StrykeValue::UNDEF
16584 };
16585 ty.check_value(&val).map_err(|msg| {
16586 StrykeError::type_error(
16587 format!("class {} field `{}`: {}", def.name, name, msg),
16588 _line,
16589 )
16590 })?;
16591 values.push(val);
16592 }
16593
16594 let isa_chain = self.mro_linearize(&def.name);
16596 let instance = StrykeValue::class_inst(Arc::new(ClassInstance::new_with_isa(
16597 Arc::clone(def),
16598 values,
16599 isa_chain,
16600 )));
16601
16602 let build_chain = self.collect_build_chain(def);
16604 if !build_chain.is_empty() {
16605 for (body, params) in &build_chain {
16606 let call_args = vec![instance.clone()];
16607 match self.call_class_method(body, params, call_args, _line) {
16608 Ok(_) => {}
16609 Err(FlowOrError::Flow(Flow::Return(_))) => {}
16610 Err(e) => return Err(e),
16611 }
16612 }
16613 }
16614
16615 Ok(instance)
16616 }
16617
16618 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
16620 let mut chain = Vec::new();
16621 for parent_name in &def.extends {
16623 if let Some(parent_def) = self.class_defs.get(parent_name) {
16624 chain.extend(self.collect_build_chain(parent_def));
16625 }
16626 }
16627 if let Some(m) = def.method("BUILD") {
16629 if let Some(ref body) = m.body {
16630 chain.push((body.clone(), m.params.clone()));
16631 }
16632 }
16633 chain
16634 }
16635
16636 pub(crate) fn deep_to_hash_value(&self, v: &StrykeValue) -> StrykeValue {
16642 if let Some(c) = v.as_class_inst() {
16644 let all_fields = self.collect_class_fields_full(&c.def);
16645 let values = c.get_values();
16646 let mut map = IndexMap::new();
16647 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
16648 if let Some(elem) = values.get(i) {
16649 map.insert(name.clone(), self.deep_to_hash_value(elem));
16650 }
16651 }
16652 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16653 }
16654 if let Some(s) = v.as_struct_inst() {
16656 let values = s.get_values();
16657 let mut map = IndexMap::new();
16658 for (i, field) in s.def.fields.iter().enumerate() {
16659 if let Some(elem) = values.get(i) {
16660 map.insert(field.name.clone(), self.deep_to_hash_value(elem));
16661 }
16662 }
16663 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16664 }
16665 if let Some(r) = v.as_hash_ref() {
16667 let inner = r.read().clone();
16668 let mut map = IndexMap::new();
16669 for (k, val) in inner.into_iter() {
16670 map.insert(k, self.deep_to_hash_value(&val));
16671 }
16672 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16673 }
16674 if let Some(r) = v.as_array_ref() {
16676 let inner = r.read().clone();
16677 let out: Vec<StrykeValue> = inner.iter().map(|e| self.deep_to_hash_value(e)).collect();
16678 return StrykeValue::array_ref(Arc::new(RwLock::new(out)));
16679 }
16680 v.clone()
16684 }
16685
16686 fn collect_class_fields(
16689 &self,
16690 def: &ClassDef,
16691 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
16692 self.collect_class_fields_full(def)
16693 .into_iter()
16694 .map(|(name, default, ty, _, _)| (name, default, ty))
16695 .collect()
16696 }
16697
16698 fn collect_class_fields_full(
16700 &self,
16701 def: &ClassDef,
16702 ) -> Vec<(
16703 String,
16704 Option<Expr>,
16705 crate::ast::PerlTypeName,
16706 crate::ast::Visibility,
16707 String,
16708 )> {
16709 let mut all_fields = Vec::new();
16710
16711 for parent_name in &def.extends {
16712 if let Some(parent_def) = self.class_defs.get(parent_name) {
16713 let parent_fields = self.collect_class_fields_full(parent_def);
16714 all_fields.extend(parent_fields);
16715 }
16716 }
16717
16718 for field in &def.fields {
16719 all_fields.push((
16720 field.name.clone(),
16721 field.default.clone(),
16722 field.ty.clone(),
16723 field.visibility,
16724 def.name.clone(),
16725 ));
16726 }
16727
16728 all_fields
16729 }
16730
16731 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
16733 for parent_name in &def.extends {
16735 if let Some(parent_def) = self.class_defs.get(parent_name) {
16736 self.collect_class_method_names(parent_def, names);
16737 }
16738 }
16739 for m in &def.methods {
16741 if !m.is_static && !names.contains(&m.name) {
16742 names.push(m.name.clone());
16743 }
16744 }
16745 }
16746
16747 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
16749 let mut chain = Vec::new();
16750 if let Some(m) = def.method("DESTROY") {
16752 if let Some(ref body) = m.body {
16753 chain.push((body.clone(), m.params.clone()));
16754 }
16755 }
16756 for parent_name in &def.extends {
16758 if let Some(parent_def) = self.class_defs.get(parent_name) {
16759 chain.extend(self.collect_destroy_chain(parent_def));
16760 }
16761 }
16762 chain
16763 }
16764
16765 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
16767 if let Some(def) = self.class_defs.get(child) {
16768 for parent in &def.extends {
16769 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
16770 return true;
16771 }
16772 }
16773 }
16774 false
16775 }
16776
16777 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
16779 if let Some(m) = def.method(method) {
16781 return Some((m.clone(), def.name.clone()));
16782 }
16783 for parent_name in &def.extends {
16785 if let Some(parent_def) = self.class_defs.get(parent_name) {
16786 if let Some(result) = self.find_class_method(parent_def, method) {
16787 return Some(result);
16788 }
16789 }
16790 }
16791 None
16792 }
16793
16794 pub(crate) fn enum_construct(
16796 &mut self,
16797 def: &Arc<EnumDef>,
16798 variant_name: &str,
16799 args: Vec<StrykeValue>,
16800 line: usize,
16801 ) -> ExecResult {
16802 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
16803 FlowOrError::Error(StrykeError::runtime(
16804 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
16805 line,
16806 ))
16807 })?;
16808 let variant = &def.variants[variant_idx];
16809 let data = if variant.ty.is_some() {
16810 if args.is_empty() {
16811 return Err(StrykeError::runtime(
16812 format!(
16813 "enum variant `{}::{}` requires data",
16814 def.name, variant_name
16815 ),
16816 line,
16817 )
16818 .into());
16819 }
16820 if args.len() == 1 {
16821 args.into_iter().next().unwrap()
16822 } else {
16823 StrykeValue::array(args)
16824 }
16825 } else {
16826 if !args.is_empty() {
16827 return Err(StrykeError::runtime(
16828 format!(
16829 "enum variant `{}::{}` does not take data",
16830 def.name, variant_name
16831 ),
16832 line,
16833 )
16834 .into());
16835 }
16836 StrykeValue::UNDEF
16837 };
16838 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
16839 Ok(StrykeValue::enum_inst(Arc::new(inst)))
16840 }
16841
16842 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
16844 matches!(name, "STDIN" | "STDOUT" | "STDERR")
16845 || self.input_handles.contains_key(name)
16846 || self.output_handles.contains_key(name)
16847 || self.io_file_slots.contains_key(name)
16848 || self.pipe_children.contains_key(name)
16849 }
16850
16851 pub(crate) fn io_handle_method(
16853 &mut self,
16854 name: &str,
16855 method: &str,
16856 args: &[StrykeValue],
16857 line: usize,
16858 ) -> StrykeResult<StrykeValue> {
16859 match method {
16860 "print" => self.io_handle_print(name, args, false, line),
16861 "say" => self.io_handle_print(name, args, true, line),
16862 "printf" => self.io_handle_printf(name, args, line),
16863 "getline" | "readline" => {
16864 if !args.is_empty() {
16865 return Err(StrykeError::runtime(
16866 format!("{}: too many arguments", method),
16867 line,
16868 ));
16869 }
16870 self.readline_builtin_execute(Some(name))
16871 }
16872 "close" => {
16873 if !args.is_empty() {
16874 return Err(StrykeError::runtime("close: too many arguments", line));
16875 }
16876 self.close_builtin_execute(name.to_string())
16877 }
16878 "eof" => {
16879 if !args.is_empty() {
16880 return Err(StrykeError::runtime("eof: too many arguments", line));
16881 }
16882 let at_eof = !self.has_input_handle(name);
16883 Ok(StrykeValue::integer(if at_eof { 1 } else { 0 }))
16884 }
16885 "getc" => {
16886 if !args.is_empty() {
16887 return Err(StrykeError::runtime("getc: too many arguments", line));
16888 }
16889 match crate::builtins::try_builtin(
16890 self,
16891 "getc",
16892 &[StrykeValue::string(name.to_string())],
16893 line,
16894 ) {
16895 Some(r) => r,
16896 None => Err(StrykeError::runtime("getc: not available", line)),
16897 }
16898 }
16899 "binmode" => match crate::builtins::try_builtin(
16900 self,
16901 "binmode",
16902 &[StrykeValue::string(name.to_string())],
16903 line,
16904 ) {
16905 Some(r) => r,
16906 None => Err(StrykeError::runtime("binmode: not available", line)),
16907 },
16908 "fileno" => match crate::builtins::try_builtin(
16909 self,
16910 "fileno",
16911 &[StrykeValue::string(name.to_string())],
16912 line,
16913 ) {
16914 Some(r) => r,
16915 None => Err(StrykeError::runtime("fileno: not available", line)),
16916 },
16917 "flush" => {
16918 if !args.is_empty() {
16919 return Err(StrykeError::runtime("flush: too many arguments", line));
16920 }
16921 self.io_handle_flush(name, line)
16922 }
16923 _ => Err(StrykeError::runtime(
16924 format!("Unknown method for filehandle: {}", method),
16925 line,
16926 )),
16927 }
16928 }
16929
16930 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> StrykeResult<StrykeValue> {
16931 match handle_name {
16932 "STDOUT" => {
16933 let _ = IoWrite::flush(&mut io::stdout());
16934 }
16935 "STDERR" => {
16936 let _ = IoWrite::flush(&mut io::stderr());
16937 }
16938 name => {
16939 if let Some(writer) = self.output_handles.get_mut(name) {
16940 let _ = IoWrite::flush(&mut *writer);
16941 } else {
16942 return Err(StrykeError::runtime(
16943 format!("flush on unopened filehandle {}", name),
16944 line,
16945 ));
16946 }
16947 }
16948 }
16949 Ok(StrykeValue::integer(1))
16950 }
16951
16952 fn io_handle_print(
16953 &mut self,
16954 handle_name: &str,
16955 args: &[StrykeValue],
16956 newline: bool,
16957 line: usize,
16958 ) -> StrykeResult<StrykeValue> {
16959 if newline && (self.feature_bits & FEAT_SAY) == 0 {
16960 return Err(StrykeError::runtime(
16961 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
16962 line,
16963 ));
16964 }
16965 let mut output = String::new();
16966 if args.is_empty() {
16967 output.push_str(&self.scope.get_scalar("_").to_string());
16969 } else {
16970 for (i, val) in args.iter().enumerate() {
16971 if i > 0 && !self.ofs.is_empty() {
16972 output.push_str(&self.ofs);
16973 }
16974 output.push_str(&val.to_string());
16975 }
16976 }
16977 if newline {
16978 output.push('\n');
16979 }
16980 output.push_str(&self.ors);
16981
16982 self.write_formatted_print(handle_name, &output, line)?;
16983 Ok(StrykeValue::integer(1))
16984 }
16985
16986 pub(crate) fn write_formatted_print(
16989 &mut self,
16990 handle_name: &str,
16991 output: &str,
16992 line: usize,
16993 ) -> StrykeResult<()> {
16994 match handle_name {
16995 "STDOUT" => {
16996 if !self.suppress_stdout {
16997 print!("{}", output);
16998 if self.output_autoflush {
16999 let _ = io::stdout().flush();
17000 }
17001 }
17002 }
17003 "STDERR" => {
17004 eprint!("{}", output);
17005 let _ = io::stderr().flush();
17006 }
17007 name => {
17008 if let Some(writer) = self.output_handles.get_mut(name) {
17009 let _ = writer.write_all(output.as_bytes());
17010 if self.output_autoflush {
17011 let _ = writer.flush();
17012 }
17013 } else {
17014 return Err(StrykeError::runtime(
17015 format!("print on unopened filehandle {}", name),
17016 line,
17017 ));
17018 }
17019 }
17020 }
17021 Ok(())
17022 }
17023
17024 fn io_handle_printf(
17025 &mut self,
17026 handle_name: &str,
17027 args: &[StrykeValue],
17028 line: usize,
17029 ) -> StrykeResult<StrykeValue> {
17030 let (fmt, rest): (String, &[StrykeValue]) = if args.is_empty() {
17031 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
17032 Ok(s) => s,
17033 Err(FlowOrError::Error(e)) => return Err(e),
17034 Err(FlowOrError::Flow(_)) => {
17035 return Err(StrykeError::runtime(
17036 "printf: unexpected control flow in sprintf",
17037 line,
17038 ));
17039 }
17040 };
17041 (s, &[])
17042 } else {
17043 (args[0].to_string(), &args[1..])
17044 };
17045 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
17046 Ok(s) => s,
17047 Err(FlowOrError::Error(e)) => return Err(e),
17048 Err(FlowOrError::Flow(_)) => {
17049 return Err(StrykeError::runtime(
17050 "printf: unexpected control flow in sprintf",
17051 line,
17052 ));
17053 }
17054 };
17055 match handle_name {
17056 "STDOUT" => {
17057 if !self.suppress_stdout {
17058 print!("{}", output);
17059 if self.output_autoflush {
17060 let _ = IoWrite::flush(&mut io::stdout());
17061 }
17062 }
17063 }
17064 "STDERR" => {
17065 eprint!("{}", output);
17066 let _ = IoWrite::flush(&mut io::stderr());
17067 }
17068 name => {
17069 if let Some(writer) = self.output_handles.get_mut(name) {
17070 let _ = writer.write_all(output.as_bytes());
17071 if self.output_autoflush {
17072 let _ = writer.flush();
17073 }
17074 } else {
17075 return Err(StrykeError::runtime(
17076 format!("printf on unopened filehandle {}", name),
17077 line,
17078 ));
17079 }
17080 }
17081 }
17082 Ok(StrykeValue::integer(1))
17083 }
17084
17085 pub(crate) fn try_native_method(
17087 &mut self,
17088 receiver: &StrykeValue,
17089 method: &str,
17090 args: &[StrykeValue],
17091 line: usize,
17092 ) -> Option<StrykeResult<StrykeValue>> {
17093 if let Some(name) = receiver.as_io_handle_name() {
17094 return Some(self.io_handle_method(&name, method, args, line));
17095 }
17096 if let Some(ref s) = receiver.as_str() {
17097 if self.is_bound_handle(s) {
17098 return Some(self.io_handle_method(s, method, args, line));
17099 }
17100 }
17101 if let Some(c) = receiver.as_sqlite_conn() {
17102 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
17103 }
17104 if let Some(s) = receiver.as_struct_inst() {
17105 if let Some(idx) = s.def.field_index(method) {
17107 match args.len() {
17108 0 => {
17109 return Some(Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF)));
17110 }
17111 1 => {
17112 let field = &s.def.fields[idx];
17113 let new_val = args[0].clone();
17114 if let Err(msg) = field.ty.check_value(&new_val) {
17115 return Some(Err(StrykeError::type_error(
17116 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
17117 line,
17118 )));
17119 }
17120 s.set_field(idx, new_val.clone());
17121 return Some(Ok(new_val));
17122 }
17123 _ => {
17124 return Some(Err(StrykeError::runtime(
17125 format!(
17126 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
17127 method,
17128 args.len()
17129 ),
17130 line,
17131 )));
17132 }
17133 }
17134 }
17135 match method {
17137 "with" => {
17138 let mut new_values = s.get_values();
17140 let mut i = 0;
17141 while i + 1 < args.len() {
17142 let k = args[i].to_string();
17143 let v = args[i + 1].clone();
17144 if let Some(idx) = s.def.field_index(&k) {
17145 let field = &s.def.fields[idx];
17146 if let Err(msg) = field.ty.check_value(&v) {
17147 return Some(Err(StrykeError::type_error(
17148 format!(
17149 "struct {} field `{}`: {}",
17150 s.def.name, field.name, msg
17151 ),
17152 line,
17153 )));
17154 }
17155 new_values[idx] = v;
17156 } else {
17157 return Some(Err(StrykeError::runtime(
17158 format!("struct {}: unknown field `{}`", s.def.name, k),
17159 line,
17160 )));
17161 }
17162 i += 2;
17163 }
17164 return Some(Ok(StrykeValue::struct_inst(Arc::new(
17165 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
17166 ))));
17167 }
17168 "to_hash" => {
17169 if !args.is_empty() {
17171 return Some(Err(StrykeError::runtime(
17172 "struct to_hash takes no arguments",
17173 line,
17174 )));
17175 }
17176 let mut map = IndexMap::new();
17177 let values = s.get_values();
17178 for (i, field) in s.def.fields.iter().enumerate() {
17179 map.insert(field.name.clone(), values[i].clone());
17180 }
17181 return Some(Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map)))));
17182 }
17183 "to_hash_rec" | "to_hash_deep" => {
17184 if !args.is_empty() {
17187 return Some(Err(StrykeError::runtime(
17188 "struct to_hash_rec takes no arguments",
17189 line,
17190 )));
17191 }
17192 return Some(Ok(self.deep_to_hash_value(receiver)));
17193 }
17194 "fields" => {
17195 if !args.is_empty() {
17197 return Some(Err(StrykeError::runtime(
17198 "struct fields takes no arguments",
17199 line,
17200 )));
17201 }
17202 let names: Vec<StrykeValue> = s
17203 .def
17204 .fields
17205 .iter()
17206 .map(|f| StrykeValue::string(f.name.clone()))
17207 .collect();
17208 return Some(Ok(StrykeValue::array(names)));
17209 }
17210 "clone" => {
17211 if !args.is_empty() {
17213 return Some(Err(StrykeError::runtime(
17214 "struct clone takes no arguments",
17215 line,
17216 )));
17217 }
17218 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
17219 return Some(Ok(StrykeValue::struct_inst(Arc::new(
17220 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
17221 ))));
17222 }
17223 _ => {}
17224 }
17225 if let Some(m) = s.def.method(method) {
17227 let body = m.body.clone();
17228 let params = m.params.clone();
17229 let mut call_args = vec![receiver.clone()];
17231 call_args.extend(args.iter().cloned());
17232 return Some(
17233 match self.call_struct_method(&body, ¶ms, call_args, line) {
17234 Ok(v) => Ok(v),
17235 Err(FlowOrError::Error(e)) => Err(e),
17236 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17237 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
17238 "unexpected control flow in struct method",
17239 line,
17240 )),
17241 },
17242 );
17243 }
17244 return None;
17245 }
17246 if let Some(c) = receiver.as_class_inst() {
17248 let all_fields_full = self.collect_class_fields_full(&c.def);
17250 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
17251 .iter()
17252 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
17253 .collect();
17254
17255 if let Some(idx) = all_fields_full
17257 .iter()
17258 .position(|(name, _, _, _, _)| name == method)
17259 {
17260 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
17261
17262 match vis {
17264 crate::ast::Visibility::Private => {
17265 let caller_class = self
17267 .scope
17268 .get_scalar("self")
17269 .as_class_inst()
17270 .map(|ci| ci.def.name.clone());
17271 if caller_class.as_deref() != Some(owner_class.as_str()) {
17272 return Some(Err(StrykeError::runtime(
17273 format!("field `{}` of class {} is private", method, owner_class),
17274 line,
17275 )));
17276 }
17277 }
17278 crate::ast::Visibility::Protected => {
17279 let caller_class = self
17281 .scope
17282 .get_scalar("self")
17283 .as_class_inst()
17284 .map(|ci| ci.def.name.clone());
17285 let allowed = caller_class.as_deref().is_some_and(|caller| {
17286 caller == owner_class || self.class_inherits_from(caller, owner_class)
17287 });
17288 if !allowed {
17289 return Some(Err(StrykeError::runtime(
17290 format!("field `{}` of class {} is protected", method, owner_class),
17291 line,
17292 )));
17293 }
17294 }
17295 crate::ast::Visibility::Public => {}
17296 }
17297
17298 match args.len() {
17299 0 => {
17300 return Some(Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF)));
17301 }
17302 1 => {
17303 let new_val = args[0].clone();
17304 if let Err(msg) = ty.check_value(&new_val) {
17305 return Some(Err(StrykeError::type_error(
17306 format!("class {} field `{}`: {}", c.def.name, method, msg),
17307 line,
17308 )));
17309 }
17310 c.set_field(idx, new_val.clone());
17311 return Some(Ok(new_val));
17312 }
17313 _ => {
17314 return Some(Err(StrykeError::runtime(
17315 format!(
17316 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
17317 method,
17318 args.len()
17319 ),
17320 line,
17321 )));
17322 }
17323 }
17324 }
17325 match method {
17327 "with" => {
17328 let mut new_values = c.get_values();
17329 let mut i = 0;
17330 while i + 1 < args.len() {
17331 let k = args[i].to_string();
17332 let v = args[i + 1].clone();
17333 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
17334 let (_, _, ref ty) = all_fields[idx];
17335 if let Err(msg) = ty.check_value(&v) {
17336 return Some(Err(StrykeError::type_error(
17337 format!("class {} field `{}`: {}", c.def.name, k, msg),
17338 line,
17339 )));
17340 }
17341 new_values[idx] = v;
17342 } else {
17343 return Some(Err(StrykeError::runtime(
17344 format!("class {}: unknown field `{}`", c.def.name, k),
17345 line,
17346 )));
17347 }
17348 i += 2;
17349 }
17350 return Some(Ok(StrykeValue::class_inst(Arc::new(
17351 crate::value::ClassInstance::new_with_isa(
17352 Arc::clone(&c.def),
17353 new_values,
17354 c.isa_chain.clone(),
17355 ),
17356 ))));
17357 }
17358 "to_hash" => {
17359 if !args.is_empty() {
17360 return Some(Err(StrykeError::runtime(
17361 "class to_hash takes no arguments",
17362 line,
17363 )));
17364 }
17365 let mut map = IndexMap::new();
17366 let values = c.get_values();
17367 for (i, (name, _, _)) in all_fields.iter().enumerate() {
17368 if let Some(v) = values.get(i) {
17369 map.insert(name.clone(), v.clone());
17370 }
17371 }
17372 return Some(Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map)))));
17373 }
17374 "to_hash_rec" | "to_hash_deep" => {
17375 if !args.is_empty() {
17380 return Some(Err(StrykeError::runtime(
17381 "class to_hash_rec takes no arguments",
17382 line,
17383 )));
17384 }
17385 return Some(Ok(self.deep_to_hash_value(receiver)));
17386 }
17387 "fields" => {
17388 if !args.is_empty() {
17389 return Some(Err(StrykeError::runtime(
17390 "class fields takes no arguments",
17391 line,
17392 )));
17393 }
17394 let names: Vec<StrykeValue> = all_fields
17395 .iter()
17396 .map(|(name, _, _)| StrykeValue::string(name.clone()))
17397 .collect();
17398 return Some(Ok(StrykeValue::array(names)));
17399 }
17400 "clone" => {
17401 if !args.is_empty() {
17402 return Some(Err(StrykeError::runtime(
17403 "class clone takes no arguments",
17404 line,
17405 )));
17406 }
17407 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
17408 return Some(Ok(StrykeValue::class_inst(Arc::new(
17409 crate::value::ClassInstance::new_with_isa(
17410 Arc::clone(&c.def),
17411 new_values,
17412 c.isa_chain.clone(),
17413 ),
17414 ))));
17415 }
17416 "isa" => {
17417 if args.len() != 1 {
17418 return Some(Err(StrykeError::runtime("isa requires one argument", line)));
17419 }
17420 let class_name = args[0].to_string();
17421 let is_a = c.isa(&class_name);
17422 return Some(Ok(if is_a {
17423 StrykeValue::integer(1)
17424 } else {
17425 StrykeValue::string(String::new())
17426 }));
17427 }
17428 "does" => {
17429 if args.len() != 1 {
17430 return Some(Err(StrykeError::runtime(
17431 "does requires one argument",
17432 line,
17433 )));
17434 }
17435 let trait_name = args[0].to_string();
17436 let implements = c.def.implements.contains(&trait_name);
17437 return Some(Ok(if implements {
17438 StrykeValue::integer(1)
17439 } else {
17440 StrykeValue::string(String::new())
17441 }));
17442 }
17443 "methods" => {
17444 if !args.is_empty() {
17445 return Some(Err(StrykeError::runtime(
17446 "methods takes no arguments",
17447 line,
17448 )));
17449 }
17450 let mut names = Vec::new();
17451 self.collect_class_method_names(&c.def, &mut names);
17452 let values: Vec<StrykeValue> =
17453 names.into_iter().map(StrykeValue::string).collect();
17454 return Some(Ok(StrykeValue::array(values)));
17455 }
17456 "superclass" => {
17457 if !args.is_empty() {
17458 return Some(Err(StrykeError::runtime(
17459 "superclass takes no arguments",
17460 line,
17461 )));
17462 }
17463 let parents: Vec<StrykeValue> = c
17464 .def
17465 .extends
17466 .iter()
17467 .map(|s| StrykeValue::string(s.clone()))
17468 .collect();
17469 return Some(Ok(StrykeValue::array(parents)));
17470 }
17471 "destroy" => {
17472 let destroy_chain = self.collect_destroy_chain(&c.def);
17474 for (body, params) in &destroy_chain {
17475 let call_args = vec![receiver.clone()];
17476 match self.call_class_method(body, params, call_args, line) {
17477 Ok(_) => {}
17478 Err(FlowOrError::Flow(Flow::Return(_))) => {}
17479 Err(FlowOrError::Error(e)) => return Some(Err(e)),
17480 Err(_) => {}
17481 }
17482 }
17483 return Some(Ok(StrykeValue::UNDEF));
17484 }
17485 _ => {}
17486 }
17487 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
17489 match m.visibility {
17491 crate::ast::Visibility::Private => {
17492 let caller_class = self
17493 .scope
17494 .get_scalar("self")
17495 .as_class_inst()
17496 .map(|ci| ci.def.name.clone());
17497 if caller_class.as_deref() != Some(owner_class.as_str()) {
17498 return Some(Err(StrykeError::runtime(
17499 format!("method `{}` of class {} is private", method, owner_class),
17500 line,
17501 )));
17502 }
17503 }
17504 crate::ast::Visibility::Protected => {
17505 let caller_class = self
17506 .scope
17507 .get_scalar("self")
17508 .as_class_inst()
17509 .map(|ci| ci.def.name.clone());
17510 let allowed = caller_class.as_deref().is_some_and(|caller| {
17511 caller == owner_class.as_str()
17512 || self.class_inherits_from(caller, owner_class)
17513 });
17514 if !allowed {
17515 return Some(Err(StrykeError::runtime(
17516 format!(
17517 "method `{}` of class {} is protected",
17518 method, owner_class
17519 ),
17520 line,
17521 )));
17522 }
17523 }
17524 crate::ast::Visibility::Public => {}
17525 }
17526 if let Some(ref body) = m.body {
17527 let params = m.params.clone();
17528 let mut call_args = vec![receiver.clone()];
17529 call_args.extend(args.iter().cloned());
17530 return Some(
17531 match self.call_class_method(body, ¶ms, call_args, line) {
17532 Ok(v) => Ok(v),
17533 Err(FlowOrError::Error(e)) => Err(e),
17534 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17535 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
17536 "unexpected control flow in class method",
17537 line,
17538 )),
17539 },
17540 );
17541 }
17542 }
17543 return None;
17544 }
17545 if let Some(d) = receiver.as_dataframe() {
17546 return Some(self.dataframe_method(d, method, args, line));
17547 }
17548 if let Some(s) = crate::value::set_payload(receiver) {
17549 return Some(self.set_method(s, method, args, line));
17550 }
17551 if let Some(d) = receiver.as_deque() {
17552 return Some(self.deque_method(d, method, args, line));
17553 }
17554 if let Some(h) = receiver.as_heap_pq() {
17555 return Some(self.heap_method(h, method, args, line));
17556 }
17557 if let Some(p) = receiver.as_pipeline() {
17558 return Some(self.pipeline_method(p, method, args, line));
17559 }
17560 if let Some(c) = receiver.as_capture() {
17561 return Some(self.capture_method(c, method, args, line));
17562 }
17563 if let Some(p) = receiver.as_ppool() {
17564 return Some(self.ppool_method(p, method, args, line));
17565 }
17566 if let Some(b) = receiver.as_barrier() {
17567 return Some(self.barrier_method(b, method, args, line));
17568 }
17569 if let Some(g) = receiver.as_generator() {
17570 if method == "next" {
17571 if !args.is_empty() {
17572 return Some(Err(StrykeError::runtime(
17573 "generator->next takes no arguments",
17574 line,
17575 )));
17576 }
17577 return Some(self.generator_next(&g));
17578 }
17579 return None;
17580 }
17581 if let Some(arc) = receiver.as_atomic_arc() {
17582 let inner = arc.lock().clone();
17583 if let Some(d) = inner.as_deque() {
17584 return Some(self.deque_method(d, method, args, line));
17585 }
17586 if let Some(h) = inner.as_heap_pq() {
17587 return Some(self.heap_method(h, method, args, line));
17588 }
17589 }
17590 None
17591 }
17592
17593 fn dataframe_method(
17595 &mut self,
17596 d: Arc<Mutex<PerlDataFrame>>,
17597 method: &str,
17598 args: &[StrykeValue],
17599 line: usize,
17600 ) -> StrykeResult<StrykeValue> {
17601 match method {
17602 "nrow" | "nrows" => {
17603 if !args.is_empty() {
17604 return Err(StrykeError::runtime(
17605 format!("dataframe {} takes no arguments", method),
17606 line,
17607 ));
17608 }
17609 Ok(StrykeValue::integer(d.lock().nrows() as i64))
17610 }
17611 "ncol" | "ncols" => {
17612 if !args.is_empty() {
17613 return Err(StrykeError::runtime(
17614 format!("dataframe {} takes no arguments", method),
17615 line,
17616 ));
17617 }
17618 Ok(StrykeValue::integer(d.lock().ncols() as i64))
17619 }
17620 "filter" => {
17621 if args.len() != 1 {
17622 return Err(StrykeError::runtime(
17623 "dataframe filter expects 1 argument (sub)",
17624 line,
17625 ));
17626 }
17627 let Some(sub) = args[0].as_code_ref() else {
17628 return Err(StrykeError::runtime(
17629 "dataframe filter expects a code reference",
17630 line,
17631 ));
17632 };
17633 let df_guard = d.lock();
17634 let n = df_guard.nrows();
17635 let mut keep = vec![false; n];
17636 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
17637 let row = df_guard.row_hashref(r);
17638 self.scope_push_hook();
17639 self.scope.set_topic(row);
17640 if let Some(ref env) = sub.closure_env {
17641 self.scope.restore_capture(env);
17642 }
17643 let pass = match self.exec_block_no_scope(&sub.body) {
17644 Ok(v) => v.is_true(),
17645 Err(_) => false,
17646 };
17647 self.scope_pop_hook();
17648 *row_keep = pass;
17649 }
17650 let columns = df_guard.columns.clone();
17651 let cols: Vec<Vec<StrykeValue>> = (0..df_guard.ncols())
17652 .map(|i| {
17653 let mut out = Vec::new();
17654 for (r, pass_row) in keep.iter().enumerate().take(n) {
17655 if *pass_row {
17656 out.push(df_guard.cols[i][r].clone());
17657 }
17658 }
17659 out
17660 })
17661 .collect();
17662 let group_by = df_guard.group_by.clone();
17663 drop(df_guard);
17664 let new_df = PerlDataFrame {
17665 columns,
17666 cols,
17667 group_by,
17668 };
17669 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(new_df))))
17670 }
17671 "group_by" => {
17672 if args.len() != 1 {
17673 return Err(StrykeError::runtime(
17674 "dataframe group_by expects 1 column name",
17675 line,
17676 ));
17677 }
17678 let key = args[0].to_string();
17679 let inner = d.lock();
17680 if inner.col_index(&key).is_none() {
17681 return Err(StrykeError::runtime(
17682 format!("dataframe group_by: unknown column \"{}\"", key),
17683 line,
17684 ));
17685 }
17686 let new_df = PerlDataFrame {
17687 columns: inner.columns.clone(),
17688 cols: inner.cols.clone(),
17689 group_by: Some(key),
17690 };
17691 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(new_df))))
17692 }
17693 "sum" => {
17694 if args.len() != 1 {
17695 return Err(StrykeError::runtime(
17696 "dataframe sum expects 1 column name",
17697 line,
17698 ));
17699 }
17700 let col_name = args[0].to_string();
17701 let inner = d.lock();
17702 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
17703 StrykeError::runtime(
17704 format!("dataframe sum: unknown column \"{}\"", col_name),
17705 line,
17706 )
17707 })?;
17708 match &inner.group_by {
17709 Some(gcol) => {
17710 let gi = inner.col_index(gcol).ok_or_else(|| {
17711 StrykeError::runtime(
17712 format!("dataframe sum: unknown group column \"{}\"", gcol),
17713 line,
17714 )
17715 })?;
17716 let mut acc: IndexMap<String, f64> = IndexMap::new();
17717 for r in 0..inner.nrows() {
17718 let k = inner.cols[gi][r].to_string();
17719 let v = inner.cols[val_idx][r].to_number();
17720 *acc.entry(k).or_insert(0.0) += v;
17721 }
17722 let keys: Vec<String> = acc.keys().cloned().collect();
17723 let sums: Vec<f64> = acc.values().copied().collect();
17724 let cols = vec![
17725 keys.into_iter().map(StrykeValue::string).collect(),
17726 sums.into_iter().map(StrykeValue::float).collect(),
17727 ];
17728 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
17729 let out = PerlDataFrame {
17730 columns,
17731 cols,
17732 group_by: None,
17733 };
17734 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(out))))
17735 }
17736 None => {
17737 let total: f64 = (0..inner.nrows())
17738 .map(|r| inner.cols[val_idx][r].to_number())
17739 .sum();
17740 Ok(StrykeValue::float(total))
17741 }
17742 }
17743 }
17744 _ => Err(StrykeError::runtime(
17745 format!("Unknown method for dataframe: {}", method),
17746 line,
17747 )),
17748 }
17749 }
17750
17751 fn set_method(
17753 &self,
17754 s: Arc<crate::value::PerlSet>,
17755 method: &str,
17756 args: &[StrykeValue],
17757 line: usize,
17758 ) -> StrykeResult<StrykeValue> {
17759 match method {
17760 "has" | "contains" | "member" => {
17761 if args.len() != 1 {
17762 return Err(StrykeError::runtime(
17763 "set->has expects one argument (element)",
17764 line,
17765 ));
17766 }
17767 let k = crate::value::set_member_key(&args[0]);
17768 Ok(StrykeValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
17769 }
17770 "size" | "len" | "count" => {
17771 if !args.is_empty() {
17772 return Err(StrykeError::runtime("set->size takes no arguments", line));
17773 }
17774 Ok(StrykeValue::integer(s.len() as i64))
17775 }
17776 "values" | "list" | "elements" => {
17777 if !args.is_empty() {
17778 return Err(StrykeError::runtime("set->values takes no arguments", line));
17779 }
17780 Ok(StrykeValue::array(s.values().cloned().collect()))
17781 }
17782 _ => Err(StrykeError::runtime(
17783 format!("Unknown method for set: {}", method),
17784 line,
17785 )),
17786 }
17787 }
17788
17789 fn deque_method(
17790 &mut self,
17791 d: Arc<Mutex<VecDeque<StrykeValue>>>,
17792 method: &str,
17793 args: &[StrykeValue],
17794 line: usize,
17795 ) -> StrykeResult<StrykeValue> {
17796 match method {
17797 "push_back" => {
17798 if args.len() != 1 {
17799 return Err(StrykeError::runtime("push_back expects 1 argument", line));
17800 }
17801 d.lock().push_back(args[0].clone());
17802 Ok(StrykeValue::integer(d.lock().len() as i64))
17803 }
17804 "push_front" => {
17805 if args.len() != 1 {
17806 return Err(StrykeError::runtime("push_front expects 1 argument", line));
17807 }
17808 d.lock().push_front(args[0].clone());
17809 Ok(StrykeValue::integer(d.lock().len() as i64))
17810 }
17811 "pop_back" => Ok(d.lock().pop_back().unwrap_or(StrykeValue::UNDEF)),
17812 "pop_front" => Ok(d.lock().pop_front().unwrap_or(StrykeValue::UNDEF)),
17813 "size" | "len" => Ok(StrykeValue::integer(d.lock().len() as i64)),
17814 _ => Err(StrykeError::runtime(
17815 format!("Unknown method for deque: {}", method),
17816 line,
17817 )),
17818 }
17819 }
17820
17821 fn heap_method(
17822 &mut self,
17823 h: Arc<Mutex<PerlHeap>>,
17824 method: &str,
17825 args: &[StrykeValue],
17826 line: usize,
17827 ) -> StrykeResult<StrykeValue> {
17828 match method {
17829 "push" => {
17830 if args.len() != 1 {
17831 return Err(StrykeError::runtime("heap push expects 1 argument", line));
17832 }
17833 let mut g = h.lock();
17834 let n = g.items.len();
17835 g.items.push(args[0].clone());
17836 let cmp = g.cmp.clone();
17837 drop(g);
17838 let mut g = h.lock();
17839 self.heap_sift_up(&mut g.items, &cmp, n);
17840 Ok(StrykeValue::integer(g.items.len() as i64))
17841 }
17842 "pop" => {
17843 let mut g = h.lock();
17844 if g.items.is_empty() {
17845 return Ok(StrykeValue::UNDEF);
17846 }
17847 let cmp = g.cmp.clone();
17848 let n = g.items.len();
17849 g.items.swap(0, n - 1);
17850 let v = g.items.pop().unwrap();
17851 if !g.items.is_empty() {
17852 self.heap_sift_down(&mut g.items, &cmp, 0);
17853 }
17854 Ok(v)
17855 }
17856 "peek" => Ok(h
17857 .lock()
17858 .items
17859 .first()
17860 .cloned()
17861 .unwrap_or(StrykeValue::UNDEF)),
17862 _ => Err(StrykeError::runtime(
17863 format!("Unknown method for heap: {}", method),
17864 line,
17865 )),
17866 }
17867 }
17868
17869 fn ppool_method(
17870 &mut self,
17871 pool: PerlPpool,
17872 method: &str,
17873 args: &[StrykeValue],
17874 line: usize,
17875 ) -> StrykeResult<StrykeValue> {
17876 match method {
17877 "submit" => pool.submit(self, args, line),
17878 "collect" => {
17879 if !args.is_empty() {
17880 return Err(StrykeError::runtime("collect() takes no arguments", line));
17881 }
17882 pool.collect(line)
17883 }
17884 _ => Err(StrykeError::runtime(
17885 format!("Unknown method for ppool: {}", method),
17886 line,
17887 )),
17888 }
17889 }
17890
17891 fn barrier_method(
17892 &self,
17893 barrier: PerlBarrier,
17894 method: &str,
17895 args: &[StrykeValue],
17896 line: usize,
17897 ) -> StrykeResult<StrykeValue> {
17898 match method {
17899 "wait" => {
17900 if !args.is_empty() {
17901 return Err(StrykeError::runtime("wait() takes no arguments", line));
17902 }
17903 let _ = barrier.0.wait();
17904 Ok(StrykeValue::integer(1))
17905 }
17906 _ => Err(StrykeError::runtime(
17907 format!("Unknown method for barrier: {}", method),
17908 line,
17909 )),
17910 }
17911 }
17912
17913 fn capture_method(
17914 &self,
17915 c: Arc<CaptureResult>,
17916 method: &str,
17917 args: &[StrykeValue],
17918 line: usize,
17919 ) -> StrykeResult<StrykeValue> {
17920 if !args.is_empty() {
17921 return Err(StrykeError::runtime(
17922 format!("capture: {} takes no arguments", method),
17923 line,
17924 ));
17925 }
17926 match method {
17927 "stdout" => Ok(StrykeValue::string(c.stdout.clone())),
17928 "stderr" => Ok(StrykeValue::string(c.stderr.clone())),
17929 "exitcode" => Ok(StrykeValue::integer(c.exitcode)),
17930 "failed" => Ok(StrykeValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
17931 _ => Err(StrykeError::runtime(
17932 format!("Unknown method for capture: {}", method),
17933 line,
17934 )),
17935 }
17936 }
17937
17938 pub(crate) fn builtin_par_pipeline_stream(
17939 &mut self,
17940 args: &[StrykeValue],
17941 _line: usize,
17942 ) -> StrykeResult<StrykeValue> {
17943 let mut items = Vec::new();
17944 for v in args {
17945 if let Some(a) = v.as_array_vec() {
17946 items.extend(a);
17947 } else {
17948 items.push(v.clone());
17949 }
17950 }
17951 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
17952 source: items,
17953 ops: Vec::new(),
17954 has_scalar_terminal: false,
17955 par_stream: true,
17956 streaming: false,
17957 streaming_workers: 0,
17958 streaming_buffer: 256,
17959 }))))
17960 }
17961
17962 pub(crate) fn builtin_par_pipeline_stream_new(
17965 &mut self,
17966 args: &[StrykeValue],
17967 _line: usize,
17968 ) -> StrykeResult<StrykeValue> {
17969 let mut items = Vec::new();
17970 let mut workers: usize = 0;
17971 let mut buffer: usize = 256;
17972 let mut i = 0;
17974 while i < args.len() {
17975 let s = args[i].to_string();
17976 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
17977 let val = args[i + 1].to_int().max(1) as usize;
17978 if s == "workers" {
17979 workers = val;
17980 } else {
17981 buffer = val;
17982 }
17983 i += 2;
17984 } else if let Some(a) = args[i].as_array_vec() {
17985 items.extend(a);
17986 i += 1;
17987 } else {
17988 items.push(args[i].clone());
17989 i += 1;
17990 }
17991 }
17992 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
17993 source: items,
17994 ops: Vec::new(),
17995 has_scalar_terminal: false,
17996 par_stream: false,
17997 streaming: true,
17998 streaming_workers: workers,
17999 streaming_buffer: buffer,
18000 }))))
18001 }
18002
18003 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<StrykeSub> {
18005 let line = 1usize;
18006 let body = vec![Statement {
18007 label: None,
18008 kind: StmtKind::Expression(Expr {
18009 kind: ExprKind::BinOp {
18010 left: Box::new(Expr {
18011 kind: ExprKind::ScalarVar("_".into()),
18012 line,
18013 }),
18014 op: BinOp::Mul,
18015 right: Box::new(Expr {
18016 kind: ExprKind::Integer(k),
18017 line,
18018 }),
18019 },
18020 line,
18021 }),
18022 line,
18023 }];
18024 Arc::new(StrykeSub {
18025 name: "__pipeline_int_mul__".into(),
18026 params: vec![],
18027 body,
18028 closure_env: None,
18029 prototype: None,
18030 fib_like: None,
18031 })
18032 }
18033
18034 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<StrykeSub> {
18035 let captured = self.scope.capture();
18036 Arc::new(StrykeSub {
18037 name: "__ANON__".into(),
18038 params: vec![],
18039 body: block.clone(),
18040 closure_env: Some(captured),
18041 prototype: None,
18042 fib_like: None,
18043 })
18044 }
18045
18046 pub(crate) fn builtin_collect_execute(
18047 &mut self,
18048 args: &[StrykeValue],
18049 line: usize,
18050 ) -> StrykeResult<StrykeValue> {
18051 if args.is_empty() {
18052 return Err(StrykeError::runtime(
18053 "collect() expects at least one argument",
18054 line,
18055 ));
18056 }
18057 if args.len() == 1 {
18060 if let Some(p) = args[0].as_pipeline() {
18061 return self.pipeline_collect(&p, line);
18062 }
18063 return Ok(StrykeValue::array(args[0].to_list()));
18064 }
18065 Ok(StrykeValue::array(args.to_vec()))
18066 }
18067
18068 pub(crate) fn pipeline_push(
18069 &self,
18070 p: &Arc<Mutex<PipelineInner>>,
18071 op: PipelineOp,
18072 line: usize,
18073 ) -> StrykeResult<()> {
18074 let mut g = p.lock();
18075 if g.has_scalar_terminal {
18076 return Err(StrykeError::runtime(
18077 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
18078 line,
18079 ));
18080 }
18081 if matches!(
18082 &op,
18083 PipelineOp::PReduce { .. }
18084 | PipelineOp::PReduceInit { .. }
18085 | PipelineOp::PMapReduce { .. }
18086 ) {
18087 g.has_scalar_terminal = true;
18088 }
18089 g.ops.push(op);
18090 Ok(())
18091 }
18092
18093 fn pipeline_parse_sub_progress(
18094 args: &[StrykeValue],
18095 line: usize,
18096 name: &str,
18097 ) -> StrykeResult<(Arc<StrykeSub>, bool)> {
18098 if args.is_empty() {
18099 return Err(StrykeError::runtime(
18100 format!("pipeline {}: expects at least 1 argument (code ref)", name),
18101 line,
18102 ));
18103 }
18104 let Some(sub) = args[0].as_code_ref() else {
18105 return Err(StrykeError::runtime(
18106 format!("pipeline {}: first argument must be a code reference", name),
18107 line,
18108 ));
18109 };
18110 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
18111 if args.len() > 2 {
18112 return Err(StrykeError::runtime(
18113 format!(
18114 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
18115 name
18116 ),
18117 line,
18118 ));
18119 }
18120 Ok((sub, progress))
18121 }
18122
18123 pub(crate) fn pipeline_method(
18124 &mut self,
18125 p: Arc<Mutex<PipelineInner>>,
18126 method: &str,
18127 args: &[StrykeValue],
18128 line: usize,
18129 ) -> StrykeResult<StrykeValue> {
18130 match method {
18131 "filter" | "f" | "grep" => {
18132 if args.len() != 1 {
18133 return Err(StrykeError::runtime(
18134 "pipeline filter/grep expects 1 argument (sub)",
18135 line,
18136 ));
18137 }
18138 let Some(sub) = args[0].as_code_ref() else {
18139 return Err(StrykeError::runtime(
18140 "pipeline filter/grep expects a code reference",
18141 line,
18142 ));
18143 };
18144 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
18145 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18146 }
18147 "map" => {
18148 if args.len() != 1 {
18149 return Err(StrykeError::runtime(
18150 "pipeline map expects 1 argument (sub)",
18151 line,
18152 ));
18153 }
18154 let Some(sub) = args[0].as_code_ref() else {
18155 return Err(StrykeError::runtime(
18156 "pipeline map expects a code reference",
18157 line,
18158 ));
18159 };
18160 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
18161 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18162 }
18163 "tap" | "peek" => {
18164 if args.len() != 1 {
18165 return Err(StrykeError::runtime(
18166 "pipeline tap/peek expects 1 argument (sub)",
18167 line,
18168 ));
18169 }
18170 let Some(sub) = args[0].as_code_ref() else {
18171 return Err(StrykeError::runtime(
18172 "pipeline tap/peek expects a code reference",
18173 line,
18174 ));
18175 };
18176 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
18177 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18178 }
18179 "take" => {
18180 if args.len() != 1 {
18181 return Err(StrykeError::runtime(
18182 "pipeline take expects 1 argument",
18183 line,
18184 ));
18185 }
18186 let n = args[0].to_int();
18187 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
18188 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18189 }
18190 "pmap" => {
18191 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
18192 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
18193 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18194 }
18195 "pgrep" => {
18196 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
18197 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
18198 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18199 }
18200 "pfor" => {
18201 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
18202 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
18203 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18204 }
18205 "pmap_chunked" => {
18206 if args.len() < 2 {
18207 return Err(StrykeError::runtime(
18208 "pipeline pmap_chunked expects chunk size and a code reference",
18209 line,
18210 ));
18211 }
18212 let chunk = args[0].to_int().max(1);
18213 let Some(sub) = args[1].as_code_ref() else {
18214 return Err(StrykeError::runtime(
18215 "pipeline pmap_chunked: second argument must be a code reference",
18216 line,
18217 ));
18218 };
18219 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
18220 if args.len() > 3 {
18221 return Err(StrykeError::runtime(
18222 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
18223 line,
18224 ));
18225 }
18226 self.pipeline_push(
18227 &p,
18228 PipelineOp::PMapChunked {
18229 chunk,
18230 sub,
18231 progress,
18232 },
18233 line,
18234 )?;
18235 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18236 }
18237 "psort" => {
18238 let (cmp, progress) = match args.len() {
18239 0 => (None, false),
18240 1 => {
18241 if let Some(s) = args[0].as_code_ref() {
18242 (Some(s), false)
18243 } else {
18244 (None, args[0].is_true())
18245 }
18246 }
18247 2 => {
18248 let Some(s) = args[0].as_code_ref() else {
18249 return Err(StrykeError::runtime(
18250 "pipeline psort: with two arguments, the first must be a comparator sub",
18251 line,
18252 ));
18253 };
18254 (Some(s), args[1].is_true())
18255 }
18256 _ => {
18257 return Err(StrykeError::runtime(
18258 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
18259 line,
18260 ));
18261 }
18262 };
18263 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
18264 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18265 }
18266 "pcache" => {
18267 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
18268 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
18269 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18270 }
18271 "preduce" => {
18272 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
18273 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
18274 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18275 }
18276 "preduce_init" => {
18277 if args.len() < 2 {
18278 return Err(StrykeError::runtime(
18279 "pipeline preduce_init expects init value and a code reference",
18280 line,
18281 ));
18282 }
18283 let init = args[0].clone();
18284 let Some(sub) = args[1].as_code_ref() else {
18285 return Err(StrykeError::runtime(
18286 "pipeline preduce_init: second argument must be a code reference",
18287 line,
18288 ));
18289 };
18290 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
18291 if args.len() > 3 {
18292 return Err(StrykeError::runtime(
18293 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
18294 line,
18295 ));
18296 }
18297 self.pipeline_push(
18298 &p,
18299 PipelineOp::PReduceInit {
18300 init,
18301 sub,
18302 progress,
18303 },
18304 line,
18305 )?;
18306 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18307 }
18308 "pmap_reduce" => {
18309 if args.len() < 2 {
18310 return Err(StrykeError::runtime(
18311 "pipeline pmap_reduce expects map sub and reduce sub",
18312 line,
18313 ));
18314 }
18315 let Some(map) = args[0].as_code_ref() else {
18316 return Err(StrykeError::runtime(
18317 "pipeline pmap_reduce: first argument must be a code reference (map)",
18318 line,
18319 ));
18320 };
18321 let Some(reduce) = args[1].as_code_ref() else {
18322 return Err(StrykeError::runtime(
18323 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
18324 line,
18325 ));
18326 };
18327 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
18328 if args.len() > 3 {
18329 return Err(StrykeError::runtime(
18330 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
18331 line,
18332 ));
18333 }
18334 self.pipeline_push(
18335 &p,
18336 PipelineOp::PMapReduce {
18337 map,
18338 reduce,
18339 progress,
18340 },
18341 line,
18342 )?;
18343 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18344 }
18345 "collect" => {
18346 if !args.is_empty() {
18347 return Err(StrykeError::runtime(
18348 "pipeline collect takes no arguments",
18349 line,
18350 ));
18351 }
18352 self.pipeline_collect(&p, line)
18353 }
18354 _ => {
18355 if let Some(sub) = self.resolve_sub_by_name(method) {
18358 if !args.is_empty() {
18359 return Err(StrykeError::runtime(
18360 format!(
18361 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
18362 method
18363 ),
18364 line,
18365 ));
18366 }
18367 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
18368 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18369 } else {
18370 Err(StrykeError::runtime(
18371 format!("Unknown method for pipeline: {}", method),
18372 line,
18373 ))
18374 }
18375 }
18376 }
18377 }
18378
18379 fn pipeline_parallel_map(
18380 &mut self,
18381 items: Vec<StrykeValue>,
18382 sub: &Arc<StrykeSub>,
18383 progress: bool,
18384 ) -> Vec<StrykeValue> {
18385 let subs = self.subs.clone();
18386 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18387 let pmap_progress = PmapProgress::new(progress, items.len());
18388 let results: Vec<StrykeValue> = items
18389 .into_par_iter()
18390 .map(|item| {
18391 let mut local_interp = VMHelper::new();
18392 local_interp.subs = subs.clone();
18393 local_interp.scope.restore_capture(&scope_capture);
18394 local_interp
18395 .scope
18396 .restore_atomics(&atomic_arrays, &atomic_hashes);
18397 local_interp.enable_parallel_guard();
18398 local_interp.scope.set_topic(item);
18399 local_interp.scope_push_hook();
18400 let val = match local_interp.exec_block_no_scope(&sub.body) {
18401 Ok(val) => val,
18402 Err(_) => StrykeValue::UNDEF,
18403 };
18404 local_interp.scope_pop_hook();
18405 pmap_progress.tick();
18406 val
18407 })
18408 .collect();
18409 pmap_progress.finish();
18410 results
18411 }
18412
18413 fn pipeline_par_stream_filter(
18415 &mut self,
18416 items: Vec<StrykeValue>,
18417 sub: &Arc<StrykeSub>,
18418 ) -> Vec<StrykeValue> {
18419 if items.is_empty() {
18420 return items;
18421 }
18422 let subs = self.subs.clone();
18423 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18424 let indexed: Vec<(usize, StrykeValue)> = items.into_iter().enumerate().collect();
18425 let mut kept: Vec<(usize, StrykeValue)> = indexed
18426 .into_par_iter()
18427 .filter_map(|(i, item)| {
18428 let mut local_interp = VMHelper::new();
18429 local_interp.subs = subs.clone();
18430 local_interp.scope.restore_capture(&scope_capture);
18431 local_interp
18432 .scope
18433 .restore_atomics(&atomic_arrays, &atomic_hashes);
18434 local_interp.enable_parallel_guard();
18435 local_interp.scope.set_topic(item.clone());
18436 local_interp.scope_push_hook();
18437 let keep = match local_interp.exec_block_no_scope(&sub.body) {
18438 Ok(val) => val.is_true(),
18439 Err(_) => false,
18440 };
18441 local_interp.scope_pop_hook();
18442 if keep {
18443 Some((i, item))
18444 } else {
18445 None
18446 }
18447 })
18448 .collect();
18449 kept.sort_by_key(|(i, _)| *i);
18450 kept.into_iter().map(|(_, x)| x).collect()
18451 }
18452
18453 fn pipeline_par_stream_map(
18455 &mut self,
18456 items: Vec<StrykeValue>,
18457 sub: &Arc<StrykeSub>,
18458 ) -> Vec<StrykeValue> {
18459 if items.is_empty() {
18460 return items;
18461 }
18462 let subs = self.subs.clone();
18463 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18464 let indexed: Vec<(usize, StrykeValue)> = items.into_iter().enumerate().collect();
18465 let mut mapped: Vec<(usize, StrykeValue)> = indexed
18466 .into_par_iter()
18467 .map(|(i, item)| {
18468 let mut local_interp = VMHelper::new();
18469 local_interp.subs = subs.clone();
18470 local_interp.scope.restore_capture(&scope_capture);
18471 local_interp
18472 .scope
18473 .restore_atomics(&atomic_arrays, &atomic_hashes);
18474 local_interp.enable_parallel_guard();
18475 local_interp.scope.set_topic(item);
18476 local_interp.scope_push_hook();
18477 let val = match local_interp.exec_block_no_scope(&sub.body) {
18478 Ok(val) => val,
18479 Err(_) => StrykeValue::UNDEF,
18480 };
18481 local_interp.scope_pop_hook();
18482 (i, val)
18483 })
18484 .collect();
18485 mapped.sort_by_key(|(i, _)| *i);
18486 mapped.into_iter().map(|(_, x)| x).collect()
18487 }
18488
18489 fn pipeline_collect(
18490 &mut self,
18491 p: &Arc<Mutex<PipelineInner>>,
18492 line: usize,
18493 ) -> StrykeResult<StrykeValue> {
18494 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
18495 let g = p.lock();
18496 (
18497 g.source.clone(),
18498 g.ops.clone(),
18499 g.par_stream,
18500 g.streaming,
18501 g.streaming_workers,
18502 g.streaming_buffer,
18503 )
18504 };
18505 if streaming {
18506 return self.pipeline_collect_streaming(
18507 v,
18508 &ops,
18509 streaming_workers,
18510 streaming_buffer,
18511 line,
18512 );
18513 }
18514 for op in ops {
18515 match op {
18516 PipelineOp::Filter(sub) => {
18517 if par_stream {
18518 v = self.pipeline_par_stream_filter(v, &sub);
18519 } else {
18520 let mut out = Vec::new();
18521 for item in v {
18522 self.scope_push_hook();
18523 self.scope.set_topic(item.clone());
18524 if let Some(ref env) = sub.closure_env {
18525 self.scope.restore_capture(env);
18526 }
18527 let keep = match self.exec_block_no_scope(&sub.body) {
18528 Ok(val) => val.is_true(),
18529 Err(_) => false,
18530 };
18531 self.scope_pop_hook();
18532 if keep {
18533 out.push(item);
18534 }
18535 }
18536 v = out;
18537 }
18538 }
18539 PipelineOp::Map(sub) => {
18540 if par_stream {
18541 v = self.pipeline_par_stream_map(v, &sub);
18542 } else {
18543 let mut out = Vec::new();
18544 for item in v {
18545 self.scope_push_hook();
18546 self.scope.set_topic(item);
18547 if let Some(ref env) = sub.closure_env {
18548 self.scope.restore_capture(env);
18549 }
18550 let mapped = match self.exec_block_no_scope(&sub.body) {
18551 Ok(val) => val,
18552 Err(_) => StrykeValue::UNDEF,
18553 };
18554 self.scope_pop_hook();
18555 out.push(mapped);
18556 }
18557 v = out;
18558 }
18559 }
18560 PipelineOp::Tap(sub) => {
18561 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
18562 Ok(_) => {}
18563 Err(FlowOrError::Error(e)) => return Err(e),
18564 Err(FlowOrError::Flow(_)) => {
18565 return Err(StrykeError::runtime(
18566 "tap: unsupported control flow in block",
18567 line,
18568 ));
18569 }
18570 }
18571 }
18572 PipelineOp::Take(n) => {
18573 let n = n.max(0) as usize;
18574 if v.len() > n {
18575 v.truncate(n);
18576 }
18577 }
18578 PipelineOp::PMap { sub, progress } => {
18579 v = self.pipeline_parallel_map(v, &sub, progress);
18580 }
18581 PipelineOp::PGrep { sub, progress } => {
18582 let subs = self.subs.clone();
18583 let (scope_capture, atomic_arrays, atomic_hashes) =
18584 self.scope.capture_with_atomics();
18585 let pmap_progress = PmapProgress::new(progress, v.len());
18586 v = v
18587 .into_par_iter()
18588 .filter_map(|item| {
18589 let mut local_interp = VMHelper::new();
18590 local_interp.subs = subs.clone();
18591 local_interp.scope.restore_capture(&scope_capture);
18592 local_interp
18593 .scope
18594 .restore_atomics(&atomic_arrays, &atomic_hashes);
18595 local_interp.enable_parallel_guard();
18596 local_interp.scope.set_topic(item.clone());
18597 local_interp.scope_push_hook();
18598 let keep = match local_interp.exec_block_no_scope(&sub.body) {
18599 Ok(val) => val.is_true(),
18600 Err(_) => false,
18601 };
18602 local_interp.scope_pop_hook();
18603 pmap_progress.tick();
18604 if keep {
18605 Some(item)
18606 } else {
18607 None
18608 }
18609 })
18610 .collect();
18611 pmap_progress.finish();
18612 }
18613 PipelineOp::PFor { sub, progress } => {
18614 let subs = self.subs.clone();
18615 let (scope_capture, atomic_arrays, atomic_hashes) =
18616 self.scope.capture_with_atomics();
18617 let pmap_progress = PmapProgress::new(progress, v.len());
18618 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
18619 v.clone().into_par_iter().for_each(|item| {
18620 if first_err.lock().is_some() {
18621 return;
18622 }
18623 let mut local_interp = VMHelper::new();
18624 local_interp.subs = subs.clone();
18625 local_interp.scope.restore_capture(&scope_capture);
18626 local_interp
18627 .scope
18628 .restore_atomics(&atomic_arrays, &atomic_hashes);
18629 local_interp.enable_parallel_guard();
18630 local_interp.scope.set_topic(item);
18631 local_interp.scope_push_hook();
18632 match local_interp.exec_block_no_scope(&sub.body) {
18633 Ok(_) => {}
18634 Err(e) => {
18635 let stryke = match e {
18636 FlowOrError::Error(stryke) => stryke,
18637 FlowOrError::Flow(_) => StrykeError::runtime(
18638 "return/last/next/redo not supported inside pipeline pfor block",
18639 line,
18640 ),
18641 };
18642 let mut g = first_err.lock();
18643 if g.is_none() {
18644 *g = Some(stryke);
18645 }
18646 }
18647 }
18648 local_interp.scope_pop_hook();
18649 pmap_progress.tick();
18650 });
18651 pmap_progress.finish();
18652 let pfor_err = first_err.lock().take();
18653 if let Some(e) = pfor_err {
18654 return Err(e);
18655 }
18656 }
18657 PipelineOp::PMapChunked {
18658 chunk,
18659 sub,
18660 progress,
18661 } => {
18662 let chunk_n = chunk.max(1) as usize;
18663 let subs = self.subs.clone();
18664 let (scope_capture, atomic_arrays, atomic_hashes) =
18665 self.scope.capture_with_atomics();
18666 let indexed_chunks: Vec<(usize, Vec<StrykeValue>)> = v
18667 .chunks(chunk_n)
18668 .enumerate()
18669 .map(|(i, c)| (i, c.to_vec()))
18670 .collect();
18671 let n_chunks = indexed_chunks.len();
18672 let pmap_progress = PmapProgress::new(progress, n_chunks);
18673 let mut chunk_results: Vec<(usize, Vec<StrykeValue>)> = indexed_chunks
18674 .into_par_iter()
18675 .map(|(chunk_idx, chunk)| {
18676 let mut local_interp = VMHelper::new();
18677 local_interp.subs = subs.clone();
18678 local_interp.scope.restore_capture(&scope_capture);
18679 local_interp
18680 .scope
18681 .restore_atomics(&atomic_arrays, &atomic_hashes);
18682 local_interp.enable_parallel_guard();
18683 let mut out = Vec::with_capacity(chunk.len());
18684 for item in chunk {
18685 local_interp.scope.set_topic(item);
18686 local_interp.scope_push_hook();
18687 match local_interp.exec_block_no_scope(&sub.body) {
18688 Ok(val) => {
18689 local_interp.scope_pop_hook();
18690 out.push(val);
18691 }
18692 Err(_) => {
18693 local_interp.scope_pop_hook();
18694 out.push(StrykeValue::UNDEF);
18695 }
18696 }
18697 }
18698 pmap_progress.tick();
18699 (chunk_idx, out)
18700 })
18701 .collect();
18702 pmap_progress.finish();
18703 chunk_results.sort_by_key(|(i, _)| *i);
18704 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
18705 }
18706 PipelineOp::PSort { cmp, progress } => {
18707 let pmap_progress = PmapProgress::new(progress, 2);
18708 pmap_progress.tick();
18709 match cmp {
18710 Some(cmp_block) => {
18711 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
18712 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
18713 } else {
18714 let subs = self.subs.clone();
18715 let scope_capture = self.scope.capture();
18716 v.par_sort_by(|a, b| {
18717 let mut local_interp = VMHelper::new();
18718 local_interp.subs = subs.clone();
18719 local_interp.scope.restore_capture(&scope_capture);
18720 local_interp.enable_parallel_guard();
18721 local_interp.scope.set_sort_pair(a.clone(), b.clone());
18722 local_interp.scope_push_hook();
18723 let ord =
18724 match local_interp.exec_block_no_scope(&cmp_block.body) {
18725 Ok(v) => {
18726 let n = v.to_int();
18727 if n < 0 {
18728 std::cmp::Ordering::Less
18729 } else if n > 0 {
18730 std::cmp::Ordering::Greater
18731 } else {
18732 std::cmp::Ordering::Equal
18733 }
18734 }
18735 Err(_) => std::cmp::Ordering::Equal,
18736 };
18737 local_interp.scope_pop_hook();
18738 ord
18739 });
18740 }
18741 }
18742 None => {
18743 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
18744 }
18745 }
18746 pmap_progress.tick();
18747 pmap_progress.finish();
18748 }
18749 PipelineOp::PCache { sub, progress } => {
18750 let subs = self.subs.clone();
18751 let scope_capture = self.scope.capture();
18752 let cache = &*crate::pcache::GLOBAL_PCACHE;
18753 let pmap_progress = PmapProgress::new(progress, v.len());
18754 v = v
18755 .into_par_iter()
18756 .map(|item| {
18757 let k = crate::pcache::cache_key(&item);
18758 if let Some(cached) = cache.get(&k) {
18759 pmap_progress.tick();
18760 return cached.clone();
18761 }
18762 let mut local_interp = VMHelper::new();
18763 local_interp.subs = subs.clone();
18764 local_interp.scope.restore_capture(&scope_capture);
18765 local_interp.enable_parallel_guard();
18766 local_interp.scope.set_topic(item.clone());
18767 local_interp.scope_push_hook();
18768 let val = match local_interp.exec_block_no_scope(&sub.body) {
18769 Ok(v) => v,
18770 Err(_) => StrykeValue::UNDEF,
18771 };
18772 local_interp.scope_pop_hook();
18773 cache.insert(k, val.clone());
18774 pmap_progress.tick();
18775 val
18776 })
18777 .collect();
18778 pmap_progress.finish();
18779 }
18780 PipelineOp::PReduce { sub, progress } => {
18781 if v.is_empty() {
18782 return Ok(StrykeValue::UNDEF);
18783 }
18784 if v.len() == 1 {
18785 return Ok(v.into_iter().next().unwrap());
18786 }
18787 let block = sub.body.clone();
18788 let subs = self.subs.clone();
18789 let scope_capture = self.scope.capture();
18790 let pmap_progress = PmapProgress::new(progress, v.len());
18791 let result = v
18792 .into_par_iter()
18793 .map(|x| {
18794 pmap_progress.tick();
18795 x
18796 })
18797 .reduce_with(|a, b| {
18798 let mut local_interp = VMHelper::new();
18799 local_interp.subs = subs.clone();
18800 local_interp.scope.restore_capture(&scope_capture);
18801 local_interp.enable_parallel_guard();
18802 local_interp.scope.set_sort_pair(a, b);
18803 match local_interp.exec_block(&block) {
18804 Ok(val) => val,
18805 Err(_) => StrykeValue::UNDEF,
18806 }
18807 });
18808 pmap_progress.finish();
18809 return Ok(result.unwrap_or(StrykeValue::UNDEF));
18810 }
18811 PipelineOp::PReduceInit {
18812 init,
18813 sub,
18814 progress,
18815 } => {
18816 if v.is_empty() {
18817 return Ok(init);
18818 }
18819 let block = sub.body.clone();
18820 let subs = self.subs.clone();
18821 let scope_capture = self.scope.capture();
18822 let cap: &[(String, StrykeValue)] = scope_capture.as_slice();
18823 if v.len() == 1 {
18824 return Ok(fold_preduce_init_step(
18825 &subs,
18826 cap,
18827 &block,
18828 preduce_init_fold_identity(&init),
18829 v.into_iter().next().unwrap(),
18830 ));
18831 }
18832 let pmap_progress = PmapProgress::new(progress, v.len());
18833 let result = v
18834 .into_par_iter()
18835 .fold(
18836 || preduce_init_fold_identity(&init),
18837 |acc, item| {
18838 pmap_progress.tick();
18839 fold_preduce_init_step(&subs, cap, &block, acc, item)
18840 },
18841 )
18842 .reduce(
18843 || preduce_init_fold_identity(&init),
18844 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
18845 );
18846 pmap_progress.finish();
18847 return Ok(result);
18848 }
18849 PipelineOp::PMapReduce {
18850 map,
18851 reduce,
18852 progress,
18853 } => {
18854 if v.is_empty() {
18855 return Ok(StrykeValue::UNDEF);
18856 }
18857 let map_block = map.body.clone();
18858 let reduce_block = reduce.body.clone();
18859 let subs = self.subs.clone();
18860 let scope_capture = self.scope.capture();
18861 if v.len() == 1 {
18862 let mut local_interp = VMHelper::new();
18863 local_interp.subs = subs.clone();
18864 local_interp.scope.restore_capture(&scope_capture);
18865 local_interp.scope.set_topic(v[0].clone());
18866 return match local_interp.exec_block_no_scope(&map_block) {
18867 Ok(val) => Ok(val),
18868 Err(_) => Ok(StrykeValue::UNDEF),
18869 };
18870 }
18871 let pmap_progress = PmapProgress::new(progress, v.len());
18872 let result = v
18873 .into_par_iter()
18874 .map(|item| {
18875 let mut local_interp = VMHelper::new();
18876 local_interp.subs = subs.clone();
18877 local_interp.scope.restore_capture(&scope_capture);
18878 local_interp.scope.set_topic(item);
18879 let val = match local_interp.exec_block_no_scope(&map_block) {
18880 Ok(val) => val,
18881 Err(_) => StrykeValue::UNDEF,
18882 };
18883 pmap_progress.tick();
18884 val
18885 })
18886 .reduce_with(|a, b| {
18887 let mut local_interp = VMHelper::new();
18888 local_interp.subs = subs.clone();
18889 local_interp.scope.restore_capture(&scope_capture);
18890 local_interp.scope.set_sort_pair(a, b);
18891 match local_interp.exec_block_no_scope(&reduce_block) {
18892 Ok(val) => val,
18893 Err(_) => StrykeValue::UNDEF,
18894 }
18895 });
18896 pmap_progress.finish();
18897 return Ok(result.unwrap_or(StrykeValue::UNDEF));
18898 }
18899 }
18900 }
18901 Ok(StrykeValue::array(v))
18902 }
18903
18904 fn pipeline_collect_streaming(
18907 &mut self,
18908 source: Vec<StrykeValue>,
18909 ops: &[PipelineOp],
18910 workers_per_stage: usize,
18911 buffer: usize,
18912 line: usize,
18913 ) -> StrykeResult<StrykeValue> {
18914 use crossbeam::channel::{bounded, Receiver, Sender};
18915
18916 for op in ops {
18918 match op {
18919 PipelineOp::PSort { .. }
18920 | PipelineOp::PReduce { .. }
18921 | PipelineOp::PReduceInit { .. }
18922 | PipelineOp::PMapReduce { .. }
18923 | PipelineOp::PMapChunked { .. } => {
18924 return Err(StrykeError::runtime(
18925 format!(
18926 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
18927 std::mem::discriminant(op)
18928 ),
18929 line,
18930 ));
18931 }
18932 _ => {}
18933 }
18934 }
18935
18936 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
18939 if streamable_ops.is_empty() {
18940 return Ok(StrykeValue::array(source));
18941 }
18942
18943 let n_stages = streamable_ops.len();
18944 let wn = if workers_per_stage > 0 {
18945 workers_per_stage
18946 } else {
18947 self.parallel_thread_count()
18948 };
18949 let subs = self.subs.clone();
18950 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18951
18952 let mut channels: Vec<(Sender<StrykeValue>, Receiver<StrykeValue>)> =
18957 (0..=n_stages).map(|_| bounded(buffer)).collect();
18958
18959 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
18960 let take_done: Arc<std::sync::atomic::AtomicBool> =
18961 Arc::new(std::sync::atomic::AtomicBool::new(false));
18962
18963 let source_tx = channels[0].0.clone();
18966 let result_rx = channels[n_stages].1.clone();
18967 let results: Arc<Mutex<Vec<StrykeValue>>> = Arc::new(Mutex::new(Vec::new()));
18968
18969 std::thread::scope(|scope| {
18970 let result_rx_c = result_rx.clone();
18973 let results_c = Arc::clone(&results);
18974 scope.spawn(move || {
18975 while let Ok(item) = result_rx_c.recv() {
18976 results_c.lock().push(item);
18977 }
18978 });
18979
18980 let err_s = Arc::clone(&err);
18982 let take_done_s = Arc::clone(&take_done);
18983 scope.spawn(move || {
18984 for item in source {
18985 if err_s.lock().is_some()
18986 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
18987 {
18988 break;
18989 }
18990 if source_tx.send(item).is_err() {
18991 break;
18992 }
18993 }
18994 });
18995
18996 for (stage_idx, op) in streamable_ops.iter().enumerate() {
18998 let rx = channels[stage_idx].1.clone();
18999 let tx = channels[stage_idx + 1].0.clone();
19000
19001 for _ in 0..wn {
19002 let rx = rx.clone();
19003 let tx = tx.clone();
19004 let subs = subs.clone();
19005 let capture = capture.clone();
19006 let atomic_arrays = atomic_arrays.clone();
19007 let atomic_hashes = atomic_hashes.clone();
19008 let err_w = Arc::clone(&err);
19009 let take_done_w = Arc::clone(&take_done);
19010
19011 match *op {
19012 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
19013 let sub = Arc::clone(sub);
19014 scope.spawn(move || {
19015 while let Ok(item) = rx.recv() {
19016 if err_w.lock().is_some() {
19017 break;
19018 }
19019 let mut interp = VMHelper::new();
19020 interp.subs = subs.clone();
19021 interp.scope.restore_capture(&capture);
19022 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
19023 interp.enable_parallel_guard();
19024 interp.scope.set_topic(item.clone());
19025 interp.scope_push_hook();
19026 let keep = match interp.exec_block_no_scope(&sub.body) {
19027 Ok(val) => val.is_true(),
19028 Err(_) => false,
19029 };
19030 interp.scope_pop_hook();
19031 if keep && tx.send(item).is_err() {
19032 break;
19033 }
19034 }
19035 });
19036 }
19037 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
19038 let sub = Arc::clone(sub);
19039 scope.spawn(move || {
19040 while let Ok(item) = rx.recv() {
19041 if err_w.lock().is_some() {
19042 break;
19043 }
19044 let mut interp = VMHelper::new();
19045 interp.subs = subs.clone();
19046 interp.scope.restore_capture(&capture);
19047 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
19048 interp.enable_parallel_guard();
19049 interp.scope.set_topic(item);
19050 interp.scope_push_hook();
19051 let mapped = match interp.exec_block_no_scope(&sub.body) {
19052 Ok(val) => val,
19053 Err(_) => StrykeValue::UNDEF,
19054 };
19055 interp.scope_pop_hook();
19056 if tx.send(mapped).is_err() {
19057 break;
19058 }
19059 }
19060 });
19061 }
19062 PipelineOp::Take(n) => {
19063 let limit = (*n).max(0) as usize;
19064 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
19065 let count_w = Arc::clone(&count);
19066 scope.spawn(move || {
19067 while let Ok(item) = rx.recv() {
19068 let prev =
19069 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
19070 if prev >= limit {
19071 take_done_w
19072 .store(true, std::sync::atomic::Ordering::Relaxed);
19073 break;
19074 }
19075 if tx.send(item).is_err() {
19076 break;
19077 }
19078 }
19079 });
19080 break;
19082 }
19083 PipelineOp::PFor { ref sub, .. } => {
19084 let sub = Arc::clone(sub);
19085 scope.spawn(move || {
19086 while let Ok(item) = rx.recv() {
19087 if err_w.lock().is_some() {
19088 break;
19089 }
19090 let mut interp = VMHelper::new();
19091 interp.subs = subs.clone();
19092 interp.scope.restore_capture(&capture);
19093 interp
19094 .scope
19095 .restore_atomics(&atomic_arrays, &atomic_hashes);
19096 interp.enable_parallel_guard();
19097 interp.scope.set_topic(item.clone());
19098 interp.scope_push_hook();
19099 match interp.exec_block_no_scope(&sub.body) {
19100 Ok(_) => {}
19101 Err(e) => {
19102 let msg = match e {
19103 FlowOrError::Error(stryke) => stryke.to_string(),
19104 FlowOrError::Flow(_) => {
19105 "unexpected control flow in par_pipeline_stream pfor".into()
19106 }
19107 };
19108 let mut g = err_w.lock();
19109 if g.is_none() {
19110 *g = Some(msg);
19111 }
19112 interp.scope_pop_hook();
19113 break;
19114 }
19115 }
19116 interp.scope_pop_hook();
19117 if tx.send(item).is_err() {
19118 break;
19119 }
19120 }
19121 });
19122 }
19123 PipelineOp::Tap(ref sub) => {
19124 let sub = Arc::clone(sub);
19125 scope.spawn(move || {
19126 while let Ok(item) = rx.recv() {
19127 if err_w.lock().is_some() {
19128 break;
19129 }
19130 let mut interp = VMHelper::new();
19131 interp.subs = subs.clone();
19132 interp.scope.restore_capture(&capture);
19133 interp
19134 .scope
19135 .restore_atomics(&atomic_arrays, &atomic_hashes);
19136 interp.enable_parallel_guard();
19137 match interp.call_sub(
19138 &sub,
19139 vec![item.clone()],
19140 WantarrayCtx::Void,
19141 line,
19142 )
19143 {
19144 Ok(_) => {}
19145 Err(e) => {
19146 let msg = match e {
19147 FlowOrError::Error(stryke) => stryke.to_string(),
19148 FlowOrError::Flow(_) => {
19149 "unexpected control flow in par_pipeline_stream tap"
19150 .into()
19151 }
19152 };
19153 let mut g = err_w.lock();
19154 if g.is_none() {
19155 *g = Some(msg);
19156 }
19157 break;
19158 }
19159 }
19160 if tx.send(item).is_err() {
19161 break;
19162 }
19163 }
19164 });
19165 }
19166 PipelineOp::PCache { ref sub, .. } => {
19167 let sub = Arc::clone(sub);
19168 scope.spawn(move || {
19169 while let Ok(item) = rx.recv() {
19170 if err_w.lock().is_some() {
19171 break;
19172 }
19173 let k = crate::pcache::cache_key(&item);
19174 let val = if let Some(cached) =
19175 crate::pcache::GLOBAL_PCACHE.get(&k)
19176 {
19177 cached.clone()
19178 } else {
19179 let mut interp = VMHelper::new();
19180 interp.subs = subs.clone();
19181 interp.scope.restore_capture(&capture);
19182 interp
19183 .scope
19184 .restore_atomics(&atomic_arrays, &atomic_hashes);
19185 interp.enable_parallel_guard();
19186 interp.scope.set_topic(item);
19187 interp.scope_push_hook();
19188 let v = match interp.exec_block_no_scope(&sub.body) {
19189 Ok(v) => v,
19190 Err(_) => StrykeValue::UNDEF,
19191 };
19192 interp.scope_pop_hook();
19193 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
19194 v
19195 };
19196 if tx.send(val).is_err() {
19197 break;
19198 }
19199 }
19200 });
19201 }
19202 _ => unreachable!(),
19204 }
19205 }
19206 }
19207
19208 channels.clear();
19212 drop(result_rx);
19213 });
19214
19215 if let Some(msg) = err.lock().take() {
19216 return Err(StrykeError::runtime(msg, line));
19217 }
19218
19219 let results = std::mem::take(&mut *results.lock());
19220 Ok(StrykeValue::array(results))
19221 }
19222
19223 fn heap_compare(&mut self, cmp: &Arc<StrykeSub>, a: &StrykeValue, b: &StrykeValue) -> Ordering {
19224 self.scope_push_hook();
19225 if let Some(ref env) = cmp.closure_env {
19226 self.scope.restore_capture(env);
19227 }
19228 self.scope.set_sort_pair(a.clone(), b.clone());
19229 let ord = match self.exec_block_no_scope(&cmp.body) {
19230 Ok(v) => {
19231 let n = v.to_int();
19232 if n < 0 {
19233 Ordering::Less
19234 } else if n > 0 {
19235 Ordering::Greater
19236 } else {
19237 Ordering::Equal
19238 }
19239 }
19240 Err(_) => Ordering::Equal,
19241 };
19242 self.scope_pop_hook();
19243 ord
19244 }
19245
19246 fn heap_sift_up(&mut self, items: &mut [StrykeValue], cmp: &Arc<StrykeSub>, mut i: usize) {
19247 while i > 0 {
19248 let p = (i - 1) / 2;
19249 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
19250 break;
19251 }
19252 items.swap(i, p);
19253 i = p;
19254 }
19255 }
19256
19257 fn heap_sift_down(&mut self, items: &mut [StrykeValue], cmp: &Arc<StrykeSub>, mut i: usize) {
19258 let n = items.len();
19259 loop {
19260 let mut sm = i;
19261 let l = 2 * i + 1;
19262 let r = 2 * i + 2;
19263 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
19264 sm = l;
19265 }
19266 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
19267 sm = r;
19268 }
19269 if sm == i {
19270 break;
19271 }
19272 items.swap(i, sm);
19273 i = sm;
19274 }
19275 }
19276
19277 fn hash_for_signature_destruct(
19278 &mut self,
19279 v: &StrykeValue,
19280 line: usize,
19281 ) -> StrykeResult<IndexMap<String, StrykeValue>> {
19282 let Some(m) = self.match_subject_as_hash(v) else {
19283 return Err(StrykeError::runtime(
19284 format!(
19285 "sub signature hash destruct: expected HASH or HASH reference, got {}",
19286 v.ref_type()
19287 ),
19288 line,
19289 ));
19290 };
19291 Ok(m)
19292 }
19293
19294 pub(crate) fn apply_sub_signature(
19296 &mut self,
19297 sub: &StrykeSub,
19298 argv: &[StrykeValue],
19299 line: usize,
19300 ) -> StrykeResult<()> {
19301 if sub.params.is_empty() {
19302 return Ok(());
19303 }
19304 let mut i = 0usize;
19305 for p in &sub.params {
19306 match p {
19307 SubSigParam::Scalar(name, ty, default) => {
19308 let val = if i < argv.len() {
19309 argv[i].clone()
19310 } else if let Some(default_expr) = default {
19311 match self.eval_expr(default_expr) {
19312 Ok(v) => v,
19313 Err(FlowOrError::Error(e)) => return Err(e),
19314 Err(FlowOrError::Flow(_)) => {
19315 return Err(StrykeError::runtime(
19316 "unexpected control flow in parameter default",
19317 line,
19318 ))
19319 }
19320 }
19321 } else {
19322 StrykeValue::UNDEF
19323 };
19324 i += 1;
19325 if let Some(t) = ty {
19326 if let Err(e) = t.check_value(&val) {
19327 return Err(StrykeError::runtime(
19328 format!("sub parameter ${}: {}", name, e),
19329 line,
19330 ));
19331 }
19332 }
19333 let n = self.english_scalar_name(name);
19334 self.scope.declare_scalar(n, val);
19335 }
19336 SubSigParam::Array(name, default) => {
19337 let rest: Vec<StrykeValue> = if i < argv.len() {
19338 let r = argv[i..].to_vec();
19339 i = argv.len();
19340 r
19341 } else if let Some(default_expr) = default {
19342 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19343 Ok(v) => v,
19344 Err(FlowOrError::Error(e)) => return Err(e),
19345 Err(FlowOrError::Flow(_)) => {
19346 return Err(StrykeError::runtime(
19347 "unexpected control flow in parameter default",
19348 line,
19349 ))
19350 }
19351 };
19352 val.to_list()
19353 } else {
19354 vec![]
19355 };
19356 let aname = self.stash_array_name_for_package(name);
19357 self.scope.declare_array(&aname, rest);
19358 }
19359 SubSigParam::Hash(name, default) => {
19360 let rest: Vec<StrykeValue> = if i < argv.len() {
19361 let r = argv[i..].to_vec();
19362 i = argv.len();
19363 r
19364 } else if let Some(default_expr) = default {
19365 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19366 Ok(v) => v,
19367 Err(FlowOrError::Error(e)) => return Err(e),
19368 Err(FlowOrError::Flow(_)) => {
19369 return Err(StrykeError::runtime(
19370 "unexpected control flow in parameter default",
19371 line,
19372 ))
19373 }
19374 };
19375 val.to_list()
19376 } else {
19377 vec![]
19378 };
19379 let mut map = IndexMap::new();
19380 let mut j = 0;
19381 while j + 1 < rest.len() {
19382 map.insert(rest[j].to_string(), rest[j + 1].clone());
19383 j += 2;
19384 }
19385 self.scope.declare_hash(name, map);
19386 }
19387 SubSigParam::ArrayDestruct(elems) => {
19388 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19389 i += 1;
19390 let Some(arr) = self.match_subject_as_array(&arg) else {
19391 return Err(StrykeError::runtime(
19392 format!(
19393 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
19394 arg.ref_type()
19395 ),
19396 line,
19397 ));
19398 };
19399 let binds = self
19400 .match_array_pattern_elems(&arr, elems, line)
19401 .map_err(|e| match e {
19402 FlowOrError::Error(stryke) => stryke,
19403 FlowOrError::Flow(_) => StrykeError::runtime(
19404 "unexpected flow in sub signature array destruct",
19405 line,
19406 ),
19407 })?;
19408 let Some(binds) = binds else {
19409 return Err(StrykeError::runtime(
19410 "sub signature array destruct: length or element mismatch",
19411 line,
19412 ));
19413 };
19414 for b in binds {
19415 match b {
19416 PatternBinding::Scalar(name, v) => {
19417 let n = self.english_scalar_name(&name);
19418 self.scope.declare_scalar(n, v);
19419 }
19420 PatternBinding::Array(name, elems) => {
19421 self.scope.declare_array(&name, elems);
19422 }
19423 }
19424 }
19425 }
19426 SubSigParam::HashDestruct(pairs) => {
19427 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19428 i += 1;
19429 let map = self.hash_for_signature_destruct(&arg, line)?;
19430 for (key, varname) in pairs {
19431 let v = map.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
19432 let n = self.english_scalar_name(varname);
19433 self.scope.declare_scalar(n, v);
19434 }
19435 }
19436 }
19437 }
19438 Ok(())
19439 }
19440
19441 pub(crate) fn try_hof_dispatch(
19445 &mut self,
19446 sub: &StrykeSub,
19447 args: &[StrykeValue],
19448 want: WantarrayCtx,
19449 line: usize,
19450 ) -> Option<ExecResult> {
19451 let env = sub.closure_env.as_ref()?;
19452 fn env_get<'a>(env: &'a [(String, StrykeValue)], key: &str) -> Option<&'a StrykeValue> {
19453 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
19454 }
19455
19456 match sub.name.as_str() {
19457 "__comp__" => {
19459 let fns = env_get(env, "__comp_fns__")?.to_list();
19460 let mut val = args.first().cloned().unwrap_or(StrykeValue::UNDEF);
19461 for f in fns.iter().rev() {
19462 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
19463 Ok(v) => val = v,
19464 Err(e) => return Some(Err(e)),
19465 }
19466 }
19467 Some(Ok(val))
19468 }
19469 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
19471 "__juxt__" => {
19473 let fns = env_get(env, "__juxt_fns__")?.to_list();
19474 let mut results = Vec::with_capacity(fns.len());
19475 for f in &fns {
19476 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
19477 Ok(v) => results.push(v),
19478 Err(e) => return Some(Err(e)),
19479 }
19480 }
19481 Some(Ok(StrykeValue::array(results)))
19482 }
19483 "__partial__" => {
19485 let fn_val = env_get(env, "__partial_fn__")?.clone();
19486 let bound = env_get(env, "__partial_args__")?.to_list();
19487 let mut all_args = bound;
19488 all_args.extend_from_slice(args);
19489 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
19490 }
19491 "__complement__" => {
19493 let fn_val = env_get(env, "__complement_fn__")?.clone();
19494 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19495 Ok(v) => Some(Ok(StrykeValue::integer(if v.is_true() { 0 } else { 1 }))),
19496 Err(e) => Some(Err(e)),
19497 }
19498 }
19499 "__fnil__" => {
19501 let fn_val = env_get(env, "__fnil_fn__")?.clone();
19502 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
19503 let mut patched = args.to_vec();
19504 for (i, d) in defaults.iter().enumerate() {
19505 if i < patched.len() {
19506 if patched[i].is_undef() {
19507 patched[i] = d.clone();
19508 }
19509 } else {
19510 patched.push(d.clone());
19511 }
19512 }
19513 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
19514 }
19515 "__memoize__" => {
19517 let fn_val = env_get(env, "__memoize_fn__")?.clone();
19518 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
19519 let key = args
19520 .iter()
19521 .map(|a| a.to_string())
19522 .collect::<Vec<_>>()
19523 .join("\x00");
19524 if let Some(href) = cache_ref.as_hash_ref() {
19525 if let Some(cached) = href.read().get(&key) {
19526 return Some(Ok(cached.clone()));
19527 }
19528 }
19529 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19530 Ok(v) => {
19531 if let Some(href) = cache_ref.as_hash_ref() {
19532 href.write().insert(key, v.clone());
19533 }
19534 Some(Ok(v))
19535 }
19536 Err(e) => Some(Err(e)),
19537 }
19538 }
19539 "__curry__" => {
19541 let fn_val = env_get(env, "__curry_fn__")?.clone();
19542 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
19543 let bound = env_get(env, "__curry_bound__")?.to_list();
19544 let mut all = bound;
19545 all.extend_from_slice(args);
19546 if all.len() >= arity {
19547 Some(self.dispatch_indirect_call(fn_val, all, want, line))
19548 } else {
19549 let curry_sub = StrykeSub {
19550 name: "__curry__".to_string(),
19551 params: vec![],
19552 body: vec![],
19553 closure_env: Some(vec![
19554 ("__curry_fn__".to_string(), fn_val),
19555 (
19556 "__curry_arity__".to_string(),
19557 StrykeValue::integer(arity as i64),
19558 ),
19559 ("__curry_bound__".to_string(), StrykeValue::array(all)),
19560 ]),
19561 prototype: None,
19562 fib_like: None,
19563 };
19564 Some(Ok(StrykeValue::code_ref(Arc::new(curry_sub))))
19565 }
19566 }
19567 "__once__" => {
19569 let cache_ref = env_get(env, "__once_cache__")?.clone();
19570 if let Some(href) = cache_ref.as_hash_ref() {
19571 let r = href.read();
19572 if r.contains_key("done") {
19573 return Some(Ok(r.get("val").cloned().unwrap_or(StrykeValue::UNDEF)));
19574 }
19575 }
19576 let fn_val = env_get(env, "__once_fn__")?.clone();
19577 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19578 Ok(v) => {
19579 if let Some(href) = cache_ref.as_hash_ref() {
19580 let mut w = href.write();
19581 w.insert("done".to_string(), StrykeValue::integer(1));
19582 w.insert("val".to_string(), v.clone());
19583 }
19584 Some(Ok(v))
19585 }
19586 Err(e) => Some(Err(e)),
19587 }
19588 }
19589 _ => None,
19590 }
19591 }
19592
19593 pub(crate) fn call_sub(
19594 &mut self,
19595 sub: &StrykeSub,
19596 args: Vec<StrykeValue>,
19597 want: WantarrayCtx,
19598 line: usize,
19599 ) -> ExecResult {
19600 let pkg = sub.name.rsplit_once("::").map(|(p, _)| p.to_string());
19603 self.call_sub_with_package(sub, args, want, line, pkg)
19604 }
19605
19606 fn call_sub_with_package(
19610 &mut self,
19611 sub: &StrykeSub,
19612 args: Vec<StrykeValue>,
19613 want: WantarrayCtx,
19614 _line: usize,
19615 home_package: Option<String>,
19616 ) -> ExecResult {
19617 self.current_sub_stack.push(Arc::new(sub.clone()));
19619
19620 self.scope_push_hook();
19623 self.scope.declare_array("_", args.clone());
19624 if let Some(ref env) = sub.closure_env {
19625 self.scope.restore_capture(env);
19626 }
19627 if let Some(pkg) = home_package {
19633 self.scope
19634 .declare_scalar("__PACKAGE__", StrykeValue::string(pkg));
19635 }
19636 self.scope.set_closure_args(&args);
19640 let argv = self.scope.take_sub_underscore().unwrap_or_default();
19642 self.apply_sub_signature(sub, &argv, _line)?;
19643 let saved = self.wantarray_kind;
19644 self.wantarray_kind = want;
19645 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
19646 self.wantarray_kind = saved;
19647 self.scope_pop_hook();
19648 self.current_sub_stack.pop();
19649 return match r {
19650 Ok(v) => Ok(v),
19651 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19652 Err(e) => Err(e),
19653 };
19654 }
19655 if let Some(pat) = sub.fib_like.as_ref() {
19656 if argv.len() == 1 {
19657 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
19658 let t0 = self.profiler.is_some().then(std::time::Instant::now);
19659 if let Some(p) = &mut self.profiler {
19660 p.enter_sub(&sub.name);
19661 }
19662 self.debugger_enter_sub(&sub.name);
19663 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
19664 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
19665 p.exit_sub(t0.elapsed());
19666 }
19667 self.debugger_leave_sub();
19668 self.wantarray_kind = saved;
19669 self.scope_pop_hook();
19670 self.current_sub_stack.pop();
19671 return Ok(StrykeValue::integer(n));
19672 }
19673 }
19674 }
19675 self.scope.declare_array("_", argv.clone());
19676 let t0 = self.profiler.is_some().then(std::time::Instant::now);
19679 if let Some(p) = &mut self.profiler {
19680 p.enter_sub(&sub.name);
19681 }
19682 self.debugger_enter_sub(&sub.name);
19683 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
19687 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
19688 p.exit_sub(t0.elapsed());
19689 }
19690 self.debugger_leave_sub();
19691 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
19693 Some(self.scope.get_array("_"))
19694 } else {
19695 None
19696 };
19697 self.wantarray_kind = saved;
19698 self.scope_pop_hook();
19699 self.current_sub_stack.pop();
19700 match result {
19701 Ok(v) => Ok(v),
19702 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19703 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
19704 let goto_args = goto_args.unwrap_or_default();
19706 let fqn = if target_name.contains("::") {
19707 target_name.clone()
19708 } else {
19709 format!("{}::{}", self.current_package(), target_name)
19710 };
19711 if let Some(target_sub) = self
19712 .subs
19713 .get(&fqn)
19714 .cloned()
19715 .or_else(|| self.subs.get(&target_name).cloned())
19716 {
19717 self.call_sub(&target_sub, goto_args, want, _line)
19718 } else {
19719 Err(StrykeError::runtime(
19720 format!("Undefined subroutine &{}", target_name),
19721 _line,
19722 )
19723 .into())
19724 }
19725 }
19726 Err(FlowOrError::Flow(Flow::Yield(_))) => {
19727 Err(StrykeError::runtime("yield is only valid inside gen { }", 0).into())
19728 }
19729 Err(e) => Err(e),
19730 }
19731 }
19732
19733 fn call_struct_method(
19735 &mut self,
19736 body: &Block,
19737 params: &[SubSigParam],
19738 args: Vec<StrykeValue>,
19739 line: usize,
19740 ) -> ExecResult {
19741 self.scope_push_hook();
19742 self.scope.declare_array("_", args.clone());
19743 if let Some(self_val) = args.first() {
19745 self.scope.declare_scalar("self", self_val.clone());
19746 }
19747 if args.len() > 1 {
19753 self.scope.set_closure_args(&args[1..]);
19754 }
19755 let user_args: Vec<StrykeValue> = args.iter().skip(1).cloned().collect();
19757 self.apply_params_to_argv(params, &user_args, line)?;
19758 let result = self.exec_block_no_scope(body);
19759 self.scope_pop_hook();
19760 match result {
19761 Ok(v) => Ok(v),
19762 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19763 Err(e) => Err(e),
19764 }
19765 }
19766
19767 pub(crate) fn call_class_method(
19769 &mut self,
19770 body: &Block,
19771 params: &[SubSigParam],
19772 args: Vec<StrykeValue>,
19773 line: usize,
19774 ) -> ExecResult {
19775 self.call_class_method_inner(body, params, args, line, false)
19776 }
19777
19778 pub(crate) fn call_static_class_method(
19780 &mut self,
19781 body: &Block,
19782 params: &[SubSigParam],
19783 args: Vec<StrykeValue>,
19784 line: usize,
19785 ) -> ExecResult {
19786 self.call_class_method_inner(body, params, args, line, true)
19787 }
19788
19789 fn call_class_method_inner(
19790 &mut self,
19791 body: &Block,
19792 params: &[SubSigParam],
19793 args: Vec<StrykeValue>,
19794 line: usize,
19795 is_static: bool,
19796 ) -> ExecResult {
19797 self.scope_push_hook();
19798 self.scope.declare_array("_", args.clone());
19799 if !is_static {
19800 if let Some(self_val) = args.first() {
19802 self.scope.declare_scalar("self", self_val.clone());
19803 }
19804 }
19805 if is_static {
19812 self.scope.set_closure_args(&args);
19813 } else if args.len() > 1 {
19814 self.scope.set_closure_args(&args[1..]);
19815 }
19816 let user_args: Vec<StrykeValue> = if is_static {
19818 args.clone()
19819 } else {
19820 args.iter().skip(1).cloned().collect()
19821 };
19822 self.apply_params_to_argv(params, &user_args, line)?;
19823 let result = self.exec_block_no_scope(body);
19824 self.scope_pop_hook();
19825 match result {
19826 Ok(v) => Ok(v),
19827 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19828 Err(e) => Err(e),
19829 }
19830 }
19831
19832 fn apply_params_to_argv(
19834 &mut self,
19835 params: &[SubSigParam],
19836 argv: &[StrykeValue],
19837 line: usize,
19838 ) -> StrykeResult<()> {
19839 let mut i = 0;
19840 for param in params {
19841 match param {
19842 SubSigParam::Scalar(name, ty_opt, default) => {
19843 let v = if i < argv.len() {
19844 argv[i].clone()
19845 } else if let Some(default_expr) = default {
19846 match self.eval_expr(default_expr) {
19847 Ok(v) => v,
19848 Err(FlowOrError::Error(e)) => return Err(e),
19849 Err(FlowOrError::Flow(_)) => {
19850 return Err(StrykeError::runtime(
19851 "unexpected control flow in parameter default",
19852 line,
19853 ))
19854 }
19855 }
19856 } else {
19857 StrykeValue::UNDEF
19858 };
19859 i += 1;
19860 if let Some(ty) = ty_opt {
19861 ty.check_value(&v).map_err(|msg| {
19862 StrykeError::type_error(
19863 format!("method parameter ${}: {}", name, msg),
19864 line,
19865 )
19866 })?;
19867 }
19868 let n = self.english_scalar_name(name);
19869 self.scope.declare_scalar(n, v);
19870 }
19871 SubSigParam::Array(name, default) => {
19872 let rest: Vec<StrykeValue> = if i < argv.len() {
19873 let r = argv[i..].to_vec();
19874 i = argv.len();
19875 r
19876 } else if let Some(default_expr) = default {
19877 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19878 Ok(v) => v,
19879 Err(FlowOrError::Error(e)) => return Err(e),
19880 Err(FlowOrError::Flow(_)) => {
19881 return Err(StrykeError::runtime(
19882 "unexpected control flow in parameter default",
19883 line,
19884 ))
19885 }
19886 };
19887 val.to_list()
19888 } else {
19889 vec![]
19890 };
19891 let aname = self.stash_array_name_for_package(name);
19892 self.scope.declare_array(&aname, rest);
19893 }
19894 SubSigParam::Hash(name, default) => {
19895 let rest: Vec<StrykeValue> = if i < argv.len() {
19896 let r = argv[i..].to_vec();
19897 i = argv.len();
19898 r
19899 } else if let Some(default_expr) = default {
19900 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19901 Ok(v) => v,
19902 Err(FlowOrError::Error(e)) => return Err(e),
19903 Err(FlowOrError::Flow(_)) => {
19904 return Err(StrykeError::runtime(
19905 "unexpected control flow in parameter default",
19906 line,
19907 ))
19908 }
19909 };
19910 val.to_list()
19911 } else {
19912 vec![]
19913 };
19914 let mut map = IndexMap::new();
19915 let mut j = 0;
19916 while j + 1 < rest.len() {
19917 map.insert(rest[j].to_string(), rest[j + 1].clone());
19918 j += 2;
19919 }
19920 self.scope.declare_hash(name, map);
19921 }
19922 SubSigParam::ArrayDestruct(elems) => {
19923 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19924 i += 1;
19925 let Some(arr) = self.match_subject_as_array(&arg) else {
19926 return Err(StrykeError::runtime(
19927 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
19928 line,
19929 ));
19930 };
19931 let binds = self
19932 .match_array_pattern_elems(&arr, elems, line)
19933 .map_err(|e| match e {
19934 FlowOrError::Error(stryke) => stryke,
19935 FlowOrError::Flow(_) => StrykeError::runtime(
19936 "unexpected flow in method array destruct",
19937 line,
19938 ),
19939 })?;
19940 let Some(binds) = binds else {
19941 return Err(StrykeError::runtime(
19942 format!(
19943 "method parameter: array destructure failed at position {}",
19944 i
19945 ),
19946 line,
19947 ));
19948 };
19949 for b in binds {
19950 match b {
19951 PatternBinding::Scalar(name, v) => {
19952 let n = self.english_scalar_name(&name);
19953 self.scope.declare_scalar(n, v);
19954 }
19955 PatternBinding::Array(name, elems) => {
19956 self.scope.declare_array(&name, elems);
19957 }
19958 }
19959 }
19960 }
19961 SubSigParam::HashDestruct(pairs) => {
19962 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19963 i += 1;
19964 let map = self.hash_for_signature_destruct(&arg, line)?;
19965 for (key, varname) in pairs {
19966 let v = map.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
19967 let n = self.english_scalar_name(varname);
19968 self.scope.declare_scalar(n, v);
19969 }
19970 }
19971 }
19972 }
19973 Ok(())
19974 }
19975
19976 fn builtin_new(&mut self, class: &str, args: Vec<StrykeValue>, line: usize) -> ExecResult {
19977 if class == "Set" {
19978 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
19979 }
19980 if let Some(def) = self.struct_defs.get(class).cloned() {
19981 let mut provided = Vec::new();
19982 let mut i = 1;
19983 while i + 1 < args.len() {
19984 let k = args[i].to_string();
19985 let v = args[i + 1].clone();
19986 provided.push((k, v));
19987 i += 2;
19988 }
19989 let mut defaults = Vec::with_capacity(def.fields.len());
19990 for field in &def.fields {
19991 if let Some(ref expr) = field.default {
19992 let val = self.eval_expr(expr)?;
19993 defaults.push(Some(val));
19994 } else {
19995 defaults.push(None);
19996 }
19997 }
19998 return Ok(crate::native_data::struct_new_with_defaults(
19999 &def, &provided, &defaults, line,
20000 )?);
20001 }
20002 if let Some(def) = self.class_defs.get(class).cloned() {
20011 let user_args: Vec<StrykeValue> = args.into_iter().skip(1).collect();
20012 return self.class_construct(&def, user_args, line);
20013 }
20014 let mut map = IndexMap::new();
20016 let mut i = 1; while i + 1 < args.len() {
20018 let k = args[i].to_string();
20019 let v = args[i + 1].clone();
20020 map.insert(k, v);
20021 i += 2;
20022 }
20023 Ok(StrykeValue::blessed(Arc::new(
20024 crate::value::BlessedRef::new_blessed(class.to_string(), StrykeValue::hash(map)),
20025 )))
20026 }
20027
20028 fn exec_print(
20029 &mut self,
20030 handle: Option<&str>,
20031 args: &[Expr],
20032 newline: bool,
20033 line: usize,
20034 ) -> ExecResult {
20035 if newline && (self.feature_bits & FEAT_SAY) == 0 {
20036 return Err(StrykeError::runtime(
20037 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
20038 line,
20039 )
20040 .into());
20041 }
20042 let mut output = String::new();
20043 if args.is_empty() {
20044 let topic = self.scope.get_scalar("_").clone();
20046 let s = self.stringify_value(topic, line)?;
20047 output.push_str(&s);
20048 } else {
20049 for (i, a) in args.iter().enumerate() {
20052 if i > 0 {
20053 output.push_str(&self.ofs);
20054 }
20055 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
20056 for item in val.to_list() {
20057 let s = self.stringify_value(item, line)?;
20058 output.push_str(&s);
20059 }
20060 }
20061 }
20062 if newline {
20063 output.push('\n');
20064 }
20065 output.push_str(&self.ors);
20066
20067 let handle_name =
20068 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
20069 self.write_formatted_print(handle_name.as_str(), &output, line)?;
20070 Ok(StrykeValue::integer(1))
20071 }
20072
20073 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
20074 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
20075 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
20077 (s, &[])
20078 } else {
20079 (self.eval_expr(&args[0])?.to_string(), &args[1..])
20080 };
20081 let mut arg_vals = Vec::new();
20085 for a in rest {
20086 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
20087 if let Some(items) = v.as_array_vec() {
20088 arg_vals.extend(items);
20089 } else {
20090 arg_vals.push(v);
20091 }
20092 }
20093 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
20094 let handle_name =
20095 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
20096 match handle_name.as_str() {
20097 "STDOUT" => {
20098 if !self.suppress_stdout {
20099 print!("{}", output);
20100 if self.output_autoflush {
20101 let _ = io::stdout().flush();
20102 }
20103 }
20104 }
20105 "STDERR" => {
20106 eprint!("{}", output);
20107 let _ = io::stderr().flush();
20108 }
20109 name => {
20110 if let Some(writer) = self.output_handles.get_mut(name) {
20111 let _ = writer.write_all(output.as_bytes());
20112 if self.output_autoflush {
20113 let _ = writer.flush();
20114 }
20115 }
20116 }
20117 }
20118 Ok(StrykeValue::integer(1))
20119 }
20120
20121 pub(crate) fn eval_substr_expr(
20123 &mut self,
20124 string: &Expr,
20125 offset: &Expr,
20126 length: Option<&Expr>,
20127 replacement: Option<&Expr>,
20128 _line: usize,
20129 ) -> Result<StrykeValue, FlowOrError> {
20130 let s = self.eval_expr(string)?.to_string();
20131 let off = self.eval_expr(offset)?.to_int();
20132 let start = if off < 0 {
20133 (s.len() as i64 + off).max(0) as usize
20134 } else {
20135 off as usize
20136 };
20137 let len = if let Some(l) = length {
20138 let len_val = self.eval_expr(l)?.to_int();
20139 if len_val < 0 {
20140 let remaining = s.len().saturating_sub(start) as i64;
20142 (remaining + len_val).max(0) as usize
20143 } else {
20144 len_val as usize
20145 }
20146 } else {
20147 s.len().saturating_sub(start)
20148 };
20149 let end = start.saturating_add(len).min(s.len());
20150 let result = s.get(start..end).unwrap_or("").to_string();
20151 if let Some(rep) = replacement {
20152 let rep_s = self.eval_expr(rep)?.to_string();
20153 let mut new_s = String::new();
20154 new_s.push_str(&s[..start]);
20155 new_s.push_str(&rep_s);
20156 new_s.push_str(&s[end..]);
20157 self.assign_value(string, StrykeValue::string(new_s))?;
20158 }
20159 Ok(StrykeValue::string(result))
20160 }
20161
20162 pub(crate) fn eval_push_expr(
20163 &mut self,
20164 array: &Expr,
20165 values: &[Expr],
20166 line: usize,
20167 ) -> Result<StrykeValue, FlowOrError> {
20168 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20169 for v in values {
20170 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20171 self.push_array_deref_value(aref.clone(), val, line)?;
20172 }
20173 let len = self.array_deref_len(aref, line)?;
20174 return Ok(StrykeValue::integer(len));
20175 }
20176 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20177 if self.scope.is_array_frozen(&arr_name) {
20178 return Err(StrykeError::runtime(
20179 format!("Modification of a frozen value: @{}", arr_name),
20180 line,
20181 )
20182 .into());
20183 }
20184 for v in values {
20185 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20186 if let Some(items) = val.as_array_vec() {
20187 for item in items {
20188 self.scope
20189 .push_to_array(&arr_name, item)
20190 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20191 }
20192 } else {
20193 self.scope
20194 .push_to_array(&arr_name, val)
20195 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20196 }
20197 }
20198 let len = self.scope.array_len(&arr_name);
20199 Ok(StrykeValue::integer(len as i64))
20200 }
20201
20202 pub(crate) fn eval_pop_expr(
20203 &mut self,
20204 array: &Expr,
20205 line: usize,
20206 ) -> Result<StrykeValue, FlowOrError> {
20207 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20208 return self.pop_array_deref(aref, line);
20209 }
20210 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20211 self.scope
20212 .pop_from_array(&arr_name)
20213 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20214 }
20215
20216 pub(crate) fn eval_shift_expr(
20217 &mut self,
20218 array: &Expr,
20219 line: usize,
20220 ) -> Result<StrykeValue, FlowOrError> {
20221 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20222 return self.shift_array_deref(aref, line);
20223 }
20224 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20225 self.scope
20226 .shift_from_array(&arr_name)
20227 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20228 }
20229
20230 pub(crate) fn eval_unshift_expr(
20231 &mut self,
20232 array: &Expr,
20233 values: &[Expr],
20234 line: usize,
20235 ) -> Result<StrykeValue, FlowOrError> {
20236 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20237 let mut vals = Vec::new();
20238 for v in values {
20239 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20240 if let Some(items) = val.as_array_vec() {
20241 vals.extend(items);
20242 } else {
20243 vals.push(val);
20244 }
20245 }
20246 let len = self.unshift_array_deref_multi(aref, vals, line)?;
20247 return Ok(StrykeValue::integer(len));
20248 }
20249 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20250 let mut vals = Vec::new();
20251 for v in values {
20252 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20253 if let Some(items) = val.as_array_vec() {
20254 vals.extend(items);
20255 } else {
20256 vals.push(val);
20257 }
20258 }
20259 let arr = self
20260 .scope
20261 .get_array_mut(&arr_name)
20262 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20263 for (i, v) in vals.into_iter().enumerate() {
20264 arr.insert(i, v);
20265 }
20266 let len = arr.len();
20267 Ok(StrykeValue::integer(len as i64))
20268 }
20269
20270 pub(crate) fn push_array_deref_value(
20272 &mut self,
20273 arr_ref: StrykeValue,
20274 val: StrykeValue,
20275 line: usize,
20276 ) -> Result<(), FlowOrError> {
20277 let val = self.scope.resolve_container_binding_ref(val);
20280 if let Some(r) = arr_ref.as_array_ref() {
20281 let mut w = r.write();
20282 if let Some(items) = val.as_array_vec() {
20283 w.extend(items.iter().cloned());
20284 } else {
20285 w.push(val);
20286 }
20287 return Ok(());
20288 }
20289 if let Some(name) = arr_ref.as_array_binding_name() {
20290 if let Some(items) = val.as_array_vec() {
20291 for item in items {
20292 self.scope
20293 .push_to_array(&name, item)
20294 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20295 }
20296 } else {
20297 self.scope
20298 .push_to_array(&name, val)
20299 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20300 }
20301 return Ok(());
20302 }
20303 if let Some(s) = arr_ref.as_str() {
20304 if self.strict_refs {
20305 return Err(StrykeError::runtime(
20306 format!(
20307 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20308 s
20309 ),
20310 line,
20311 )
20312 .into());
20313 }
20314 let name = s.to_string();
20315 if let Some(items) = val.as_array_vec() {
20316 for item in items {
20317 self.scope
20318 .push_to_array(&name, item)
20319 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20320 }
20321 } else {
20322 self.scope
20323 .push_to_array(&name, val)
20324 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20325 }
20326 return Ok(());
20327 }
20328 Err(StrykeError::runtime("push argument is not an ARRAY reference", line).into())
20329 }
20330
20331 pub(crate) fn array_deref_len(
20332 &self,
20333 arr_ref: StrykeValue,
20334 line: usize,
20335 ) -> Result<i64, FlowOrError> {
20336 if let Some(r) = arr_ref.as_array_ref() {
20337 return Ok(r.read().len() as i64);
20338 }
20339 if let Some(name) = arr_ref.as_array_binding_name() {
20340 return Ok(self.scope.array_len(&name) as i64);
20341 }
20342 if let Some(s) = arr_ref.as_str() {
20343 if self.strict_refs {
20344 return Err(StrykeError::runtime(
20345 format!(
20346 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20347 s
20348 ),
20349 line,
20350 )
20351 .into());
20352 }
20353 return Ok(self.scope.array_len(&s) as i64);
20354 }
20355 Err(StrykeError::runtime("argument is not an ARRAY reference", line).into())
20356 }
20357
20358 pub(crate) fn pop_array_deref(
20359 &mut self,
20360 arr_ref: StrykeValue,
20361 line: usize,
20362 ) -> Result<StrykeValue, FlowOrError> {
20363 if let Some(r) = arr_ref.as_array_ref() {
20364 let mut w = r.write();
20365 return Ok(w.pop().unwrap_or(StrykeValue::UNDEF));
20366 }
20367 if let Some(name) = arr_ref.as_array_binding_name() {
20368 return self
20369 .scope
20370 .pop_from_array(&name)
20371 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20372 }
20373 if let Some(s) = arr_ref.as_str() {
20374 if self.strict_refs {
20375 return Err(StrykeError::runtime(
20376 format!(
20377 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20378 s
20379 ),
20380 line,
20381 )
20382 .into());
20383 }
20384 return self
20385 .scope
20386 .pop_from_array(&s)
20387 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20388 }
20389 Err(StrykeError::runtime("pop argument is not an ARRAY reference", line).into())
20390 }
20391
20392 pub(crate) fn shift_array_deref(
20393 &mut self,
20394 arr_ref: StrykeValue,
20395 line: usize,
20396 ) -> Result<StrykeValue, FlowOrError> {
20397 if let Some(r) = arr_ref.as_array_ref() {
20398 let mut w = r.write();
20399 return Ok(if w.is_empty() {
20400 StrykeValue::UNDEF
20401 } else {
20402 w.remove(0)
20403 });
20404 }
20405 if let Some(name) = arr_ref.as_array_binding_name() {
20406 return self
20407 .scope
20408 .shift_from_array(&name)
20409 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20410 }
20411 if let Some(s) = arr_ref.as_str() {
20412 if self.strict_refs {
20413 return Err(StrykeError::runtime(
20414 format!(
20415 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20416 s
20417 ),
20418 line,
20419 )
20420 .into());
20421 }
20422 return self
20423 .scope
20424 .shift_from_array(&s)
20425 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20426 }
20427 Err(StrykeError::runtime("shift argument is not an ARRAY reference", line).into())
20428 }
20429
20430 pub(crate) fn unshift_array_deref_multi(
20431 &mut self,
20432 arr_ref: StrykeValue,
20433 vals: Vec<StrykeValue>,
20434 line: usize,
20435 ) -> Result<i64, FlowOrError> {
20436 let mut flat: Vec<StrykeValue> = Vec::new();
20437 for v in vals {
20438 if let Some(items) = v.as_array_vec() {
20439 flat.extend(items);
20440 } else {
20441 flat.push(v);
20442 }
20443 }
20444 if let Some(r) = arr_ref.as_array_ref() {
20445 let mut w = r.write();
20446 for (i, v) in flat.into_iter().enumerate() {
20447 w.insert(i, v);
20448 }
20449 return Ok(w.len() as i64);
20450 }
20451 if let Some(name) = arr_ref.as_array_binding_name() {
20452 let arr = self
20453 .scope
20454 .get_array_mut(&name)
20455 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20456 for (i, v) in flat.into_iter().enumerate() {
20457 arr.insert(i, v);
20458 }
20459 return Ok(arr.len() as i64);
20460 }
20461 if let Some(s) = arr_ref.as_str() {
20462 if self.strict_refs {
20463 return Err(StrykeError::runtime(
20464 format!(
20465 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20466 s
20467 ),
20468 line,
20469 )
20470 .into());
20471 }
20472 let name = s.to_string();
20473 let arr = self
20474 .scope
20475 .get_array_mut(&name)
20476 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20477 for (i, v) in flat.into_iter().enumerate() {
20478 arr.insert(i, v);
20479 }
20480 return Ok(arr.len() as i64);
20481 }
20482 Err(StrykeError::runtime("unshift argument is not an ARRAY reference", line).into())
20483 }
20484
20485 pub(crate) fn splice_array_deref(
20488 &mut self,
20489 aref: StrykeValue,
20490 offset_val: StrykeValue,
20491 length_val: StrykeValue,
20492 rep_vals: Vec<StrykeValue>,
20493 line: usize,
20494 ) -> Result<StrykeValue, FlowOrError> {
20495 let ctx = self.wantarray_kind;
20496 if let Some(r) = aref.as_array_ref() {
20497 let arr_len = r.read().len();
20498 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20499 let mut w = r.write();
20500 let removed: Vec<StrykeValue> = w.drain(off..end).collect();
20501 for (i, v) in rep_vals.into_iter().enumerate() {
20502 w.insert(off + i, v);
20503 }
20504 return Ok(match ctx {
20505 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20506 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20507 });
20508 }
20509 if let Some(name) = aref.as_array_binding_name() {
20510 let arr_len = self.scope.array_len(&name);
20511 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20512 let removed = self
20513 .scope
20514 .splice_in_place(&name, off, end, rep_vals)
20515 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20516 return Ok(match ctx {
20517 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20518 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20519 });
20520 }
20521 if let Some(s) = aref.as_str() {
20522 if self.strict_refs {
20523 return Err(StrykeError::runtime(
20524 format!(
20525 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20526 s
20527 ),
20528 line,
20529 )
20530 .into());
20531 }
20532 let arr_len = self.scope.array_len(&s);
20533 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20534 let removed = self
20535 .scope
20536 .splice_in_place(&s, off, end, rep_vals)
20537 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20538 return Ok(match ctx {
20539 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20540 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20541 });
20542 }
20543 Err(StrykeError::runtime("splice argument is not an ARRAY reference", line).into())
20544 }
20545
20546 fn eval_splice_replacement(
20551 &mut self,
20552 replacement: &[Expr],
20553 ) -> Result<Vec<StrykeValue>, FlowOrError> {
20554 let saved = self.wantarray_kind;
20555 self.wantarray_kind = WantarrayCtx::List;
20556 let mut out = Vec::new();
20557 for r in replacement {
20558 let v = self.eval_expr_ctx(r, WantarrayCtx::List)?;
20559 if let Some(items) = v.as_array_vec() {
20560 out.extend(items);
20561 } else if v.is_iterator() {
20562 out.extend(v.into_iterator().collect_all());
20563 } else {
20564 out.push(v);
20565 }
20566 }
20567 self.wantarray_kind = saved;
20568 Ok(out)
20569 }
20570
20571 pub(crate) fn eval_splice_expr(
20572 &mut self,
20573 array: &Expr,
20574 offset: Option<&Expr>,
20575 length: Option<&Expr>,
20576 replacement: &[Expr],
20577 ctx: WantarrayCtx,
20578 line: usize,
20579 ) -> Result<StrykeValue, FlowOrError> {
20580 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20581 let offset_val = if let Some(o) = offset {
20582 self.eval_expr(o)?
20583 } else {
20584 StrykeValue::integer(0)
20585 };
20586 let length_val = if let Some(l) = length {
20587 self.eval_expr(l)?
20588 } else {
20589 StrykeValue::UNDEF
20590 };
20591 let rep_vals = self.eval_splice_replacement(replacement)?;
20592 let saved = self.wantarray_kind;
20593 self.wantarray_kind = ctx;
20594 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
20595 self.wantarray_kind = saved;
20596 return out;
20597 }
20598 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20599 let arr_len = self.scope.array_len(&arr_name);
20600 let offset_val = if let Some(o) = offset {
20601 self.eval_expr(o)?
20602 } else {
20603 StrykeValue::integer(0)
20604 };
20605 let length_val = if let Some(l) = length {
20606 self.eval_expr(l)?
20607 } else {
20608 StrykeValue::UNDEF
20609 };
20610 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20611 let rep_vals = self.eval_splice_replacement(replacement)?;
20612 let removed = self
20613 .scope
20614 .splice_in_place(&arr_name, off, end, rep_vals)
20615 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20616 Ok(match ctx {
20617 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20618 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20619 })
20620 }
20621
20622 pub(crate) fn keys_from_value(
20624 val: StrykeValue,
20625 line: usize,
20626 ) -> Result<StrykeValue, FlowOrError> {
20627 if let Some(h) = val.as_hash_map() {
20628 Ok(StrykeValue::array(
20629 h.keys().map(|k| StrykeValue::string(k.clone())).collect(),
20630 ))
20631 } else if let Some(r) = val.as_hash_ref() {
20632 Ok(StrykeValue::array(
20633 r.read()
20634 .keys()
20635 .map(|k| StrykeValue::string(k.clone()))
20636 .collect(),
20637 ))
20638 } else {
20639 Err(StrykeError::runtime("keys requires hash", line).into())
20640 }
20641 }
20642
20643 pub(crate) fn eval_keys_expr(
20644 &mut self,
20645 expr: &Expr,
20646 line: usize,
20647 ) -> Result<StrykeValue, FlowOrError> {
20648 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
20651 Self::keys_from_value(val, line)
20652 }
20653
20654 pub(crate) fn values_from_value(
20656 val: StrykeValue,
20657 line: usize,
20658 ) -> Result<StrykeValue, FlowOrError> {
20659 if let Some(h) = val.as_hash_map() {
20660 Ok(StrykeValue::array(h.values().cloned().collect()))
20661 } else if let Some(r) = val.as_hash_ref() {
20662 Ok(StrykeValue::array(r.read().values().cloned().collect()))
20663 } else {
20664 Err(StrykeError::runtime("values requires hash", line).into())
20665 }
20666 }
20667
20668 pub(crate) fn eval_values_expr(
20669 &mut self,
20670 expr: &Expr,
20671 line: usize,
20672 ) -> Result<StrykeValue, FlowOrError> {
20673 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
20674 Self::values_from_value(val, line)
20675 }
20676
20677 pub(crate) fn eval_delete_operand(
20678 &mut self,
20679 expr: &Expr,
20680 line: usize,
20681 ) -> Result<StrykeValue, FlowOrError> {
20682 match &expr.kind {
20683 ExprKind::HashElement { hash, key } => {
20684 let k = self.eval_expr(key)?.to_string();
20685 self.touch_env_hash(hash);
20686 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
20687 let class = obj
20688 .as_blessed_ref()
20689 .map(|b| b.class.clone())
20690 .unwrap_or_default();
20691 let full = format!("{}::DELETE", class);
20692 if let Some(sub) = self.subs.get(&full).cloned() {
20693 return self.call_sub(
20694 &sub,
20695 vec![obj, StrykeValue::string(k)],
20696 WantarrayCtx::Scalar,
20697 line,
20698 );
20699 }
20700 }
20701 self.scope
20702 .delete_hash_element(hash, &k)
20703 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20704 }
20705 ExprKind::ArrayElement { array, index } => {
20706 self.check_strict_array_var(array, line)?;
20707 let idx = self.eval_expr(index)?.to_int();
20708 let aname = self.stash_array_name_for_package(array);
20709 self.scope
20710 .delete_array_element(&aname, idx)
20711 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20712 }
20713 ExprKind::ArrowDeref {
20714 expr: inner,
20715 index,
20716 kind: DerefKind::Hash,
20717 } => {
20718 let k = self.eval_expr(index)?.to_string();
20719 let container = self.eval_expr(inner)?;
20720 self.delete_arrow_hash_element(container, &k, line)
20721 .map_err(Into::into)
20722 }
20723 ExprKind::ArrowDeref {
20724 expr: inner,
20725 index,
20726 kind: DerefKind::Array,
20727 } => {
20728 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
20729 return Err(StrykeError::runtime(
20730 "delete on array element needs scalar subscript",
20731 line,
20732 )
20733 .into());
20734 }
20735 let container = self.eval_expr(inner)?;
20736 let idx = self.eval_expr(index)?.to_int();
20737 self.delete_arrow_array_element(container, idx, line)
20738 .map_err(Into::into)
20739 }
20740 ExprKind::HashSlice { hash, keys } => {
20743 self.touch_env_hash(hash);
20744 let mut all_keys: Vec<String> = Vec::new();
20745 for key_expr in keys {
20746 all_keys.extend(self.eval_hash_slice_key_components(key_expr)?);
20747 }
20748 let mut deleted = Vec::with_capacity(all_keys.len());
20749 for k in &all_keys {
20750 let v = self
20751 .scope
20752 .delete_hash_element(hash, k)
20753 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20754 deleted.push(v);
20755 }
20756 Ok(StrykeValue::array(deleted))
20757 }
20758 ExprKind::ArraySlice { array, indices } => {
20760 self.check_strict_array_var(array, line)?;
20761 let aname = self.stash_array_name_for_package(array);
20762 let mut all_idx: Vec<i64> = Vec::new();
20763 for idx_expr in indices {
20764 let v = self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?;
20765 if let Some(items) = v.as_array_vec() {
20766 all_idx.extend(items.iter().map(|x| x.to_int()));
20767 } else if let Some(r) = v.as_array_ref() {
20768 all_idx.extend(r.read().iter().map(|x| x.to_int()));
20769 } else if v.is_iterator() {
20770 all_idx.extend(v.into_iterator().collect_all().iter().map(|x| x.to_int()));
20771 } else {
20772 all_idx.push(v.to_int());
20773 }
20774 }
20775 let mut deleted = Vec::with_capacity(all_idx.len());
20776 for idx in &all_idx {
20777 let v = self
20778 .scope
20779 .delete_array_element(&aname, *idx)
20780 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20781 deleted.push(v);
20782 }
20783 Ok(StrykeValue::array(deleted))
20784 }
20785 _ => Err(StrykeError::runtime("delete requires hash or array element", line).into()),
20786 }
20787 }
20788
20789 fn eval_expr_exists_mode(&mut self, expr: &Expr) -> Result<StrykeValue, FlowOrError> {
20795 match &expr.kind {
20796 ExprKind::ArrowDeref {
20797 expr: inner,
20798 index,
20799 kind: DerefKind::Hash,
20800 } => {
20801 let inner_val = self.eval_expr_exists_mode(inner)?;
20802 if inner_val.is_undef() {
20803 return Ok(StrykeValue::UNDEF);
20804 }
20805 if let Some(r) = inner_val.as_hash_ref() {
20806 let k = self.eval_expr(index)?.to_string();
20807 return Ok(r.read().get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
20808 }
20809 if let Some(b) = inner_val.as_blessed_ref() {
20810 let data = b.data.read();
20811 if let Some(r) = data.as_hash_ref() {
20812 let k = self.eval_expr(index)?.to_string();
20813 return Ok(r.read().get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
20814 }
20815 }
20816 if let Some(s) = inner_val.as_struct_inst() {
20820 let k = self.eval_expr(index)?.to_string();
20821 if let Some(idx) = s.def.field_index(&k) {
20822 return Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF));
20823 }
20824 return Ok(StrykeValue::UNDEF);
20825 }
20826 if let Some(c) = inner_val.as_class_inst() {
20827 let k = self.eval_expr(index)?.to_string();
20828 if let Some(idx) = c.def.field_index(&k) {
20829 return Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF));
20830 }
20831 return Ok(StrykeValue::UNDEF);
20832 }
20833 Ok(StrykeValue::UNDEF)
20834 }
20835 ExprKind::ArrowDeref {
20836 expr: inner,
20837 index,
20838 kind: DerefKind::Array,
20839 } => {
20840 let inner_val = self.eval_expr_exists_mode(inner)?;
20841 if inner_val.is_undef() {
20842 return Ok(StrykeValue::UNDEF);
20843 }
20844 if let Some(r) = inner_val.as_array_ref() {
20845 let idx = self.eval_expr(index)?.to_int();
20846 let arr = r.read();
20847 let i = if idx < 0 {
20848 (arr.len() as i64 + idx).max(0) as usize
20849 } else {
20850 idx as usize
20851 };
20852 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
20853 }
20854 Ok(StrykeValue::UNDEF)
20855 }
20856 _ => self.eval_expr(expr),
20857 }
20858 }
20859
20860 pub(crate) fn eval_exists_operand(
20861 &mut self,
20862 expr: &Expr,
20863 line: usize,
20864 ) -> Result<StrykeValue, FlowOrError> {
20865 match &expr.kind {
20866 ExprKind::HashElement { hash, key } => {
20867 let k = self.eval_expr(key)?.to_string();
20868 self.touch_env_hash(hash);
20869 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
20870 let class = obj
20871 .as_blessed_ref()
20872 .map(|b| b.class.clone())
20873 .unwrap_or_default();
20874 let full = format!("{}::EXISTS", class);
20875 if let Some(sub) = self.subs.get(&full).cloned() {
20876 return self.call_sub(
20877 &sub,
20878 vec![obj, StrykeValue::string(k)],
20879 WantarrayCtx::Scalar,
20880 line,
20881 );
20882 }
20883 }
20884 Ok(StrykeValue::integer(
20885 if self.scope.exists_hash_element(hash, &k) {
20886 1
20887 } else {
20888 0
20889 },
20890 ))
20891 }
20892 ExprKind::ArrayElement { array, index } => {
20893 self.check_strict_array_var(array, line)?;
20894 let idx = self.eval_expr(index)?.to_int();
20895 let aname = self.stash_array_name_for_package(array);
20896 Ok(StrykeValue::integer(
20897 if self.scope.exists_array_element(&aname, idx) {
20898 1
20899 } else {
20900 0
20901 },
20902 ))
20903 }
20904 ExprKind::ArrowDeref {
20905 expr: inner,
20906 index,
20907 kind: DerefKind::Hash,
20908 } => {
20909 let k = self.eval_expr(index)?.to_string();
20910 let container = match self.eval_expr_exists_mode(inner) {
20915 Ok(v) => v,
20916 Err(_) => return Ok(StrykeValue::integer(0)),
20917 };
20918 if container.is_undef() {
20919 return Ok(StrykeValue::integer(0));
20920 }
20921 let yes = self.exists_arrow_hash_element(container, &k, line)?;
20922 Ok(StrykeValue::integer(if yes { 1 } else { 0 }))
20923 }
20924 ExprKind::ArrowDeref {
20925 expr: inner,
20926 index,
20927 kind: DerefKind::Array,
20928 } => {
20929 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
20930 return Err(StrykeError::runtime(
20931 "exists on array element needs scalar subscript",
20932 line,
20933 )
20934 .into());
20935 }
20936 let container = match self.eval_expr_exists_mode(inner) {
20937 Ok(v) => v,
20938 Err(_) => return Ok(StrykeValue::integer(0)),
20939 };
20940 if container.is_undef() {
20941 return Ok(StrykeValue::integer(0));
20942 }
20943 let idx = self.eval_expr(index)?.to_int();
20944 let yes = self.exists_arrow_array_element(container, idx, line)?;
20945 Ok(StrykeValue::integer(if yes { 1 } else { 0 }))
20946 }
20947 ExprKind::SubroutineRef(name) => {
20948 let resolved = self.resolve_sub_by_name(name);
20952 Ok(StrykeValue::integer(if resolved.is_some() { 1 } else { 0 }))
20953 }
20954 _ => Err(StrykeError::runtime("exists requires hash or array element", line).into()),
20955 }
20956 }
20957
20958 pub(crate) fn eval_pmap_remote(
20966 &mut self,
20967 cluster_pv: StrykeValue,
20968 list_pv: StrykeValue,
20969 show_progress: bool,
20970 block: &Block,
20971 flat_outputs: bool,
20972 line: usize,
20973 ) -> Result<StrykeValue, FlowOrError> {
20974 let Some(cluster) = cluster_pv.as_remote_cluster() else {
20975 return Err(StrykeError::runtime("pmap_on: expected cluster(...) value", line).into());
20976 };
20977 let items = list_pv.to_list();
20978 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
20979 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
20980 return Err(StrykeError::runtime(
20981 "pmap_on: mysync/atomic capture is not supported for remote workers",
20982 line,
20983 )
20984 .into());
20985 }
20986 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
20987 .map_err(|e| StrykeError::runtime(e, line))?;
20988 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
20989 let block_src = crate::fmt::format_block(block);
20990 let item_jsons = crate::cluster::perl_items_to_json(&items)
20991 .map_err(|e| StrykeError::runtime(e, line))?;
20992
20993 let pmap_progress = PmapProgress::new(show_progress, items.len());
20996 let result_values =
20997 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
20998 .map_err(|e| StrykeError::runtime(format!("pmap_on remote: {e}"), line))?;
20999 for _ in 0..result_values.len() {
21000 pmap_progress.tick();
21001 }
21002 pmap_progress.finish();
21003
21004 if flat_outputs {
21005 let flattened: Vec<StrykeValue> = result_values
21006 .into_iter()
21007 .flat_map(|v| v.map_flatten_outputs(true))
21008 .collect();
21009 Ok(StrykeValue::array(flattened))
21010 } else {
21011 Ok(StrykeValue::array(result_values))
21012 }
21013 }
21014
21015 pub(crate) fn eval_par_lines_expr(
21017 &mut self,
21018 path: &Expr,
21019 callback: &Expr,
21020 progress: Option<&Expr>,
21021 line: usize,
21022 ) -> Result<StrykeValue, FlowOrError> {
21023 let show_progress = progress
21024 .map(|p| self.eval_expr(p))
21025 .transpose()?
21026 .map(|v| v.is_true())
21027 .unwrap_or(false);
21028 let raw = self.eval_expr(path)?.to_string();
21029 let path_s = self.resolve_stryke_path_string(&raw);
21030 let cb_val = self.eval_expr(callback)?;
21031 let sub = if let Some(s) = cb_val.as_code_ref() {
21032 s
21033 } else {
21034 return Err(StrykeError::runtime(
21035 "par_lines: second argument must be a code reference",
21036 line,
21037 )
21038 .into());
21039 };
21040 let subs = self.subs.clone();
21041 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21042 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
21043 FlowOrError::Error(StrykeError::runtime(format!("par_lines: {}", e), line))
21044 })?;
21045 let mmap = unsafe {
21046 memmap2::Mmap::map(&file).map_err(|e| {
21047 FlowOrError::Error(StrykeError::runtime(
21048 format!("par_lines: mmap: {}", e),
21049 line,
21050 ))
21051 })?
21052 };
21053 let data: &[u8] = &mmap;
21054 if data.is_empty() {
21055 return Ok(StrykeValue::UNDEF);
21056 }
21057 let line_total = crate::par_lines::line_count_bytes(data);
21058 let pmap_progress = PmapProgress::new(show_progress, line_total);
21059 if self.num_threads == 0 {
21060 self.num_threads = rayon::current_num_threads();
21061 }
21062 let num_chunks = self.num_threads.saturating_mul(8).max(1);
21063 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
21064 chunks.into_par_iter().try_for_each(|(start, end)| {
21065 let slice = &data[start..end];
21066 let mut s = 0usize;
21067 while s < slice.len() {
21068 let e = slice[s..]
21069 .iter()
21070 .position(|&b| b == b'\n')
21071 .map(|p| s + p)
21072 .unwrap_or(slice.len());
21073 let line_bytes = &slice[s..e];
21074 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
21075 let mut local_interp = VMHelper::new();
21076 local_interp.subs = subs.clone();
21077 local_interp.scope.restore_capture(&scope_capture);
21078 local_interp
21079 .scope
21080 .restore_atomics(&atomic_arrays, &atomic_hashes);
21081 local_interp.enable_parallel_guard();
21082 local_interp.scope.set_topic(StrykeValue::string(line_str));
21083 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
21084 Ok(_) => {}
21085 Err(e) => return Err(e),
21086 }
21087 pmap_progress.tick();
21088 if e >= slice.len() {
21089 break;
21090 }
21091 s = e + 1;
21092 }
21093 Ok(())
21094 })?;
21095 pmap_progress.finish();
21096 Ok(StrykeValue::UNDEF)
21097 }
21098
21099 pub(crate) fn eval_par_walk_expr(
21101 &mut self,
21102 path: &Expr,
21103 callback: &Expr,
21104 progress: Option<&Expr>,
21105 line: usize,
21106 ) -> Result<StrykeValue, FlowOrError> {
21107 let show_progress = progress
21108 .map(|p| self.eval_expr(p))
21109 .transpose()?
21110 .map(|v| v.is_true())
21111 .unwrap_or(false);
21112 let path_val = self.eval_expr(path)?;
21113 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
21114 arr.into_iter()
21115 .map(|v| PathBuf::from(v.to_string()))
21116 .collect()
21117 } else {
21118 vec![PathBuf::from(path_val.to_string())]
21119 };
21120 let cb_val = self.eval_expr(callback)?;
21121 let sub = if let Some(s) = cb_val.as_code_ref() {
21122 s
21123 } else {
21124 return Err(StrykeError::runtime(
21125 "par_walk: second argument must be a code reference",
21126 line,
21127 )
21128 .into());
21129 };
21130 let subs = self.subs.clone();
21131 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21132
21133 if show_progress {
21134 let paths = crate::par_walk::collect_paths(&roots);
21135 let pmap_progress = PmapProgress::new(true, paths.len());
21136 paths.into_par_iter().try_for_each(|p| {
21137 let s = p.to_string_lossy().into_owned();
21138 let mut local_interp = VMHelper::new();
21139 local_interp.subs = subs.clone();
21140 local_interp.scope.restore_capture(&scope_capture);
21141 local_interp
21142 .scope
21143 .restore_atomics(&atomic_arrays, &atomic_hashes);
21144 local_interp.enable_parallel_guard();
21145 local_interp.scope.set_topic(StrykeValue::string(s));
21146 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
21147 Ok(_) => {}
21148 Err(e) => return Err(e),
21149 }
21150 pmap_progress.tick();
21151 Ok(())
21152 })?;
21153 pmap_progress.finish();
21154 } else {
21155 for r in &roots {
21156 par_walk_recursive(
21157 r.as_path(),
21158 &sub,
21159 &subs,
21160 &scope_capture,
21161 &atomic_arrays,
21162 &atomic_hashes,
21163 line,
21164 )?;
21165 }
21166 }
21167 Ok(StrykeValue::UNDEF)
21168 }
21169
21170 pub(crate) fn builtin_par_sed(
21172 &mut self,
21173 args: &[StrykeValue],
21174 line: usize,
21175 has_progress: bool,
21176 ) -> StrykeResult<StrykeValue> {
21177 let show_progress = if has_progress {
21178 args.last().map(|v| v.is_true()).unwrap_or(false)
21179 } else {
21180 false
21181 };
21182 let slice = if has_progress {
21183 &args[..args.len().saturating_sub(1)]
21184 } else {
21185 args
21186 };
21187 if slice.len() < 3 {
21188 return Err(StrykeError::runtime(
21189 "par_sed: need pattern, replacement, and at least one file path",
21190 line,
21191 ));
21192 }
21193 let pat_val = &slice[0];
21194 let repl = slice[1].to_string();
21195 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
21196
21197 let re = if let Some(rx) = pat_val.as_regex() {
21198 rx
21199 } else {
21200 let pattern = pat_val.to_string();
21201 match self.compile_regex(&pattern, "g", line) {
21202 Ok(r) => r,
21203 Err(FlowOrError::Error(e)) => return Err(e),
21204 Err(FlowOrError::Flow(f)) => {
21205 return Err(StrykeError::runtime(format!("par_sed: {:?}", f), line))
21206 }
21207 }
21208 };
21209
21210 let pmap = PmapProgress::new(show_progress, files.len());
21211 let touched = AtomicUsize::new(0);
21212 files.par_iter().try_for_each(|path| {
21213 let content = read_file_text_perl_compat(path)
21214 .map_err(|e| StrykeError::runtime(format!("par_sed {}: {}", path, e), line))?;
21215 let new_s = re.replace_all(&content, &repl);
21216 if new_s != content {
21217 std::fs::write(path, new_s.as_bytes())
21218 .map_err(|e| StrykeError::runtime(format!("par_sed {}: {}", path, e), line))?;
21219 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
21220 }
21221 pmap.tick();
21222 Ok(())
21223 })?;
21224 pmap.finish();
21225 Ok(StrykeValue::integer(
21226 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
21227 ))
21228 }
21229
21230 pub(crate) fn eval_pwatch_expr(
21232 &mut self,
21233 path: &Expr,
21234 callback: &Expr,
21235 line: usize,
21236 ) -> Result<StrykeValue, FlowOrError> {
21237 let pattern_s = self.eval_expr(path)?.to_string();
21238 let cb_val = self.eval_expr(callback)?;
21239 let sub = if let Some(s) = cb_val.as_code_ref() {
21240 s
21241 } else {
21242 return Err(StrykeError::runtime(
21243 "pwatch: second argument must be a code reference",
21244 line,
21245 )
21246 .into());
21247 };
21248 let subs = self.subs.clone();
21249 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21250 crate::pwatch::run_pwatch(
21251 &pattern_s,
21252 sub,
21253 subs,
21254 scope_capture,
21255 atomic_arrays,
21256 atomic_hashes,
21257 line,
21258 )
21259 .map_err(FlowOrError::Error)
21260 }
21261
21262 fn interpolate_replacement_string(&self, replacement: &str) -> String {
21264 let mut out = String::with_capacity(replacement.len());
21265 let chars: Vec<char> = replacement.chars().collect();
21266 let mut i = 0;
21267 while i < chars.len() {
21268 if chars[i] == '\\' && i + 1 < chars.len() {
21269 out.push(chars[i]);
21270 out.push(chars[i + 1]);
21271 i += 2;
21272 continue;
21273 }
21274 if chars[i] == '$' && i + 1 < chars.len() {
21275 let start = i;
21276 i += 1;
21277 if chars[i].is_ascii_digit() {
21278 out.push('$');
21279 while i < chars.len() && chars[i].is_ascii_digit() {
21280 out.push(chars[i]);
21281 i += 1;
21282 }
21283 continue;
21284 }
21285 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
21286 out.push('$');
21287 out.push(chars[i]);
21288 i += 1;
21289 continue;
21290 }
21291 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
21292 out.push('$');
21293 continue;
21294 }
21295 let mut name = String::new();
21296 if chars[i] == '{' {
21297 i += 1;
21298 while i < chars.len() && chars[i] != '}' {
21299 name.push(chars[i]);
21300 i += 1;
21301 }
21302 if i < chars.len() {
21303 i += 1;
21304 }
21305 } else {
21306 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
21307 name.push(chars[i]);
21308 i += 1;
21309 }
21310 }
21311 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
21312 let val = self.scope.get_scalar(&name);
21313 out.push_str(&val.to_string());
21314 } else if !name.is_empty() {
21315 out.push_str(&replacement[start..i]);
21316 } else {
21317 out.push('$');
21318 }
21319 continue;
21320 }
21321 out.push(chars[i]);
21322 i += 1;
21323 }
21324 out
21325 }
21326
21327 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
21329 let mut out = String::with_capacity(pattern.len());
21330 let chars: Vec<char> = pattern.chars().collect();
21331 let mut i = 0;
21332 while i < chars.len() {
21333 if chars[i] == '\\' && i + 1 < chars.len() {
21334 out.push(chars[i]);
21336 out.push(chars[i + 1]);
21337 i += 2;
21338 continue;
21339 }
21340 if chars[i] == '$' && i + 1 < chars.len() {
21341 i += 1;
21342 if i >= chars.len()
21344 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
21345 {
21346 out.push('$');
21347 continue;
21348 }
21349 let mut name = String::new();
21350 if chars[i] == '{' {
21351 i += 1;
21352 while i < chars.len() && chars[i] != '}' {
21353 name.push(chars[i]);
21354 i += 1;
21355 }
21356 if i < chars.len() {
21357 i += 1;
21358 } } else {
21360 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
21361 name.push(chars[i]);
21362 i += 1;
21363 }
21364 }
21365 if !name.is_empty() {
21366 let val = self.scope.get_scalar(&name);
21367 out.push_str(&val.to_string());
21368 } else {
21369 out.push('$');
21370 }
21371 continue;
21372 }
21373 out.push(chars[i]);
21374 i += 1;
21375 }
21376 out
21377 }
21378
21379 pub(crate) fn compile_regex(
21380 &mut self,
21381 pattern: &str,
21382 flags: &str,
21383 line: usize,
21384 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
21385 let pattern = if pattern.contains('$') || pattern.contains('@') {
21387 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
21388 } else {
21389 std::borrow::Cow::Borrowed(pattern)
21390 };
21391 let pattern = pattern.as_ref();
21392 let multiline = self.multiline_match;
21395 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
21396 if lp == pattern && lf == flags && *lm == multiline {
21397 return Ok(lr.clone());
21398 }
21399 }
21400 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
21402 if let Some(cached) = self.regex_cache.get(&key) {
21403 self.regex_last = Some((
21404 pattern.to_string(),
21405 flags.to_string(),
21406 multiline,
21407 cached.clone(),
21408 ));
21409 return Ok(cached.clone());
21410 }
21411 let expanded = expand_perl_regex_quotemeta(pattern);
21412 let expanded = expand_perl_regex_octal_escapes(&expanded);
21413 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
21414 let mut re_str = String::new();
21415 if flags.contains('i') {
21416 re_str.push_str("(?i)");
21417 }
21418 if flags.contains('s') {
21419 re_str.push_str("(?s)");
21420 }
21421 if flags.contains('m') {
21422 re_str.push_str("(?m)");
21423 }
21424 if flags.contains('x') {
21425 re_str.push_str("(?x)");
21426 }
21427 if multiline {
21429 re_str.push_str("(?s)");
21430 }
21431 re_str.push_str(&expanded);
21432 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
21433 FlowOrError::Error(StrykeError::runtime(
21434 format!("Invalid regex /{}/: {}", pattern, e),
21435 line,
21436 ))
21437 })?;
21438 let arc = re;
21439 self.regex_last = Some((
21440 pattern.to_string(),
21441 flags.to_string(),
21442 multiline,
21443 arc.clone(),
21444 ));
21445 self.regex_cache.insert(key, arc.clone());
21446 Ok(arc)
21447 }
21448
21449 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
21451 if self.last_readline_handle.is_empty() {
21452 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
21453 }
21454 let n = *self
21455 .handle_line_numbers
21456 .get(&self.last_readline_handle)
21457 .unwrap_or(&0);
21458 if n <= 0 {
21459 return None;
21460 }
21461 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
21462 {
21463 return Some(("<>".to_string(), n));
21464 }
21465 if self.last_readline_handle == "STDIN" {
21466 return Some((self.last_stdin_die_bracket.clone(), n));
21467 }
21468 Some((format!("<{}>", self.last_readline_handle), n))
21469 }
21470
21471 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
21473 let mut s = format!(" at {} line {}", self.file, source_line);
21474 if let Some((bracket, n)) = self.die_warn_io_annotation() {
21475 s.push_str(&format!(", {} line {}.", bracket, n));
21476 } else {
21477 s.push('.');
21478 }
21479 s
21480 }
21481
21482 pub fn process_line(
21487 &mut self,
21488 line_str: &str,
21489 _program: &Program,
21490 is_last_input_line: bool,
21491 ) -> StrykeResult<Option<String>> {
21492 let chunk = self
21493 .line_mode_chunk
21494 .as_ref()
21495 .expect("process_line called without compiled chunk — execute() must run first")
21496 .clone();
21497 crate::run_line_body(&chunk, self, line_str, is_last_input_line)
21498 }
21499}
21500
21501fn both_non_numeric_strings_iv(a: &StrykeValue, b: &StrykeValue) -> bool {
21505 if !a.is_string_like() || !b.is_string_like() {
21506 return false;
21507 }
21508 let sa = a.to_string();
21509 let sb = b.to_string();
21510 let looks = |s: &str| {
21511 let t = s.trim();
21512 !t.is_empty() && t.parse::<f64>().is_ok()
21513 };
21514 !looks(&sa) && !looks(&sb)
21515}
21516
21517fn par_walk_invoke_entry(
21518 path: &Path,
21519 sub: &Arc<StrykeSub>,
21520 subs: &HashMap<String, Arc<StrykeSub>>,
21521 scope_capture: &[(String, StrykeValue)],
21522 atomic_arrays: &[(String, crate::scope::AtomicArray)],
21523 atomic_hashes: &[(String, crate::scope::AtomicHash)],
21524 line: usize,
21525) -> Result<(), FlowOrError> {
21526 let s = path.to_string_lossy().into_owned();
21527 let mut local_interp = VMHelper::new();
21528 local_interp.subs = subs.clone();
21529 local_interp.scope.restore_capture(scope_capture);
21530 local_interp
21531 .scope
21532 .restore_atomics(atomic_arrays, atomic_hashes);
21533 local_interp.enable_parallel_guard();
21534 local_interp.scope.set_topic(StrykeValue::string(s));
21535 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
21536 Ok(())
21537}
21538
21539fn par_walk_recursive(
21540 path: &Path,
21541 sub: &Arc<StrykeSub>,
21542 subs: &HashMap<String, Arc<StrykeSub>>,
21543 scope_capture: &[(String, StrykeValue)],
21544 atomic_arrays: &[(String, crate::scope::AtomicArray)],
21545 atomic_hashes: &[(String, crate::scope::AtomicHash)],
21546 line: usize,
21547) -> Result<(), FlowOrError> {
21548 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
21549 return par_walk_invoke_entry(
21550 path,
21551 sub,
21552 subs,
21553 scope_capture,
21554 atomic_arrays,
21555 atomic_hashes,
21556 line,
21557 );
21558 }
21559 if !path.is_dir() {
21560 return Ok(());
21561 }
21562 par_walk_invoke_entry(
21563 path,
21564 sub,
21565 subs,
21566 scope_capture,
21567 atomic_arrays,
21568 atomic_hashes,
21569 line,
21570 )?;
21571 let read = match std::fs::read_dir(path) {
21572 Ok(r) => r,
21573 Err(_) => return Ok(()),
21574 };
21575 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
21576 entries.par_iter().try_for_each(|e| {
21577 par_walk_recursive(
21578 &e.path(),
21579 sub,
21580 subs,
21581 scope_capture,
21582 atomic_arrays,
21583 atomic_hashes,
21584 line,
21585 )
21586 })?;
21587 Ok(())
21588}
21589
21590fn par_chunk_value(v: &StrykeValue, n_threads: usize) -> Vec<StrykeValue> {
21618 let n = n_threads.max(1);
21619 if let Some(s) = v.as_str() {
21621 let bytes = s.as_bytes();
21622 if bytes.len() < 16_384 || n < 2 {
21623 return vec![StrykeValue::string(s)];
21624 }
21625 let target = bytes.len().div_ceil(n);
21626 let mut splits = vec![0usize];
21627 let mut cursor = target;
21628 while cursor < bytes.len() {
21629 while cursor < bytes.len() && (bytes[cursor] & 0xC0) == 0x80 {
21631 cursor += 1;
21632 }
21633 splits.push(cursor);
21634 cursor += target;
21635 }
21636 splits.push(bytes.len());
21637 return splits
21638 .windows(2)
21639 .map(|w| {
21640 let chunk = std::str::from_utf8(&bytes[w[0]..w[1]]).unwrap_or("");
21641 StrykeValue::string(chunk.to_string())
21642 })
21643 .collect();
21644 }
21645 if let Some(arr) = v.as_array_vec() {
21647 if arr.len() < 32 || n < 2 {
21648 return vec![StrykeValue::array(arr)];
21649 }
21650 let target = arr.len().div_ceil(n);
21651 let mut chunks = Vec::with_capacity(n);
21652 for slice in arr.chunks(target) {
21653 chunks.push(StrykeValue::array(slice.to_vec()));
21654 }
21655 return chunks;
21656 }
21657 if let Some(arr_ref) = v.as_array_ref() {
21658 let arr = arr_ref.read().clone();
21659 if arr.len() < 32 || n < 2 {
21660 return vec![StrykeValue::array(arr)];
21661 }
21662 let target = arr.len().div_ceil(n);
21663 let mut chunks = Vec::with_capacity(n);
21664 for slice in arr.chunks(target) {
21665 chunks.push(StrykeValue::array(slice.to_vec()));
21666 }
21667 return chunks;
21668 }
21669 vec![v.clone()]
21671}
21672
21673fn par_reduce_auto_merge(chunks: Vec<StrykeValue>) -> StrykeValue {
21683 if chunks.is_empty() {
21684 return StrykeValue::UNDEF;
21685 }
21686 let first = &chunks[0];
21687 if let Some(_h) = first.as_hash_ref() {
21689 let mut out: indexmap::IndexMap<String, f64> = indexmap::IndexMap::new();
21690 for chunk in &chunks {
21691 if let Some(hr) = chunk.as_hash_ref() {
21692 for (k, v) in hr.read().iter() {
21693 *out.entry(k.clone()).or_insert(0.0) += v.to_number();
21694 }
21695 }
21696 }
21697 let mut indexmap_out: indexmap::IndexMap<String, StrykeValue> = indexmap::IndexMap::new();
21700 for (k, v) in out {
21701 let pv = if v == v.trunc() && v.abs() < 1e15 {
21702 StrykeValue::integer(v as i64)
21703 } else {
21704 StrykeValue::float(v)
21705 };
21706 indexmap_out.insert(k, pv);
21707 }
21708 return StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(indexmap_out)));
21709 }
21710 if first.is_integer_like() || first.is_float_like() {
21712 let s: f64 = chunks.iter().map(|v| v.to_number()).sum();
21713 if s == s.trunc() && s.abs() < 1e15 {
21714 return StrykeValue::integer(s as i64);
21715 }
21716 return StrykeValue::float(s);
21717 }
21718 if first.as_array_vec().is_some() || first.as_array_ref().is_some() {
21720 let mut out = Vec::new();
21721 for v in &chunks {
21722 out.extend(v.map_flatten_outputs(true));
21723 }
21724 return StrykeValue::array(out);
21725 }
21726 if first.is_string_like() {
21728 let mut out = String::new();
21729 for v in &chunks {
21730 out.push_str(&v.to_string());
21731 }
21732 return StrykeValue::string(out);
21733 }
21734 StrykeValue::array(chunks)
21736}
21737
21738fn perl_magic_str_inc(s: &str) -> Option<String> {
21741 if s.is_empty() {
21742 return Some("1".to_string());
21743 }
21744 let bytes = s.as_bytes();
21745 let mut i = 0;
21746 while i < bytes.len() && bytes[i].is_ascii_alphabetic() {
21747 i += 1;
21748 }
21749 let letters_end = i;
21750 while i < bytes.len() && bytes[i].is_ascii_digit() {
21751 i += 1;
21752 }
21753 if i != bytes.len() {
21754 return None;
21755 }
21756 if letters_end == 0 {
21757 return None;
21759 }
21760
21761 let mut result: Vec<u8> = bytes.to_vec();
21762 let mut carry = true;
21763 let mut idx = result.len();
21764
21765 while carry && idx > letters_end {
21767 idx -= 1;
21768 if result[idx] == b'9' {
21769 result[idx] = b'0';
21770 } else {
21772 result[idx] += 1;
21773 carry = false;
21774 }
21775 }
21776
21777 while carry && idx > 0 {
21779 idx -= 1;
21780 let c = result[idx];
21781 if c == b'z' {
21782 result[idx] = b'a';
21783 } else if c == b'Z' {
21784 result[idx] = b'A';
21785 } else {
21786 result[idx] += 1;
21787 carry = false;
21788 }
21789 }
21790
21791 if carry {
21793 let prepend = if bytes[0].is_ascii_uppercase() {
21794 b'A'
21795 } else {
21796 b'a'
21797 };
21798 let mut grown = Vec::with_capacity(result.len() + 1);
21799 grown.push(prepend);
21800 grown.extend_from_slice(&result);
21801 return String::from_utf8(grown).ok();
21802 }
21803
21804 String::from_utf8(result).ok()
21805}
21806
21807pub(crate) fn perl_inc(v: &StrykeValue) -> StrykeValue {
21811 if let Some(s) = v.as_str() {
21812 if let Some(new_s) = perl_magic_str_inc(&s) {
21813 return StrykeValue::string(new_s);
21814 }
21815 }
21816 StrykeValue::integer(v.to_int() + 1)
21817}
21818
21819fn perl_exponent_form(rust_repr: &str, upper: bool) -> String {
21820 let marker = if upper { 'E' } else { 'e' };
21821 if let Some(pos) = rust_repr.find(marker) {
21822 let (mantissa, after) = rust_repr.split_at(pos);
21823 let exp_part = &after[1..]; let (sign, digits) = match exp_part.chars().next() {
21825 Some('+') => ("+", &exp_part[1..]),
21826 Some('-') => ("-", &exp_part[1..]),
21827 _ => ("+", exp_part),
21828 };
21829 let padded = if digits.len() < 2 {
21830 format!("0{}", digits)
21831 } else {
21832 digits.to_string()
21833 };
21834 return format!("{}{}{}{}", mantissa, marker, sign, padded);
21835 }
21836 rust_repr.to_string()
21837}
21838
21839fn perl_hex_float(n: f64, upper: bool) -> String {
21843 if n.is_nan() {
21844 return if upper { "NAN" } else { "nan" }.to_string();
21845 }
21846 if n.is_infinite() {
21847 let sign = if n.is_sign_negative() { "-" } else { "" };
21848 let body = if upper { "INF" } else { "inf" };
21849 return format!("{}{}", sign, body);
21850 }
21851 let prefix = if upper { "0X" } else { "0x" };
21852 let p_letter = if upper { 'P' } else { 'p' };
21853 let bits = n.to_bits();
21854 let sign_bit = bits >> 63;
21855 let exp_bits = (bits >> 52) & 0x7FF;
21856 let mant_bits = bits & 0x000F_FFFF_FFFF_FFFF;
21857 let sign_str = if sign_bit == 1 { "-" } else { "" };
21858 if exp_bits == 0 && mant_bits == 0 {
21859 return format!("{}{}{}{}{}", sign_str, prefix, "0", p_letter, "+0");
21860 }
21861 let (lead_digit, exp_unbiased): (u64, i32) = if exp_bits == 0 {
21862 (0, -1022)
21864 } else {
21865 (1, (exp_bits as i32) - 1023)
21866 };
21867 let exp_sign = if exp_unbiased >= 0 { "+" } else { "-" };
21868 let exp_abs = exp_unbiased.unsigned_abs();
21869 if mant_bits == 0 {
21870 return format!(
21871 "{}{}{}{}{}{}",
21872 sign_str, prefix, lead_digit, p_letter, exp_sign, exp_abs
21873 );
21874 }
21875 let mant_hex = format!("{:013x}", mant_bits);
21877 let trimmed = mant_hex.trim_end_matches('0');
21878 let mant_str = if upper {
21879 trimmed.to_uppercase()
21880 } else {
21881 trimmed.to_string()
21882 };
21883 format!(
21884 "{}{}{}.{}{}{}{}",
21885 sign_str, prefix, lead_digit, mant_str, p_letter, exp_sign, exp_abs
21886 )
21887}
21888
21889fn perl_g_form(n: f64, prec: usize, upper: bool) -> String {
21892 let prec = prec.max(1);
21893 if !n.is_finite() {
21894 return if upper {
21895 format!("{}", n).to_uppercase()
21896 } else {
21897 format!("{}", n)
21898 };
21899 }
21900 let abs = n.abs();
21902 let x = if abs == 0.0 {
21903 0i32
21904 } else {
21905 abs.log10().floor() as i32
21906 };
21907 let use_e = x < -4 || x >= prec as i32;
21909 let formatted = if use_e {
21912 let raw = format!("{:.*e}", prec - 1, n);
21913 perl_exponent_form(&raw, false)
21914 } else {
21915 let f_prec = (prec as i32 - 1 - x).max(0) as usize;
21916 format!("{:.*}", f_prec, n)
21917 };
21918 let (mant, exp) = if let Some(pos) = formatted.find('e') {
21921 (formatted[..pos].to_string(), formatted[pos..].to_string())
21922 } else {
21923 (formatted.clone(), String::new())
21924 };
21925 let trimmed = if mant.contains('.') {
21926 let t = mant.trim_end_matches('0');
21927 let t = t.trim_end_matches('.');
21928 t.to_string()
21929 } else {
21930 mant
21931 };
21932 let combined = format!("{}{}", trimmed, exp);
21933 if upper {
21934 combined.replace('e', "E")
21935 } else {
21936 combined
21937 }
21938}
21939
21940pub(crate) fn perl_sprintf_format_full<F>(
21945 fmt: &str,
21946 args: &[StrykeValue],
21947 string_for_s: &mut F,
21948) -> Result<(String, Vec<(StrykeValue, i64)>), FlowOrError>
21949where
21950 F: FnMut(&StrykeValue) -> Result<String, FlowOrError>,
21951{
21952 let mut pending_n: Vec<(StrykeValue, i64)> = Vec::new();
21953 let mut result = String::new();
21954 let mut arg_idx = 0;
21955 let chars: Vec<char> = fmt.chars().collect();
21956 let mut i = 0;
21957
21958 let take_arg_int = |args: &[StrykeValue], idx: &mut usize| -> i64 {
21960 let v = args.get(*idx).cloned().unwrap_or(StrykeValue::UNDEF);
21961 *idx += 1;
21962 v.to_int()
21963 };
21964
21965 while i < chars.len() {
21966 if chars[i] == '%' {
21967 i += 1;
21968 if i >= chars.len() {
21969 break;
21970 }
21971 if chars[i] == '%' {
21972 result.push('%');
21973 i += 1;
21974 continue;
21975 }
21976
21977 let mut positional: Option<usize> = None;
21982 {
21983 let saved = i;
21984 let mut digits = String::new();
21985 let mut j = i;
21986 while j < chars.len() && chars[j].is_ascii_digit() {
21987 digits.push(chars[j]);
21988 j += 1;
21989 }
21990 if j < chars.len() && chars[j] == '$' && !digits.is_empty() {
21991 if let Ok(n) = digits.parse::<usize>() {
21992 if n >= 1 {
21993 positional = Some(n - 1);
21994 i = j + 1; }
21996 }
21997 }
21998 if positional.is_none() {
21999 i = saved;
22000 }
22001 }
22002
22003 let mut flags = String::new();
22005 while i < chars.len() && "-+ #0".contains(chars[i]) {
22006 flags.push(chars[i]);
22007 i += 1;
22008 }
22009 let mut vector_sep: Option<String> = None;
22013 if i < chars.len() && chars[i] == 'v' {
22014 vector_sep = Some(".".to_string());
22015 i += 1;
22016 } else if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == 'v' {
22017 let sep_arg = args.get(arg_idx).cloned().unwrap_or(StrykeValue::UNDEF);
22018 arg_idx += 1;
22019 vector_sep = Some(sep_arg.to_string());
22020 i += 2;
22021 }
22022 let mut width = String::new();
22024 let mut left_align = flags.contains('-');
22025 if i < chars.len() && chars[i] == '*' {
22026 let n = take_arg_int(args, &mut arg_idx);
22027 if n < 0 {
22028 left_align = true;
22030 width = (-n).to_string();
22031 } else {
22032 width = n.to_string();
22033 }
22034 i += 1;
22035 } else {
22036 while i < chars.len() && chars[i].is_ascii_digit() {
22037 width.push(chars[i]);
22038 i += 1;
22039 }
22040 }
22041 let mut precision = String::new();
22043 if i < chars.len() && chars[i] == '.' {
22044 i += 1;
22045 if i < chars.len() && chars[i] == '*' {
22046 let n = take_arg_int(args, &mut arg_idx);
22047 precision = n.max(0).to_string();
22048 i += 1;
22049 } else {
22050 while i < chars.len() && chars[i].is_ascii_digit() {
22051 precision.push(chars[i]);
22052 i += 1;
22053 }
22054 if precision.is_empty() {
22056 precision = "0".to_string();
22057 }
22058 }
22059 }
22060 if i >= chars.len() {
22061 break;
22062 }
22063 let spec = chars[i];
22064 i += 1;
22065
22066 let arg = if let Some(idx) = positional {
22071 args.get(idx).cloned().unwrap_or(StrykeValue::UNDEF)
22072 } else {
22073 let v = args.get(arg_idx).cloned().unwrap_or(StrykeValue::UNDEF);
22074 arg_idx += 1;
22075 v
22076 };
22077
22078 let w: usize = width.parse().unwrap_or(0);
22079 let p: usize = precision.parse().unwrap_or(6);
22080
22081 let zero_pad = flags.contains('0') && !left_align;
22082 let plus = flags.contains('+');
22083 let space = flags.contains(' ');
22084 let hash = flags.contains('#');
22085
22086 let pad_align = |body: &str, width: usize, left: bool, zero: bool| -> String {
22090 if width == 0 || body.len() >= width {
22091 return body.to_string();
22092 }
22093 if zero && !left {
22094 if let Some(rest) = body.strip_prefix('-') {
22095 return format!("-{:0>width$}", rest, width = width - 1);
22096 }
22097 if let Some(rest) = body.strip_prefix('+') {
22098 return format!("+{:0>width$}", rest, width = width - 1);
22099 }
22100 return format!("{:0>width$}", body, width = width);
22101 }
22102 if left {
22103 format!("{:<width$}", body, width = width)
22104 } else {
22105 format!("{:>width$}", body, width = width)
22106 }
22107 };
22108
22109 let format_int_for_vector = |n: i64, spec: char| -> String {
22113 match spec {
22114 'd' | 'i' => format!("{}", n),
22115 'u' => format!("{}", n as u64),
22116 'x' => {
22117 if hash && n != 0 {
22118 format!("0x{:x}", n)
22119 } else {
22120 format!("{:x}", n)
22121 }
22122 }
22123 'X' => {
22124 if hash && n != 0 {
22125 format!("0X{:X}", n)
22126 } else {
22127 format!("{:X}", n)
22128 }
22129 }
22130 'o' => {
22131 if hash && n != 0 {
22132 format!("0{:o}", n)
22133 } else {
22134 format!("{:o}", n)
22135 }
22136 }
22137 'b' => {
22138 if hash && n != 0 {
22139 format!("0b{:b}", n)
22140 } else {
22141 format!("{:b}", n)
22142 }
22143 }
22144 'c' => char::from_u32(n as u32)
22145 .map(|c| c.to_string())
22146 .unwrap_or_default(),
22147 _ => format!("{}", n),
22148 }
22149 };
22150
22151 if let Some(ref sep) = vector_sep {
22155 let s = arg.to_string();
22156 let parts: Vec<String> = s
22157 .bytes()
22158 .map(|b| format_int_for_vector(b as i64, spec))
22159 .collect();
22160 let body = parts.join(sep);
22161 let final_body = if width.is_empty() {
22162 body
22163 } else if left_align {
22164 format!("{:<width$}", body, width = w)
22165 } else {
22166 format!("{:>width$}", body, width = w)
22167 };
22168 result.push_str(&final_body);
22169 continue;
22170 }
22171
22172 let formatted = match spec {
22173 'd' | 'i' => {
22174 let v = arg.to_int();
22175 let body = if plus && v >= 0 {
22176 format!("+{}", v)
22177 } else if space && v >= 0 {
22178 format!(" {}", v)
22179 } else {
22180 format!("{}", v)
22181 };
22182 pad_align(&body, w, left_align, zero_pad)
22183 }
22184 'u' => {
22185 let v = arg.to_int() as u64;
22186 pad_align(&format!("{}", v), w, left_align, zero_pad)
22187 }
22188 'f' => {
22189 let n = arg.to_number();
22190 let body = if plus && n.is_sign_positive() {
22191 format!("+{:.*}", p, n)
22192 } else if space && n.is_sign_positive() {
22193 format!(" {:.*}", p, n)
22194 } else {
22195 format!("{:.*}", p, n)
22196 };
22197 pad_align(&body, w, left_align, zero_pad)
22198 }
22199 'e' => {
22200 let n = arg.to_number();
22201 let raw = format!("{:.*e}", p, n);
22202 let body0 = perl_exponent_form(&raw, false);
22203 let body = if plus && n.is_sign_positive() {
22204 format!("+{}", body0)
22205 } else if space && n.is_sign_positive() {
22206 format!(" {}", body0)
22207 } else {
22208 body0
22209 };
22210 pad_align(&body, w, left_align, zero_pad)
22211 }
22212 'E' => {
22213 let n = arg.to_number();
22214 let raw = format!("{:.*E}", p, n);
22215 let body0 = perl_exponent_form(&raw, true);
22216 let body = if plus && n.is_sign_positive() {
22217 format!("+{}", body0)
22218 } else if space && n.is_sign_positive() {
22219 format!(" {}", body0)
22220 } else {
22221 body0
22222 };
22223 pad_align(&body, w, left_align, zero_pad)
22224 }
22225 'g' => {
22226 let n = arg.to_number();
22227 let prec_g = if precision.is_empty() { 6 } else { p };
22229 let body0 = perl_g_form(n, prec_g, false);
22230 let body = if plus && n.is_sign_positive() {
22231 format!("+{}", body0)
22232 } else if space && n.is_sign_positive() {
22233 format!(" {}", body0)
22234 } else {
22235 body0
22236 };
22237 pad_align(&body, w, left_align, zero_pad)
22238 }
22239 'G' => {
22240 let n = arg.to_number();
22241 let prec_g = if precision.is_empty() { 6 } else { p };
22242 let body0 = perl_g_form(n, prec_g, true);
22243 let body = if plus && n.is_sign_positive() {
22244 format!("+{}", body0)
22245 } else if space && n.is_sign_positive() {
22246 format!(" {}", body0)
22247 } else {
22248 body0
22249 };
22250 pad_align(&body, w, left_align, zero_pad)
22251 }
22252 's' => {
22253 let s = string_for_s(&arg)?;
22254 let body = if !precision.is_empty() {
22255 s.chars().take(p).collect::<String>()
22256 } else {
22257 s
22258 };
22259 if left_align {
22260 format!("{:<width$}", body, width = w)
22261 } else {
22262 format!("{:>width$}", body, width = w)
22263 }
22264 }
22265 'x' => {
22266 let v = arg.to_int();
22267 let body = if hash && v != 0 {
22268 format!("0x{:x}", v)
22269 } else {
22270 format!("{:x}", v)
22271 };
22272 pad_align(&body, w, left_align, zero_pad)
22273 }
22274 'X' => {
22275 let v = arg.to_int();
22276 let body = if hash && v != 0 {
22277 format!("0X{:X}", v)
22278 } else {
22279 format!("{:X}", v)
22280 };
22281 pad_align(&body, w, left_align, zero_pad)
22282 }
22283 'o' => {
22284 let v = arg.to_int();
22285 let body = if hash && v != 0 {
22286 format!("0{:o}", v)
22287 } else {
22288 format!("{:o}", v)
22289 };
22290 pad_align(&body, w, left_align, zero_pad)
22291 }
22292 'b' => {
22293 let v = arg.to_int();
22294 let body = if hash && v != 0 {
22295 format!("0b{:b}", v)
22296 } else {
22297 format!("{:b}", v)
22298 };
22299 pad_align(&body, w, left_align, zero_pad)
22300 }
22301 'c' => char::from_u32(arg.to_int() as u32)
22302 .map(|c| c.to_string())
22303 .unwrap_or_default(),
22304 'a' | 'A' => {
22305 let upper = spec == 'A';
22306 let body0 = perl_hex_float(arg.to_number(), upper);
22307 let body = if plus && !body0.starts_with('-') {
22308 format!("+{}", body0)
22309 } else if space && !body0.starts_with('-') {
22310 format!(" {}", body0)
22311 } else {
22312 body0
22313 };
22314 pad_align(&body, w, left_align, zero_pad)
22315 }
22316 'p' => {
22317 pad_align("0x...", w, left_align, false)
22321 }
22322 'n' => {
22323 pending_n.push((arg.clone(), result.len() as i64));
22331 String::new()
22332 }
22333 _ => arg.to_string(),
22334 };
22335
22336 result.push_str(&formatted);
22337 } else {
22338 result.push(chars[i]);
22339 i += 1;
22340 }
22341 }
22342 Ok((result, pending_n))
22343}
22344
22345#[cfg(test)]
22346mod regex_expand_tests {
22347 use super::VMHelper;
22348
22349 #[test]
22350 fn compile_regex_quotemeta_qe_matches_literal() {
22351 let mut i = VMHelper::new();
22352 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
22353 assert!(re.is_match("a.c"));
22354 assert!(!re.is_match("abc"));
22355 }
22356
22357 #[test]
22360 fn compile_regex_char_class_leading_close_bracket_is_literal() {
22361 let mut i = VMHelper::new();
22362 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
22363 assert!(re.is_match("$"));
22364 assert!(re.is_match("]"));
22365 assert!(!re.is_match("x"));
22366 }
22367}
22368
22369#[cfg(test)]
22370mod special_scalar_name_tests {
22371 use super::VMHelper;
22372
22373 #[test]
22374 fn special_scalar_name_for_get_matches_magic_globals() {
22375 assert!(VMHelper::is_special_scalar_name_for_get("0"));
22376 assert!(VMHelper::is_special_scalar_name_for_get("!"));
22377 assert!(VMHelper::is_special_scalar_name_for_get("^W"));
22378 assert!(VMHelper::is_special_scalar_name_for_get("^O"));
22379 assert!(VMHelper::is_special_scalar_name_for_get("^MATCH"));
22380 assert!(VMHelper::is_special_scalar_name_for_get("<"));
22381 assert!(VMHelper::is_special_scalar_name_for_get("?"));
22382 assert!(VMHelper::is_special_scalar_name_for_get("|"));
22383 assert!(VMHelper::is_special_scalar_name_for_get("^UNICODE"));
22384 assert!(VMHelper::is_special_scalar_name_for_get("\""));
22385 assert!(!VMHelper::is_special_scalar_name_for_get("foo"));
22386 assert!(!VMHelper::is_special_scalar_name_for_get("plainvar"));
22387 }
22388
22389 #[test]
22390 fn special_scalar_name_for_set_matches_set_special_var_arms() {
22391 assert!(VMHelper::is_special_scalar_name_for_set("0"));
22392 assert!(VMHelper::is_special_scalar_name_for_set("^D"));
22393 assert!(VMHelper::is_special_scalar_name_for_set("^H"));
22394 assert!(VMHelper::is_special_scalar_name_for_set("^WARNING_BITS"));
22395 assert!(VMHelper::is_special_scalar_name_for_set("ARGV"));
22396 assert!(VMHelper::is_special_scalar_name_for_set("|"));
22397 assert!(VMHelper::is_special_scalar_name_for_set("?"));
22398 assert!(VMHelper::is_special_scalar_name_for_set("^UNICODE"));
22399 assert!(VMHelper::is_special_scalar_name_for_set("."));
22400 assert!(!VMHelper::is_special_scalar_name_for_set("foo"));
22401 assert!(!VMHelper::is_special_scalar_name_for_set("__PACKAGE__"));
22402 }
22403
22404 #[test]
22405 fn caret_and_id_specials_roundtrip_get() {
22406 let i = VMHelper::new();
22407 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
22408 assert_eq!(
22409 i.get_special_var("^V").to_string(),
22410 format!("v{}", env!("CARGO_PKG_VERSION"))
22411 );
22412 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
22413 assert!(i.get_special_var("^T").to_int() >= 0);
22414 #[cfg(unix)]
22415 {
22416 assert!(i.get_special_var("<").to_int() >= 0);
22417 }
22418 }
22419
22420 #[test]
22421 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
22422 let mut i = VMHelper::new();
22423 i.last_readline_handle.clear();
22424 i.line_number = 3;
22425 i.prepare_flip_flop_vm_slots(1);
22426 assert_eq!(
22427 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
22428 1
22429 );
22430 assert!(i.flip_flop_active[0]);
22431 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
22432 assert_eq!(
22434 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
22435 1
22436 );
22437 assert!(i.flip_flop_active[0]);
22438 }
22439
22440 #[test]
22441 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
22442 let mut i = VMHelper::new();
22443 i.last_readline_handle.clear();
22444 i.line_number = 2;
22445 i.prepare_flip_flop_vm_slots(1);
22446 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
22447 assert!(i.flip_flop_active[0]);
22448 i.line_number = 3;
22449 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
22450 assert!(!i.flip_flop_active[0]);
22451 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
22452 }
22453}