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,
579 pub(crate) subs: HashMap<String, Arc<StrykeSub>>,
580 pub(crate) intercepts: Vec<crate::aop::Intercept>,
582 pub(crate) next_intercept_id: u32,
584 pub(crate) intercept_ctx_stack: Vec<crate::aop::InterceptCtx>,
586 pub(crate) intercept_active_names: Vec<String>,
590 pub(crate) file: String,
591 pub(crate) output_handles: HashMap<String, Box<dyn IoWrite + Send>>,
593 pub(crate) input_handles: HashMap<String, BufReader<Box<dyn Read + Send>>>,
594 pub ofs: String,
596 pub ors: String,
598 pub irs: Option<String>,
601 pub errno: String,
603 pub errno_code: i32,
605 pub eval_error: String,
607 pub eval_error_code: i32,
609 pub eval_error_value: Option<StrykeValue>,
611 pub argv: Vec<String>,
613 pub env: IndexMap<String, StrykeValue>,
615 pub env_materialized: bool,
617 pub program_name: String,
619 pub line_number: i64,
623 pub nr: i64,
626 pub last_readline_handle: String,
628 pub(crate) last_stdin_die_bracket: String,
630 pub handle_line_numbers: HashMap<String, i64>,
632 pub(crate) flip_flop_active: Vec<bool>,
636 pub(crate) flip_flop_exclusive_left_line: Vec<Option<i64>>,
639 pub(crate) flip_flop_sequence: Vec<i64>,
643 pub(crate) flip_flop_last_dot: Vec<Option<i64>>,
647 flip_flop_tree: HashMap<usize, FlipFlopTreeState>,
649 pub sigint_pending_caret: Cell<bool>,
651 pub auto_split: bool,
653 pub field_separator: Option<String>,
655 begin_blocks: Vec<Block>,
657 unit_check_blocks: Vec<Block>,
659 check_blocks: Vec<Block>,
661 init_blocks: Vec<Block>,
663 end_blocks: Vec<Block>,
665 pub warnings: bool,
667 pub output_autoflush: bool,
669 pub default_print_handle: String,
671 pub suppress_stdout: bool,
673 pub test_pass_count: std::sync::atomic::AtomicUsize,
680 pub test_fail_count: std::sync::atomic::AtomicUsize,
682 pub test_skip_count: std::sync::atomic::AtomicUsize,
684 pub test_pass_total: std::sync::atomic::AtomicUsize,
694 pub test_fail_total: std::sync::atomic::AtomicUsize,
696 pub test_skip_total: std::sync::atomic::AtomicUsize,
698 pub test_run_failed: std::sync::atomic::AtomicBool,
704 pub child_exit_status: i64,
706 pub last_match: String,
708 pub prematch: String,
710 pub postmatch: String,
712 pub last_paren_match: String,
714 pub list_separator: String,
716 pub script_start_time: i64,
718 pub compile_hints: i64,
720 pub warning_bits: i64,
722 pub global_phase: String,
724 pub subscript_sep: String,
726 pub inplace_edit: String,
729 pub debug_flags: i64,
731 pub perl_debug_flags: i64,
733 pub eval_nesting: u32,
735 pub argv_current_file: String,
737 pub(crate) diamond_next_idx: usize,
739 pub(crate) diamond_reader: Option<BufReader<File>>,
741 pub strict_refs: bool,
743 pub strict_subs: bool,
745 pub strict_vars: bool,
747 pub utf8_pragma: bool,
749 pub open_pragma_utf8: bool,
751 pub feature_bits: u64,
753 pub num_threads: usize,
755 regex_cache: HashMap<String, Arc<PerlCompiledRegex>>,
757 regex_last: Option<(String, String, bool, Arc<PerlCompiledRegex>)>,
760 regex_match_memo: Option<RegexMatchMemo>,
769 regex_capture_scope_fresh: bool,
773 pub(crate) regex_pos: HashMap<String, Option<usize>>,
775 pub(crate) state_vars: HashMap<String, StrykeValue>,
777 pub(crate) state_bindings_stack: Vec<Vec<(String, String)>>,
779 pub(crate) rand_rng: StdRng,
781 pub(crate) dir_handles: HashMap<String, DirHandleState>,
783 pub(crate) io_file_slots: HashMap<String, Arc<Mutex<File>>>,
785 pub(crate) pipe_children: HashMap<String, Child>,
787 pub(crate) socket_handles: HashMap<String, StrykeSocket>,
789 pub(crate) wantarray_kind: WantarrayCtx,
791 pub struct_defs: HashMap<String, Arc<StructDef>>,
793 pub enum_defs: HashMap<String, Arc<EnumDef>>,
795 pub class_defs: HashMap<String, Arc<ClassDef>>,
797 pub trait_defs: HashMap<String, Arc<TraitDef>>,
799 pub profiler: Option<Profiler>,
802 pub(crate) module_export_lists: HashMap<String, ModuleExportLists>,
804 pub(crate) virtual_modules: HashMap<String, String>,
806 pub(crate) tied_hashes: HashMap<String, StrykeValue>,
808 pub(crate) tied_scalars: HashMap<String, StrykeValue>,
810 pub(crate) tied_arrays: HashMap<String, StrykeValue>,
812 pub(crate) overload_table: HashMap<String, HashMap<String, String>>,
814 pub(crate) format_templates: HashMap<String, Arc<crate::format::FormatTemplate>>,
816 pub(crate) special_caret_scalars: HashMap<String, StrykeValue>,
818 pub format_page_number: i64,
820 pub format_lines_per_page: i64,
822 pub format_lines_left: i64,
824 pub format_line_break_chars: String,
826 pub format_top_name: String,
828 pub accumulator_format: String,
830 pub max_system_fd: i64,
832 pub emergency_memory: String,
834 pub last_subpattern_name: String,
836 pub inc_hook_index: i64,
838 pub multiline_match: bool,
840 pub executable_path: String,
842 pub formfeed_string: String,
844 pub(crate) glob_handle_alias: HashMap<String, String>,
846 glob_restore_frames: Vec<Vec<(String, Option<String>)>>,
848 pub(crate) special_var_restore_frames: Vec<Vec<(String, StrykeValue)>>,
853 pub(crate) reflection_hashes_ready: bool,
857 pub(crate) english_enabled: bool,
858 pub(crate) english_no_match_vars: bool,
860 pub(crate) english_match_vars_ever_enabled: bool,
864 english_lexical_scalars: Vec<HashSet<String>>,
866 our_lexical_scalars: Vec<HashSet<String>>,
868 our_lexical_arrays: Vec<HashSet<String>>,
872 our_lexical_hashes: Vec<HashSet<String>>,
875 pub vm_jit_enabled: bool,
878 pub disasm_bytecode: bool,
880 pub cached_chunk: Option<crate::bytecode::Chunk>,
884 pub cache_script_path: Option<std::path::PathBuf>,
886 pub(crate) stryke_pwd: PathBuf,
888 pub(crate) in_generator: bool,
890 pub line_mode_skip_main: bool,
892 pub line_mode_chunk: Option<crate::bytecode::Chunk>,
895 pub(crate) line_mode_eof_pending: bool,
899 pub line_mode_stdin_pending: VecDeque<String>,
902 pub(crate) rate_limit_slots: Vec<VecDeque<Instant>>,
904 pub(crate) log_level_override: Option<LogLevelFilter>,
906 pub(crate) current_sub_stack: Vec<Arc<StrykeSub>>,
909 pub debugger: Option<crate::debugger::Debugger>,
911 pub(crate) debug_call_stack: Vec<(String, usize)>,
913}
914
915#[derive(Debug, Clone, Default)]
917pub struct ReplCompletionSnapshot {
918 pub subs: Vec<String>,
920 pub blessed_scalars: HashMap<String, String>,
922 pub isa_for_class: HashMap<String, Vec<String>>,
924}
925
926impl ReplCompletionSnapshot {
927 pub fn methods_for_class(&self, class: &str) -> Vec<String> {
929 let parents = |c: &str| self.isa_for_class.get(c).cloned().unwrap_or_default();
930 let mro = linearize_c3(class, &parents, 0);
931 let mut names = HashSet::new();
932 for pkg in &mro {
933 if pkg == "UNIVERSAL" {
934 continue;
935 }
936 let prefix = format!("{}::", pkg);
937 for k in &self.subs {
938 if k.starts_with(&prefix) {
939 let rest = &k[prefix.len()..];
940 if !rest.contains("::") {
941 names.insert(rest.to_string());
942 }
943 }
944 }
945 }
946 for k in &self.subs {
947 if let Some(rest) = k.strip_prefix("UNIVERSAL::") {
948 if !rest.contains("::") {
949 names.insert(rest.to_string());
950 }
951 }
952 }
953 let mut v: Vec<String> = names.into_iter().collect();
954 v.sort();
955 v
956 }
957}
958
959fn repl_resolve_class_for_arrow(state: &ReplCompletionSnapshot, left: &str) -> Option<String> {
960 let left = left.trim_end();
961 if left.is_empty() {
962 return None;
963 }
964 if let Some(i) = left.rfind('$') {
965 let name = left[i + 1..].trim();
966 if name.chars().all(|c| c.is_alphanumeric() || c == '_') && !name.is_empty() {
967 return state.blessed_scalars.get(name).cloned();
968 }
969 }
970 let tok = left.split_whitespace().last()?;
971 if tok.contains("::") {
972 return Some(tok.to_string());
973 }
974 if tok.chars().all(|c| c.is_alphanumeric() || c == '_') && !tok.starts_with('$') {
975 return Some(tok.to_string());
976 }
977 None
978}
979
980pub fn repl_arrow_method_completions(
982 state: &ReplCompletionSnapshot,
983 line: &str,
984 pos: usize,
985) -> Option<(usize, Vec<String>)> {
986 let pos = pos.min(line.len());
987 let before = &line[..pos];
988 let arrow_idx = before.rfind("->")?;
989 let after_arrow = &before[arrow_idx + 2..];
990 let rest = after_arrow.trim_start();
991 let ws_len = after_arrow.len() - rest.len();
992 let method_start = arrow_idx + 2 + ws_len;
993 let method_prefix = &line[method_start..pos];
994 if !method_prefix
995 .chars()
996 .all(|c| c.is_alphanumeric() || c == '_')
997 {
998 return None;
999 }
1000 let left = line[..arrow_idx].trim_end();
1001 let class = repl_resolve_class_for_arrow(state, left)?;
1002 let mut methods = state.methods_for_class(&class);
1003 methods.retain(|m| m.starts_with(method_prefix));
1004 Some((method_start, methods))
1005}
1006
1007#[derive(Debug, Clone, Default)]
1009pub(crate) struct ModuleExportLists {
1010 pub export: Vec<String>,
1012 pub export_ok: Vec<String>,
1014}
1015
1016fn piped_shell_command(cmd: &str) -> Command {
1018 if cfg!(windows) {
1019 let mut c = Command::new("cmd");
1020 c.arg("/C").arg(cmd);
1021 c
1022 } else {
1023 let mut c = Command::new("sh");
1024 c.arg("-c").arg(cmd);
1025 c
1026 }
1027}
1028
1029fn expand_perl_regex_octal_escapes(pat: &str) -> String {
1036 let mut out = String::with_capacity(pat.len());
1037 let mut it = pat.chars().peekable();
1038 while let Some(c) = it.next() {
1039 if c == '\\' {
1040 if let Some(&'0') = it.peek() {
1041 let mut oct = String::new();
1043 while oct.len() < 3 {
1044 if let Some(&d) = it.peek() {
1045 if ('0'..='7').contains(&d) {
1046 oct.push(d);
1047 it.next();
1048 } else {
1049 break;
1050 }
1051 } else {
1052 break;
1053 }
1054 }
1055 if let Ok(val) = u8::from_str_radix(&oct, 8) {
1056 out.push_str(&format!("\\x{:02x}", val));
1057 } else {
1058 out.push('\\');
1059 out.push_str(&oct);
1060 }
1061 continue;
1062 }
1063 }
1064 out.push(c);
1065 }
1066 out
1067}
1068
1069fn expand_perl_regex_quotemeta(pat: &str) -> String {
1070 let mut out = String::with_capacity(pat.len().saturating_mul(2));
1071 let mut it = pat.chars().peekable();
1072 let mut in_q = false;
1073 while let Some(c) = it.next() {
1074 if in_q {
1075 if c == '\\' && it.peek() == Some(&'E') {
1076 it.next();
1077 in_q = false;
1078 continue;
1079 }
1080 out.push_str(&perl_quotemeta(&c.to_string()));
1081 continue;
1082 }
1083 if c == '\\' && it.peek() == Some(&'Q') {
1084 it.next();
1085 in_q = true;
1086 continue;
1087 }
1088 out.push(c);
1089 }
1090 out
1091}
1092
1093pub(crate) fn normalize_replacement_backrefs(replacement: &str) -> String {
1099 let mut out = String::with_capacity(replacement.len() + 8);
1100 let mut it = replacement.chars().peekable();
1101 while let Some(c) = it.next() {
1102 if c == '\\' {
1103 match it.peek() {
1104 Some(&d) if d.is_ascii_digit() => {
1105 it.next();
1106 out.push_str("${");
1107 out.push(d);
1108 while let Some(&d2) = it.peek() {
1109 if !d2.is_ascii_digit() {
1110 break;
1111 }
1112 it.next();
1113 out.push(d2);
1114 }
1115 out.push('}');
1116 }
1117 Some(&'\\') => {
1118 it.next();
1119 out.push('\\');
1120 }
1121 _ => out.push('\\'),
1122 }
1123 } else if c == '$' {
1124 match it.peek() {
1125 Some(&d) if d.is_ascii_digit() => {
1126 it.next();
1127 out.push_str("${");
1128 out.push(d);
1129 while let Some(&d2) = it.peek() {
1130 if !d2.is_ascii_digit() {
1131 break;
1132 }
1133 it.next();
1134 out.push(d2);
1135 }
1136 out.push('}');
1137 }
1138 Some(&'{') => {
1139 out.push('$');
1141 }
1142 _ => out.push('$'),
1143 }
1144 } else {
1145 out.push(c);
1146 }
1147 }
1148 out
1149}
1150
1151fn copy_regex_char_class(chars: &[char], mut i: usize, out: &mut String) -> usize {
1154 debug_assert_eq!(chars.get(i), Some(&'['));
1155 out.push('[');
1156 i += 1;
1157 if i < chars.len() && chars[i] == '^' {
1158 out.push('^');
1159 i += 1;
1160 }
1161 if i >= chars.len() {
1162 return i;
1163 }
1164 if chars[i] == ']' {
1168 if i + 1 < chars.len() && chars[i + 1] == ']' {
1169 out.push(']');
1171 i += 1;
1172 } else {
1173 let mut scan = i + 1;
1174 let mut found_closing = false;
1175 while scan < chars.len() {
1176 if chars[scan] == '\\' && scan + 1 < chars.len() {
1177 scan += 2;
1178 continue;
1179 }
1180 if chars[scan] == ']' {
1181 found_closing = true;
1182 break;
1183 }
1184 scan += 1;
1185 }
1186 if found_closing {
1187 out.push(']');
1188 i += 1;
1189 } else {
1190 out.push(']');
1191 return i + 1;
1192 }
1193 }
1194 }
1195 while i < chars.len() && chars[i] != ']' {
1196 if chars[i] == '\\' && i + 1 < chars.len() {
1197 out.push(chars[i]);
1198 out.push(chars[i + 1]);
1199 i += 2;
1200 continue;
1201 }
1202 out.push(chars[i]);
1203 i += 1;
1204 }
1205 if i < chars.len() {
1206 out.push(']');
1207 i += 1;
1208 }
1209 i
1210}
1211
1212fn rewrite_perl_regex_dollar_end_anchor(pat: &str, multiline_flag: bool) -> String {
1217 if multiline_flag {
1218 return pat.to_string();
1219 }
1220 let chars: Vec<char> = pat.chars().collect();
1221 let mut out = String::with_capacity(pat.len().saturating_add(16));
1222 let mut i = 0usize;
1223 while i < chars.len() {
1224 let c = chars[i];
1225 if c == '\\' && i + 1 < chars.len() {
1226 out.push(c);
1227 out.push(chars[i + 1]);
1228 i += 2;
1229 continue;
1230 }
1231 if c == '[' {
1232 i = copy_regex_char_class(&chars, i, &mut out);
1233 continue;
1234 }
1235 if c == '$' {
1236 if let Some(&next) = chars.get(i + 1) {
1237 if next.is_ascii_digit() {
1238 out.push(c);
1239 i += 1;
1240 continue;
1241 }
1242 if next == '{' {
1243 out.push(c);
1244 i += 1;
1245 continue;
1246 }
1247 if next.is_ascii_alphanumeric() || next == '_' {
1248 out.push(c);
1249 i += 1;
1250 continue;
1251 }
1252 }
1253 out.push_str("(?=\\n?\\z)");
1254 i += 1;
1255 continue;
1256 }
1257 out.push(c);
1258 i += 1;
1259 }
1260 out
1261}
1262
1263#[derive(Debug, Clone)]
1265pub(crate) struct DirHandleState {
1266 pub entries: Vec<String>,
1267 pub pos: usize,
1268}
1269
1270pub(crate) fn perl_osname() -> String {
1272 match std::env::consts::OS {
1273 "linux" => "linux".to_string(),
1274 "macos" => "darwin".to_string(),
1275 "windows" => "MSWin32".to_string(),
1276 other => other.to_string(),
1277 }
1278}
1279
1280fn perl_version_v_string() -> String {
1281 format!("v{}", env!("CARGO_PKG_VERSION"))
1282}
1283
1284fn extended_os_error_string() -> String {
1285 std::io::Error::last_os_error().to_string()
1286}
1287
1288#[cfg(unix)]
1289fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1290 unsafe {
1291 (
1292 libc::getuid() as i64,
1293 libc::geteuid() as i64,
1294 libc::getgid() as i64,
1295 libc::getegid() as i64,
1296 )
1297 }
1298}
1299
1300#[cfg(not(unix))]
1301fn unix_real_effective_ids() -> (i64, i64, i64, i64) {
1302 (0, 0, 0, 0)
1303}
1304
1305fn unix_id_for_special(name: &str) -> i64 {
1306 let (r, e, _, _) = unix_real_effective_ids();
1307 match name {
1308 "<" => r,
1309 ">" => e,
1310 _ => 0,
1311 }
1312}
1313
1314#[cfg(unix)]
1315fn unix_group_list_string(primary: libc::gid_t) -> String {
1316 let mut buf = vec![0 as libc::gid_t; 256];
1317 let n = unsafe { libc::getgroups(256, buf.as_mut_ptr()) };
1318 if n <= 0 {
1319 return format!("{}", primary);
1320 }
1321 let mut parts = vec![format!("{}", primary)];
1322 for g in buf.iter().take(n as usize) {
1323 parts.push(format!("{}", g));
1324 }
1325 parts.join(" ")
1326}
1327
1328#[cfg(unix)]
1330fn unix_group_list_for_special(name: &str) -> String {
1331 let (_, _, gid, egid) = unix_real_effective_ids();
1332 match name {
1333 "(" => unix_group_list_string(gid as libc::gid_t),
1334 ")" => unix_group_list_string(egid as libc::gid_t),
1335 _ => String::new(),
1336 }
1337}
1338
1339#[cfg(not(unix))]
1340fn unix_group_list_for_special(_name: &str) -> String {
1341 String::new()
1342}
1343
1344#[cfg(unix)]
1347fn pw_home_dir_for_current_uid() -> Option<std::ffi::OsString> {
1348 use libc::{getpwuid_r, getuid};
1349 use std::ffi::CStr;
1350 use std::os::unix::ffi::OsStringExt;
1351 let uid = unsafe { getuid() };
1352 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1353 let mut result: *mut libc::passwd = std::ptr::null_mut();
1354 let mut buf = vec![0u8; 16_384];
1355 let rc = unsafe {
1356 getpwuid_r(
1357 uid,
1358 &mut pw,
1359 buf.as_mut_ptr().cast::<libc::c_char>(),
1360 buf.len(),
1361 &mut result,
1362 )
1363 };
1364 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1365 return None;
1366 }
1367 let bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1368 if bytes.is_empty() {
1369 return None;
1370 }
1371 Some(std::ffi::OsString::from_vec(bytes.to_vec()))
1372}
1373
1374#[cfg(unix)]
1376fn pw_home_dir_for_login_name(login: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
1377 use libc::getpwnam_r;
1378 use std::ffi::{CStr, CString};
1379 use std::os::unix::ffi::{OsStrExt, OsStringExt};
1380 let bytes = login.as_bytes();
1381 if bytes.is_empty() || bytes.contains(&0) {
1382 return None;
1383 }
1384 let cname = CString::new(bytes).ok()?;
1385 let mut pw: libc::passwd = unsafe { std::mem::zeroed() };
1386 let mut result: *mut libc::passwd = std::ptr::null_mut();
1387 let mut buf = vec![0u8; 16_384];
1388 let rc = unsafe {
1389 getpwnam_r(
1390 cname.as_ptr(),
1391 &mut pw,
1392 buf.as_mut_ptr().cast::<libc::c_char>(),
1393 buf.len(),
1394 &mut result,
1395 )
1396 };
1397 if rc != 0 || result.is_null() || pw.pw_dir.is_null() {
1398 return None;
1399 }
1400 let dir_bytes = unsafe { CStr::from_ptr(pw.pw_dir).to_bytes() };
1401 if dir_bytes.is_empty() {
1402 return None;
1403 }
1404 Some(std::ffi::OsString::from_vec(dir_bytes.to_vec()))
1405}
1406
1407impl Default for VMHelper {
1408 fn default() -> Self {
1409 Self::new()
1410 }
1411}
1412
1413#[derive(Clone, Copy)]
1415pub(crate) enum CaptureAllMode {
1416 Empty,
1418 Append,
1420 Skip,
1422}
1423
1424pub(crate) enum LockfileResolution {
1438 Found(std::path::PathBuf),
1439 NotFound,
1440 PinUnsatisfied(String),
1441}
1442
1443impl VMHelper {
1444 pub fn new() -> Self {
1446 let mut scope = Scope::new();
1447 scope.declare_array("INC", vec![StrykeValue::string(".".to_string())]);
1448 scope.declare_hash("INC", IndexMap::new());
1449 scope.declare_array("ARGV", vec![]);
1450 scope.declare_array("_", vec![]);
1451
1452 let path_vec: Vec<StrykeValue> = std::env::var("PATH")
1454 .unwrap_or_default()
1455 .split(if cfg!(windows) { ';' } else { ':' })
1456 .filter(|s| !s.is_empty())
1457 .map(|p| StrykeValue::string(p.to_string()))
1458 .collect();
1459 scope.declare_array_frozen("path", path_vec.clone(), true);
1460 scope.declare_array_frozen("p", path_vec, true);
1461
1462 let fpath_vec: Vec<StrykeValue> = std::env::var("FPATH")
1464 .unwrap_or_default()
1465 .split(':')
1466 .filter(|s| !s.is_empty())
1467 .map(|p| StrykeValue::string(p.to_string()))
1468 .collect();
1469 scope.declare_array_frozen("fpath", fpath_vec.clone(), true);
1470 scope.declare_array_frozen("f", fpath_vec, true);
1471 scope.declare_hash("ENV", IndexMap::new());
1472 scope.declare_hash("SIG", IndexMap::new());
1473
1474 let term_map = build_term_hash();
1476 scope.declare_hash_global_frozen("term", term_map);
1477
1478 #[cfg(unix)]
1480 {
1481 let uname_map = build_uname_hash();
1482 scope.declare_hash_global_frozen("uname", uname_map);
1483 }
1484 #[cfg(not(unix))]
1485 {
1486 scope.declare_hash_global_frozen("uname", IndexMap::new());
1487 }
1488
1489 #[cfg(unix)]
1491 {
1492 let limits_map = build_limits_hash();
1493 scope.declare_hash_global_frozen("limits", limits_map);
1494 }
1495 #[cfg(not(unix))]
1496 {
1497 scope.declare_hash_global_frozen("limits", IndexMap::new());
1498 }
1499
1500 scope.declare_scalar(
1523 "stryke::VERSION",
1524 StrykeValue::string(env!("CARGO_PKG_VERSION").to_string()),
1525 );
1526 scope.declare_array("-", vec![]);
1527 scope.declare_array("+", vec![]);
1528 scope.declare_array("^CAPTURE", vec![]);
1529 scope.declare_array("^CAPTURE_ALL", vec![]);
1530 scope.declare_hash("^HOOK", IndexMap::new());
1531 scope.declare_scalar("~", StrykeValue::string("STDOUT".to_string()));
1532
1533 let script_start_time = std::time::SystemTime::now()
1534 .duration_since(std::time::UNIX_EPOCH)
1535 .map(|d| d.as_secs() as i64)
1536 .unwrap_or(0);
1537
1538 let executable_path = cached_executable_path();
1539
1540 let stryke_pwd_init = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1541 let stryke_pwd = std::fs::canonicalize(&stryke_pwd_init).unwrap_or(stryke_pwd_init);
1542
1543 let mut special_caret_scalars: HashMap<String, StrykeValue> = HashMap::new();
1544 for name in crate::special_vars::PERL5_DOCUMENTED_CARET_NAMES {
1545 special_caret_scalars.insert(format!("^{}", name), StrykeValue::UNDEF);
1546 }
1547
1548 let mut s = Self {
1549 scope,
1550 subs: HashMap::new(),
1551 intercepts: Vec::new(),
1552 next_intercept_id: 1,
1553 intercept_ctx_stack: Vec::new(),
1554 intercept_active_names: Vec::new(),
1555 struct_defs: HashMap::new(),
1556 enum_defs: HashMap::new(),
1557 class_defs: HashMap::new(),
1558 trait_defs: HashMap::new(),
1559 file: "-e".to_string(),
1560 output_handles: HashMap::new(),
1561 input_handles: HashMap::new(),
1562 ofs: String::new(),
1563 ors: String::new(),
1564 irs: Some("\n".to_string()),
1565 errno: String::new(),
1566 errno_code: 0,
1567 eval_error: String::new(),
1568 eval_error_code: 0,
1569 eval_error_value: None,
1570 argv: Vec::new(),
1571 env: IndexMap::new(),
1572 env_materialized: false,
1573 program_name: "stryke".to_string(),
1574 line_number: 0,
1575 nr: 0,
1576 last_readline_handle: String::new(),
1577 last_stdin_die_bracket: "<STDIN>".to_string(),
1578 handle_line_numbers: HashMap::new(),
1579 flip_flop_active: Vec::new(),
1580 flip_flop_exclusive_left_line: Vec::new(),
1581 flip_flop_sequence: Vec::new(),
1582 flip_flop_last_dot: Vec::new(),
1583 flip_flop_tree: HashMap::new(),
1584 sigint_pending_caret: Cell::new(false),
1585 auto_split: false,
1586 field_separator: None,
1587 begin_blocks: Vec::new(),
1588 unit_check_blocks: Vec::new(),
1589 check_blocks: Vec::new(),
1590 init_blocks: Vec::new(),
1591 end_blocks: Vec::new(),
1592 warnings: false,
1593 output_autoflush: false,
1594 default_print_handle: "STDOUT".to_string(),
1595 suppress_stdout: false,
1596 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1597 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1598 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1599 test_pass_total: std::sync::atomic::AtomicUsize::new(0),
1600 test_fail_total: std::sync::atomic::AtomicUsize::new(0),
1601 test_skip_total: std::sync::atomic::AtomicUsize::new(0),
1602 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1603 child_exit_status: 0,
1604 last_match: String::new(),
1605 prematch: String::new(),
1606 postmatch: String::new(),
1607 last_paren_match: String::new(),
1608 list_separator: " ".to_string(),
1609 script_start_time,
1610 compile_hints: 0,
1611 warning_bits: 0,
1612 global_phase: "RUN".to_string(),
1613 subscript_sep: "\x1c".to_string(),
1614 inplace_edit: String::new(),
1615 debug_flags: 0,
1616 perl_debug_flags: 0,
1617 eval_nesting: 0,
1618 argv_current_file: String::new(),
1619 diamond_next_idx: 0,
1620 diamond_reader: None,
1621 strict_refs: false,
1622 strict_subs: false,
1623 strict_vars: false,
1624 utf8_pragma: false,
1625 open_pragma_utf8: false,
1626 feature_bits: FEAT_SAY,
1628 num_threads: 0, regex_cache: HashMap::new(),
1630 regex_last: None,
1631 regex_match_memo: None,
1632 regex_capture_scope_fresh: false,
1633 regex_pos: HashMap::new(),
1634 state_vars: HashMap::new(),
1635 state_bindings_stack: Vec::new(),
1636 rand_rng: StdRng::seed_from_u64(fast_rng_seed()),
1637 dir_handles: HashMap::new(),
1638 io_file_slots: HashMap::new(),
1639 pipe_children: HashMap::new(),
1640 socket_handles: HashMap::new(),
1641 wantarray_kind: WantarrayCtx::Scalar,
1642 profiler: None,
1643 module_export_lists: HashMap::new(),
1644 virtual_modules: HashMap::new(),
1645 tied_hashes: HashMap::new(),
1646 tied_scalars: HashMap::new(),
1647 tied_arrays: HashMap::new(),
1648 overload_table: HashMap::new(),
1649 format_templates: HashMap::new(),
1650 special_caret_scalars,
1651 format_page_number: 0,
1652 format_lines_per_page: 60,
1653 format_lines_left: 0,
1654 format_line_break_chars: "\n".to_string(),
1655 format_top_name: String::new(),
1656 accumulator_format: String::new(),
1657 max_system_fd: 2,
1658 emergency_memory: String::new(),
1659 last_subpattern_name: String::new(),
1660 inc_hook_index: 0,
1661 multiline_match: false,
1662 executable_path,
1663 formfeed_string: "\x0c".to_string(),
1664 glob_handle_alias: HashMap::new(),
1665 glob_restore_frames: vec![Vec::new()],
1666 special_var_restore_frames: vec![Vec::new()],
1667 reflection_hashes_ready: false,
1668 english_enabled: false,
1669 english_no_match_vars: false,
1670 english_match_vars_ever_enabled: false,
1671 english_lexical_scalars: vec![HashSet::new()],
1672 our_lexical_scalars: vec![HashSet::new()],
1673 our_lexical_arrays: vec![HashSet::new()],
1674 our_lexical_hashes: vec![HashSet::new()],
1675 vm_jit_enabled: !matches!(
1676 std::env::var("STRYKE_NO_JIT"),
1677 Ok(v)
1678 if v == "1"
1679 || v.eq_ignore_ascii_case("true")
1680 || v.eq_ignore_ascii_case("yes")
1681 ),
1682 disasm_bytecode: false,
1683 cached_chunk: None,
1684 cache_script_path: None,
1685 stryke_pwd,
1686 in_generator: false,
1687 line_mode_skip_main: false,
1688 line_mode_chunk: None,
1689 line_mode_eof_pending: false,
1690 line_mode_stdin_pending: VecDeque::new(),
1691 rate_limit_slots: Vec::new(),
1692 log_level_override: None,
1693 current_sub_stack: Vec::new(),
1694 debugger: None,
1695 debug_call_stack: Vec::new(),
1696 };
1697 s.install_overload_pragma_stubs();
1698 s
1699 }
1700
1701 pub(crate) fn ensure_reflection_hashes(&mut self) {
1705 if self.reflection_hashes_ready {
1706 return;
1707 }
1708 self.reflection_hashes_ready = true;
1709 self.refresh_package_stashes();
1712 if crate::compat_mode() {
1716 return;
1717 }
1718 let builtins_map = crate::builtins::builtins_hash_map();
1719 let perl_compats_map = crate::builtins::perl_compats_hash_map();
1720 let extensions_map = crate::builtins::extensions_hash_map();
1721 let aliases_map = crate::builtins::aliases_hash_map();
1722 let descriptions_map = crate::builtins::descriptions_hash_map();
1723 let categories_map = crate::builtins::categories_hash_map();
1724 let primaries_map = crate::builtins::primaries_hash_map();
1725 let keywords_map = crate::builtins::keywords_hash_map();
1726 let operators_map = crate::builtins::operators_hash_map();
1727 let special_vars_map = crate::builtins::special_vars_hash_map();
1728 let all_map = crate::builtins::all_hash_map();
1729 self.scope
1730 .declare_hash_global_frozen("stryke::builtins", builtins_map.clone());
1731 self.scope
1732 .declare_hash_global_frozen("stryke::perl_compats", perl_compats_map.clone());
1733 self.scope
1734 .declare_hash_global_frozen("stryke::extensions", extensions_map.clone());
1735 self.scope
1736 .declare_hash_global_frozen("stryke::aliases", aliases_map.clone());
1737 self.scope
1738 .declare_hash_global_frozen("stryke::descriptions", descriptions_map.clone());
1739 self.scope
1740 .declare_hash_global_frozen("stryke::categories", categories_map.clone());
1741 self.scope
1742 .declare_hash_global_frozen("stryke::primaries", primaries_map.clone());
1743 self.scope
1744 .declare_hash_global_frozen("stryke::keywords", keywords_map.clone());
1745 self.scope
1746 .declare_hash_global_frozen("stryke::operators", operators_map.clone());
1747 self.scope
1748 .declare_hash_global_frozen("stryke::special_vars", special_vars_map.clone());
1749 self.scope
1750 .declare_hash_global_frozen("stryke::all", all_map.clone());
1751 for (name, val) in [
1754 ("b", builtins_map),
1755 ("pc", perl_compats_map),
1756 ("e", extensions_map),
1757 ("a", aliases_map),
1758 ("d", descriptions_map),
1759 ("c", categories_map),
1760 ("p", primaries_map),
1761 ("k", keywords_map),
1762 ("o", operators_map),
1763 ("v", special_vars_map),
1764 ("all", all_map),
1765 ] {
1766 if !self.scope.any_frame_has_hash(name) {
1767 self.scope.declare_hash_global_frozen(name, val);
1768 }
1769 }
1770 if !self.scope.has_lexical_hash("parameters") {
1773 self.refresh_parameters_hash();
1774 }
1775 }
1776
1777 pub fn refresh_parameters_hash(&mut self) {
1786 let pairs = self.scope.parameters_pairs();
1787 let mut h: indexmap::IndexMap<String, StrykeValue> =
1788 indexmap::IndexMap::with_capacity(pairs.len());
1789 for (name, kind) in pairs {
1790 h.insert(name, StrykeValue::string(kind.to_string()));
1791 }
1792 self.scope.declare_hash_global_frozen("parameters", h);
1795 }
1796
1797 pub fn refresh_package_stashes(&mut self) {
1807 use indexmap::IndexMap;
1808
1809 let mut by_pkg: std::collections::HashMap<String, IndexMap<String, StrykeValue>> =
1810 std::collections::HashMap::new();
1811
1812 let record = |pkg: &str,
1813 sym: &str,
1814 kind: &str,
1815 map: &mut std::collections::HashMap<
1816 String,
1817 IndexMap<String, StrykeValue>,
1818 >| {
1819 map.entry(pkg.to_string())
1820 .or_default()
1821 .insert(sym.to_string(), StrykeValue::string(kind.to_string()));
1822 };
1823
1824 for key in self.subs.keys() {
1826 if let Some(idx) = key.rfind("::") {
1827 let (pkg, rest) = key.split_at(idx);
1828 let sym = &rest[2..];
1829 if pkg.is_empty() || sym.is_empty() {
1830 continue;
1831 }
1832 record(pkg, sym, "sub", &mut by_pkg);
1833 } else {
1834 record("main", key, "sub", &mut by_pkg);
1836 }
1837 }
1838
1839 for frame in self.scope.frames_for_introspection() {
1841 let (scalars, arrays, hashes) = frame;
1842 for name in scalars {
1843 if let Some(idx) = name.rfind("::") {
1844 let (pkg, rest) = name.split_at(idx);
1845 let sym = &rest[2..];
1846 if !pkg.is_empty() && !sym.is_empty() {
1847 record(pkg, sym, "scalar", &mut by_pkg);
1848 }
1849 }
1850 }
1851 for name in arrays {
1852 if let Some(idx) = name.rfind("::") {
1853 let (pkg, rest) = name.split_at(idx);
1854 let sym = &rest[2..];
1855 if !pkg.is_empty() && !sym.is_empty() {
1856 record(pkg, sym, "array", &mut by_pkg);
1857 }
1858 }
1859 }
1860 for name in hashes {
1861 if let Some(idx) = name.rfind("::") {
1862 let (pkg, rest) = name.split_at(idx);
1863 let sym = &rest[2..];
1864 if !pkg.is_empty() && !sym.is_empty() {
1865 record(pkg, sym, "hash", &mut by_pkg);
1866 }
1867 }
1868 }
1869 }
1870
1871 for (pkg, mut entries) in by_pkg {
1875 entries.sort_keys();
1876 let key = format!("{}::", pkg);
1877 self.scope.declare_hash_global_frozen(&key, entries);
1878 }
1879 }
1880
1881 fn install_overload_pragma_stubs(&mut self) {
1885 let empty: Block = vec![];
1886 for key in ["overload::import", "overload::unimport"] {
1887 let name = key.to_string();
1888 self.subs.insert(
1889 name.clone(),
1890 Arc::new(StrykeSub {
1891 name,
1892 params: vec![],
1893 body: empty.clone(),
1894 prototype: None,
1895 closure_env: None,
1896 fib_like: None,
1897 }),
1898 );
1899 }
1900 }
1901
1902 pub fn line_mode_worker_clone(&self) -> VMHelper {
1905 VMHelper {
1906 scope: self.scope.clone(),
1907 subs: self.subs.clone(),
1908 intercepts: self.intercepts.clone(),
1909 next_intercept_id: self.next_intercept_id,
1910 intercept_ctx_stack: self.intercept_ctx_stack.clone(),
1911 intercept_active_names: self.intercept_active_names.clone(),
1912 struct_defs: self.struct_defs.clone(),
1913 enum_defs: self.enum_defs.clone(),
1914 class_defs: self.class_defs.clone(),
1915 trait_defs: self.trait_defs.clone(),
1916 file: self.file.clone(),
1917 output_handles: HashMap::new(),
1918 input_handles: HashMap::new(),
1919 ofs: self.ofs.clone(),
1920 ors: self.ors.clone(),
1921 irs: self.irs.clone(),
1922 errno: self.errno.clone(),
1923 errno_code: self.errno_code,
1924 eval_error: self.eval_error.clone(),
1925 eval_error_code: self.eval_error_code,
1926 eval_error_value: self.eval_error_value.clone(),
1927 argv: self.argv.clone(),
1928 env: self.env.clone(),
1929 env_materialized: self.env_materialized,
1930 program_name: self.program_name.clone(),
1931 line_number: 0,
1932 nr: 0,
1933 last_readline_handle: String::new(),
1934 last_stdin_die_bracket: "<STDIN>".to_string(),
1935 handle_line_numbers: HashMap::new(),
1936 flip_flop_active: Vec::new(),
1937 flip_flop_exclusive_left_line: Vec::new(),
1938 flip_flop_sequence: Vec::new(),
1939 flip_flop_last_dot: Vec::new(),
1940 flip_flop_tree: HashMap::new(),
1941 sigint_pending_caret: Cell::new(false),
1942 auto_split: self.auto_split,
1943 field_separator: self.field_separator.clone(),
1944 begin_blocks: self.begin_blocks.clone(),
1945 unit_check_blocks: self.unit_check_blocks.clone(),
1946 check_blocks: self.check_blocks.clone(),
1947 init_blocks: self.init_blocks.clone(),
1948 end_blocks: self.end_blocks.clone(),
1949 warnings: self.warnings,
1950 output_autoflush: self.output_autoflush,
1951 default_print_handle: self.default_print_handle.clone(),
1952 suppress_stdout: self.suppress_stdout,
1953 test_pass_count: std::sync::atomic::AtomicUsize::new(0),
1957 test_fail_count: std::sync::atomic::AtomicUsize::new(0),
1958 test_skip_count: std::sync::atomic::AtomicUsize::new(0),
1959 test_pass_total: std::sync::atomic::AtomicUsize::new(0),
1960 test_fail_total: std::sync::atomic::AtomicUsize::new(0),
1961 test_skip_total: std::sync::atomic::AtomicUsize::new(0),
1962 test_run_failed: std::sync::atomic::AtomicBool::new(false),
1963 child_exit_status: self.child_exit_status,
1964 last_match: self.last_match.clone(),
1965 prematch: self.prematch.clone(),
1966 postmatch: self.postmatch.clone(),
1967 last_paren_match: self.last_paren_match.clone(),
1968 list_separator: self.list_separator.clone(),
1969 script_start_time: self.script_start_time,
1970 compile_hints: self.compile_hints,
1971 warning_bits: self.warning_bits,
1972 global_phase: self.global_phase.clone(),
1973 subscript_sep: self.subscript_sep.clone(),
1974 inplace_edit: self.inplace_edit.clone(),
1975 debug_flags: self.debug_flags,
1976 perl_debug_flags: self.perl_debug_flags,
1977 eval_nesting: self.eval_nesting,
1978 argv_current_file: String::new(),
1979 diamond_next_idx: 0,
1980 diamond_reader: None,
1981 strict_refs: self.strict_refs,
1982 strict_subs: self.strict_subs,
1983 strict_vars: self.strict_vars,
1984 utf8_pragma: self.utf8_pragma,
1985 open_pragma_utf8: self.open_pragma_utf8,
1986 feature_bits: self.feature_bits,
1987 num_threads: 0,
1988 regex_cache: self.regex_cache.clone(),
1989 regex_last: self.regex_last.clone(),
1990 regex_match_memo: self.regex_match_memo.clone(),
1991 regex_capture_scope_fresh: false,
1992 regex_pos: self.regex_pos.clone(),
1993 state_vars: self.state_vars.clone(),
1994 state_bindings_stack: Vec::new(),
1995 rand_rng: self.rand_rng.clone(),
1996 dir_handles: HashMap::new(),
1997 io_file_slots: HashMap::new(),
1998 pipe_children: HashMap::new(),
1999 socket_handles: HashMap::new(),
2000 wantarray_kind: self.wantarray_kind,
2001 profiler: None,
2002 module_export_lists: self.module_export_lists.clone(),
2003 virtual_modules: self.virtual_modules.clone(),
2004 tied_hashes: self.tied_hashes.clone(),
2005 tied_scalars: self.tied_scalars.clone(),
2006 tied_arrays: self.tied_arrays.clone(),
2007 overload_table: self.overload_table.clone(),
2008 format_templates: self.format_templates.clone(),
2009 special_caret_scalars: self.special_caret_scalars.clone(),
2010 format_page_number: self.format_page_number,
2011 format_lines_per_page: self.format_lines_per_page,
2012 format_lines_left: self.format_lines_left,
2013 format_line_break_chars: self.format_line_break_chars.clone(),
2014 format_top_name: self.format_top_name.clone(),
2015 accumulator_format: self.accumulator_format.clone(),
2016 max_system_fd: self.max_system_fd,
2017 emergency_memory: self.emergency_memory.clone(),
2018 last_subpattern_name: self.last_subpattern_name.clone(),
2019 inc_hook_index: self.inc_hook_index,
2020 multiline_match: self.multiline_match,
2021 executable_path: self.executable_path.clone(),
2022 formfeed_string: self.formfeed_string.clone(),
2023 glob_handle_alias: self.glob_handle_alias.clone(),
2024 glob_restore_frames: self.glob_restore_frames.clone(),
2025 special_var_restore_frames: self.special_var_restore_frames.clone(),
2026 reflection_hashes_ready: self.reflection_hashes_ready,
2027 english_enabled: self.english_enabled,
2028 english_no_match_vars: self.english_no_match_vars,
2029 english_match_vars_ever_enabled: self.english_match_vars_ever_enabled,
2030 english_lexical_scalars: self.english_lexical_scalars.clone(),
2031 our_lexical_scalars: self.our_lexical_scalars.clone(),
2032 our_lexical_arrays: self.our_lexical_arrays.clone(),
2033 our_lexical_hashes: self.our_lexical_hashes.clone(),
2034 vm_jit_enabled: self.vm_jit_enabled,
2035 disasm_bytecode: self.disasm_bytecode,
2036 cached_chunk: None,
2038 cache_script_path: None,
2039 stryke_pwd: self.stryke_pwd.clone(),
2040 in_generator: false,
2041 line_mode_skip_main: false,
2042 line_mode_chunk: self.line_mode_chunk.clone(),
2043 line_mode_eof_pending: false,
2044 line_mode_stdin_pending: VecDeque::new(),
2045 rate_limit_slots: Vec::new(),
2046 log_level_override: self.log_level_override,
2047 current_sub_stack: Vec::new(),
2048 debugger: None,
2049 debug_call_stack: Vec::new(),
2050 }
2051 }
2052
2053 pub(crate) fn parallel_thread_count(&mut self) -> usize {
2055 if self.num_threads == 0 {
2056 self.num_threads = rayon::current_num_threads();
2057 }
2058 self.num_threads
2059 }
2060
2061 pub(crate) fn eval_par_list_call(
2063 &mut self,
2064 name: &str,
2065 args: &[StrykeValue],
2066 ctx: WantarrayCtx,
2067 line: usize,
2068 ) -> StrykeResult<StrykeValue> {
2069 match name {
2070 "puniq" => {
2071 let (list_src, show_prog) = match args.len() {
2072 0 => return Err(StrykeError::runtime("puniq: expected LIST", line)),
2073 1 => (&args[0], false),
2074 2 => (&args[0], args[1].is_true()),
2075 _ => {
2076 return Err(StrykeError::runtime(
2077 "puniq: expected LIST [, progress => EXPR]",
2078 line,
2079 ));
2080 }
2081 };
2082 let list = list_src.to_list();
2083 let n_threads = self.parallel_thread_count();
2084 let pmap_progress = PmapProgress::new(show_prog, list.len());
2085 let out = crate::par_list::puniq_run(list, n_threads, &pmap_progress);
2086 pmap_progress.finish();
2087 if ctx == WantarrayCtx::List {
2088 Ok(StrykeValue::array(out))
2089 } else {
2090 Ok(StrykeValue::integer(out.len() as i64))
2091 }
2092 }
2093 "pfirst" => {
2094 let (code_val, list_src, show_prog) = match args.len() {
2095 2 => (&args[0], &args[1], false),
2096 3 => (&args[0], &args[1], args[2].is_true()),
2097 _ => {
2098 return Err(StrykeError::runtime(
2099 "pfirst: expected BLOCK, LIST [, progress => EXPR]",
2100 line,
2101 ));
2102 }
2103 };
2104 let Some(sub) = code_val.as_code_ref() else {
2105 return Err(StrykeError::runtime(
2106 "pfirst: first argument must be a code reference",
2107 line,
2108 ));
2109 };
2110 let sub = sub.clone();
2111 let list = list_src.to_list();
2112 if list.is_empty() {
2113 return Ok(StrykeValue::UNDEF);
2114 }
2115 let pmap_progress = PmapProgress::new(show_prog, list.len());
2116 let subs = self.subs.clone();
2117 let (scope_capture, atomic_arrays, atomic_hashes) =
2118 self.scope.capture_with_atomics();
2119 let out = crate::par_list::pfirst_run(list, &pmap_progress, |item| {
2120 let mut local_interp = VMHelper::new();
2121 local_interp.subs = subs.clone();
2122 local_interp.scope.restore_capture(&scope_capture);
2123 local_interp
2124 .scope
2125 .restore_atomics(&atomic_arrays, &atomic_hashes);
2126 local_interp.enable_parallel_guard();
2127 local_interp.scope.set_topic(item);
2128 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2129 Ok(v) => v.is_true(),
2130 Err(_) => false,
2131 }
2132 });
2133 pmap_progress.finish();
2134 Ok(out.unwrap_or(StrykeValue::UNDEF))
2135 }
2136 "pany" => {
2137 let (code_val, list_src, show_prog) = match args.len() {
2138 2 => (&args[0], &args[1], false),
2139 3 => (&args[0], &args[1], args[2].is_true()),
2140 _ => {
2141 return Err(StrykeError::runtime(
2142 "pany: expected BLOCK, LIST [, progress => EXPR]",
2143 line,
2144 ));
2145 }
2146 };
2147 let Some(sub) = code_val.as_code_ref() else {
2148 return Err(StrykeError::runtime(
2149 "pany: first argument must be a code reference",
2150 line,
2151 ));
2152 };
2153 let sub = sub.clone();
2154 let list = list_src.to_list();
2155 let pmap_progress = PmapProgress::new(show_prog, list.len());
2156 let subs = self.subs.clone();
2157 let (scope_capture, atomic_arrays, atomic_hashes) =
2158 self.scope.capture_with_atomics();
2159 let b = crate::par_list::pany_run(list, &pmap_progress, |item| {
2160 let mut local_interp = VMHelper::new();
2161 local_interp.subs = subs.clone();
2162 local_interp.scope.restore_capture(&scope_capture);
2163 local_interp
2164 .scope
2165 .restore_atomics(&atomic_arrays, &atomic_hashes);
2166 local_interp.enable_parallel_guard();
2167 local_interp.scope.set_topic(item);
2168 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Scalar, line) {
2169 Ok(v) => v.is_true(),
2170 Err(_) => false,
2171 }
2172 });
2173 pmap_progress.finish();
2174 Ok(StrykeValue::integer(if b { 1 } else { 0 }))
2175 }
2176 _ => Err(StrykeError::runtime(
2177 format!("internal: unknown par_list builtin {name}"),
2178 line,
2179 )),
2180 }
2181 }
2182
2183 fn encode_exit_status(&self, s: std::process::ExitStatus) -> i64 {
2184 #[cfg(unix)]
2185 if let Some(sig) = s.signal() {
2186 return sig as i64 & 0x7f;
2187 }
2188 let code = s.code().unwrap_or(0) as i64;
2189 code << 8
2190 }
2191
2192 pub(crate) fn record_child_exit_status(&mut self, s: std::process::ExitStatus) {
2193 self.child_exit_status = self.encode_exit_status(s);
2194 }
2195
2196 pub(crate) fn apply_io_error_to_errno(&mut self, e: &std::io::Error) {
2198 let s = e.to_string();
2201 let stripped = s
2202 .rfind(" (os error ")
2203 .map(|i| s[..i].to_string())
2204 .unwrap_or(s);
2205 self.errno = stripped;
2206 self.errno_code = e.raw_os_error().unwrap_or(0);
2207 }
2208
2209 pub(crate) fn ssh_builtin_execute(
2220 &mut self,
2221 args: &[StrykeValue],
2222 ) -> StrykeResult<StrykeValue> {
2223 use std::process::Command;
2224 let mut cmd = Command::new("ssh");
2225 #[cfg(unix)]
2226 {
2227 use libc::geteuid;
2228 let home_for_ssh = if unsafe { geteuid() } == 0 {
2229 std::env::var_os("SUDO_USER").and_then(|u| pw_home_dir_for_login_name(&u))
2230 } else {
2231 None
2232 };
2233 if let Some(h) = home_for_ssh {
2234 cmd.env("HOME", h);
2235 } else if std::env::var_os("HOME").is_none() {
2236 if let Some(h) = pw_home_dir_for_current_uid() {
2237 cmd.env("HOME", h);
2238 }
2239 }
2240 }
2241 for a in args {
2242 cmd.arg(a.to_string());
2243 }
2244 match cmd.status() {
2245 Ok(s) => {
2246 self.record_child_exit_status(s);
2247 Ok(StrykeValue::integer(s.code().unwrap_or(-1) as i64))
2248 }
2249 Err(e) => {
2250 self.apply_io_error_to_errno(&e);
2251 Ok(StrykeValue::integer(-1))
2252 }
2253 }
2254 }
2255
2256 pub(crate) fn set_eval_error(&mut self, msg: String) {
2258 self.eval_error = msg;
2259 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2260 self.eval_error_value = None;
2261 }
2262
2263 pub(crate) fn set_eval_error_from_perl_error(&mut self, e: &StrykeError) {
2264 self.eval_error = e.to_string();
2265 self.eval_error_code = if self.eval_error.is_empty() { 0 } else { 1 };
2266 self.eval_error_value = e.die_value.clone();
2267 }
2268
2269 pub(crate) fn clear_eval_error(&mut self) {
2270 self.eval_error = String::new();
2271 self.eval_error_code = 0;
2272 self.eval_error_value = None;
2273 }
2274
2275 fn bump_line_for_handle(&mut self, handle_key: &str) {
2277 self.last_readline_handle = handle_key.to_string();
2278 *self
2279 .handle_line_numbers
2280 .entry(handle_key.to_string())
2281 .or_insert(0) += 1;
2282 }
2283
2284 pub(crate) fn stash_array_name_for_package(&self, name: &str) -> String {
2286 if name.starts_with('^') {
2287 return name.to_string();
2288 }
2289 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
2290 let pkg = self.current_package();
2291 if !pkg.is_empty() && pkg != "main" {
2292 return format!("{}::{}", pkg, name);
2293 }
2294 }
2295 name.to_string()
2296 }
2297
2298 pub(crate) fn stash_scalar_name_for_package(&self, name: &str) -> String {
2300 if name.contains("::") {
2301 return name.to_string();
2302 }
2303 let pkg = self.current_package();
2304 if pkg.is_empty() || pkg == "main" {
2305 format!("main::{}", name)
2306 } else {
2307 format!("{}::{}", pkg, name)
2308 }
2309 }
2310
2311 pub(crate) fn tree_scalar_storage_name(&self, name: &str) -> String {
2313 if name.contains("::") {
2314 return name.to_string();
2315 }
2316 for (lex, our) in self
2317 .english_lexical_scalars
2318 .iter()
2319 .zip(self.our_lexical_scalars.iter())
2320 .rev()
2321 {
2322 if lex.contains(name) {
2323 if our.contains(name) {
2324 return self.stash_scalar_name_for_package(name);
2325 }
2326 return name.to_string();
2327 }
2328 }
2329 name.to_string()
2330 }
2331
2332 pub(crate) fn tie_execute(
2334 &mut self,
2335 target_kind: u8,
2336 target_name: &str,
2337 class_and_args: Vec<StrykeValue>,
2338 line: usize,
2339 ) -> StrykeResult<StrykeValue> {
2340 let mut it = class_and_args.into_iter();
2341 let class = it.next().unwrap_or(StrykeValue::UNDEF);
2342 let pkg = class.to_string();
2343 let pkg = pkg.trim_matches(|c| c == '\'' || c == '"').to_string();
2344 let tie_ctor = match target_kind {
2345 0 => "TIESCALAR",
2346 1 => "TIEARRAY",
2347 2 => "TIEHASH",
2348 _ => return Err(StrykeError::runtime("tie: invalid target kind", line)),
2349 };
2350 let tie_fn = format!("{}::{}", pkg, tie_ctor);
2351 let sub =
2352 self.subs.get(&tie_fn).cloned().ok_or_else(|| {
2353 StrykeError::runtime(format!("tie: cannot find &{}", tie_fn), line)
2354 })?;
2355 let mut call_args = vec![StrykeValue::string(pkg.clone())];
2356 call_args.extend(it);
2357 let obj = match self.call_sub(&sub, call_args, WantarrayCtx::Scalar, line) {
2358 Ok(v) => v,
2359 Err(FlowOrError::Flow(_)) => StrykeValue::UNDEF,
2360 Err(FlowOrError::Error(e)) => return Err(e),
2361 };
2362 match target_kind {
2363 0 => {
2364 self.tied_scalars.insert(target_name.to_string(), obj);
2365 }
2366 1 => {
2367 let key = self.stash_array_name_for_package(target_name);
2368 self.tied_arrays.insert(key, obj);
2369 }
2370 2 => {
2371 self.tied_hashes.insert(target_name.to_string(), obj);
2372 }
2373 _ => return Err(StrykeError::runtime("tie: invalid target kind", line)),
2374 }
2375 Ok(StrykeValue::UNDEF)
2376 }
2377
2378 pub(crate) fn parents_of_class(&self, class: &str) -> Vec<String> {
2380 let key = format!("{}::ISA", class);
2381 self.scope
2382 .get_array(&key)
2383 .into_iter()
2384 .map(|v| v.to_string())
2385 .collect()
2386 }
2387
2388 pub(crate) fn mro_linearize(&self, class: &str) -> Vec<String> {
2389 let p = |c: &str| self.parents_of_class(c);
2390 linearize_c3(class, &p, 0)
2391 }
2392
2393 pub(crate) fn resolve_method_full_name(
2395 &self,
2396 invocant_class: &str,
2397 method: &str,
2398 super_mode: bool,
2399 ) -> Option<String> {
2400 let mro = self.mro_linearize(invocant_class);
2401 let start = if super_mode {
2405 mro.iter()
2406 .position(|p| p == invocant_class)
2407 .map(|i| i + 1)
2408 .unwrap_or(1)
2411 } else {
2412 0
2413 };
2414 for pkg in mro.iter().skip(start) {
2415 if pkg == "UNIVERSAL" {
2416 continue;
2417 }
2418 let fq = format!("{}::{}", pkg, method);
2419 if self.subs.contains_key(&fq) {
2420 return Some(fq);
2421 }
2422 }
2423 mro.iter()
2424 .skip(start)
2425 .find(|p| *p != "UNIVERSAL")
2426 .map(|pkg| format!("{}::{}", pkg, method))
2427 }
2428
2429 pub(crate) fn resolve_io_handle_name(&self, name: &str) -> String {
2430 if let Some(alias) = self.glob_handle_alias.get(name) {
2431 return alias.clone();
2432 }
2433 if let Some(var_name) = name.strip_prefix('$') {
2436 let val = self.scope.get_scalar(var_name);
2437 let s = val.to_string();
2438 if !s.is_empty() {
2439 return self.resolve_io_handle_name(&s);
2440 }
2441 }
2442 name.to_string()
2443 }
2444
2445 pub(crate) fn qualify_typeglob_sub_key(&self, name: &str) -> String {
2447 if name.contains("::") {
2448 name.to_string()
2449 } else {
2450 self.qualify_sub_key(name)
2451 }
2452 }
2453
2454 pub(crate) fn copy_typeglob_slots(
2456 &mut self,
2457 lhs: &str,
2458 rhs: &str,
2459 line: usize,
2460 ) -> StrykeResult<()> {
2461 let lhs_sub = self.qualify_typeglob_sub_key(lhs);
2462 let rhs_sub = self.qualify_typeglob_sub_key(rhs);
2463 match self.subs.get(&rhs_sub).cloned() {
2464 Some(s) => {
2465 self.subs.insert(lhs_sub, s);
2466 }
2467 None => {
2468 self.subs.remove(&lhs_sub);
2469 }
2470 }
2471 let sv = self.scope.get_scalar(rhs);
2472 self.scope
2473 .set_scalar(lhs, sv.clone())
2474 .map_err(|e| e.at_line(line))?;
2475 let lhs_an = self.stash_array_name_for_package(lhs);
2476 let rhs_an = self.stash_array_name_for_package(rhs);
2477 let av = self.scope.get_array(&rhs_an);
2478 self.scope
2479 .set_array(&lhs_an, av.clone())
2480 .map_err(|e| e.at_line(line))?;
2481 let hv = self.scope.get_hash(rhs);
2482 self.scope
2483 .set_hash(lhs, hv.clone())
2484 .map_err(|e| e.at_line(line))?;
2485 match self.glob_handle_alias.get(rhs).cloned() {
2486 Some(t) => {
2487 self.glob_handle_alias.insert(lhs.to_string(), t);
2488 }
2489 None => {
2490 self.glob_handle_alias.remove(lhs);
2491 }
2492 }
2493 Ok(())
2494 }
2495
2496 pub(crate) fn install_format_decl(
2498 &mut self,
2499 basename: &str,
2500 lines: &[String],
2501 line: usize,
2502 ) -> StrykeResult<()> {
2503 let pkg = self.current_package();
2504 let key = format!("{}::{}", pkg, basename);
2505 let tmpl = crate::format::parse_format_template(lines).map_err(|e| e.at_line(line))?;
2506 self.format_templates.insert(key, Arc::new(tmpl));
2507 Ok(())
2508 }
2509
2510 pub(crate) fn install_use_overload_pairs(&mut self, pairs: &[(String, String)]) {
2516 let pkg = self.current_package();
2517 for (_, v) in pairs {
2518 if v.starts_with("__overload_anon_") {
2519 let pkg_key = format!("{}::{}", pkg, v);
2526 if !self.subs.contains_key(&pkg_key) {
2527 let src = if let Some(s) = self.subs.get(v) {
2528 Some(s.clone())
2529 } else {
2530 self.subs.get(&format!("main::{}", v)).cloned()
2531 };
2532 if let Some(sub) = src {
2533 self.subs.insert(pkg_key, sub);
2534 }
2535 }
2536 }
2537 }
2538 let ent = self.overload_table.entry(pkg).or_default();
2539 for (k, v) in pairs {
2540 ent.insert(k.clone(), v.clone());
2541 }
2542 }
2543
2544 pub(crate) fn local_declare_typeglob(
2547 &mut self,
2548 lhs: &str,
2549 rhs: Option<&str>,
2550 line: usize,
2551 ) -> StrykeResult<()> {
2552 let old = self.glob_handle_alias.remove(lhs);
2553 let Some(frame) = self.glob_restore_frames.last_mut() else {
2554 return Err(StrykeError::runtime(
2555 "internal: no glob restore frame for local *GLOB",
2556 line,
2557 ));
2558 };
2559 frame.push((lhs.to_string(), old));
2560 if let Some(r) = rhs {
2561 self.glob_handle_alias
2562 .insert(lhs.to_string(), r.to_string());
2563 }
2564 Ok(())
2565 }
2566
2567 pub(crate) fn scope_push_hook(&mut self) {
2568 self.scope.push_frame();
2569 self.glob_restore_frames.push(Vec::new());
2570 self.special_var_restore_frames.push(Vec::new());
2571 self.english_lexical_scalars.push(HashSet::new());
2572 self.our_lexical_scalars.push(HashSet::new());
2573 self.our_lexical_arrays.push(HashSet::new());
2574 self.our_lexical_hashes.push(HashSet::new());
2575 self.state_bindings_stack.push(Vec::new());
2576 }
2577
2578 #[inline]
2579 pub(crate) fn english_note_lexical_scalar(&mut self, name: &str) {
2580 if let Some(s) = self.english_lexical_scalars.last_mut() {
2581 s.insert(name.to_string());
2582 }
2583 }
2584
2585 #[inline]
2588 pub(crate) fn english_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2589 self.english_lexical_scalars.clone()
2590 }
2591
2592 #[inline]
2595 pub(crate) fn our_lexical_scalars_clone(&self) -> Vec<HashSet<String>> {
2596 self.our_lexical_scalars.clone()
2597 }
2598
2599 #[inline]
2601 pub(crate) fn set_english_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2602 self.english_lexical_scalars = v;
2603 }
2604
2605 #[inline]
2607 pub(crate) fn set_our_lexical_scalars(&mut self, v: Vec<HashSet<String>>) {
2608 self.our_lexical_scalars = v;
2609 }
2610
2611 #[inline]
2612 fn note_our_scalar(&mut self, bare_name: &str) {
2613 if let Some(s) = self.our_lexical_scalars.last_mut() {
2614 s.insert(bare_name.to_string());
2615 }
2616 }
2617
2618 #[inline]
2619 fn note_our_array(&mut self, bare_name: &str) {
2620 if let Some(s) = self.our_lexical_arrays.last_mut() {
2621 s.insert(bare_name.to_string());
2622 }
2623 }
2624
2625 #[inline]
2626 fn note_our_hash(&mut self, bare_name: &str) {
2627 if let Some(s) = self.our_lexical_hashes.last_mut() {
2628 s.insert(bare_name.to_string());
2629 }
2630 }
2631
2632 pub(crate) fn stash_array_full_name_for_package(&self, name: &str) -> String {
2637 if name.contains("::") {
2638 return name.to_string();
2639 }
2640 let pkg = self.current_package();
2641 if pkg.is_empty() || pkg == "main" {
2642 format!("main::{}", name)
2643 } else {
2644 format!("{}::{}", pkg, name)
2645 }
2646 }
2647
2648 pub(crate) fn stash_hash_full_name_for_package(&self, name: &str) -> String {
2651 if name.contains("::") {
2652 return name.to_string();
2653 }
2654 let pkg = self.current_package();
2655 if pkg.is_empty() || pkg == "main" {
2656 format!("main::{}", name)
2657 } else {
2658 format!("{}::{}", pkg, name)
2659 }
2660 }
2661
2662 pub(crate) fn tree_array_storage_name(&self, name: &str) -> String {
2666 if name.contains("::") {
2667 return name.to_string();
2668 }
2669 for ours in self.our_lexical_arrays.iter().rev() {
2670 if ours.contains(name) {
2671 return self.stash_array_full_name_for_package(name);
2672 }
2673 }
2674 name.to_string()
2675 }
2676
2677 pub(crate) fn tree_hash_storage_name(&self, name: &str) -> String {
2680 if name.contains("::") {
2681 return name.to_string();
2682 }
2683 for ours in self.our_lexical_hashes.iter().rev() {
2684 if ours.contains(name) {
2685 return self.stash_hash_full_name_for_package(name);
2686 }
2687 }
2688 name.to_string()
2689 }
2690
2691 #[inline]
2695 pub(crate) fn english_note_lexical_scalar_pub(&mut self, name: &str) {
2696 self.english_note_lexical_scalar(name);
2697 }
2698
2699 #[inline]
2701 pub(crate) fn note_our_scalar_pub(&mut self, bare_name: &str) {
2702 self.note_our_scalar(bare_name);
2703 }
2704
2705 pub(crate) fn scope_pop_hook(&mut self) {
2706 if !self.scope.can_pop_frame() {
2707 return;
2708 }
2709 let defers = self.scope.take_defers();
2713 for coderef in defers {
2714 if let Some(sub) = coderef.as_code_ref() {
2715 let saved_wa = self.wantarray_kind;
2719 self.wantarray_kind = WantarrayCtx::Void;
2720 let _ = self.exec_block_no_scope(&sub.body);
2721 self.wantarray_kind = saved_wa;
2722 }
2723 }
2724 if let Some(bindings) = self.state_bindings_stack.pop() {
2726 for (var_name, state_key) in &bindings {
2727 let val = self.scope.get_scalar(var_name).clone();
2728 self.state_vars.insert(state_key.clone(), val);
2729 }
2730 }
2731 if let Some(entries) = self.special_var_restore_frames.pop() {
2734 for (name, old) in entries.into_iter().rev() {
2735 let _ = self.set_special_var(&name, &old);
2736 }
2737 }
2738 if let Some(entries) = self.glob_restore_frames.pop() {
2739 for (name, old) in entries.into_iter().rev() {
2740 match old {
2741 Some(s) => {
2742 self.glob_handle_alias.insert(name, s);
2743 }
2744 None => {
2745 self.glob_handle_alias.remove(&name);
2746 }
2747 }
2748 }
2749 }
2750 self.scope.pop_frame();
2751 let _ = self.english_lexical_scalars.pop();
2752 let _ = self.our_lexical_scalars.pop();
2753 let _ = self.our_lexical_arrays.pop();
2754 let _ = self.our_lexical_hashes.pop();
2755 }
2756
2757 #[inline]
2760 pub(crate) fn enable_parallel_guard(&mut self) {
2761 self.scope.set_parallel_guard(true);
2762 }
2763
2764 pub(crate) fn clear_begin_end_blocks_after_vm_compile(&mut self) {
2766 self.begin_blocks.clear();
2767 self.unit_check_blocks.clear();
2768 self.check_blocks.clear();
2769 self.init_blocks.clear();
2770 self.end_blocks.clear();
2771 }
2772
2773 pub(crate) fn pop_scope_to_depth(&mut self, target_depth: usize) {
2779 while self.scope.depth() > target_depth && self.scope.can_pop_frame() {
2780 self.scope_pop_hook();
2781 }
2782 }
2783
2784 pub(crate) fn invoke_sig_handler(&mut self, sig: &str) -> StrykeResult<()> {
2791 self.touch_env_hash("SIG");
2792 let v = self.scope.get_hash_element("SIG", sig);
2793 if v.is_undef() {
2794 return Self::default_sig_action(sig);
2795 }
2796 if let Some(s) = v.as_str() {
2797 if s == "IGNORE" {
2798 return Ok(());
2799 }
2800 if s == "DEFAULT" {
2801 return Self::default_sig_action(sig);
2802 }
2803 }
2804 if let Some(sub) = v.as_code_ref() {
2805 match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, 0) {
2806 Ok(_) => Ok(()),
2807 Err(FlowOrError::Flow(_)) => Ok(()),
2808 Err(FlowOrError::Error(e)) => Err(e),
2809 }
2810 } else {
2811 Self::default_sig_action(sig)
2812 }
2813 }
2814
2815 pub(crate) fn fire_pseudosig_warn(&mut self, msg: &str, line: usize) -> StrykeResult<()> {
2819 self.touch_env_hash("SIG");
2820 let slot = self.scope.get_hash_element("SIG", "__WARN__");
2821 if let Some(sub) = slot.as_code_ref() {
2822 let prev = slot;
2823 let _ = self
2824 .scope
2825 .set_hash_element("SIG", "__WARN__", StrykeValue::UNDEF);
2826 let arg = StrykeValue::string(msg.to_string());
2827 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2828 let _ = self.scope.set_hash_element("SIG", "__WARN__", prev);
2829 return match r {
2830 Ok(_) => Ok(()),
2831 Err(FlowOrError::Flow(_)) => Ok(()),
2832 Err(FlowOrError::Error(e)) => Err(e),
2833 };
2834 }
2835 eprint!("{}", msg);
2836 Ok(())
2837 }
2838
2839 pub(crate) fn fire_pseudosig_die(&mut self, msg: &str, line: usize) -> StrykeResult<()> {
2845 self.touch_env_hash("SIG");
2846 let slot = self.scope.get_hash_element("SIG", "__DIE__");
2847 if let Some(sub) = slot.as_code_ref() {
2848 let prev = slot;
2849 let _ = self
2850 .scope
2851 .set_hash_element("SIG", "__DIE__", StrykeValue::UNDEF);
2852 let arg = StrykeValue::string(msg.to_string());
2853 let r = self.call_sub(&sub, vec![arg], WantarrayCtx::Void, line);
2854 let _ = self.scope.set_hash_element("SIG", "__DIE__", prev);
2855 return match r {
2856 Ok(_) => Ok(()),
2857 Err(FlowOrError::Flow(_)) => Ok(()),
2858 Err(FlowOrError::Error(e)) => Err(e),
2859 };
2860 }
2861 Ok(())
2862 }
2863
2864 #[inline]
2866 fn default_sig_action(sig: &str) -> StrykeResult<()> {
2867 match sig {
2868 "INT" => std::process::exit(130),
2870 "TERM" => std::process::exit(143),
2871 "ALRM" => std::process::exit(142),
2872 "CHLD" => Ok(()),
2874 _ => Ok(()),
2875 }
2876 }
2877
2878 #[inline]
2883 pub fn debugger_enter_sub(&mut self, name: &str) {
2884 if let Some(dbg) = &mut self.debugger {
2885 dbg.enter_sub(name);
2886 }
2887 }
2888
2889 #[inline]
2891 pub fn debugger_leave_sub(&mut self) {
2892 if let Some(dbg) = &mut self.debugger {
2893 dbg.leave_sub();
2894 }
2895 }
2896
2897 pub fn materialize_env_if_needed(&mut self) {
2900 if self.env_materialized {
2901 return;
2902 }
2903 self.env = std::env::vars()
2904 .map(|(k, v)| (k, StrykeValue::string(v)))
2905 .collect();
2906 self.scope
2907 .set_hash("ENV", self.env.clone())
2908 .expect("set %ENV");
2909 self.env_materialized = true;
2910 }
2911
2912 pub(crate) fn log_filter_effective(&mut self) -> LogLevelFilter {
2914 self.materialize_env_if_needed();
2915 if let Some(x) = self.log_level_override {
2916 return x;
2917 }
2918 let s = self.scope.get_hash_element("ENV", "LOG_LEVEL").to_string();
2919 LogLevelFilter::parse(&s).unwrap_or(LogLevelFilter::Info)
2920 }
2921
2922 pub(crate) fn no_color_effective(&mut self) -> bool {
2924 self.materialize_env_if_needed();
2925 let v = self.scope.get_hash_element("ENV", "NO_COLOR");
2926 if v.is_undef() {
2927 return false;
2928 }
2929 !v.to_string().is_empty()
2930 }
2931
2932 #[inline]
2933 pub(crate) fn touch_env_hash(&mut self, hash_name: &str) {
2934 let hash_name: &str = crate::scope::strip_main_prefix(hash_name).unwrap_or(hash_name);
2941 if hash_name == "ENV" {
2942 self.materialize_env_if_needed();
2943 } else if hash_name == "parameters"
2944 && !crate::compat_mode()
2945 && !self.scope.has_lexical_hash("parameters")
2946 {
2947 self.ensure_reflection_hashes();
2953 self.refresh_parameters_hash();
2954 } else if hash_name.ends_with("::") && hash_name.len() > 2 {
2955 self.refresh_package_stashes();
2960 } else if !self.reflection_hashes_ready && !self.scope.has_lexical_hash(hash_name) {
2961 match hash_name {
2962 "b"
2963 | "pc"
2964 | "e"
2965 | "a"
2966 | "d"
2967 | "c"
2968 | "p"
2969 | "k"
2970 | "o"
2971 | "v"
2972 | "all"
2973 | "stryke::builtins"
2974 | "stryke::perl_compats"
2975 | "stryke::extensions"
2976 | "stryke::aliases"
2977 | "stryke::descriptions"
2978 | "stryke::categories"
2979 | "stryke::primaries"
2980 | "stryke::keywords"
2981 | "stryke::operators"
2982 | "stryke::special_vars"
2983 | "stryke::all" => {
2984 self.ensure_reflection_hashes();
2985 }
2986 _ => {}
2987 }
2988 }
2989 }
2990
2991 pub(crate) fn exists_arrow_hash_element(
2993 &self,
2994 container: StrykeValue,
2995 key: &str,
2996 line: usize,
2997 ) -> StrykeResult<bool> {
2998 if let Some(r) = container.as_hash_ref() {
2999 return Ok(r.read().contains_key(key));
3000 }
3001 if let Some(b) = container.as_blessed_ref() {
3002 let data = b.data.read();
3003 if let Some(r) = data.as_hash_ref() {
3004 return Ok(r.read().contains_key(key));
3005 }
3006 if let Some(hm) = data.as_hash_map() {
3007 return Ok(hm.contains_key(key));
3008 }
3009 return Err(StrykeError::runtime(
3010 "exists argument is not a HASH reference",
3011 line,
3012 ));
3013 }
3014 let _ = line;
3018 Ok(false)
3019 }
3020
3021 pub(crate) fn delete_arrow_hash_element(
3023 &self,
3024 container: StrykeValue,
3025 key: &str,
3026 line: usize,
3027 ) -> StrykeResult<StrykeValue> {
3028 if let Some(r) = container.as_hash_ref() {
3029 return Ok(r.write().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
3030 }
3031 if let Some(b) = container.as_blessed_ref() {
3032 let mut data = b.data.write();
3033 if let Some(r) = data.as_hash_ref() {
3034 return Ok(r.write().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
3035 }
3036 if let Some(mut map) = data.as_hash_map() {
3037 let v = map.shift_remove(key).unwrap_or(StrykeValue::UNDEF);
3038 *data = StrykeValue::hash(map);
3039 return Ok(v);
3040 }
3041 return Err(StrykeError::runtime(
3042 "delete argument is not a HASH reference",
3043 line,
3044 ));
3045 }
3046 Err(StrykeError::runtime(
3047 "delete argument is not a HASH reference",
3048 line,
3049 ))
3050 }
3051
3052 pub(crate) fn exists_arrow_array_element(
3054 &self,
3055 container: StrykeValue,
3056 idx: i64,
3057 line: usize,
3058 ) -> StrykeResult<bool> {
3059 if let Some(a) = container.as_array_ref() {
3060 let arr = a.read();
3061 let i = if idx < 0 {
3062 (arr.len() as i64 + idx) as usize
3063 } else {
3064 idx as usize
3065 };
3066 return Ok(i < arr.len());
3067 }
3068 let _ = line;
3071 Ok(false)
3072 }
3073
3074 pub(crate) fn delete_arrow_array_element(
3076 &self,
3077 container: StrykeValue,
3078 idx: i64,
3079 line: usize,
3080 ) -> StrykeResult<StrykeValue> {
3081 if let Some(a) = container.as_array_ref() {
3082 let mut arr = a.write();
3083 let i = if idx < 0 {
3084 (arr.len() as i64 + idx) as usize
3085 } else {
3086 idx as usize
3087 };
3088 if i >= arr.len() {
3089 return Ok(StrykeValue::UNDEF);
3090 }
3091 let old = arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
3092 arr[i] = StrykeValue::UNDEF;
3093 return Ok(old);
3094 }
3095 Err(StrykeError::runtime(
3096 "delete argument is not an ARRAY reference",
3097 line,
3098 ))
3099 }
3100
3101 pub(crate) fn inc_directories(&self) -> Vec<String> {
3103 let mut v: Vec<String> = self
3104 .scope
3105 .get_array("INC")
3106 .into_iter()
3107 .map(|x| x.to_string())
3108 .filter(|s| !s.is_empty())
3109 .collect();
3110 if v.is_empty() {
3111 v.push(".".to_string());
3112 }
3113 v
3114 }
3115
3116 #[inline]
3117 pub(crate) fn strict_scalar_exempt(name: &str) -> bool {
3118 matches!(
3119 name,
3120 "_" | "0"
3121 | "!"
3122 | "@"
3123 | "/"
3124 | "\\"
3125 | ","
3126 | "."
3127 | "__PACKAGE__"
3128 | "$$"
3129 | "|"
3130 | "?"
3131 | "\""
3132 | "&"
3133 | "`"
3134 | "'"
3135 | "+"
3136 | "<"
3137 | ">"
3138 | "("
3139 | ")"
3140 | "]"
3141 | ";"
3142 | "ARGV"
3143 | "%"
3144 | "="
3145 | "-"
3146 | ":"
3147 | "*"
3148 | "INC"
3149 | "a"
3153 | "b"
3154 ) || name.chars().all(|c| c.is_ascii_digit())
3155 || name.starts_with('^')
3156 || (name.starts_with('#') && name.len() > 1)
3157 || (name.starts_with('_')
3164 && name.len() > 1
3165 && name[1..].chars().all(|c| c.is_ascii_digit()))
3166 }
3167
3168 fn check_strict_scalar_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3169 if !self.strict_vars
3170 || Self::strict_scalar_exempt(name)
3171 || name.contains("::")
3172 || self.scope.scalar_binding_exists(name)
3173 {
3174 return Ok(());
3175 }
3176 Err(StrykeError::runtime(
3177 format!(
3178 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
3179 name, name
3180 ),
3181 line,
3182 )
3183 .into())
3184 }
3185
3186 fn check_strict_array_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3187 if !self.strict_vars || name.contains("::") || self.scope.array_binding_exists(name) {
3188 return Ok(());
3189 }
3190 Err(StrykeError::runtime(
3191 format!(
3192 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
3193 name, name
3194 ),
3195 line,
3196 )
3197 .into())
3198 }
3199
3200 fn check_strict_hash_var(&self, name: &str, line: usize) -> Result<(), FlowOrError> {
3201 if !self.strict_vars
3203 || name.contains("::")
3204 || self.scope.hash_binding_exists(name)
3205 || matches!(name, "+" | "-" | "ENV" | "SIG" | "!" | "^H")
3206 {
3207 return Ok(());
3208 }
3209 Err(StrykeError::runtime(
3210 format!(
3211 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
3212 name, name
3213 ),
3214 line,
3215 )
3216 .into())
3217 }
3218
3219 fn looks_like_version_only(spec: &str) -> bool {
3220 let t = spec.trim();
3221 !t.is_empty()
3222 && !t.contains('/')
3223 && !t.contains('\\')
3224 && !t.contains("::")
3225 && t.chars()
3226 .all(|c| c.is_ascii_digit() || c == '.' || c == '_' || c == 'v')
3227 && t.chars().any(|c| c.is_ascii_digit())
3228 }
3229
3230 fn module_spec_to_relpath(spec: &str) -> String {
3231 let t = spec.trim();
3232 if t.contains("::") {
3233 format!("{}.pm", t.replace("::", "/"))
3234 } else if t.ends_with(".pm") || t.ends_with(".pl") || t.contains('/') {
3235 t.replace('\\', "/")
3236 } else {
3237 format!("{}.pm", t)
3238 }
3239 }
3240
3241 fn try_resolve_via_lockfile(
3256 script_file: &str,
3257 relpath: &str,
3258 version: Option<&str>,
3259 ) -> LockfileResolution {
3260 let stem = relpath
3261 .strip_suffix(".pm")
3262 .or_else(|| relpath.strip_suffix(".pl"))
3263 .or_else(|| relpath.strip_suffix(".stk"))
3264 .unwrap_or(relpath);
3265 let logical = stem.replace('/', "::");
3266
3267 let mut anchors: Vec<std::path::PathBuf> = Vec::new();
3268 if !script_file.is_empty()
3269 && script_file != "-e"
3270 && script_file != "-"
3271 && script_file != "repl"
3272 {
3273 let script_dir = std::path::Path::new(script_file)
3274 .parent()
3275 .map(std::path::Path::to_path_buf)
3276 .unwrap_or_else(|| std::path::PathBuf::from("."));
3277 if let Some(root) = crate::pkg::commands::find_project_root(&script_dir) {
3278 anchors.push(root);
3279 }
3280 anchors.push(script_dir);
3281 }
3282 if let Ok(cwd) = std::env::current_dir() {
3283 if let Some(root) = crate::pkg::commands::find_project_root(&cwd) {
3284 anchors.push(root);
3285 }
3286 anchors.push(cwd);
3287 }
3288
3289 let mut pin_err: Option<String> = None;
3294 for anchor in anchors {
3295 match crate::pkg::commands::resolve_module(&anchor, &logical, version) {
3296 Ok(Some(found)) => return LockfileResolution::Found(found),
3297 Ok(None) => continue,
3298 Err(e) => {
3299 if pin_err.is_none() {
3300 pin_err = Some(e.to_string());
3301 }
3302 }
3303 }
3304 }
3305 if let Some(msg) = pin_err {
3306 return LockfileResolution::PinUnsatisfied(msg);
3307 }
3308 LockfileResolution::NotFound
3309 }
3310
3311 pub(crate) fn qualify_sub_key(&self, name: &str) -> String {
3316 if name.contains("::") {
3317 return name.to_string();
3318 }
3319 let pkg = self.current_package();
3320 if pkg.is_empty() || pkg == "main" {
3321 name.to_string()
3322 } else {
3323 format!("{}::{}", pkg, name)
3324 }
3325 }
3326
3327 pub(crate) fn undefined_subroutine_call_message(&self, name: &str) -> String {
3329 let mut msg = format!("Undefined subroutine &{}", name);
3330 if self.strict_subs {
3331 msg.push_str(
3332 " (strict subs: declare the sub or use a fully qualified name before calling)",
3333 );
3334 }
3335 msg
3336 }
3337
3338 pub(crate) fn undefined_subroutine_resolve_message(&self, name: &str) -> String {
3340 let mut msg = format!("Undefined subroutine {}", self.qualify_sub_key(name));
3341 if self.strict_subs {
3342 msg.push_str(
3343 " (strict subs: declare the sub or use a fully qualified name before calling)",
3344 );
3345 }
3346 msg
3347 }
3348
3349 fn import_alias_key(&self, short: &str) -> String {
3351 self.qualify_sub_key(short)
3352 }
3353
3354 fn is_explicit_empty_import_list(imports: &[Expr]) -> bool {
3356 if imports.len() == 1 {
3357 match &imports[0].kind {
3358 ExprKind::QW(ws) => return ws.is_empty(),
3359 ExprKind::List(xs) => return xs.is_empty(),
3361 _ => {}
3362 }
3363 }
3364 false
3365 }
3366
3367 fn apply_module_import(
3369 &mut self,
3370 module: &str,
3371 imports: &[Expr],
3372 line: usize,
3373 ) -> StrykeResult<()> {
3374 if imports.is_empty() {
3375 return self.import_all_from_module(module, line);
3376 }
3377 if Self::is_explicit_empty_import_list(imports) {
3378 return Ok(());
3379 }
3380 let names = Self::pragma_import_strings(imports, line)?;
3381 if names.is_empty() {
3382 return Ok(());
3383 }
3384 for name in names {
3385 self.import_one_symbol(module, &name, line)?;
3386 }
3387 Ok(())
3388 }
3389
3390 fn import_all_from_module(&mut self, module: &str, line: usize) -> StrykeResult<()> {
3391 if let Some(lists) = self.module_export_lists.get(module) {
3392 let export: Vec<String> = lists.export.clone();
3393 for short in export {
3394 self.import_named_sub(module, &short, line)?;
3395 }
3396 return Ok(());
3397 }
3398 let prefix = format!("{}::", module);
3400 let keys: Vec<String> = self
3401 .subs
3402 .keys()
3403 .filter(|k| k.starts_with(&prefix) && !k[prefix.len()..].contains("::"))
3404 .cloned()
3405 .collect();
3406 for k in keys {
3407 let short = k[prefix.len()..].to_string();
3408 if let Some(sub) = self.subs.get(&k).cloned() {
3409 let alias = self.import_alias_key(&short);
3410 self.subs.insert(alias, sub);
3411 }
3412 }
3413 Ok(())
3414 }
3415
3416 fn import_named_sub(&mut self, module: &str, short: &str, line: usize) -> StrykeResult<()> {
3418 let qual = format!("{}::{}", module, short);
3419 let sub = self.subs.get(&qual).cloned().ok_or_else(|| {
3420 StrykeError::runtime(
3421 format!(
3422 "`{}` is not defined in module `{}` (expected `{}`)",
3423 short, module, qual
3424 ),
3425 line,
3426 )
3427 })?;
3428 let alias = self.import_alias_key(short);
3429 self.subs.insert(alias, sub);
3430 Ok(())
3431 }
3432
3433 fn import_one_symbol(&mut self, module: &str, export: &str, line: usize) -> StrykeResult<()> {
3434 if let Some(lists) = self.module_export_lists.get(module) {
3435 let allowed: HashSet<&str> = lists
3436 .export
3437 .iter()
3438 .map(|s| s.as_str())
3439 .chain(lists.export_ok.iter().map(|s| s.as_str()))
3440 .collect();
3441 if !allowed.contains(export) {
3442 return Err(StrykeError::runtime(
3443 format!(
3444 "`{}` is not exported by `{}` (not in @EXPORT or @EXPORT_OK)",
3445 export, module
3446 ),
3447 line,
3448 ));
3449 }
3450 }
3451 self.import_named_sub(module, export, line)
3452 }
3453
3454 fn record_exporter_our_array_name(&mut self, name: &str, items: &[StrykeValue]) {
3456 if name != "EXPORT" && name != "EXPORT_OK" {
3457 return;
3458 }
3459 let pkg = self.current_package();
3460 if pkg.is_empty() || pkg == "main" {
3461 return;
3462 }
3463 let names: Vec<String> = items.iter().map(|v| v.to_string()).collect();
3464 let ent = self.module_export_lists.entry(pkg).or_default();
3465 if name == "EXPORT" {
3466 ent.export = names;
3467 } else {
3468 ent.export_ok = names;
3469 }
3470 }
3471
3472 pub(crate) fn rebind_sub_closure(&mut self, name: &str) {
3476 let key = self.qualify_sub_key(name);
3477 let Some(sub) = self.subs.get(&key).cloned() else {
3478 return;
3479 };
3480 let captured = self.scope.capture();
3481 let closure_env = if captured.is_empty() {
3482 None
3483 } else {
3484 Some(captured)
3485 };
3486 let mut new_sub = (*sub).clone();
3487 new_sub.closure_env = closure_env;
3488 new_sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&new_sub);
3489 self.subs.insert(key, Arc::new(new_sub));
3490 }
3491
3492 pub(crate) fn resolve_sub_by_name(&self, name: &str) -> Option<Arc<StrykeSub>> {
3493 if let Some(s) = self.subs.get(name) {
3494 return Some(s.clone());
3495 }
3496 if !name.contains("::") {
3497 let pkg = self.current_package();
3499 if !pkg.is_empty() && pkg != "main" {
3500 let mut q = String::with_capacity(pkg.len() + 2 + name.len());
3501 q.push_str(&pkg);
3502 q.push_str("::");
3503 q.push_str(name);
3504 return self.subs.get(&q).cloned();
3505 }
3506 return None;
3507 }
3508 if let Some(rest) = name.strip_prefix("main::") {
3513 if !rest.contains("::") {
3514 return self.subs.get(rest).cloned();
3515 }
3516 }
3517 None
3518 }
3519
3520 fn imports_after_leading_use_version(imports: &[Expr]) -> &[Expr] {
3523 if let Some(first) = imports.first() {
3524 if matches!(first.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
3525 return &imports[1..];
3526 }
3527 }
3528 imports
3529 }
3530
3531 fn pragma_import_strings(imports: &[Expr], default_line: usize) -> StrykeResult<Vec<String>> {
3533 let mut out = Vec::new();
3534 for e in imports {
3535 match &e.kind {
3536 ExprKind::String(s) => out.push(s.clone()),
3537 ExprKind::QW(ws) => out.extend(ws.iter().cloned()),
3538 ExprKind::Integer(n) => out.push(n.to_string()),
3539 ExprKind::InterpolatedString(parts) => {
3542 let mut s = String::new();
3543 for p in parts {
3544 match p {
3545 StringPart::Literal(l) => s.push_str(l),
3546 StringPart::ScalarVar(v) => {
3547 s.push('$');
3548 s.push_str(v);
3549 }
3550 StringPart::ArrayVar(v) => {
3551 s.push('@');
3552 s.push_str(v);
3553 }
3554 _ => {
3555 return Err(StrykeError::runtime(
3556 "pragma import must be a compile-time string, qw(), or integer",
3557 e.line.max(default_line),
3558 ));
3559 }
3560 }
3561 }
3562 out.push(s);
3563 }
3564 _ => {
3565 return Err(StrykeError::runtime(
3566 "pragma import must be a compile-time string, qw(), or integer",
3567 e.line.max(default_line),
3568 ));
3569 }
3570 }
3571 }
3572 Ok(out)
3573 }
3574
3575 fn apply_use_strict(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3576 if imports.is_empty() {
3577 self.strict_refs = true;
3578 self.strict_subs = true;
3579 self.strict_vars = true;
3580 return Ok(());
3581 }
3582 let names = Self::pragma_import_strings(imports, line)?;
3583 for name in names {
3584 match name.as_str() {
3585 "refs" => self.strict_refs = true,
3586 "subs" => self.strict_subs = true,
3587 "vars" => self.strict_vars = true,
3588 _ => {
3589 return Err(StrykeError::runtime(
3590 format!("Unknown strict mode `{}`", name),
3591 line,
3592 ));
3593 }
3594 }
3595 }
3596 Ok(())
3597 }
3598
3599 fn apply_no_strict(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3600 if imports.is_empty() {
3601 self.strict_refs = false;
3602 self.strict_subs = false;
3603 self.strict_vars = false;
3604 return Ok(());
3605 }
3606 let names = Self::pragma_import_strings(imports, line)?;
3607 for name in names {
3608 match name.as_str() {
3609 "refs" => self.strict_refs = false,
3610 "subs" => self.strict_subs = false,
3611 "vars" => self.strict_vars = false,
3612 _ => {
3613 return Err(StrykeError::runtime(
3614 format!("Unknown strict mode `{}`", name),
3615 line,
3616 ));
3617 }
3618 }
3619 }
3620 Ok(())
3621 }
3622
3623 fn apply_use_feature(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3624 let items = Self::pragma_import_strings(imports, line)?;
3625 if items.is_empty() {
3626 return Err(StrykeError::runtime(
3627 "use feature requires a feature name or bundle (e.g. qw(say) or :5.10)",
3628 line,
3629 ));
3630 }
3631 for item in items {
3632 let s = item.trim();
3633 if let Some(rest) = s.strip_prefix(':') {
3634 self.apply_feature_bundle(rest, line)?;
3635 } else {
3636 self.apply_feature_name(s, true, line)?;
3637 }
3638 }
3639 Ok(())
3640 }
3641
3642 fn apply_no_feature(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
3643 if imports.is_empty() {
3644 self.feature_bits = 0;
3645 return Ok(());
3646 }
3647 let items = Self::pragma_import_strings(imports, line)?;
3648 for item in items {
3649 let s = item.trim();
3650 if let Some(rest) = s.strip_prefix(':') {
3651 self.clear_feature_bundle(rest);
3652 } else {
3653 self.apply_feature_name(s, false, line)?;
3654 }
3655 }
3656 Ok(())
3657 }
3658
3659 fn apply_feature_bundle(&mut self, v: &str, line: usize) -> StrykeResult<()> {
3660 let key = v.trim();
3661 match key {
3662 "5.10" | "5.010" | "5.10.0" => {
3663 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3664 }
3665 "5.12" | "5.012" | "5.12.0" => {
3666 self.feature_bits |= FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS;
3667 }
3668 _ => {
3669 return Err(StrykeError::runtime(
3670 format!("unsupported feature bundle :{}", key),
3671 line,
3672 ));
3673 }
3674 }
3675 Ok(())
3676 }
3677
3678 fn clear_feature_bundle(&mut self, v: &str) {
3679 let key = v.trim();
3680 if matches!(
3681 key,
3682 "5.10" | "5.010" | "5.10.0" | "5.12" | "5.012" | "5.12.0"
3683 ) {
3684 self.feature_bits &= !(FEAT_SAY | FEAT_SWITCH | FEAT_STATE | FEAT_UNICODE_STRINGS);
3685 }
3686 }
3687
3688 fn apply_feature_name(&mut self, name: &str, enable: bool, line: usize) -> StrykeResult<()> {
3689 let bit = match name {
3690 "say" => FEAT_SAY,
3691 "state" => FEAT_STATE,
3692 "switch" => FEAT_SWITCH,
3693 "unicode_strings" => FEAT_UNICODE_STRINGS,
3694 "postderef"
3698 | "postderef_qq"
3699 | "evalbytes"
3700 | "current_sub"
3701 | "fc"
3702 | "lexical_subs"
3703 | "signatures"
3704 | "refaliasing"
3705 | "bitwise"
3706 | "isa"
3707 | "indirect"
3708 | "multidimensional"
3709 | "bareword_filehandles"
3710 | "try"
3711 | "defer"
3712 | "extra_paired_delimiters"
3713 | "module_true"
3714 | "class"
3715 | "array_base" => return Ok(()),
3716 _ => {
3717 return Err(StrykeError::runtime(
3718 format!("unknown feature `{}`", name),
3719 line,
3720 ));
3721 }
3722 };
3723 if enable {
3724 self.feature_bits |= bit;
3725 } else {
3726 self.feature_bits &= !bit;
3727 }
3728 Ok(())
3729 }
3730
3731 pub(crate) fn require_execute_versioned(
3737 &mut self,
3738 spec: &str,
3739 version: Option<&str>,
3740 line: usize,
3741 ) -> StrykeResult<StrykeValue> {
3742 if version.is_none() {
3746 return self.require_execute(spec, line);
3747 }
3748 let t = spec.trim();
3749 if t.is_empty() {
3750 return Err(StrykeError::runtime("require: empty argument", line));
3751 }
3752 let p = Path::new(t);
3753 if p.is_absolute() || t.starts_with("./") || t.starts_with("../") {
3754 return self.require_execute(spec, line);
3757 }
3758 if Self::looks_like_version_only(t) {
3759 return Ok(StrykeValue::integer(1));
3760 }
3761 let relpath = Self::module_spec_to_relpath(t);
3762 self.require_from_inc_versioned(&relpath, version, line)
3763 }
3764
3765 pub(crate) fn require_execute(&mut self, spec: &str, line: usize) -> StrykeResult<StrykeValue> {
3767 let t = spec.trim();
3768 if t.is_empty() {
3769 return Err(StrykeError::runtime("require: empty argument", line));
3770 }
3771 match t {
3772 "strict" => {
3773 self.apply_use_strict(&[], line)?;
3774 return Ok(StrykeValue::integer(1));
3775 }
3776 "utf8" => {
3777 self.utf8_pragma = true;
3778 return Ok(StrykeValue::integer(1));
3779 }
3780 "feature" | "v5" => {
3781 return Ok(StrykeValue::integer(1));
3782 }
3783 "warnings" => {
3784 self.warnings = true;
3785 return Ok(StrykeValue::integer(1));
3786 }
3787 "threads" | "Thread::Pool" | "Parallel::ForkManager" => {
3788 return Ok(StrykeValue::integer(1));
3789 }
3790 _ => {}
3791 }
3792 let p = Path::new(t);
3793 if p.is_absolute() {
3794 return self.require_absolute_path(p, line);
3795 }
3796 if t.starts_with("./") || t.starts_with("../") {
3797 return self.require_relative_path(p, line);
3798 }
3799 if Self::looks_like_version_only(t) {
3800 return Ok(StrykeValue::integer(1));
3801 }
3802 let relpath = Self::module_spec_to_relpath(t);
3803 self.require_from_inc(&relpath, line)
3804 }
3805
3806 fn invoke_require_hook(&mut self, key: &str, path: &str, line: usize) -> StrykeResult<()> {
3808 let v = self.scope.get_hash_element("^HOOK", key);
3809 if v.is_undef() {
3810 return Ok(());
3811 }
3812 let Some(sub) = v.as_code_ref() else {
3813 return Ok(());
3814 };
3815 let r = self.call_sub(
3816 sub.as_ref(),
3817 vec![StrykeValue::string(path.to_string())],
3818 WantarrayCtx::Scalar,
3819 line,
3820 );
3821 match r {
3822 Ok(_) => Ok(()),
3823 Err(FlowOrError::Error(e)) => Err(e),
3824 Err(FlowOrError::Flow(Flow::Return(_))) => Ok(()),
3825 Err(FlowOrError::Flow(other)) => Err(StrykeError::runtime(
3826 format!(
3827 "require hook {:?} returned unexpected control flow: {:?}",
3828 key, other
3829 ),
3830 line,
3831 )),
3832 }
3833 }
3834
3835 fn require_absolute_path(&mut self, path: &Path, line: usize) -> StrykeResult<StrykeValue> {
3836 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
3837 let key = canon.to_string_lossy().into_owned();
3838 if self.scope.exists_hash_element("INC", &key) {
3839 return Ok(StrykeValue::integer(1));
3840 }
3841 self.invoke_require_hook("require__before", &key, line)?;
3842 let code = read_file_text_perl_compat(&canon).map_err(|e| {
3843 StrykeError::runtime(
3844 format!("Can't open {} for reading: {}", canon.display(), e),
3845 line,
3846 )
3847 })?;
3848 let code = crate::data_section::strip_perl_end_marker(&code);
3849 self.scope
3850 .set_hash_element("INC", &key, StrykeValue::string(key.clone()))?;
3851 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3852 let r = crate::parse_and_run_module_in_file(code, self, &key);
3853 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3854 r?;
3855 self.invoke_require_hook("require__after", &key, line)?;
3856 Ok(StrykeValue::integer(1))
3857 }
3858
3859 fn require_relative_path(&mut self, path: &Path, line: usize) -> StrykeResult<StrykeValue> {
3860 if path.exists() {
3864 return self.require_absolute_path(path, line);
3865 }
3866 if !self.file.is_empty() {
3877 let caller = Path::new(&self.file);
3878 let mut anchor = caller.parent();
3879 while let Some(dir) = anchor {
3880 let candidate = dir.join(path);
3881 if candidate.exists() {
3882 return self.require_absolute_path(&candidate, line);
3883 }
3884 anchor = dir.parent();
3885 if anchor.map(|p| p.as_os_str().is_empty()).unwrap_or(false) {
3888 break;
3889 }
3890 }
3891 }
3892 Err(StrykeError::runtime(
3893 format!(
3894 "Can't locate {} (relative path does not exist)",
3895 path.display()
3896 ),
3897 line,
3898 ))
3899 }
3900
3901 fn require_from_inc(&mut self, relpath: &str, line: usize) -> StrykeResult<StrykeValue> {
3902 self.require_from_inc_versioned(relpath, None, line)
3903 }
3904
3905 fn require_from_inc_versioned(
3906 &mut self,
3907 relpath: &str,
3908 version: Option<&str>,
3909 line: usize,
3910 ) -> StrykeResult<StrykeValue> {
3911 if self.scope.exists_hash_element("INC", relpath) {
3912 return Ok(StrykeValue::integer(1));
3913 }
3914 self.invoke_require_hook("require__before", relpath, line)?;
3915
3916 match Self::try_resolve_via_lockfile(&self.file, relpath, version) {
3928 LockfileResolution::Found(found) => {
3929 let code = read_file_text_perl_compat(&found).map_err(|e| {
3930 StrykeError::runtime(
3931 format!("Can't open {} for reading: {}", found.display(), e),
3932 line,
3933 )
3934 })?;
3935 let code = crate::data_section::strip_perl_end_marker(&code);
3936 let abs = found.canonicalize().unwrap_or(found);
3937 let abs_s = abs.to_string_lossy().into_owned();
3938 self.scope
3939 .set_hash_element("INC", relpath, StrykeValue::string(abs_s.clone()))?;
3940 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3941 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3942 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3943 r?;
3944 self.invoke_require_hook("require__after", relpath, line)?;
3945 return Ok(StrykeValue::integer(1));
3946 }
3947 LockfileResolution::PinUnsatisfied(msg) => {
3955 return Err(StrykeError::runtime(msg, line));
3956 }
3957 LockfileResolution::NotFound => {}
3958 }
3959
3960 if let Some(code) = self.virtual_modules.get(relpath).cloned() {
3962 let code = crate::data_section::strip_perl_end_marker(&code);
3963 self.scope.set_hash_element(
3964 "INC",
3965 relpath,
3966 StrykeValue::string(format!("(virtual)/{}", relpath)),
3967 )?;
3968 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3969 let r = crate::parse_and_run_module_in_file(code, self, relpath);
3970 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3971 r?;
3972 self.invoke_require_hook("require__after", relpath, line)?;
3973 return Ok(StrykeValue::integer(1));
3974 }
3975
3976 for dir in self.inc_directories() {
3977 let full = Path::new(&dir).join(relpath);
3978 if full.is_file() {
3979 let code = read_file_text_perl_compat(&full).map_err(|e| {
3980 StrykeError::runtime(
3981 format!("Can't open {} for reading: {}", full.display(), e),
3982 line,
3983 )
3984 })?;
3985 let code = crate::data_section::strip_perl_end_marker(&code);
3986 let abs = full.canonicalize().unwrap_or(full);
3987 let abs_s = abs.to_string_lossy().into_owned();
3988 self.scope
3989 .set_hash_element("INC", relpath, StrykeValue::string(abs_s.clone()))?;
3990 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
3991 let r = crate::parse_and_run_module_in_file(code, self, &abs_s);
3992 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
3993 r?;
3994 self.invoke_require_hook("require__after", relpath, line)?;
3995 return Ok(StrykeValue::integer(1));
3996 }
3997 }
3998 Err(StrykeError::runtime(
3999 format!(
4000 "Can't locate {} in @INC (push paths onto @INC or use -I DIR)",
4001 relpath
4002 ),
4003 line,
4004 ))
4005 }
4006
4007 pub fn register_virtual_module(&mut self, path: String, source: String) {
4009 self.virtual_modules.insert(path, source);
4010 }
4011
4012 pub(crate) fn exec_use_stmt(
4014 &mut self,
4015 module: &str,
4016 imports: &[Expr],
4017 version: Option<&str>,
4018 line: usize,
4019 ) -> StrykeResult<()> {
4020 match module {
4021 "strict" => self.apply_use_strict(imports, line),
4022 "utf8" => {
4023 if !imports.is_empty() {
4024 return Err(StrykeError::runtime("use utf8 takes no arguments", line));
4025 }
4026 self.utf8_pragma = true;
4027 Ok(())
4028 }
4029 "feature" => self.apply_use_feature(imports, line),
4030 "v5" => Ok(()),
4031 "warnings" => {
4032 self.warnings = true;
4033 Ok(())
4034 }
4035 "English" => {
4036 self.english_enabled = true;
4037 let args = Self::pragma_import_strings(imports, line)?;
4038 let no_match = args.iter().any(|a| a == "-no_match_vars");
4039 if !no_match {
4043 self.english_match_vars_ever_enabled = true;
4044 }
4045 self.english_no_match_vars = no_match && !self.english_match_vars_ever_enabled;
4046 Ok(())
4047 }
4048 "Env" => self.apply_use_env(imports, line),
4049 "open" => self.apply_use_open(imports, line),
4050 "constant" => self.apply_use_constant(imports, line),
4051 "bigint" | "bignum" | "bigrat" => {
4052 crate::set_bigint_pragma(true);
4059 Ok(())
4060 }
4061 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
4062 _ => {
4063 self.require_execute_versioned(module, version, line)?;
4064 let imports = Self::imports_after_leading_use_version(imports);
4065 self.apply_module_import(module, imports, line)?;
4066 Ok(())
4067 }
4068 }
4069 }
4070
4071 pub(crate) fn exec_no_stmt(
4073 &mut self,
4074 module: &str,
4075 imports: &[Expr],
4076 line: usize,
4077 ) -> StrykeResult<()> {
4078 match module {
4079 "strict" => self.apply_no_strict(imports, line),
4080 "utf8" => {
4081 if !imports.is_empty() {
4082 return Err(StrykeError::runtime("no utf8 takes no arguments", line));
4083 }
4084 self.utf8_pragma = false;
4085 Ok(())
4086 }
4087 "feature" => self.apply_no_feature(imports, line),
4088 "v5" => Ok(()),
4089 "warnings" => {
4090 self.warnings = false;
4091 Ok(())
4092 }
4093 "English" => {
4094 self.english_enabled = false;
4095 if !self.english_match_vars_ever_enabled {
4098 self.english_no_match_vars = false;
4099 }
4100 Ok(())
4101 }
4102 "open" => {
4103 self.open_pragma_utf8 = false;
4104 Ok(())
4105 }
4106 "bigint" | "bignum" | "bigrat" => {
4107 crate::set_bigint_pragma(false);
4108 Ok(())
4109 }
4110 "threads" | "Thread::Pool" | "Parallel::ForkManager" => Ok(()),
4111 _ => Ok(()),
4112 }
4113 }
4114
4115 fn apply_use_env(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
4117 let names = Self::pragma_import_strings(imports, line)?;
4118 for n in names {
4119 let key = n.trim_start_matches('@');
4120 if key.eq_ignore_ascii_case("PATH") {
4121 let path_env = std::env::var("PATH").unwrap_or_default();
4122 let path_vec: Vec<StrykeValue> = std::env::split_paths(&path_env)
4123 .map(|p| StrykeValue::string(p.to_string_lossy().into_owned()))
4124 .collect();
4125 let aname = self.stash_array_name_for_package("PATH");
4126 self.scope.declare_array(&aname, path_vec);
4127 }
4128 }
4129 Ok(())
4130 }
4131
4132 fn apply_use_open(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
4134 let items = Self::pragma_import_strings(imports, line)?;
4135 for item in items {
4136 let s = item.trim();
4137 if s.eq_ignore_ascii_case(":utf8") || s == ":std" || s.eq_ignore_ascii_case("std") {
4138 self.open_pragma_utf8 = true;
4139 continue;
4140 }
4141 if let Some(rest) = s.strip_prefix(":encoding(") {
4142 if let Some(inner) = rest.strip_suffix(')') {
4143 if inner.eq_ignore_ascii_case("UTF-8") || inner.eq_ignore_ascii_case("utf8") {
4144 self.open_pragma_utf8 = true;
4145 }
4146 }
4147 }
4148 }
4149 Ok(())
4150 }
4151
4152 fn apply_use_constant(&mut self, imports: &[Expr], line: usize) -> StrykeResult<()> {
4154 if imports.is_empty() {
4155 return Ok(());
4156 }
4157 if imports.len() == 1 {
4159 match &imports[0].kind {
4160 ExprKind::Float(_) | ExprKind::Integer(_) => return Ok(()),
4161 _ => {}
4162 }
4163 }
4164 for imp in imports {
4165 match &imp.kind {
4166 ExprKind::List(items) => {
4167 if items.len() % 2 != 0 {
4168 return Err(StrykeError::runtime(
4169 format!(
4170 "use constant: expected even-length list of NAME => VALUE pairs, got {}",
4171 items.len()
4172 ),
4173 line,
4174 ));
4175 }
4176 let mut i = 0;
4177 while i < items.len() {
4178 let name = self.use_constant_name_from_expr(&items[i], line)?;
4179 self.install_constant_from_expr(&name, &items[i + 1], line)?;
4180 i += 2;
4181 }
4182 }
4183 ExprKind::HashRef(pairs) => {
4189 for (key_expr, value_expr) in pairs {
4190 let name = self.use_constant_name_from_expr(key_expr, line)?;
4191 self.install_constant_from_expr(&name, value_expr, line)?;
4192 }
4193 }
4194 _ => {
4195 return Err(StrykeError::runtime(
4196 "use constant: expected list of NAME => VALUE pairs",
4197 line,
4198 ));
4199 }
4200 }
4201 }
4202 Ok(())
4203 }
4204
4205 fn use_constant_name_from_expr(&self, e: &Expr, line: usize) -> StrykeResult<String> {
4210 match &e.kind {
4211 ExprKind::String(s) => Ok(s.clone()),
4212 ExprKind::Bareword(s) => Ok(s.clone()),
4213 _ => Err(StrykeError::runtime(
4214 "use constant: constant name must be a string literal",
4215 line,
4216 )),
4217 }
4218 }
4219
4220 fn install_constant_from_expr(
4229 &mut self,
4230 name: &str,
4231 value: &Expr,
4232 line: usize,
4233 ) -> StrykeResult<()> {
4234 if matches!(value.kind, ExprKind::List(_)) {
4242 let key = self.qualify_sub_key(name);
4243 let body = vec![Statement {
4244 label: None,
4245 kind: StmtKind::Expression(value.clone()),
4246 line,
4247 }];
4248 self.subs.insert(
4249 key.clone(),
4250 Arc::new(StrykeSub {
4251 name: key,
4252 params: vec![],
4253 body,
4254 prototype: None,
4255 closure_env: None,
4256 fib_like: None,
4257 }),
4258 );
4259 return Ok(());
4260 }
4261 let val = match self.eval_expr(value) {
4264 Ok(v) => v,
4265 Err(FlowOrError::Error(e)) => return Err(e),
4266 Err(FlowOrError::Flow(_)) => {
4267 return Err(StrykeError::runtime(
4268 "use constant: unexpected control flow in initializer",
4269 line,
4270 ));
4271 }
4272 };
4273 self.install_constant_sub(name, &val, line)
4274 }
4275
4276 fn install_constant_sub(
4277 &mut self,
4278 name: &str,
4279 val: &StrykeValue,
4280 line: usize,
4281 ) -> StrykeResult<()> {
4282 let key = self.qualify_sub_key(name);
4283 let ret_expr = self.perl_value_to_const_literal_expr(val, line)?;
4284 let body = vec![Statement {
4285 label: None,
4286 kind: StmtKind::Return(Some(ret_expr)),
4287 line,
4288 }];
4289 self.subs.insert(
4290 key.clone(),
4291 Arc::new(StrykeSub {
4292 name: key,
4293 params: vec![],
4294 body,
4295 prototype: None,
4296 closure_env: None,
4297 fib_like: None,
4298 }),
4299 );
4300 Ok(())
4301 }
4302
4303 fn perl_value_to_const_literal_expr(&self, v: &StrykeValue, line: usize) -> StrykeResult<Expr> {
4305 if v.is_undef() {
4306 return Ok(Expr {
4307 kind: ExprKind::Undef,
4308 line,
4309 });
4310 }
4311 if let Some(n) = v.as_integer() {
4312 return Ok(Expr {
4313 kind: ExprKind::Integer(n),
4314 line,
4315 });
4316 }
4317 if let Some(f) = v.as_float() {
4318 return Ok(Expr {
4319 kind: ExprKind::Float(f),
4320 line,
4321 });
4322 }
4323 if let Some(s) = v.as_str() {
4324 return Ok(Expr {
4325 kind: ExprKind::String(s),
4326 line,
4327 });
4328 }
4329 if let Some(arr) = v.as_array_vec() {
4330 let mut elems = Vec::with_capacity(arr.len());
4331 for e in &arr {
4332 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
4333 }
4334 return Ok(Expr {
4335 kind: ExprKind::ArrayRef(elems),
4336 line,
4337 });
4338 }
4339 if let Some(h) = v.as_hash_map() {
4340 let mut pairs = Vec::with_capacity(h.len());
4341 for (k, vv) in h.iter() {
4342 pairs.push((
4343 Expr {
4344 kind: ExprKind::String(k.clone()),
4345 line,
4346 },
4347 self.perl_value_to_const_literal_expr(vv, line)?,
4348 ));
4349 }
4350 return Ok(Expr {
4351 kind: ExprKind::HashRef(pairs),
4352 line,
4353 });
4354 }
4355 if let Some(aref) = v.as_array_ref() {
4356 let arr = aref.read();
4357 let mut elems = Vec::with_capacity(arr.len());
4358 for e in arr.iter() {
4359 elems.push(self.perl_value_to_const_literal_expr(e, line)?);
4360 }
4361 return Ok(Expr {
4362 kind: ExprKind::ArrayRef(elems),
4363 line,
4364 });
4365 }
4366 if let Some(href) = v.as_hash_ref() {
4367 let h = href.read();
4368 let mut pairs = Vec::with_capacity(h.len());
4369 for (k, vv) in h.iter() {
4370 pairs.push((
4371 Expr {
4372 kind: ExprKind::String(k.clone()),
4373 line,
4374 },
4375 self.perl_value_to_const_literal_expr(vv, line)?,
4376 ));
4377 }
4378 return Ok(Expr {
4379 kind: ExprKind::HashRef(pairs),
4380 line,
4381 });
4382 }
4383 Err(StrykeError::runtime(
4384 format!("use constant: unsupported value type ({v:?})"),
4385 line,
4386 ))
4387 }
4388
4389 pub(crate) fn prepare_program_top_level(&mut self, program: &Program) -> StrykeResult<()> {
4391 self.utf8_pragma = false;
4397 for stmt in &program.statements {
4398 match &stmt.kind {
4399 StmtKind::Package { name } => {
4400 let _ = self
4401 .scope
4402 .set_scalar("__PACKAGE__", StrykeValue::string(name.clone()));
4403 }
4404 StmtKind::SubDecl {
4405 name,
4406 params,
4407 body,
4408 prototype,
4409 } => {
4410 let key = self.qualify_sub_key(name);
4411 let mut sub = StrykeSub {
4412 name: name.clone(),
4413 params: params.clone(),
4414 body: body.clone(),
4415 closure_env: None,
4416 prototype: prototype.clone(),
4417 fib_like: None,
4418 };
4419 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
4420 self.subs.insert(key, Arc::new(sub));
4421 }
4422 StmtKind::UsePerlVersion { .. } => {}
4423 StmtKind::Use { module, imports, version } => {
4424 self.exec_use_stmt(module, imports, version.as_deref(), stmt.line)?;
4425 }
4426 StmtKind::UseOverload { pairs } => {
4427 self.install_use_overload_pairs(pairs);
4428 }
4429 StmtKind::FormatDecl { name, lines } => {
4430 self.install_format_decl(name, lines, stmt.line)?;
4431 }
4432 StmtKind::No { module, imports } => {
4433 self.exec_no_stmt(module, imports, stmt.line)?;
4434 }
4435 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
4436 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
4437 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
4438 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
4439 StmtKind::End(block) => self.end_blocks.push(block.clone()),
4440 _ => {}
4441 }
4442 }
4443 Ok(())
4444 }
4445
4446 pub fn install_data_handle(&mut self, data: Vec<u8>) {
4448 self.input_handles.insert(
4449 "DATA".to_string(),
4450 BufReader::new(Box::new(Cursor::new(data)) as Box<dyn Read + Send>),
4451 );
4452 }
4453
4454 #[inline]
4456 pub(crate) fn resolve_stryke_path(&self, path: &str) -> PathBuf {
4457 if path.is_empty() {
4458 return self.stryke_pwd.clone();
4459 }
4460 let p = Path::new(path);
4461 if p.is_absolute() {
4462 PathBuf::from(path)
4463 } else {
4464 self.stryke_pwd.join(path)
4465 }
4466 }
4467
4468 pub(crate) fn resolve_stryke_path_string(&self, path: &str) -> String {
4469 self.resolve_stryke_path(path)
4470 .to_string_lossy()
4471 .into_owned()
4472 }
4473
4474 pub(crate) fn builtin_cd_execute(
4478 &mut self,
4479 args: &[StrykeValue],
4480 _line: usize,
4481 ) -> StrykeResult<StrykeValue> {
4482 let dest: PathBuf = if args.is_empty() {
4483 let home = std::env::var_os("HOME")
4484 .or_else(|| std::env::var_os("USERPROFILE"))
4485 .map(PathBuf::from);
4486 let Some(h) = home else {
4487 return Ok(StrykeValue::integer(0));
4488 };
4489 h
4490 } else {
4491 let raw = args[0].to_string();
4492 if raw.is_empty() {
4493 return Ok(StrykeValue::integer(0));
4494 }
4495 self.resolve_stryke_path(&raw)
4496 };
4497 match std::fs::metadata(&dest) {
4498 Ok(m) if m.is_dir() => {
4499 self.stryke_pwd = std::fs::canonicalize(&dest).unwrap_or(dest);
4500 Ok(StrykeValue::integer(1))
4501 }
4502 Ok(_) => Ok(StrykeValue::integer(0)),
4503 Err(e) => {
4504 self.apply_io_error_to_errno(&e);
4505 Ok(StrykeValue::integer(0))
4506 }
4507 }
4508 }
4509
4510 pub(crate) fn open_builtin_execute(
4516 &mut self,
4517 handle_name: String,
4518 mode_s: String,
4519 file_opt: Option<String>,
4520 line: usize,
4521 ) -> StrykeResult<StrykeValue> {
4522 let (actual_mode, path) = if let Some(f) = file_opt {
4527 (mode_s, f)
4528 } else {
4529 let trimmed = mode_s.trim();
4530 if let Some(rest) = trimmed.strip_prefix('|') {
4531 ("|-".to_string(), rest.trim_start().to_string())
4532 } else if trimmed.ends_with('|') {
4533 let mut cmd = trimmed.to_string();
4534 cmd.pop(); ("-|".to_string(), cmd.trim_end().to_string())
4536 } else if let Some(rest) = trimmed.strip_prefix(">>") {
4537 (">>".to_string(), rest.trim().to_string())
4538 } else if let Some(rest) = trimmed.strip_prefix('>') {
4539 (">".to_string(), rest.trim().to_string())
4540 } else if let Some(rest) = trimmed.strip_prefix('<') {
4541 ("<".to_string(), rest.trim().to_string())
4542 } else {
4543 ("<".to_string(), trimmed.to_string())
4544 }
4545 };
4546 let handle_return = handle_name.clone();
4547 let file_path = match actual_mode.as_str() {
4548 "<" | ">" | ">>" => self.resolve_stryke_path_string(&path),
4549 _ => path.clone(),
4550 };
4551 match actual_mode.as_str() {
4552 "-|" => {
4553 let mut cmd = piped_shell_command(&path);
4554 cmd.stdout(Stdio::piped());
4555 let mut child = cmd.spawn().map_err(|e| {
4556 self.apply_io_error_to_errno(&e);
4557 StrykeError::runtime(format!("Can't open pipe from command: {}", e), line)
4558 })?;
4559 let stdout = child
4560 .stdout
4561 .take()
4562 .ok_or_else(|| StrykeError::runtime("pipe: child has no stdout", line))?;
4563 self.input_handles
4564 .insert(handle_name.clone(), BufReader::new(Box::new(stdout)));
4565 self.pipe_children.insert(handle_name, child);
4566 }
4567 "|-" => {
4568 let mut cmd = piped_shell_command(&path);
4569 cmd.stdin(Stdio::piped());
4570 let mut child = cmd.spawn().map_err(|e| {
4571 self.apply_io_error_to_errno(&e);
4572 StrykeError::runtime(format!("Can't open pipe to command: {}", e), line)
4573 })?;
4574 let stdin = child
4575 .stdin
4576 .take()
4577 .ok_or_else(|| StrykeError::runtime("pipe: child has no stdin", line))?;
4578 self.output_handles
4579 .insert(handle_name.clone(), Box::new(stdin));
4580 self.pipe_children.insert(handle_name, child);
4581 }
4582 "<" => {
4583 let file = match std::fs::File::open(&file_path) {
4584 Ok(f) => f,
4585 Err(e) => {
4586 self.apply_io_error_to_errno(&e);
4587 return Ok(StrykeValue::integer(0));
4588 }
4589 };
4590 let shared = Arc::new(Mutex::new(file));
4591 self.io_file_slots
4592 .insert(handle_name.clone(), Arc::clone(&shared));
4593 self.input_handles.insert(
4594 handle_name.clone(),
4595 BufReader::new(Box::new(IoSharedFile(Arc::clone(&shared)))),
4596 );
4597 }
4598 ">" => {
4599 let file = match std::fs::File::create(&file_path) {
4600 Ok(f) => f,
4601 Err(e) => {
4602 self.apply_io_error_to_errno(&e);
4603 return Ok(StrykeValue::integer(0));
4604 }
4605 };
4606 let shared = Arc::new(Mutex::new(file));
4607 self.io_file_slots
4608 .insert(handle_name.clone(), Arc::clone(&shared));
4609 self.output_handles.insert(
4610 handle_name.clone(),
4611 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4612 );
4613 }
4614 ">>" => {
4615 let file = match std::fs::OpenOptions::new()
4616 .append(true)
4617 .create(true)
4618 .open(&file_path)
4619 {
4620 Ok(f) => f,
4621 Err(e) => {
4622 self.apply_io_error_to_errno(&e);
4623 return Ok(StrykeValue::integer(0));
4624 }
4625 };
4626 let shared = Arc::new(Mutex::new(file));
4627 self.io_file_slots
4628 .insert(handle_name.clone(), Arc::clone(&shared));
4629 self.output_handles.insert(
4630 handle_name.clone(),
4631 Box::new(IoSharedFileWrite(Arc::clone(&shared))),
4632 );
4633 }
4634 _ => {
4635 return Err(StrykeError::runtime(
4636 format!("Unknown open mode '{}'", actual_mode),
4637 line,
4638 ));
4639 }
4640 }
4641 Ok(StrykeValue::io_handle(handle_return))
4642 }
4643
4644 pub(crate) fn eval_chunk_by_builtin(
4648 &mut self,
4649 key_spec: &Expr,
4650 list_expr: &Expr,
4651 ctx: WantarrayCtx,
4652 line: usize,
4653 ) -> ExecResult {
4654 let list = self.eval_expr_ctx(list_expr, WantarrayCtx::List)?.to_list();
4655 let chunks = match &key_spec.kind {
4656 ExprKind::CodeRef { .. } => {
4657 let cr = self.eval_expr(key_spec)?;
4658 let Some(sub) = cr.as_code_ref() else {
4659 return Err(StrykeError::runtime(
4660 "group_by/chunk_by: first argument must be { BLOCK }",
4661 line,
4662 )
4663 .into());
4664 };
4665 let sub = sub.clone();
4666 let mut chunks: Vec<StrykeValue> = Vec::new();
4667 let mut run: Vec<StrykeValue> = Vec::new();
4668 let mut prev_key: Option<StrykeValue> = None;
4669 for item in list {
4670 self.scope.set_topic(item.clone());
4671 let key = match self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line) {
4672 Ok(k) => k,
4673 Err(FlowOrError::Error(e)) => return Err(FlowOrError::Error(e)),
4674 Err(FlowOrError::Flow(Flow::Return(v))) => v,
4675 Err(_) => StrykeValue::UNDEF,
4676 };
4677 match &prev_key {
4678 None => {
4679 run.push(item);
4680 prev_key = Some(key);
4681 }
4682 Some(pk) => {
4683 if key.str_eq(pk) {
4684 run.push(item);
4685 } else {
4686 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(
4687 std::mem::take(&mut run),
4688 ))));
4689 run.push(item);
4690 prev_key = Some(key);
4691 }
4692 }
4693 }
4694 }
4695 if !run.is_empty() {
4696 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(run))));
4697 }
4698 chunks
4699 }
4700 _ => {
4701 let mut chunks: Vec<StrykeValue> = Vec::new();
4702 let mut run: Vec<StrykeValue> = Vec::new();
4703 let mut prev_key: Option<StrykeValue> = None;
4704 for item in list {
4705 self.scope.set_topic(item.clone());
4706 let key = self.eval_expr_ctx(key_spec, WantarrayCtx::Scalar)?;
4707 match &prev_key {
4708 None => {
4709 run.push(item);
4710 prev_key = Some(key);
4711 }
4712 Some(pk) => {
4713 if key.str_eq(pk) {
4714 run.push(item);
4715 } else {
4716 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(
4717 std::mem::take(&mut run),
4718 ))));
4719 run.push(item);
4720 prev_key = Some(key);
4721 }
4722 }
4723 }
4724 }
4725 if !run.is_empty() {
4726 chunks.push(StrykeValue::array_ref(Arc::new(RwLock::new(run))));
4727 }
4728 chunks
4729 }
4730 };
4731 Ok(match ctx {
4732 WantarrayCtx::List => StrykeValue::array(chunks),
4733 WantarrayCtx::Scalar => StrykeValue::integer(chunks.len() as i64),
4734 WantarrayCtx::Void => StrykeValue::UNDEF,
4735 })
4736 }
4737
4738 pub(crate) fn list_higher_order_block_builtin(
4740 &mut self,
4741 name: &str,
4742 args: &[StrykeValue],
4743 line: usize,
4744 ) -> StrykeResult<StrykeValue> {
4745 match self.list_higher_order_block_builtin_exec(name, args, line) {
4746 Ok(v) => Ok(v),
4747 Err(FlowOrError::Error(e)) => Err(e),
4748 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
4749 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
4750 format!("{name}: unsupported control flow in block"),
4751 line,
4752 )),
4753 }
4754 }
4755
4756 fn list_higher_order_block_builtin_exec(
4757 &mut self,
4758 name: &str,
4759 args: &[StrykeValue],
4760 line: usize,
4761 ) -> ExecResult {
4762 if args.is_empty() {
4763 return Err(
4764 StrykeError::runtime(format!("{name}: expected {{ BLOCK }}, LIST"), line).into(),
4765 );
4766 }
4767 let Some(sub) = args[0].as_code_ref() else {
4768 return Err(StrykeError::runtime(
4769 format!("{name}: first argument must be {{ BLOCK }}"),
4770 line,
4771 )
4772 .into());
4773 };
4774 let sub = sub.clone();
4775 let items: Vec<StrykeValue> = args[1..].to_vec();
4776 if matches!(name, "tap" | "peek") && items.len() == 1 {
4777 if let Some(p) = items[0].as_pipeline() {
4778 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
4779 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
4780 }
4781 let v = &items[0];
4782 if v.is_iterator() || v.as_array_vec().is_some() {
4783 let source = crate::map_stream::into_pull_iter(v.clone());
4784 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
4785 return Ok(StrykeValue::iterator(Arc::new(
4786 crate::map_stream::TapIterator::new(
4787 source,
4788 sub,
4789 self.subs.clone(),
4790 capture,
4791 atomic_arrays,
4792 atomic_hashes,
4793 ),
4794 )));
4795 }
4796 }
4797 let wa = self.wantarray_kind;
4802 match name {
4803 "take_while" => {
4804 let mut out = Vec::new();
4805 for item in items {
4806 self.scope.set_topic(item.clone());
4811 let pred =
4812 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4813 if !pred.is_true() {
4814 break;
4815 }
4816 out.push(item);
4817 }
4818 Ok(match wa {
4819 WantarrayCtx::List => StrykeValue::array(out),
4820 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4821 WantarrayCtx::Void => StrykeValue::UNDEF,
4822 })
4823 }
4824 "drop_while" | "skip_while" => {
4825 let mut i = 0usize;
4826 while i < items.len() {
4827 let it = items[i].clone();
4828 self.scope.set_topic(it.clone());
4829 let pred = self.call_sub(&sub, vec![it], WantarrayCtx::Scalar, line)?;
4830 if !pred.is_true() {
4831 break;
4832 }
4833 i += 1;
4834 }
4835 let rest = items[i..].to_vec();
4836 Ok(match wa {
4837 WantarrayCtx::List => StrykeValue::array(rest),
4838 WantarrayCtx::Scalar => StrykeValue::integer(rest.len() as i64),
4839 WantarrayCtx::Void => StrykeValue::UNDEF,
4840 })
4841 }
4842 "reject" | "grepv" => {
4843 let mut out = Vec::new();
4844 for item in items {
4845 self.scope.set_topic(item.clone());
4846 let pred =
4847 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4848 if !pred.is_true() {
4849 out.push(item);
4850 }
4851 }
4852 Ok(match wa {
4853 WantarrayCtx::List => StrykeValue::array(out),
4854 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4855 WantarrayCtx::Void => StrykeValue::UNDEF,
4856 })
4857 }
4858 "tap" | "peek" => {
4859 let _ = self.call_sub(&sub, items.clone(), WantarrayCtx::Void, line)?;
4860 Ok(match wa {
4861 WantarrayCtx::List => StrykeValue::array(items),
4862 WantarrayCtx::Scalar => StrykeValue::integer(items.len() as i64),
4863 WantarrayCtx::Void => StrykeValue::UNDEF,
4864 })
4865 }
4866 "partition" => {
4867 let mut yes = Vec::new();
4868 let mut no = Vec::new();
4869 for item in items {
4870 self.scope.set_topic(item.clone());
4871 let pred =
4872 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4873 if pred.is_true() {
4874 yes.push(item);
4875 } else {
4876 no.push(item);
4877 }
4878 }
4879 let yes_ref = StrykeValue::array_ref(Arc::new(RwLock::new(yes)));
4880 let no_ref = StrykeValue::array_ref(Arc::new(RwLock::new(no)));
4881 Ok(match wa {
4882 WantarrayCtx::List => StrykeValue::array(vec![yes_ref, no_ref]),
4883 WantarrayCtx::Scalar => StrykeValue::integer(2),
4884 WantarrayCtx::Void => StrykeValue::UNDEF,
4885 })
4886 }
4887 "min_by" => {
4888 let mut best: Option<(StrykeValue, StrykeValue)> = None;
4889 for item in items {
4890 self.scope.set_topic(item.clone());
4891 let key =
4892 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4893 best = Some(match best {
4894 None => (item, key),
4895 Some((bv, bk)) => {
4896 if key.num_cmp(&bk) == std::cmp::Ordering::Less {
4897 (item, key)
4898 } else {
4899 (bv, bk)
4900 }
4901 }
4902 });
4903 }
4904 Ok(best.map(|(v, _)| v).unwrap_or(StrykeValue::UNDEF))
4905 }
4906 "max_by" => {
4907 let mut best: Option<(StrykeValue, StrykeValue)> = None;
4908 for item in items {
4909 self.scope.set_topic(item.clone());
4910 let key =
4911 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?;
4912 best = Some(match best {
4913 None => (item, key),
4914 Some((bv, bk)) => {
4915 if key.num_cmp(&bk) == std::cmp::Ordering::Greater {
4916 (item, key)
4917 } else {
4918 (bv, bk)
4919 }
4920 }
4921 });
4922 }
4923 Ok(best.map(|(v, _)| v).unwrap_or(StrykeValue::UNDEF))
4924 }
4925 "zip_with" => {
4926 let flat: Vec<StrykeValue> = items.into_iter().flat_map(|a| a.to_list()).collect();
4929 let refs: Vec<Vec<StrykeValue>> = flat
4930 .iter()
4931 .map(|el| {
4932 if let Some(ar) = el.as_array_ref() {
4933 ar.read().clone()
4934 } else if let Some(name) = el.as_array_binding_name() {
4935 self.scope.get_array(&name)
4936 } else {
4937 vec![el.clone()]
4938 }
4939 })
4940 .collect();
4941 let max_len = refs.iter().map(|l| l.len()).max().unwrap_or(0);
4942 let mut out = Vec::with_capacity(max_len);
4943 for i in 0..max_len {
4944 let pair: Vec<StrykeValue> = refs
4945 .iter()
4946 .map(|l| l.get(i).cloned().unwrap_or(StrykeValue::UNDEF))
4947 .collect();
4948 let result = self.call_sub(&sub, pair, WantarrayCtx::Scalar, line)?;
4949 out.push(result);
4950 }
4951 Ok(match wa {
4952 WantarrayCtx::List => StrykeValue::array(out),
4953 WantarrayCtx::Scalar => StrykeValue::integer(out.len() as i64),
4954 WantarrayCtx::Void => StrykeValue::UNDEF,
4955 })
4956 }
4957 "count_by" => {
4958 let mut counts = indexmap::IndexMap::new();
4959 for item in items {
4960 self.scope.set_topic(item.clone());
4961 let key = self.call_sub(&sub, vec![], WantarrayCtx::Scalar, line)?;
4962 let k = key.to_string();
4963 let entry = counts.entry(k).or_insert(StrykeValue::integer(0));
4964 *entry = StrykeValue::integer(entry.to_int() + 1);
4965 }
4966 Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(counts))))
4967 }
4968 _ => Err(StrykeError::runtime(
4969 format!("internal: unknown list block builtin `{name}`"),
4970 line,
4971 )
4972 .into()),
4973 }
4974 }
4975
4976 pub(crate) fn builtin_rmdir_execute(
4978 &mut self,
4979 args: &[StrykeValue],
4980 _line: usize,
4981 ) -> StrykeResult<StrykeValue> {
4982 let mut count = 0i64;
4983 for a in args {
4984 let p = a.to_string();
4985 if p.is_empty() {
4986 continue;
4987 }
4988 let p = self.resolve_stryke_path_string(&p);
4989 if std::fs::remove_dir(&p).is_ok() {
4990 count += 1;
4991 }
4992 }
4993 Ok(StrykeValue::integer(count))
4994 }
4995
4996 pub(crate) fn builtin_touch_execute(
4998 &mut self,
4999 args: &[StrykeValue],
5000 _line: usize,
5001 ) -> StrykeResult<StrykeValue> {
5002 let paths: Vec<String> = args
5003 .iter()
5004 .map(|v| self.resolve_stryke_path_string(&v.to_string()))
5005 .collect();
5006 Ok(StrykeValue::integer(crate::perl_fs::touch_paths(&paths)))
5007 }
5008
5009 pub(crate) fn builtin_utime_execute(
5011 &mut self,
5012 args: &[StrykeValue],
5013 line: usize,
5014 ) -> StrykeResult<StrykeValue> {
5015 if args.len() < 3 {
5016 return Err(StrykeError::runtime(
5017 "utime requires at least three arguments (atime, mtime, files...)",
5018 line,
5019 ));
5020 }
5021 let at = args[0].to_int();
5022 let mt = args[1].to_int();
5023 let paths: Vec<String> = args
5024 .iter()
5025 .skip(2)
5026 .map(|v| self.resolve_stryke_path_string(&v.to_string()))
5027 .collect();
5028 let n = crate::perl_fs::utime_paths(at, mt, &paths);
5029 #[cfg(not(unix))]
5030 if !paths.is_empty() && n == 0 {
5031 return Err(StrykeError::runtime(
5032 "utime is not supported on this platform",
5033 line,
5034 ));
5035 }
5036 Ok(StrykeValue::integer(n))
5037 }
5038
5039 pub(crate) fn builtin_umask_execute(
5041 &mut self,
5042 args: &[StrykeValue],
5043 line: usize,
5044 ) -> StrykeResult<StrykeValue> {
5045 #[cfg(unix)]
5046 {
5047 let _ = line;
5048 if args.is_empty() {
5049 let cur = unsafe { libc::umask(0) };
5050 unsafe { libc::umask(cur) };
5051 return Ok(StrykeValue::integer(cur as i64));
5052 }
5053 let new_m = args[0].to_int() as libc::mode_t;
5054 let old = unsafe { libc::umask(new_m) };
5055 Ok(StrykeValue::integer(old as i64))
5056 }
5057 #[cfg(not(unix))]
5058 {
5059 let _ = args;
5060 Err(StrykeError::runtime(
5061 "umask is not supported on this platform",
5062 line,
5063 ))
5064 }
5065 }
5066
5067 pub(crate) fn builtin_getcwd_execute(
5069 &mut self,
5070 args: &[StrykeValue],
5071 line: usize,
5072 ) -> StrykeResult<StrykeValue> {
5073 if !args.is_empty() {
5074 return Err(StrykeError::runtime("getcwd takes no arguments", line));
5075 }
5076 match std::env::current_dir() {
5077 Ok(p) => Ok(StrykeValue::string(p.to_string_lossy().into_owned())),
5078 Err(e) => {
5079 self.apply_io_error_to_errno(&e);
5080 Ok(StrykeValue::UNDEF)
5081 }
5082 }
5083 }
5084
5085 pub(crate) fn builtin_realpath_execute(
5087 &mut self,
5088 args: &[StrykeValue],
5089 line: usize,
5090 ) -> StrykeResult<StrykeValue> {
5091 let path = args
5092 .first()
5093 .ok_or_else(|| StrykeError::runtime("realpath: need path", line))?
5094 .to_string();
5095 if path.is_empty() {
5096 return Err(StrykeError::runtime("realpath: need path", line));
5097 }
5098 let path = self.resolve_stryke_path_string(&path);
5099 match crate::perl_fs::realpath_resolved(&path) {
5100 Ok(s) => Ok(StrykeValue::string(s)),
5101 Err(e) => {
5102 self.apply_io_error_to_errno(&e);
5103 Ok(StrykeValue::UNDEF)
5104 }
5105 }
5106 }
5107
5108 pub(crate) fn builtin_pipe_execute(
5110 &mut self,
5111 args: &[StrykeValue],
5112 line: usize,
5113 ) -> StrykeResult<StrykeValue> {
5114 if args.len() != 2 {
5115 return Err(StrykeError::runtime(
5116 "pipe requires exactly two arguments",
5117 line,
5118 ));
5119 }
5120 #[cfg(unix)]
5121 {
5122 use std::fs::File;
5123 use std::os::unix::io::FromRawFd;
5124
5125 let read_name = args[0].to_string();
5126 let write_name = args[1].to_string();
5127 if read_name.is_empty() || write_name.is_empty() {
5128 return Err(StrykeError::runtime("pipe: invalid handle name", line));
5129 }
5130 let mut fds = [0i32; 2];
5131 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
5132 let e = std::io::Error::last_os_error();
5133 self.apply_io_error_to_errno(&e);
5134 return Ok(StrykeValue::integer(0));
5135 }
5136 let read_file = unsafe { File::from_raw_fd(fds[0]) };
5137 let write_file = unsafe { File::from_raw_fd(fds[1]) };
5138
5139 let read_shared = Arc::new(Mutex::new(read_file));
5140 let write_shared = Arc::new(Mutex::new(write_file));
5141
5142 self.close_builtin_execute(read_name.clone()).ok();
5143 self.close_builtin_execute(write_name.clone()).ok();
5144
5145 self.io_file_slots
5146 .insert(read_name.clone(), Arc::clone(&read_shared));
5147 self.input_handles.insert(
5148 read_name,
5149 BufReader::new(Box::new(IoSharedFile(Arc::clone(&read_shared)))),
5150 );
5151
5152 self.io_file_slots
5153 .insert(write_name.clone(), Arc::clone(&write_shared));
5154 self.output_handles
5155 .insert(write_name, Box::new(IoSharedFileWrite(write_shared)));
5156
5157 Ok(StrykeValue::integer(1))
5158 }
5159 #[cfg(not(unix))]
5160 {
5161 let _ = args;
5162 Err(StrykeError::runtime(
5163 "pipe is not supported on this platform",
5164 line,
5165 ))
5166 }
5167 }
5168
5169 pub(crate) fn close_builtin_execute(&mut self, name: String) -> StrykeResult<StrykeValue> {
5170 self.output_handles.remove(&name);
5171 self.input_handles.remove(&name);
5172 self.io_file_slots.remove(&name);
5173 if let Some(mut child) = self.pipe_children.remove(&name) {
5174 if let Ok(st) = child.wait() {
5175 self.record_child_exit_status(st);
5176 }
5177 }
5178 Ok(StrykeValue::integer(1))
5179 }
5180
5181 pub(crate) fn has_input_handle(&self, name: &str) -> bool {
5182 self.input_handles.contains_key(name)
5183 }
5184
5185 pub(crate) fn eof_without_arg_is_true(&self) -> bool {
5189 self.line_mode_eof_pending
5190 }
5191
5192 pub(crate) fn eof_builtin_execute(
5196 &mut self,
5197 args: &[StrykeValue],
5198 line: usize,
5199 ) -> StrykeResult<StrykeValue> {
5200 match args.len() {
5201 0 => Ok(StrykeValue::integer(if self.eof_without_arg_is_true() {
5202 1
5203 } else {
5204 0
5205 })),
5206 1 => {
5207 let name = args[0].to_string();
5208 use std::io::BufRead;
5212 let at_eof = match self.input_handles.get_mut(&name) {
5213 None => true,
5214 Some(reader) => match reader.fill_buf() {
5215 Ok(buf) => buf.is_empty(),
5216 Err(_) => true,
5217 },
5218 };
5219 Ok(StrykeValue::integer(if at_eof { 1 } else { 0 }))
5220 }
5221 _ => Err(StrykeError::runtime("eof: too many arguments", line)),
5222 }
5223 }
5224
5225 pub(crate) fn study_return_value(s: &str) -> StrykeValue {
5228 if s.is_empty() {
5229 StrykeValue::string(String::new())
5230 } else {
5231 StrykeValue::integer(1)
5232 }
5233 }
5234
5235 pub(crate) fn readline_builtin_execute(
5236 &mut self,
5237 handle: Option<&str>,
5238 ) -> StrykeResult<StrykeValue> {
5239 if handle.is_none() {
5241 let argv = self.scope.get_array("ARGV");
5242 if !argv.is_empty() {
5243 loop {
5244 if self.diamond_reader.is_none() {
5245 while self.diamond_next_idx < argv.len() {
5246 let path = self.resolve_stryke_path_string(
5247 &argv[self.diamond_next_idx].to_string(),
5248 );
5249 self.diamond_next_idx += 1;
5250 match File::open(&path) {
5251 Ok(f) => {
5252 self.argv_current_file = path;
5253 self.diamond_reader = Some(BufReader::new(f));
5254 break;
5255 }
5256 Err(e) => {
5257 self.apply_io_error_to_errno(&e);
5258 }
5259 }
5260 }
5261 if self.diamond_reader.is_none() {
5262 return Ok(StrykeValue::UNDEF);
5263 }
5264 }
5265 let mut line_str = String::new();
5266 let read_result: Result<usize, io::Error> =
5267 if let Some(reader) = self.diamond_reader.as_mut() {
5268 if self.open_pragma_utf8 {
5269 let mut buf = Vec::new();
5270 reader.read_until(b'\n', &mut buf).inspect(|n| {
5271 if *n > 0 {
5272 line_str = String::from_utf8_lossy(&buf).into_owned();
5273 }
5274 })
5275 } else {
5276 let mut buf = Vec::new();
5277 match reader.read_until(b'\n', &mut buf) {
5278 Ok(n) => {
5279 if n > 0 {
5280 line_str =
5281 crate::perl_decode::decode_utf8_or_latin1_read_until(
5282 &buf,
5283 );
5284 }
5285 Ok(n)
5286 }
5287 Err(e) => Err(e),
5288 }
5289 }
5290 } else {
5291 unreachable!()
5292 };
5293 match read_result {
5294 Ok(0) => {
5295 self.diamond_reader = None;
5296 continue;
5297 }
5298 Ok(_) => {
5299 self.bump_line_for_handle(&self.argv_current_file.clone());
5300 return Ok(StrykeValue::string(line_str));
5301 }
5302 Err(e) => {
5303 self.apply_io_error_to_errno(&e);
5304 self.diamond_reader = None;
5305 continue;
5306 }
5307 }
5308 }
5309 } else {
5310 self.argv_current_file.clear();
5311 }
5312 }
5313
5314 let handle_name = handle.unwrap_or("STDIN");
5315 let mut line_str = String::new();
5316 if handle_name == "STDIN" {
5317 if let Some(queued) = self.line_mode_stdin_pending.pop_front() {
5318 self.last_stdin_die_bracket = if handle.is_none() {
5319 "<>".to_string()
5320 } else {
5321 "<STDIN>".to_string()
5322 };
5323 self.bump_line_for_handle("STDIN");
5324 return Ok(StrykeValue::string(queued));
5325 }
5326 let r: Result<usize, io::Error> = if self.open_pragma_utf8 {
5327 let mut buf = Vec::new();
5328 io::stdin().lock().read_until(b'\n', &mut buf).inspect(|n| {
5329 if *n > 0 {
5330 line_str = String::from_utf8_lossy(&buf).into_owned();
5331 }
5332 })
5333 } else {
5334 let mut buf = Vec::new();
5335 let mut lock = io::stdin().lock();
5336 match lock.read_until(b'\n', &mut buf) {
5337 Ok(n) => {
5338 if n > 0 {
5339 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
5340 }
5341 Ok(n)
5342 }
5343 Err(e) => Err(e),
5344 }
5345 };
5346 match r {
5347 Ok(0) => Ok(StrykeValue::UNDEF),
5348 Ok(_) => {
5349 self.last_stdin_die_bracket = if handle.is_none() {
5350 "<>".to_string()
5351 } else {
5352 "<STDIN>".to_string()
5353 };
5354 self.bump_line_for_handle("STDIN");
5355 Ok(StrykeValue::string(line_str))
5356 }
5357 Err(e) => {
5358 self.apply_io_error_to_errno(&e);
5359 Ok(StrykeValue::UNDEF)
5360 }
5361 }
5362 } else if let Some(reader) = self.input_handles.get_mut(handle_name) {
5363 let slurp_mode = self.irs.is_none();
5365 let r: Result<usize, io::Error> = if slurp_mode {
5366 let mut buf = Vec::new();
5368 match reader.read_to_end(&mut buf) {
5369 Ok(n) => {
5370 if n > 0 {
5371 line_str = if self.open_pragma_utf8 {
5372 String::from_utf8_lossy(&buf).into_owned()
5373 } else {
5374 crate::perl_decode::decode_utf8_or_latin1_read_until(&buf)
5375 };
5376 }
5377 Ok(n)
5378 }
5379 Err(e) => Err(e),
5380 }
5381 } else if self.open_pragma_utf8 {
5382 let mut buf = Vec::new();
5383 reader.read_until(b'\n', &mut buf).inspect(|n| {
5384 if *n > 0 {
5385 line_str = String::from_utf8_lossy(&buf).into_owned();
5386 }
5387 })
5388 } else {
5389 let mut buf = Vec::new();
5390 match reader.read_until(b'\n', &mut buf) {
5391 Ok(n) => {
5392 if n > 0 {
5393 line_str = crate::perl_decode::decode_utf8_or_latin1_read_until(&buf);
5394 }
5395 Ok(n)
5396 }
5397 Err(e) => Err(e),
5398 }
5399 };
5400 match r {
5401 Ok(0) => Ok(StrykeValue::UNDEF),
5402 Ok(_) => {
5403 self.bump_line_for_handle(handle_name);
5404 Ok(StrykeValue::string(line_str))
5405 }
5406 Err(e) => {
5407 self.apply_io_error_to_errno(&e);
5408 Ok(StrykeValue::UNDEF)
5409 }
5410 }
5411 } else {
5412 Ok(StrykeValue::UNDEF)
5413 }
5414 }
5415
5416 pub(crate) fn readline_builtin_execute_list(
5418 &mut self,
5419 handle: Option<&str>,
5420 ) -> StrykeResult<StrykeValue> {
5421 let mut lines = Vec::new();
5422 loop {
5423 let v = self.readline_builtin_execute(handle)?;
5424 if v.is_undef() {
5425 break;
5426 }
5427 lines.push(v);
5428 }
5429 Ok(StrykeValue::array(lines))
5430 }
5431
5432 pub(crate) fn opendir_handle(&mut self, handle: &str, path: &str) -> StrykeValue {
5433 let path = self.resolve_stryke_path_string(path);
5434 match std::fs::read_dir(&path) {
5435 Ok(rd) => {
5436 let mut entries: Vec<String> = vec![".".to_string(), "..".to_string()];
5443 entries.extend(rd.filter_map(|e| {
5444 e.ok().map(|e| e.file_name().to_string_lossy().into_owned())
5445 }));
5446 self.dir_handles
5447 .insert(handle.to_string(), DirHandleState { entries, pos: 0 });
5448 StrykeValue::integer(1)
5449 }
5450 Err(e) => {
5451 self.apply_io_error_to_errno(&e);
5452 StrykeValue::integer(0)
5453 }
5454 }
5455 }
5456
5457 pub(crate) fn readdir_handle(&mut self, handle: &str) -> StrykeValue {
5458 if let Some(dh) = self.dir_handles.get_mut(handle) {
5459 if dh.pos < dh.entries.len() {
5460 let s = dh.entries[dh.pos].clone();
5461 dh.pos += 1;
5462 StrykeValue::string(s)
5463 } else {
5464 StrykeValue::UNDEF
5465 }
5466 } else {
5467 StrykeValue::UNDEF
5468 }
5469 }
5470
5471 pub(crate) fn readdir_handle_list(&mut self, handle: &str) -> StrykeValue {
5473 if let Some(dh) = self.dir_handles.get_mut(handle) {
5474 let rest: Vec<StrykeValue> = dh.entries[dh.pos..]
5475 .iter()
5476 .cloned()
5477 .map(StrykeValue::string)
5478 .collect();
5479 dh.pos = dh.entries.len();
5480 StrykeValue::array(rest)
5481 } else {
5482 StrykeValue::array(Vec::new())
5483 }
5484 }
5485
5486 pub(crate) fn closedir_handle(&mut self, handle: &str) -> StrykeValue {
5487 StrykeValue::integer(if self.dir_handles.remove(handle).is_some() {
5488 1
5489 } else {
5490 0
5491 })
5492 }
5493
5494 pub(crate) fn rewinddir_handle(&mut self, handle: &str) -> StrykeValue {
5495 if let Some(dh) = self.dir_handles.get_mut(handle) {
5496 dh.pos = 0;
5497 StrykeValue::integer(1)
5498 } else {
5499 StrykeValue::integer(0)
5500 }
5501 }
5502
5503 pub(crate) fn telldir_handle(&mut self, handle: &str) -> StrykeValue {
5504 self.dir_handles
5505 .get(handle)
5506 .map(|dh| StrykeValue::integer(dh.pos as i64))
5507 .unwrap_or(StrykeValue::UNDEF)
5508 }
5509
5510 pub(crate) fn seekdir_handle(&mut self, handle: &str, pos: usize) -> StrykeValue {
5511 if let Some(dh) = self.dir_handles.get_mut(handle) {
5512 dh.pos = pos.min(dh.entries.len());
5513 StrykeValue::integer(1)
5514 } else {
5515 StrykeValue::integer(0)
5516 }
5517 }
5518
5519 #[inline]
5524 pub(crate) fn is_regex_capture_scope_var(name: &str) -> bool {
5525 crate::special_vars::is_regex_match_scalar_name(name)
5526 }
5527
5528 #[inline]
5532 pub(crate) fn maybe_invalidate_regex_capture_memo(&mut self, name: &str) {
5533 if self.regex_capture_scope_fresh && Self::is_regex_capture_scope_var(name) {
5534 self.regex_capture_scope_fresh = false;
5535 }
5536 }
5537
5538 pub(crate) fn apply_regex_captures(
5539 &mut self,
5540 haystack: &str,
5541 offset: usize,
5542 re: &PerlCompiledRegex,
5543 caps: &PerlCaptures<'_>,
5544 capture_all: CaptureAllMode,
5545 ) -> Result<(), FlowOrError> {
5546 let m0 = caps.get(0).expect("regex capture 0");
5547 let s0 = offset + m0.start;
5548 let e0 = offset + m0.end;
5549 self.last_match = haystack.get(s0..e0).unwrap_or("").to_string();
5550 self.prematch = haystack.get(..s0).unwrap_or("").to_string();
5551 self.postmatch = haystack.get(e0..).unwrap_or("").to_string();
5552 let mut last_paren = String::new();
5553 for i in 1..caps.len() {
5554 if let Some(m) = caps.get(i) {
5555 last_paren = m.text.to_string();
5556 }
5557 }
5558 self.last_paren_match = last_paren;
5559 self.last_subpattern_name = String::new();
5560 for n in re.capture_names().flatten() {
5561 if caps.name(n).is_some() {
5562 self.last_subpattern_name = n.to_string();
5563 }
5564 }
5565 self.scope
5566 .set_scalar("&", StrykeValue::string(self.last_match.clone()))?;
5567 self.scope
5568 .set_scalar("`", StrykeValue::string(self.prematch.clone()))?;
5569 self.scope
5570 .set_scalar("'", StrykeValue::string(self.postmatch.clone()))?;
5571 self.scope
5572 .set_scalar("+", StrykeValue::string(self.last_paren_match.clone()))?;
5573 for i in 1..caps.len() {
5574 if let Some(m) = caps.get(i) {
5575 self.scope
5576 .set_scalar(&i.to_string(), StrykeValue::string(m.text.to_string()))?;
5577 }
5578 }
5579 let mut start_arr = vec![StrykeValue::integer(s0 as i64)];
5580 let mut end_arr = vec![StrykeValue::integer(e0 as i64)];
5581 for i in 1..caps.len() {
5582 if let Some(m) = caps.get(i) {
5583 start_arr.push(StrykeValue::integer((offset + m.start) as i64));
5584 end_arr.push(StrykeValue::integer((offset + m.end) as i64));
5585 } else {
5586 start_arr.push(StrykeValue::integer(-1));
5587 end_arr.push(StrykeValue::integer(-1));
5588 }
5589 }
5590 self.scope.set_array("-", start_arr)?;
5591 self.scope.set_array("+", end_arr)?;
5592 let mut named = IndexMap::new();
5593 for name in re.capture_names().flatten() {
5594 if let Some(m) = caps.name(name) {
5595 named.insert(name.to_string(), StrykeValue::string(m.text.to_string()));
5596 }
5597 }
5598 self.scope.set_hash("+", named.clone())?;
5599 let mut named_minus = IndexMap::new();
5601 for (name, val) in &named {
5602 named_minus.insert(
5603 name.clone(),
5604 StrykeValue::array_ref(Arc::new(RwLock::new(vec![val.clone()]))),
5605 );
5606 }
5607 self.scope.set_hash("-", named_minus)?;
5608 let cap_flat = crate::perl_regex::numbered_capture_flat(caps);
5609 self.scope.set_array("^CAPTURE", cap_flat.clone())?;
5610 match capture_all {
5611 CaptureAllMode::Empty => {
5612 self.scope.set_array("^CAPTURE_ALL", vec![])?;
5613 }
5614 CaptureAllMode::Append => {
5615 let mut rows = self.scope.get_array("^CAPTURE_ALL");
5616 rows.push(StrykeValue::array(cap_flat));
5617 self.scope.set_array("^CAPTURE_ALL", rows)?;
5618 }
5619 CaptureAllMode::Skip => {}
5620 }
5621 Ok(())
5622 }
5623
5624 pub(crate) fn clear_flip_flop_state(&mut self) {
5625 self.flip_flop_active.clear();
5626 self.flip_flop_exclusive_left_line.clear();
5627 self.flip_flop_sequence.clear();
5628 self.flip_flop_last_dot.clear();
5629 self.flip_flop_tree.clear();
5630 }
5631
5632 pub(crate) fn prepare_flip_flop_vm_slots(&mut self, slots: u16) {
5633 self.flip_flop_active.resize(slots as usize, false);
5634 self.flip_flop_active.fill(false);
5635 self.flip_flop_exclusive_left_line
5636 .resize(slots as usize, None);
5637 self.flip_flop_exclusive_left_line.fill(None);
5638 self.flip_flop_sequence.resize(slots as usize, 0);
5639 self.flip_flop_sequence.fill(0);
5640 self.flip_flop_last_dot.resize(slots as usize, None);
5641 self.flip_flop_last_dot.fill(None);
5642 }
5643
5644 #[inline]
5648 pub(crate) fn scalar_flipflop_dot_line(&self) -> i64 {
5649 if self.last_readline_handle.is_empty() {
5650 self.line_number
5651 } else {
5652 *self
5653 .handle_line_numbers
5654 .get(&self.last_readline_handle)
5655 .unwrap_or(&0)
5656 }
5657 }
5658
5659 pub(crate) fn scalar_flip_flop_eval(
5668 &mut self,
5669 left: i64,
5670 right: i64,
5671 slot: usize,
5672 exclusive: bool,
5673 ) -> StrykeResult<StrykeValue> {
5674 if self.flip_flop_active.len() <= slot {
5675 self.flip_flop_active.resize(slot + 1, false);
5676 }
5677 if self.flip_flop_exclusive_left_line.len() <= slot {
5678 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5679 }
5680 if self.flip_flop_sequence.len() <= slot {
5681 self.flip_flop_sequence.resize(slot + 1, 0);
5682 }
5683 if self.flip_flop_last_dot.len() <= slot {
5684 self.flip_flop_last_dot.resize(slot + 1, None);
5685 }
5686 let dot = self.scalar_flipflop_dot_line();
5687 let active = &mut self.flip_flop_active[slot];
5688 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5689 let seq = &mut self.flip_flop_sequence[slot];
5690 let last_dot = &mut self.flip_flop_last_dot[slot];
5691 if !*active {
5692 if dot == left {
5693 *active = true;
5694 *seq = 1;
5695 *last_dot = Some(dot);
5696 if exclusive {
5697 *excl_left = Some(dot);
5698 } else {
5699 *excl_left = None;
5700 if dot == right {
5701 *active = false;
5702 return Ok(StrykeValue::string(format!("{}E0", *seq)));
5703 }
5704 }
5705 return Ok(StrykeValue::string(seq.to_string()));
5706 }
5707 *last_dot = Some(dot);
5708 return Ok(StrykeValue::string(String::new()));
5709 }
5710 if *last_dot != Some(dot) {
5713 *seq += 1;
5714 *last_dot = Some(dot);
5715 }
5716 let cur_seq = *seq;
5717 if let Some(ll) = *excl_left {
5718 if dot == right && dot > ll {
5719 *active = false;
5720 *excl_left = None;
5721 *seq = 0;
5722 return Ok(StrykeValue::string(format!("{}E0", cur_seq)));
5723 }
5724 } else if dot == right {
5725 *active = false;
5726 *seq = 0;
5727 return Ok(StrykeValue::string(format!("{}E0", cur_seq)));
5728 }
5729 Ok(StrykeValue::string(cur_seq.to_string()))
5730 }
5731
5732 fn regex_flip_flop_transition(
5733 active: &mut bool,
5734 excl_left: &mut Option<i64>,
5735 exclusive: bool,
5736 dot: i64,
5737 left_m: bool,
5738 right_m: bool,
5739 ) -> i64 {
5740 if !*active {
5741 if left_m {
5742 *active = true;
5743 if exclusive {
5744 *excl_left = Some(dot);
5745 } else {
5746 *excl_left = None;
5747 if right_m {
5748 *active = false;
5749 }
5750 }
5751 return 1;
5752 }
5753 return 0;
5754 }
5755 if let Some(ll) = *excl_left {
5756 if right_m && dot > ll {
5757 *active = false;
5758 *excl_left = None;
5759 }
5760 } else if right_m {
5761 *active = false;
5762 }
5763 1
5764 }
5765
5766 #[allow(clippy::too_many_arguments)] pub(crate) fn regex_flip_flop_eval(
5771 &mut self,
5772 left_pat: &str,
5773 left_flags: &str,
5774 right_pat: &str,
5775 right_flags: &str,
5776 slot: usize,
5777 exclusive: bool,
5778 line: usize,
5779 ) -> StrykeResult<StrykeValue> {
5780 let dot = self.scalar_flipflop_dot_line();
5781 let subject = self.scope.get_scalar("_").to_string();
5782 let left_re = self
5783 .compile_regex(left_pat, left_flags, line)
5784 .map_err(|e| match e {
5785 FlowOrError::Error(err) => err,
5786 FlowOrError::Flow(_) => {
5787 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5788 }
5789 })?;
5790 let right_re = self
5791 .compile_regex(right_pat, right_flags, line)
5792 .map_err(|e| match e {
5793 FlowOrError::Error(err) => err,
5794 FlowOrError::Flow(_) => {
5795 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5796 }
5797 })?;
5798 let left_m = left_re.is_match(&subject);
5799 let right_m = right_re.is_match(&subject);
5800 if self.flip_flop_active.len() <= slot {
5801 self.flip_flop_active.resize(slot + 1, false);
5802 }
5803 if self.flip_flop_exclusive_left_line.len() <= slot {
5804 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5805 }
5806 let active = &mut self.flip_flop_active[slot];
5807 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5808 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5809 active, excl_left, exclusive, dot, left_m, right_m,
5810 )))
5811 }
5812
5813 pub(crate) fn regex_flip_flop_eval_dynamic_right(
5815 &mut self,
5816 left_pat: &str,
5817 left_flags: &str,
5818 slot: usize,
5819 exclusive: bool,
5820 line: usize,
5821 right_m: bool,
5822 ) -> StrykeResult<StrykeValue> {
5823 let dot = self.scalar_flipflop_dot_line();
5824 let subject = self.scope.get_scalar("_").to_string();
5825 let left_re = self
5826 .compile_regex(left_pat, left_flags, line)
5827 .map_err(|e| match e {
5828 FlowOrError::Error(err) => err,
5829 FlowOrError::Flow(_) => {
5830 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5831 }
5832 })?;
5833 let left_m = left_re.is_match(&subject);
5834 if self.flip_flop_active.len() <= slot {
5835 self.flip_flop_active.resize(slot + 1, false);
5836 }
5837 if self.flip_flop_exclusive_left_line.len() <= slot {
5838 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5839 }
5840 let active = &mut self.flip_flop_active[slot];
5841 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5842 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5843 active, excl_left, exclusive, dot, left_m, right_m,
5844 )))
5845 }
5846
5847 pub(crate) fn regex_flip_flop_eval_dot_line_rhs(
5849 &mut self,
5850 left_pat: &str,
5851 left_flags: &str,
5852 slot: usize,
5853 exclusive: bool,
5854 line: usize,
5855 rhs_line: i64,
5856 ) -> StrykeResult<StrykeValue> {
5857 let dot = self.scalar_flipflop_dot_line();
5858 let subject = self.scope.get_scalar("_").to_string();
5859 let left_re = self
5860 .compile_regex(left_pat, left_flags, line)
5861 .map_err(|e| match e {
5862 FlowOrError::Error(err) => err,
5863 FlowOrError::Flow(_) => {
5864 StrykeError::runtime("unexpected flow in regex flip-flop", line)
5865 }
5866 })?;
5867 let left_m = left_re.is_match(&subject);
5868 let right_m = dot == rhs_line;
5869 if self.flip_flop_active.len() <= slot {
5870 self.flip_flop_active.resize(slot + 1, false);
5871 }
5872 if self.flip_flop_exclusive_left_line.len() <= slot {
5873 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5874 }
5875 let active = &mut self.flip_flop_active[slot];
5876 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5877 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5878 active, excl_left, exclusive, dot, left_m, right_m,
5879 )))
5880 }
5881
5882 pub(crate) fn regex_eof_flip_flop_eval(
5887 &mut self,
5888 left_pat: &str,
5889 left_flags: &str,
5890 slot: usize,
5891 exclusive: bool,
5892 line: usize,
5893 ) -> StrykeResult<StrykeValue> {
5894 let dot = self.scalar_flipflop_dot_line();
5895 let subject = self.scope.get_scalar("_").to_string();
5896 let left_re = self
5897 .compile_regex(left_pat, left_flags, line)
5898 .map_err(|e| match e {
5899 FlowOrError::Error(err) => err,
5900 FlowOrError::Flow(_) => {
5901 StrykeError::runtime("unexpected flow in regex/eof flip-flop", line)
5902 }
5903 })?;
5904 let left_m = left_re.is_match(&subject);
5905 let right_m = self.eof_without_arg_is_true();
5906 if self.flip_flop_active.len() <= slot {
5907 self.flip_flop_active.resize(slot + 1, false);
5908 }
5909 if self.flip_flop_exclusive_left_line.len() <= slot {
5910 self.flip_flop_exclusive_left_line.resize(slot + 1, None);
5911 }
5912 let active = &mut self.flip_flop_active[slot];
5913 let excl_left = &mut self.flip_flop_exclusive_left_line[slot];
5914 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
5915 active, excl_left, exclusive, dot, left_m, right_m,
5916 )))
5917 }
5918
5919 pub(crate) fn builtin_read_into(
5923 &mut self,
5924 fh_val: StrykeValue,
5925 var_name: &str,
5926 length: usize,
5927 line: usize,
5928 ) -> ExecResult {
5929 use std::io::Read;
5930 let fh = fh_val
5931 .as_io_handle_name()
5932 .unwrap_or_else(|| fh_val.to_string());
5933 let mut buf = vec![0u8; length];
5934 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
5935 slot.lock().read(&mut buf).unwrap_or(0)
5936 } else if fh == "STDIN" {
5937 std::io::stdin().read(&mut buf).unwrap_or(0)
5938 } else {
5939 return Err(StrykeError::runtime(format!("read: unopened handle {}", fh), line).into());
5940 };
5941 buf.truncate(n);
5942 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
5943 let _ = self
5944 .scope
5945 .set_scalar(var_name, StrykeValue::string(read_str));
5946 Ok(StrykeValue::integer(n as i64))
5947 }
5948
5949 pub(crate) fn chomp_inplace_execute(&mut self, val: StrykeValue, target: &Expr) -> ExecResult {
5950 match &target.kind {
5956 ExprKind::ArrayVar(name) => {
5957 let arr = self.scope.get_array(name);
5958 let mut total = 0i64;
5959 let mut new_arr = Vec::with_capacity(arr.len());
5960 for v in arr {
5961 let mut s = v.to_string();
5962 if s.ends_with('\n') {
5963 s.pop();
5964 total += 1;
5965 }
5966 new_arr.push(StrykeValue::string(s));
5967 }
5968 self.scope
5969 .set_array(name, new_arr)
5970 .map_err(FlowOrError::Error)?;
5971 return Ok(StrykeValue::integer(total));
5972 }
5973 ExprKind::HashVar(name) => {
5974 let h = self.scope.get_hash(name);
5975 let mut total = 0i64;
5976 let mut new_h: indexmap::IndexMap<String, StrykeValue> =
5977 indexmap::IndexMap::with_capacity(h.len());
5978 for (k, v) in h {
5979 let mut s = v.to_string();
5980 if s.ends_with('\n') {
5981 s.pop();
5982 total += 1;
5983 }
5984 new_h.insert(k, StrykeValue::string(s));
5985 }
5986 self.scope
5987 .set_hash(name, new_h)
5988 .map_err(FlowOrError::Error)?;
5989 return Ok(StrykeValue::integer(total));
5990 }
5991 _ => {}
5992 }
5993 let mut s = val.to_string();
5994 let removed = if s.ends_with('\n') {
5995 s.pop();
5996 1i64
5997 } else {
5998 0i64
5999 };
6000 self.assign_value(target, StrykeValue::string(s))?;
6001 Ok(StrykeValue::integer(removed))
6002 }
6003
6004 pub(crate) fn chop_inplace_execute(&mut self, val: StrykeValue, target: &Expr) -> ExecResult {
6006 match &target.kind {
6012 ExprKind::ArrayVar(name) => {
6013 let arr = self.scope.get_array(name);
6014 let mut last = StrykeValue::UNDEF;
6015 let mut new_arr = Vec::with_capacity(arr.len());
6016 for v in arr {
6017 let mut s = v.to_string();
6018 if let Some(c) = s.pop() {
6019 last = StrykeValue::string(c.to_string());
6020 }
6021 new_arr.push(StrykeValue::string(s));
6022 }
6023 self.scope
6024 .set_array(name, new_arr)
6025 .map_err(FlowOrError::Error)?;
6026 return Ok(last);
6027 }
6028 ExprKind::HashVar(name) => {
6029 let h = self.scope.get_hash(name);
6030 let mut last = StrykeValue::UNDEF;
6031 let mut new_h: indexmap::IndexMap<String, StrykeValue> =
6032 indexmap::IndexMap::with_capacity(h.len());
6033 for (k, v) in h {
6034 let mut s = v.to_string();
6035 if let Some(c) = s.pop() {
6036 last = StrykeValue::string(c.to_string());
6037 }
6038 new_h.insert(k, StrykeValue::string(s));
6039 }
6040 self.scope
6041 .set_hash(name, new_h)
6042 .map_err(FlowOrError::Error)?;
6043 return Ok(last);
6044 }
6045 _ => {}
6046 }
6047 let mut s = val.to_string();
6048 let chopped = s
6049 .pop()
6050 .map(|c| StrykeValue::string(c.to_string()))
6051 .unwrap_or(StrykeValue::UNDEF);
6052 self.assign_value(target, StrykeValue::string(s))?;
6053 Ok(chopped)
6054 }
6055
6056 pub(crate) fn regex_match_execute(
6058 &mut self,
6059 s: String,
6060 pattern: &str,
6061 flags: &str,
6062 scalar_g: bool,
6063 pos_key: &str,
6064 line: usize,
6065 ) -> ExecResult {
6066 if !flags.contains('g') && !scalar_g {
6074 let memo_hit = {
6075 if let Some(ref mem) = self.regex_match_memo {
6076 mem.pattern == pattern
6077 && mem.flags == flags
6078 && mem.multiline == self.multiline_match
6079 && mem.haystack == s
6080 } else {
6081 false
6082 }
6083 };
6084 if memo_hit {
6085 if self.regex_capture_scope_fresh {
6086 return Ok(self.regex_match_memo.as_ref().expect("memo").result.clone());
6087 }
6088 let (memo_s, memo_result) = {
6091 let mem = self.regex_match_memo.as_ref().expect("memo");
6092 (mem.haystack.clone(), mem.result.clone())
6093 };
6094 let re = self.compile_regex(pattern, flags, line)?;
6095 if let Some(caps) = re.captures(&memo_s) {
6096 self.apply_regex_captures(&memo_s, 0, &re, &caps, CaptureAllMode::Empty)?;
6097 }
6098 self.regex_capture_scope_fresh = true;
6099 return Ok(memo_result);
6100 }
6101 }
6102 let re = self.compile_regex(pattern, flags, line)?;
6103 if flags.contains('g') && scalar_g {
6104 let key = pos_key.to_string();
6105 let start = self.regex_pos.get(&key).copied().flatten().unwrap_or(0);
6106 if start == 0 {
6107 self.scope.set_array("^CAPTURE_ALL", vec![])?;
6108 }
6109 if start > s.len() {
6110 self.regex_pos.insert(key, None);
6111 return Ok(StrykeValue::integer(0));
6112 }
6113 let sub = s.get(start..).unwrap_or("");
6114 if let Some(caps) = re.captures(sub) {
6115 let overall = caps.get(0).expect("capture 0");
6116 let abs_end = start + overall.end;
6117 self.regex_pos.insert(key, Some(abs_end));
6118 self.apply_regex_captures(&s, start, &re, &caps, CaptureAllMode::Append)?;
6119 Ok(StrykeValue::integer(1))
6120 } else {
6121 self.regex_pos.insert(key, None);
6122 Ok(StrykeValue::integer(0))
6123 }
6124 } else if flags.contains('g') {
6125 let mut rows = Vec::new();
6126 let mut last_caps: Option<PerlCaptures<'_>> = None;
6127 let mut has_groups = false;
6128 let mut flat_captures: Vec<StrykeValue> = Vec::new();
6132 for caps in re.captures_iter(&s) {
6133 if caps.len() > 1 {
6134 has_groups = true;
6135 }
6136 let cap_row = crate::perl_regex::numbered_capture_flat(&caps);
6137 flat_captures.extend(cap_row.iter().cloned());
6138 rows.push(StrykeValue::array(cap_row));
6139 last_caps = Some(caps);
6140 }
6141 self.scope.set_array("^CAPTURE_ALL", rows)?;
6142 if has_groups {
6143 if flat_captures.is_empty() {
6144 return Ok(StrykeValue::integer(0));
6145 }
6146 if let Some(caps) = last_caps {
6147 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
6148 }
6149 return Ok(StrykeValue::array(flat_captures));
6150 }
6151 let matches: Vec<StrykeValue> = match &*re {
6152 PerlCompiledRegex::Rust(r) => r
6153 .find_iter(&s)
6154 .map(|m| StrykeValue::string(m.as_str().to_string()))
6155 .collect(),
6156 PerlCompiledRegex::Fancy(r) => r
6157 .find_iter(&s)
6158 .filter_map(|m| m.ok())
6159 .map(|m| StrykeValue::string(m.as_str().to_string()))
6160 .collect(),
6161 PerlCompiledRegex::Pcre2(r) => r
6162 .find_iter(s.as_bytes())
6163 .filter_map(|m| m.ok())
6164 .map(|m| {
6165 let t = s.get(m.start()..m.end()).unwrap_or("");
6166 StrykeValue::string(t.to_string())
6167 })
6168 .collect(),
6169 };
6170 if matches.is_empty() {
6171 Ok(StrykeValue::integer(0))
6172 } else {
6173 if let Some(caps) = last_caps {
6174 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Skip)?;
6175 }
6176 Ok(StrykeValue::array(matches))
6177 }
6178 } else if let Some(caps) = re.captures(&s) {
6179 self.apply_regex_captures(&s, 0, &re, &caps, CaptureAllMode::Empty)?;
6180 let list_context = self.wantarray_kind == WantarrayCtx::List;
6188 let has_groups = caps.len() > 1;
6189 let result = if list_context && has_groups {
6190 let cap_vec = crate::perl_regex::numbered_capture_flat(&caps);
6191 let any_defined = cap_vec.iter().any(|v| !v.is_undef());
6192 if any_defined {
6193 StrykeValue::array(cap_vec)
6194 } else {
6195 StrykeValue::integer(1)
6196 }
6197 } else {
6198 StrykeValue::integer(1)
6199 };
6200 if matches!(result.as_integer(), Some(1)) {
6203 self.regex_match_memo = Some(RegexMatchMemo {
6204 pattern: pattern.to_string(),
6205 flags: flags.to_string(),
6206 multiline: self.multiline_match,
6207 haystack: s,
6208 result: result.clone(),
6209 });
6210 }
6211 self.regex_capture_scope_fresh = true;
6212 Ok(result)
6213 } else {
6214 let list_context = self.wantarray_kind == WantarrayCtx::List;
6216 let result = if list_context {
6217 StrykeValue::array(Vec::new())
6218 } else {
6219 StrykeValue::integer(0)
6220 };
6221 if !list_context {
6223 self.regex_match_memo = Some(RegexMatchMemo {
6224 pattern: pattern.to_string(),
6225 flags: flags.to_string(),
6226 multiline: self.multiline_match,
6227 haystack: s,
6228 result: result.clone(),
6229 });
6230 }
6231 Ok(result)
6234 }
6235 }
6236
6237 pub(crate) fn expand_env_braces_in_subst(
6241 &mut self,
6242 raw: &str,
6243 line: usize,
6244 ) -> StrykeResult<String> {
6245 self.materialize_env_if_needed();
6246 let mut out = String::new();
6247 let mut rest = raw;
6248 while let Some(idx) = rest.find("$ENV{") {
6249 out.push_str(&rest[..idx]);
6250 let after = &rest[idx + 5..];
6251 let end = after
6252 .find('}')
6253 .ok_or_else(|| StrykeError::runtime("Unclosed $ENV{...} in s///", line))?;
6254 let key = &after[..end];
6255 let val = self.scope.get_hash_element("ENV", key);
6256 out.push_str(&val.to_string());
6257 rest = &after[end + 1..];
6258 }
6259 out.push_str(rest);
6260 Ok(out)
6261 }
6262
6263 pub(crate) fn regex_subst_execute(
6269 &mut self,
6270 s: String,
6271 pattern: &str,
6272 replacement: &str,
6273 flags: &str,
6274 target: &Expr,
6275 line: usize,
6276 ) -> ExecResult {
6277 let re_flags: String = flags.chars().filter(|c| *c != 'e').collect();
6278 let pattern = self.expand_env_braces_in_subst(pattern, line)?;
6279 let re = self.compile_regex(&pattern, &re_flags, line)?;
6280 if flags.contains('e') {
6281 return self.regex_subst_execute_eval(s, re.as_ref(), replacement, flags, target, line);
6282 }
6283 let replacement = self.expand_env_braces_in_subst(replacement, line)?;
6284 let replacement = self.interpolate_replacement_string(&replacement);
6285 let replacement = normalize_replacement_backrefs(&replacement);
6286 let last_caps = if flags.contains('g') {
6287 let mut rows = Vec::new();
6288 let mut last = None;
6289 for caps in re.captures_iter(&s) {
6290 rows.push(StrykeValue::array(
6291 crate::perl_regex::numbered_capture_flat(&caps),
6292 ));
6293 last = Some(caps);
6294 }
6295 self.scope.set_array("^CAPTURE_ALL", rows)?;
6296 last
6297 } else {
6298 re.captures(&s)
6299 };
6300 if let Some(caps) = last_caps {
6301 let mode = if flags.contains('g') {
6302 CaptureAllMode::Skip
6303 } else {
6304 CaptureAllMode::Empty
6305 };
6306 self.apply_regex_captures(&s, 0, &re, &caps, mode)?;
6307 }
6308 let (new_s, count) = if flags.contains('g') {
6309 let count = re.find_iter_count(&s);
6310 (re.replace_all(&s, replacement.as_str()), count)
6311 } else {
6312 let count = if re.is_match(&s) { 1 } else { 0 };
6313 (re.replace(&s, replacement.as_str()), count)
6314 };
6315 if flags.contains('r') {
6316 Ok(StrykeValue::string(new_s))
6318 } else {
6319 self.assign_value(target, StrykeValue::string(new_s))?;
6320 Ok(StrykeValue::integer(count as i64))
6321 }
6322 }
6323
6324 fn regex_subst_run_eval_rounds(&mut self, replacement: &str, e_count: usize) -> ExecResult {
6327 let prep_source = |raw: &str| -> String {
6328 let mut code = raw.trim().to_string();
6329 if !code.ends_with(';') {
6330 code.push(';');
6331 }
6332 code
6333 };
6334 let mut cur = prep_source(replacement);
6335 let mut last = StrykeValue::UNDEF;
6336 for round in 0..e_count {
6337 last = crate::parse_and_run_string(&cur, self)?;
6338 if round + 1 < e_count {
6339 cur = prep_source(&last.to_string());
6340 }
6341 }
6342 Ok(last)
6343 }
6344
6345 fn regex_subst_execute_eval(
6346 &mut self,
6347 s: String,
6348 re: &PerlCompiledRegex,
6349 replacement: &str,
6350 flags: &str,
6351 target: &Expr,
6352 line: usize,
6353 ) -> ExecResult {
6354 let e_count = flags.chars().filter(|c| *c == 'e').count();
6355 if e_count == 0 {
6356 return Err(StrykeError::runtime("s///e: internal error (no e flag)", line).into());
6357 }
6358
6359 if flags.contains('g') {
6360 let mut rows = Vec::new();
6361 let mut out = String::new();
6362 let mut last = 0usize;
6363 let mut count = 0usize;
6364 for caps in re.captures_iter(&s) {
6365 let m0 = caps.get(0).expect("regex capture 0");
6366 out.push_str(&s[last..m0.start]);
6367 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
6368 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
6369 out.push_str(&repl_val.to_string());
6370 last = m0.end;
6371 count += 1;
6372 rows.push(StrykeValue::array(
6373 crate::perl_regex::numbered_capture_flat(&caps),
6374 ));
6375 }
6376 self.scope.set_array("^CAPTURE_ALL", rows)?;
6377 out.push_str(&s[last..]);
6378 if flags.contains('r') {
6379 return Ok(StrykeValue::string(out));
6380 }
6381 self.assign_value(target, StrykeValue::string(out))?;
6382 return Ok(StrykeValue::integer(count as i64));
6383 }
6384 if let Some(caps) = re.captures(&s) {
6385 let m0 = caps.get(0).expect("regex capture 0");
6386 self.apply_regex_captures(&s, 0, re, &caps, CaptureAllMode::Empty)?;
6387 let repl_val = self.regex_subst_run_eval_rounds(replacement, e_count)?;
6388 let mut out = String::new();
6389 out.push_str(&s[..m0.start]);
6390 out.push_str(&repl_val.to_string());
6391 out.push_str(&s[m0.end..]);
6392 if flags.contains('r') {
6393 return Ok(StrykeValue::string(out));
6394 }
6395 self.assign_value(target, StrykeValue::string(out))?;
6396 return Ok(StrykeValue::integer(1));
6397 }
6398 if flags.contains('r') {
6399 return Ok(StrykeValue::string(s));
6400 }
6401 self.assign_value(target, StrykeValue::string(s))?;
6402 Ok(StrykeValue::integer(0))
6403 }
6404
6405 pub(crate) fn regex_transliterate_execute(
6407 &mut self,
6408 s: String,
6409 from: &str,
6410 to: &str,
6411 flags: &str,
6412 target: &Expr,
6413 line: usize,
6414 ) -> ExecResult {
6415 let _ = line;
6416 let from_chars = Self::tr_expand_ranges(from);
6417 let to_chars = Self::tr_expand_ranges(to);
6418 let delete_mode = flags.contains('d');
6419 let complement = flags.contains('c');
6420 let squash = flags.contains('s');
6421
6422 let mut count = 0i64;
6423 let mut new_s = String::with_capacity(s.len());
6424 let mut last_out: Option<char> = None;
6425 for c in s.chars() {
6426 let in_from = from_chars.iter().position(|&fc| fc == c);
6427 let matched = if complement {
6428 in_from.is_none()
6429 } else {
6430 in_from.is_some()
6431 };
6432 if !matched {
6433 new_s.push(c);
6434 last_out = Some(c);
6435 continue;
6436 }
6437 count += 1;
6438 let out_c = if complement {
6445 to_chars.last().copied()
6446 } else if let Some(pos) = in_from {
6447 if pos < to_chars.len() {
6448 Some(to_chars[pos])
6449 } else if delete_mode {
6450 None
6451 } else {
6452 to_chars.last().copied().or(Some(c))
6453 }
6454 } else {
6455 None
6456 };
6457 let out_c = match out_c {
6458 Some(c) => c,
6459 None => {
6460 if delete_mode {
6464 continue;
6465 }
6466 c
6467 }
6468 };
6469 if squash && last_out == Some(out_c) {
6470 continue;
6471 }
6472 new_s.push(out_c);
6473 last_out = Some(out_c);
6474 }
6475
6476 if flags.contains('r') {
6477 Ok(StrykeValue::string(new_s))
6479 } else {
6480 self.assign_value(target, StrykeValue::string(new_s))?;
6481 Ok(StrykeValue::integer(count))
6482 }
6483 }
6484
6485 pub(crate) fn tr_expand_ranges(spec: &str) -> Vec<char> {
6488 let raw: Vec<char> = spec.chars().collect();
6489 let mut out = Vec::with_capacity(raw.len());
6490 let mut i = 0;
6491 while i < raw.len() {
6492 if i + 2 < raw.len() && raw[i + 1] == '-' && raw[i] <= raw[i + 2] {
6493 let start = raw[i] as u32;
6494 let end = raw[i + 2] as u32;
6495 for code in start..=end {
6496 if let Some(c) = char::from_u32(code) {
6497 out.push(c);
6498 }
6499 }
6500 i += 3;
6501 } else {
6502 out.push(raw[i]);
6503 i += 1;
6504 }
6505 }
6506 out
6507 }
6508
6509 pub(crate) fn splice_builtin_execute(
6511 &mut self,
6512 args: &[StrykeValue],
6513 line: usize,
6514 ) -> StrykeResult<StrykeValue> {
6515 if args.is_empty() {
6516 return Err(StrykeError::runtime("splice: missing array", line));
6517 }
6518 let arr_name = args[0].to_string();
6519 let arr_len = self.scope.array_len(&arr_name);
6520 let offset_val = args
6521 .get(1)
6522 .cloned()
6523 .unwrap_or_else(|| StrykeValue::integer(0));
6524 let length_val = match args.get(2) {
6525 None => StrykeValue::UNDEF,
6526 Some(v) => v.clone(),
6527 };
6528 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
6529 let mut rep_vals: Vec<StrykeValue> = Vec::new();
6533 for a in args.iter().skip(3) {
6534 if let Some(items) = a.as_array_vec() {
6535 rep_vals.extend(items);
6536 } else {
6537 rep_vals.push(a.clone());
6538 }
6539 }
6540 let removed = self.scope.splice_in_place(&arr_name, off, end, rep_vals)?;
6541 Ok(match self.wantarray_kind {
6542 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
6543 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
6544 })
6545 }
6546
6547 pub(crate) fn unshift_builtin_execute(
6549 &mut self,
6550 args: &[StrykeValue],
6551 line: usize,
6552 ) -> StrykeResult<StrykeValue> {
6553 if args.is_empty() {
6554 return Err(StrykeError::runtime("unshift: missing array", line));
6555 }
6556 let arr_name = args[0].to_string();
6557 let mut flat_vals: Vec<StrykeValue> = Vec::new();
6558 for a in args.iter().skip(1) {
6559 if let Some(items) = a.as_array_vec() {
6560 flat_vals.extend(items);
6561 } else {
6562 flat_vals.push(a.clone());
6563 }
6564 }
6565 let arr = self.scope.get_array_mut(&arr_name)?;
6566 for (i, v) in flat_vals.into_iter().enumerate() {
6567 arr.insert(i, v);
6568 }
6569 Ok(StrykeValue::integer(arr.len() as i64))
6570 }
6571
6572 pub(crate) fn perl_rand(&mut self, upper: f64) -> f64 {
6575 if upper == 0.0 {
6576 self.rand_rng.gen_range(0.0..1.0)
6577 } else if upper > 0.0 {
6578 self.rand_rng.gen_range(0.0..upper)
6579 } else {
6580 self.rand_rng.gen_range(upper..0.0)
6581 }
6582 }
6583
6584 pub(crate) fn perl_srand(&mut self, seed: Option<f64>) -> i64 {
6586 let n = if let Some(s) = seed {
6587 s as i64
6588 } else {
6589 std::time::SystemTime::now()
6590 .duration_since(std::time::UNIX_EPOCH)
6591 .map(|d| d.as_secs() as i64)
6592 .unwrap_or(1)
6593 };
6594 let mag = n.unsigned_abs();
6595 self.rand_rng = StdRng::seed_from_u64(mag);
6596 n.abs()
6597 }
6598 pub fn set_file(&mut self, file: &str) {
6600 self.file = file.to_string();
6601 }
6602
6603 pub fn repl_completion_names(&self) -> Vec<String> {
6605 let mut v = self.scope.repl_binding_names();
6606 v.extend(self.subs.keys().cloned());
6607 v.sort();
6608 v.dedup();
6609 v
6610 }
6611
6612 pub fn repl_completion_snapshot(&self) -> ReplCompletionSnapshot {
6614 let mut subs: Vec<String> = self.subs.keys().cloned().collect();
6615 subs.sort();
6616 let mut classes: HashSet<String> = HashSet::new();
6617 for k in &subs {
6618 if let Some((pkg, rest)) = k.split_once("::") {
6619 if !rest.contains("::") {
6620 classes.insert(pkg.to_string());
6621 }
6622 }
6623 }
6624 let mut blessed_scalars: HashMap<String, String> = HashMap::new();
6625 for bn in self.scope.repl_binding_names() {
6626 if let Some(r) = bn.strip_prefix('$') {
6627 let v = self.scope.get_scalar(r);
6628 if let Some(b) = v.as_blessed_ref() {
6629 blessed_scalars.insert(r.to_string(), b.class.clone());
6630 classes.insert(b.class.clone());
6631 }
6632 }
6633 }
6634 let mut isa_for_class: HashMap<String, Vec<String>> = HashMap::new();
6635 for c in classes {
6636 isa_for_class.insert(c.clone(), self.parents_of_class(&c));
6637 }
6638 ReplCompletionSnapshot {
6639 subs,
6640 blessed_scalars,
6641 isa_for_class,
6642 }
6643 }
6644
6645 pub(crate) fn run_bench_block(&mut self, body: &Block, n: usize, line: usize) -> ExecResult {
6646 if n == 0 {
6647 return Err(FlowOrError::Error(StrykeError::runtime(
6648 "bench: iteration count must be positive",
6649 line,
6650 )));
6651 }
6652 let mut samples = Vec::with_capacity(n);
6653 for _ in 0..n {
6654 let start = std::time::Instant::now();
6655 self.exec_block(body)?;
6656 samples.push(start.elapsed().as_secs_f64() * 1000.0);
6657 }
6658 let mut sorted = samples.clone();
6659 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6660 let min_ms = sorted[0];
6661 let mean = samples.iter().sum::<f64>() / n as f64;
6662 let p99_idx = ((n as f64 * 0.99).ceil() as usize)
6663 .saturating_sub(1)
6664 .min(n - 1);
6665 let p99_ms = sorted[p99_idx];
6666 Ok(StrykeValue::string(format!(
6667 "bench: n={} min={:.6}ms mean={:.6}ms p99={:.6}ms",
6668 n, min_ms, mean, p99_ms
6669 )))
6670 }
6671 pub fn execute(&mut self, program: &Program) -> StrykeResult<StrykeValue> {
6673 crate::serialize_normalize::install_class_defs(self.class_defs.clone());
6679 if self.line_mode_skip_main {
6681 crate::compile_and_run_prelude(program, self)?;
6682 return Ok(StrykeValue::UNDEF);
6683 }
6684 crate::try_vm_execute(program, self)
6685 .expect("VM compilation must succeed — all execution is VM-only")
6686 }
6687
6688 pub fn run_end_blocks(&mut self) -> StrykeResult<()> {
6697 let Some(chunk) = self.line_mode_chunk.take() else {
6698 return Ok(());
6699 };
6700 if let Some(end_ip) = chunk.line_mode_end_ip {
6703 let vm_jit = self.vm_jit_enabled && self.profiler.is_none();
6704 let mut vm = crate::vm::VM::new(&chunk, self);
6705 vm.set_jit_enabled(vm_jit);
6706 vm.ip = end_ip;
6707 let _ = vm.execute()?;
6708 }
6709 self.line_mode_chunk = Some(chunk);
6710 Ok(())
6711 }
6712
6713 pub fn run_global_teardown(&mut self) -> StrykeResult<()> {
6716 self.global_phase = "DESTRUCT".to_string();
6717 self.drain_pending_destroys(0)
6718 }
6719
6720 pub(crate) fn drain_pending_destroys(&mut self, line: usize) -> StrykeResult<()> {
6722 loop {
6723 let batch = crate::pending_destroy::take_queue();
6724 if batch.is_empty() {
6725 break;
6726 }
6727 for (class, payload) in batch {
6728 let fq = format!("{}::DESTROY", class);
6729 let Some(sub) = self.subs.get(&fq).cloned() else {
6730 continue;
6731 };
6732 let inv = StrykeValue::blessed(Arc::new(
6733 crate::value::BlessedRef::new_for_destroy_invocant(class, payload),
6734 ));
6735 match self.call_sub(&sub, vec![inv], WantarrayCtx::Void, line) {
6736 Ok(_) => {}
6737 Err(FlowOrError::Error(e)) => return Err(e),
6738 Err(FlowOrError::Flow(Flow::Return(_))) => {}
6739 Err(FlowOrError::Flow(other)) => {
6740 return Err(StrykeError::runtime(
6741 format!("DESTROY: unexpected control flow ({other:?})"),
6742 line,
6743 ));
6744 }
6745 }
6746 }
6747 }
6748 Ok(())
6749 }
6750
6751 pub(crate) fn exec_block(&mut self, block: &Block) -> ExecResult {
6752 self.exec_block_with_tail(block, WantarrayCtx::Void)
6753 }
6754
6755 pub(crate) fn exec_block_with_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6758 let uses_goto = block
6759 .iter()
6760 .any(|s| matches!(s.kind, StmtKind::Goto { .. }));
6761 if uses_goto {
6762 self.scope_push_hook();
6763 let r = self.exec_block_with_goto_tail(block, tail);
6764 self.scope_pop_hook();
6765 r
6766 } else {
6767 self.scope_push_hook();
6768 let result = self.exec_block_no_scope_with_tail(block, tail);
6769 self.scope_pop_hook();
6770 result
6771 }
6772 }
6773
6774 fn exec_block_with_goto_tail(&mut self, block: &Block, tail: WantarrayCtx) -> ExecResult {
6775 let mut map: HashMap<String, usize> = HashMap::new();
6776 for (i, s) in block.iter().enumerate() {
6777 if let Some(l) = &s.label {
6778 map.insert(l.clone(), i);
6779 }
6780 }
6781 let mut pc = 0usize;
6782 let mut last = StrykeValue::UNDEF;
6783 let last_idx = block.len().saturating_sub(1);
6784 while pc < block.len() {
6785 if let StmtKind::Goto { target } = &block[pc].kind {
6786 let line = block[pc].line;
6787 let name = self.eval_expr(target)?.to_string();
6788 pc = *map.get(&name).ok_or_else(|| {
6789 FlowOrError::Error(StrykeError::runtime(
6790 format!("goto: unknown label {}", name),
6791 line,
6792 ))
6793 })?;
6794 continue;
6795 }
6796 let v = if pc == last_idx {
6797 match &block[pc].kind {
6798 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail)?,
6799 _ => self.exec_statement(&block[pc])?,
6800 }
6801 } else {
6802 self.exec_statement(&block[pc])?
6803 };
6804 last = v;
6805 pc += 1;
6806 }
6807 Ok(last)
6808 }
6809
6810 #[inline]
6813 pub(crate) fn exec_block_no_scope(&mut self, block: &Block) -> ExecResult {
6814 self.exec_block_no_scope_with_tail(block, WantarrayCtx::Void)
6815 }
6816
6817 pub(crate) fn exec_block_no_scope_with_tail(
6818 &mut self,
6819 block: &Block,
6820 tail: WantarrayCtx,
6821 ) -> ExecResult {
6822 if block.is_empty() {
6823 return Ok(StrykeValue::UNDEF);
6824 }
6825 let last_i = block.len() - 1;
6826 for (i, stmt) in block.iter().enumerate() {
6827 if i < last_i {
6828 self.exec_statement(stmt)?;
6829 } else {
6830 return match &stmt.kind {
6831 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, tail),
6832 _ => self.exec_statement(stmt),
6833 };
6834 }
6835 }
6836 Ok(StrykeValue::UNDEF)
6837 }
6838
6839 pub(crate) fn spawn_async_block(&self, block: &Block) -> StrykeValue {
6841 use parking_lot::Mutex as ParkMutex;
6842
6843 let block = block.clone();
6844 let subs = self.subs.clone();
6845 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6846 let result = Arc::new(ParkMutex::new(None));
6847 let join = Arc::new(ParkMutex::new(None));
6848 let result2 = result.clone();
6849 let h = std::thread::spawn(move || {
6850 let mut interp = VMHelper::new();
6851 interp.subs = subs;
6852 interp.scope.restore_capture(&scalars);
6853 interp.scope.restore_atomics(&aar, &ahash);
6854 interp.enable_parallel_guard();
6855 let r = match interp.exec_block(&block) {
6856 Ok(v) => Ok(v),
6857 Err(FlowOrError::Error(e)) => Err(e),
6858 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6859 Err(StrykeError::runtime("yield inside async/spawn block", 0))
6860 }
6861 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
6862 };
6863 *result2.lock() = Some(r);
6864 });
6865 *join.lock() = Some(h);
6866 StrykeValue::async_task(Arc::new(StrykeAsyncTask { result, join }))
6867 }
6868
6869 pub(crate) fn eval_timeout_block(
6871 &mut self,
6872 body: &Block,
6873 secs: f64,
6874 line: usize,
6875 ) -> ExecResult {
6876 use std::sync::mpsc::channel;
6877 use std::time::Duration;
6878
6879 let block = body.clone();
6880 let subs = self.subs.clone();
6881 let struct_defs = self.struct_defs.clone();
6882 let enum_defs = self.enum_defs.clone();
6883 let (scalars, aar, ahash) = self.scope.capture_with_atomics();
6884 self.materialize_env_if_needed();
6885 let env = self.env.clone();
6886 let argv = self.argv.clone();
6887 let inc = self.scope.get_array("INC");
6888 let (tx, rx) = channel::<StrykeResult<StrykeValue>>();
6889 let _handle = std::thread::spawn(move || {
6890 let mut interp = VMHelper::new();
6891 interp.subs = subs;
6892 interp.struct_defs = struct_defs;
6893 interp.enum_defs = enum_defs;
6894 interp.env = env.clone();
6895 interp.argv = argv.clone();
6896 interp.scope.declare_array(
6897 "ARGV",
6898 argv.iter()
6899 .map(|s| StrykeValue::string(s.clone()))
6900 .collect(),
6901 );
6902 for (k, v) in env {
6903 interp
6904 .scope
6905 .set_hash_element("ENV", &k, v)
6906 .expect("set ENV in timeout thread");
6907 }
6908 interp.scope.declare_array("INC", inc);
6909 interp.scope.restore_capture(&scalars);
6910 interp.scope.restore_atomics(&aar, &ahash);
6911 interp.enable_parallel_guard();
6912 let out: StrykeResult<StrykeValue> = match interp.exec_block(&block) {
6913 Ok(v) => Ok(v),
6914 Err(FlowOrError::Error(e)) => Err(e),
6915 Err(FlowOrError::Flow(Flow::Yield(_))) => {
6916 Err(StrykeError::runtime("yield inside eval_timeout block", 0))
6917 }
6918 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
6919 };
6920 let _ = tx.send(out);
6921 });
6922 let dur = Duration::from_secs_f64(secs.max(0.0));
6923 match rx.recv_timeout(dur) {
6924 Ok(Ok(v)) => Ok(v),
6925 Ok(Err(e)) => Err(FlowOrError::Error(e)),
6926 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Err(StrykeError::runtime(
6927 format!(
6928 "eval_timeout: exceeded {} second(s) (worker continues in background)",
6929 secs
6930 ),
6931 line,
6932 )
6933 .into()),
6934 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(StrykeError::runtime(
6935 "eval_timeout: worker thread panicked or disconnected",
6936 line,
6937 )
6938 .into()),
6939 }
6940 }
6941
6942 fn exec_given_body(&mut self, body: &Block) -> ExecResult {
6943 let mut last = StrykeValue::UNDEF;
6944 for stmt in body {
6945 match &stmt.kind {
6946 StmtKind::When { cond, body: wb } => {
6947 if self.when_matches(cond)? {
6948 return self.exec_block_smart(wb);
6949 }
6950 }
6951 StmtKind::DefaultCase { body: db } => {
6952 return self.exec_block_smart(db);
6953 }
6954 _ => {
6955 last = self.exec_statement(stmt)?;
6956 }
6957 }
6958 }
6959 Ok(last)
6960 }
6961
6962 pub(crate) fn exec_given_with_topic_value(
6964 &mut self,
6965 topic: StrykeValue,
6966 body: &Block,
6967 ) -> ExecResult {
6968 self.scope_push_hook();
6969 self.scope.declare_scalar("_", topic);
6970 self.english_note_lexical_scalar("_");
6971 let r = self.exec_given_body(body);
6972 self.scope_pop_hook();
6973 r
6974 }
6975
6976 pub(crate) fn exec_given(&mut self, topic: &Expr, body: &Block) -> ExecResult {
6977 let t = self.eval_expr(topic)?;
6978 self.exec_given_with_topic_value(t, body)
6979 }
6980
6981 fn when_matches(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
6983 let topic = self.scope.get_scalar("_");
6984 let line = cond.line;
6985 match &cond.kind {
6986 ExprKind::Regex(pattern, flags) => {
6987 let re = self.compile_regex(pattern, flags, line)?;
6988 let s = topic.to_string();
6989 Ok(re.is_match(&s))
6990 }
6991 ExprKind::String(s) => Ok(topic.to_string() == *s),
6992 ExprKind::Integer(n) => Ok(topic.to_int() == *n),
6993 ExprKind::Float(f) => Ok((topic.to_number() - *f).abs() < 1e-9),
6994 _ => {
6995 let c = self.eval_expr(cond)?;
6996 Ok(self.smartmatch_when(&topic, &c))
6997 }
6998 }
6999 }
7000
7001 fn smartmatch_when(&self, topic: &StrykeValue, c: &StrykeValue) -> bool {
7002 if let Some(re) = c.as_regex() {
7003 return re.is_match(&topic.to_string());
7004 }
7005 if let Some(arr) = c.as_array_ref() {
7012 let arr = arr.read();
7013 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
7014 }
7015 if let Some(arr) = c.as_array_vec() {
7016 return arr.iter().any(|elem| self.smartmatch_when(topic, elem));
7017 }
7018 if let Some(href) = c.as_hash_ref() {
7020 return href.read().contains_key(&topic.to_string());
7021 }
7022 if let Some(h) = c.as_hash_map() {
7023 return h.contains_key(&topic.to_string());
7024 }
7025 if let Some(sub) = c.as_code_ref() {
7027 let _ = sub;
7031 }
7032 if let (Some(a), Some(b)) = (topic.as_integer(), c.as_integer()) {
7034 return a == b;
7035 }
7036 topic.to_string() == c.to_string()
7037 }
7038
7039 pub(crate) fn eval_boolean_rvalue_condition(
7041 &mut self,
7042 cond: &Expr,
7043 ) -> Result<bool, FlowOrError> {
7044 match &cond.kind {
7045 ExprKind::Regex(pattern, flags) => {
7046 let topic = self.scope.get_scalar("_");
7047 let line = cond.line;
7048 let s = topic.to_string();
7049 let v = self.regex_match_execute(s, pattern, flags, false, "_", line)?;
7050 Ok(v.is_true())
7051 }
7052 ExprKind::ReadLine(_) => {
7054 let v = self.eval_expr(cond)?;
7055 self.scope.set_topic(v.clone());
7056 Ok(!v.is_undef())
7057 }
7058 _ => {
7059 let v = self.eval_expr(cond)?;
7060 Ok(v.is_true())
7061 }
7062 }
7063 }
7064
7065 fn eval_postfix_condition(&mut self, cond: &Expr) -> Result<bool, FlowOrError> {
7067 self.eval_boolean_rvalue_condition(cond)
7068 }
7069
7070 pub(crate) fn eval_algebraic_match(
7071 &mut self,
7072 subject: &Expr,
7073 arms: &[MatchArm],
7074 line: usize,
7075 ) -> ExecResult {
7076 let val = self.eval_algebraic_match_subject(subject, line)?;
7077 self.eval_algebraic_match_with_subject_value(val, arms, line)
7078 }
7079
7080 fn eval_algebraic_match_subject(&mut self, subject: &Expr, line: usize) -> ExecResult {
7082 match &subject.kind {
7083 ExprKind::ArrayVar(name) => {
7084 self.check_strict_array_var(name, line)?;
7085 let aname = self.stash_array_name_for_package(name);
7086 Ok(StrykeValue::array_binding_ref(aname))
7087 }
7088 ExprKind::HashVar(name) => {
7089 self.check_strict_hash_var(name, line)?;
7090 self.touch_env_hash(name);
7091 Ok(StrykeValue::hash_binding_ref(name.clone()))
7092 }
7093 _ => self.eval_expr(subject),
7094 }
7095 }
7096
7097 pub(crate) fn eval_algebraic_match_with_subject_value(
7099 &mut self,
7100 val: StrykeValue,
7101 arms: &[MatchArm],
7102 line: usize,
7103 ) -> ExecResult {
7104 if let Some(e) = val.as_enum_inst() {
7106 let has_catchall = arms.iter().any(|a| matches!(a.pattern, MatchPattern::Any));
7107 if !has_catchall {
7108 let covered: Vec<String> = arms
7109 .iter()
7110 .filter_map(|a| {
7111 if let MatchPattern::Value(expr) = &a.pattern {
7112 if let ExprKind::FuncCall { name, .. } = &expr.kind {
7113 return name.rsplit_once("::").map(|(_, v)| v.to_string());
7114 }
7115 }
7116 None
7117 })
7118 .collect();
7119 let missing: Vec<&str> = e
7120 .def
7121 .variants
7122 .iter()
7123 .filter(|v| !covered.contains(&v.name))
7124 .map(|v| v.name.as_str())
7125 .collect();
7126 if !missing.is_empty() {
7127 return Err(StrykeError::runtime(
7128 format!(
7129 "non-exhaustive match on enum `{}`: missing variant(s) {}",
7130 e.def.name,
7131 missing.join(", ")
7132 ),
7133 line,
7134 )
7135 .into());
7136 }
7137 }
7138 }
7139 for arm in arms {
7140 if let MatchPattern::Regex { pattern, flags } = &arm.pattern {
7141 let re = self.compile_regex(pattern, flags, line)?;
7142 let s = val.to_string();
7143 if let Some(caps) = re.captures(&s) {
7144 self.scope_push_hook();
7145 self.scope.declare_scalar("_", val.clone());
7146 self.english_note_lexical_scalar("_");
7147 self.apply_regex_captures(&s, 0, re.as_ref(), &caps, CaptureAllMode::Empty)?;
7148 let guard_ok = if let Some(g) = &arm.guard {
7149 self.eval_expr(g)?.is_true()
7150 } else {
7151 true
7152 };
7153 if !guard_ok {
7154 self.scope_pop_hook();
7155 continue;
7156 }
7157 let out = self.eval_expr(&arm.body);
7158 self.scope_pop_hook();
7159 return out;
7160 }
7161 continue;
7162 }
7163 if let Some(bindings) = self.match_pattern_try(&val, &arm.pattern, line)? {
7164 self.scope_push_hook();
7165 self.scope.declare_scalar("_", val.clone());
7166 self.english_note_lexical_scalar("_");
7167 for b in bindings {
7168 match b {
7169 PatternBinding::Scalar(name, v) => {
7170 self.scope.declare_scalar(&name, v);
7171 self.english_note_lexical_scalar(&name);
7172 }
7173 PatternBinding::Array(name, elems) => {
7174 self.scope.declare_array(&name, elems);
7175 }
7176 }
7177 }
7178 let guard_ok = if let Some(g) = &arm.guard {
7179 self.eval_expr(g)?.is_true()
7180 } else {
7181 true
7182 };
7183 if !guard_ok {
7184 self.scope_pop_hook();
7185 continue;
7186 }
7187 let out = self.eval_expr(&arm.body);
7188 self.scope_pop_hook();
7189 return out;
7190 }
7191 }
7192 Err(StrykeError::runtime(
7193 "match: no arm matched the value (add a `_` catch-all)",
7194 line,
7195 )
7196 .into())
7197 }
7198
7199 fn parse_duration_seconds(pv: &StrykeValue) -> Option<f64> {
7200 let s = pv.to_string();
7201 let s = s.trim();
7202 if let Some(rest) = s.strip_suffix("ms") {
7203 return rest.trim().parse::<f64>().ok().map(|x| x / 1000.0);
7204 }
7205 if let Some(rest) = s.strip_suffix('s') {
7206 return rest.trim().parse::<f64>().ok();
7207 }
7208 if let Some(rest) = s.strip_suffix('m') {
7209 return rest.trim().parse::<f64>().ok().map(|x| x * 60.0);
7210 }
7211 s.parse::<f64>().ok()
7212 }
7213
7214 fn eval_retry_block(
7215 &mut self,
7216 body: &Block,
7217 times: &Expr,
7218 backoff: RetryBackoff,
7219 _line: usize,
7220 ) -> ExecResult {
7221 let max = self.eval_expr(times)?.to_int().max(1) as usize;
7222 let base_ms: u64 = 10;
7223 let mut attempt = 0usize;
7224 loop {
7225 attempt += 1;
7226 match self.exec_block(body) {
7227 Ok(v) => return Ok(v),
7228 Err(FlowOrError::Error(e)) => {
7229 if attempt >= max {
7230 return Err(FlowOrError::Error(e));
7231 }
7232 let delay_ms = match backoff {
7233 RetryBackoff::None => 0,
7234 RetryBackoff::Linear => base_ms.saturating_mul(attempt as u64),
7235 RetryBackoff::Exponential => {
7236 base_ms.saturating_mul(1u64 << (attempt as u32 - 1).min(30))
7237 }
7238 };
7239 if delay_ms > 0 {
7240 std::thread::sleep(Duration::from_millis(delay_ms));
7241 }
7242 }
7243 Err(e) => return Err(e),
7244 }
7245 }
7246 }
7247
7248 fn eval_rate_limit_block(
7249 &mut self,
7250 slot: u32,
7251 max: &Expr,
7252 window: &Expr,
7253 body: &Block,
7254 _line: usize,
7255 ) -> ExecResult {
7256 let max_n = self.eval_expr(max)?.to_int().max(0) as usize;
7257 let window_sec = Self::parse_duration_seconds(&self.eval_expr(window)?)
7258 .filter(|s| *s > 0.0)
7259 .unwrap_or(1.0);
7260 let window_d = Duration::from_secs_f64(window_sec);
7261 let slot = slot as usize;
7262 while self.rate_limit_slots.len() <= slot {
7263 self.rate_limit_slots.push(VecDeque::new());
7264 }
7265 {
7266 let dq = &mut self.rate_limit_slots[slot];
7267 loop {
7268 let now = Instant::now();
7269 while let Some(t0) = dq.front().copied() {
7270 if now.duration_since(t0) >= window_d {
7271 dq.pop_front();
7272 } else {
7273 break;
7274 }
7275 }
7276 if dq.len() < max_n || max_n == 0 {
7277 break;
7278 }
7279 let t0 = dq.front().copied().unwrap();
7280 let wait = window_d.saturating_sub(now.duration_since(t0));
7281 if wait.is_zero() {
7282 dq.pop_front();
7283 continue;
7284 }
7285 std::thread::sleep(wait);
7286 }
7287 dq.push_back(Instant::now());
7288 }
7289 self.exec_block(body)
7290 }
7291
7292 fn eval_every_block(&mut self, interval: &Expr, body: &Block, _line: usize) -> ExecResult {
7293 let sec = Self::parse_duration_seconds(&self.eval_expr(interval)?)
7294 .filter(|s| *s > 0.0)
7295 .unwrap_or(1.0);
7296 loop {
7297 match self.exec_block(body) {
7298 Ok(_) => {}
7299 Err(e) => return Err(e),
7300 }
7301 std::thread::sleep(Duration::from_secs_f64(sec));
7302 }
7303 }
7304
7305 pub(crate) fn generator_next(&mut self, gen: &Arc<PerlGenerator>) -> StrykeResult<StrykeValue> {
7307 let pair = |value: StrykeValue, more: i64| {
7308 StrykeValue::array_ref(Arc::new(RwLock::new(vec![
7309 value,
7310 StrykeValue::integer(more),
7311 ])))
7312 };
7313 let mut exhausted = gen.exhausted.lock();
7314 if *exhausted {
7315 return Ok(pair(StrykeValue::UNDEF, 0));
7316 }
7317 let mut pc = gen.pc.lock();
7318 let mut scope_started = gen.scope_started.lock();
7319 if *pc >= gen.block.len() {
7320 if *scope_started {
7321 self.scope_pop_hook();
7322 *scope_started = false;
7323 }
7324 *exhausted = true;
7325 return Ok(pair(StrykeValue::UNDEF, 0));
7326 }
7327 if !*scope_started {
7328 self.scope_push_hook();
7329 *scope_started = true;
7330 }
7331 self.in_generator = true;
7332 while *pc < gen.block.len() {
7333 let stmt = &gen.block[*pc];
7334 match self.exec_statement(stmt) {
7335 Ok(_) => {
7336 *pc += 1;
7337 }
7338 Err(FlowOrError::Flow(Flow::Yield(v))) => {
7339 *pc += 1;
7340 self.in_generator = false;
7341 if *scope_started {
7344 self.scope_pop_hook();
7345 *scope_started = false;
7346 }
7347 return Ok(pair(v, 1));
7348 }
7349 Err(e) => {
7350 self.in_generator = false;
7351 if *scope_started {
7352 self.scope_pop_hook();
7353 *scope_started = false;
7354 }
7355 return Err(match e {
7356 FlowOrError::Error(ee) => ee,
7357 FlowOrError::Flow(Flow::Yield(_)) => {
7358 unreachable!("yield handled above")
7359 }
7360 FlowOrError::Flow(flow) => StrykeError::runtime(
7361 format!("unexpected control flow in generator: {:?}", flow),
7362 0,
7363 ),
7364 });
7365 }
7366 }
7367 }
7368 self.in_generator = false;
7369 if *scope_started {
7370 self.scope_pop_hook();
7371 *scope_started = false;
7372 }
7373 *exhausted = true;
7374 Ok(pair(StrykeValue::UNDEF, 0))
7375 }
7376
7377 fn match_pattern_try(
7378 &mut self,
7379 subject: &StrykeValue,
7380 pattern: &MatchPattern,
7381 line: usize,
7382 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7383 match pattern {
7384 MatchPattern::Any => Ok(Some(vec![])),
7385 MatchPattern::Regex { .. } => {
7386 unreachable!("regex arms are handled in eval_algebraic_match")
7387 }
7388 MatchPattern::Value(expr) => {
7389 if self.match_pattern_value_alternation(subject, expr, line)? {
7390 Ok(Some(vec![]))
7391 } else {
7392 Ok(None)
7393 }
7394 }
7395 MatchPattern::Array(elems) => {
7396 let Some(arr) = self.match_subject_as_array(subject) else {
7397 return Ok(None);
7398 };
7399 self.match_array_pattern_elems(&arr, elems, line)
7400 }
7401 MatchPattern::Hash(pairs) => {
7402 let Some(h) = self.match_subject_as_hash(subject) else {
7403 return Ok(None);
7404 };
7405 self.match_hash_pattern_pairs(&h, pairs, line)
7406 }
7407 MatchPattern::OptionSome(name) => {
7408 let Some(arr) = self.match_subject_as_array(subject) else {
7409 return Ok(None);
7410 };
7411 if arr.len() < 2 {
7412 return Ok(None);
7413 }
7414 if !arr[1].is_true() {
7415 return Ok(None);
7416 }
7417 Ok(Some(vec![PatternBinding::Scalar(
7418 name.clone(),
7419 arr[0].clone(),
7420 )]))
7421 }
7422 }
7423 }
7424
7425 fn match_pattern_value_alternation(
7428 &mut self,
7429 subject: &StrykeValue,
7430 expr: &Expr,
7431 _line: usize,
7432 ) -> Result<bool, FlowOrError> {
7433 if let ExprKind::BinOp {
7434 left,
7435 op: BinOp::BitOr,
7436 right,
7437 } = &expr.kind
7438 {
7439 if self.match_pattern_value_alternation(subject, left, _line)? {
7440 return Ok(true);
7441 }
7442 return self.match_pattern_value_alternation(subject, right, _line);
7443 }
7444 let pv = self.eval_expr(expr)?;
7445 Ok(self.smartmatch_when(subject, &pv))
7446 }
7447
7448 fn match_subject_as_array(&self, v: &StrykeValue) -> Option<Vec<StrykeValue>> {
7450 if let Some(a) = v.as_array_vec() {
7451 return Some(a);
7452 }
7453 if let Some(r) = v.as_array_ref() {
7454 return Some(r.read().clone());
7455 }
7456 if let Some(name) = v.as_array_binding_name() {
7457 return Some(self.scope.get_array(&name));
7458 }
7459 None
7460 }
7461
7462 fn match_subject_as_hash(&mut self, v: &StrykeValue) -> Option<IndexMap<String, StrykeValue>> {
7463 if let Some(h) = v.as_hash_map() {
7464 return Some(h);
7465 }
7466 if let Some(r) = v.as_hash_ref() {
7467 return Some(r.read().clone());
7468 }
7469 if let Some(name) = v.as_hash_binding_name() {
7470 self.touch_env_hash(&name);
7471 return Some(self.scope.get_hash(&name));
7472 }
7473 None
7474 }
7475
7476 pub(crate) fn hash_slice_deref_values(
7479 &mut self,
7480 container: &StrykeValue,
7481 key_values: &[StrykeValue],
7482 line: usize,
7483 ) -> Result<StrykeValue, FlowOrError> {
7484 let h = if let Some(m) = self.match_subject_as_hash(container) {
7485 m
7486 } else {
7487 return Err(StrykeError::runtime(
7488 "Hash slice dereference needs a hash or hash reference value",
7489 line,
7490 )
7491 .into());
7492 };
7493 let mut result = Vec::new();
7494 for kv in key_values {
7495 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7496 vv.iter().map(|x| x.to_string()).collect()
7497 } else {
7498 vec![kv.to_string()]
7499 };
7500 for k in key_strings {
7501 result.push(h.get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
7502 }
7503 }
7504 Ok(StrykeValue::array(result))
7505 }
7506
7507 pub(crate) fn assign_hash_slice_one_key(
7510 &mut self,
7511 container: StrykeValue,
7512 key: &str,
7513 val: StrykeValue,
7514 line: usize,
7515 ) -> Result<StrykeValue, FlowOrError> {
7516 if let Some(r) = container.as_hash_ref() {
7517 r.write().insert(key.to_string(), val);
7518 return Ok(StrykeValue::UNDEF);
7519 }
7520 if let Some(name) = container.as_hash_binding_name() {
7521 self.touch_env_hash(&name);
7522 self.scope
7523 .set_hash_element(&name, key, val)
7524 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7525 return Ok(StrykeValue::UNDEF);
7526 }
7527 if let Some(s) = container.as_str() {
7528 self.touch_env_hash(&s);
7529 if self.strict_refs {
7530 return Err(StrykeError::runtime(
7531 format!(
7532 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7533 s
7534 ),
7535 line,
7536 )
7537 .into());
7538 }
7539 self.scope
7540 .set_hash_element(&s, key, val)
7541 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7542 return Ok(StrykeValue::UNDEF);
7543 }
7544 Err(StrykeError::runtime(
7545 "Hash slice assignment needs a hash or hash reference value",
7546 line,
7547 )
7548 .into())
7549 }
7550
7551 pub(crate) fn assign_named_hash_slice(
7554 &mut self,
7555 hash: &str,
7556 key_values: Vec<StrykeValue>,
7557 val: StrykeValue,
7558 line: usize,
7559 ) -> Result<StrykeValue, FlowOrError> {
7560 self.touch_env_hash(hash);
7561 let mut ks: Vec<String> = Vec::new();
7562 for kv in key_values {
7563 if let Some(vv) = kv.as_array_vec() {
7564 ks.extend(vv.iter().map(|x| x.to_string()));
7565 } else {
7566 ks.push(kv.to_string());
7567 }
7568 }
7569 if ks.is_empty() {
7570 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7571 }
7572 let items = val.to_list();
7573 for (i, k) in ks.iter().enumerate() {
7574 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7575 self.scope
7576 .set_hash_element(hash, k, v)
7577 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7578 }
7579 Ok(StrykeValue::UNDEF)
7580 }
7581
7582 pub(crate) fn assign_hash_slice_deref(
7584 &mut self,
7585 container: StrykeValue,
7586 key_values: Vec<StrykeValue>,
7587 val: StrykeValue,
7588 line: usize,
7589 ) -> Result<StrykeValue, FlowOrError> {
7590 let mut ks: Vec<String> = Vec::new();
7591 for kv in key_values {
7592 if let Some(vv) = kv.as_array_vec() {
7593 ks.extend(vv.iter().map(|x| x.to_string()));
7594 } else {
7595 ks.push(kv.to_string());
7596 }
7597 }
7598 if ks.is_empty() {
7599 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7600 }
7601 let items = val.to_list();
7602 if let Some(r) = container.as_hash_ref() {
7603 let mut h = r.write();
7604 for (i, k) in ks.iter().enumerate() {
7605 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7606 h.insert(k.clone(), v);
7607 }
7608 return Ok(StrykeValue::UNDEF);
7609 }
7610 if let Some(name) = container.as_hash_binding_name() {
7611 self.touch_env_hash(&name);
7612 for (i, k) in ks.iter().enumerate() {
7613 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7614 self.scope
7615 .set_hash_element(&name, k, v)
7616 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7617 }
7618 return Ok(StrykeValue::UNDEF);
7619 }
7620 if let Some(s) = container.as_str() {
7621 if self.strict_refs {
7622 return Err(StrykeError::runtime(
7623 format!(
7624 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
7625 s
7626 ),
7627 line,
7628 )
7629 .into());
7630 }
7631 self.touch_env_hash(&s);
7632 for (i, k) in ks.iter().enumerate() {
7633 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
7634 self.scope
7635 .set_hash_element(&s, k, v)
7636 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
7637 }
7638 return Ok(StrykeValue::UNDEF);
7639 }
7640 Err(StrykeError::runtime(
7641 "Hash slice assignment needs a hash or hash reference value",
7642 line,
7643 )
7644 .into())
7645 }
7646
7647 pub(crate) fn compound_assign_hash_slice_deref(
7650 &mut self,
7651 container: StrykeValue,
7652 key_values: Vec<StrykeValue>,
7653 op: BinOp,
7654 rhs: StrykeValue,
7655 line: usize,
7656 ) -> Result<StrykeValue, FlowOrError> {
7657 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7658 let last_old = old_list
7659 .to_list()
7660 .last()
7661 .cloned()
7662 .unwrap_or(StrykeValue::UNDEF);
7663 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7664 let mut ks: Vec<String> = Vec::new();
7665 for kv in &key_values {
7666 if let Some(vv) = kv.as_array_vec() {
7667 ks.extend(vv.iter().map(|x| x.to_string()));
7668 } else {
7669 ks.push(kv.to_string());
7670 }
7671 }
7672 if ks.is_empty() {
7673 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7674 }
7675 let last_key = ks.last().expect("non-empty ks");
7676 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7677 Ok(new_val)
7678 }
7679
7680 pub(crate) fn hash_slice_deref_inc_dec(
7686 &mut self,
7687 container: StrykeValue,
7688 key_values: Vec<StrykeValue>,
7689 kind: u8,
7690 line: usize,
7691 ) -> Result<StrykeValue, FlowOrError> {
7692 let old_list = self.hash_slice_deref_values(&container, &key_values, line)?;
7693 let last_old = old_list
7694 .to_list()
7695 .last()
7696 .cloned()
7697 .unwrap_or(StrykeValue::UNDEF);
7698 let new_val = if kind & 1 == 0 {
7699 StrykeValue::integer(last_old.to_int() + 1)
7700 } else {
7701 StrykeValue::integer(last_old.to_int() - 1)
7702 };
7703 let mut ks: Vec<String> = Vec::new();
7704 for kv in &key_values {
7705 if let Some(vv) = kv.as_array_vec() {
7706 ks.extend(vv.iter().map(|x| x.to_string()));
7707 } else {
7708 ks.push(kv.to_string());
7709 }
7710 }
7711 let last_key = ks.last().ok_or_else(|| {
7712 StrykeError::runtime("Hash slice increment needs at least one key", line)
7713 })?;
7714 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7715 Ok(if kind < 2 { new_val } else { last_old })
7716 }
7717
7718 fn hash_slice_named_values(&mut self, hash: &str, key_values: &[StrykeValue]) -> StrykeValue {
7719 self.touch_env_hash(hash);
7720 let h = self.scope.get_hash(hash);
7721 let mut result = Vec::new();
7722 for kv in key_values {
7723 let key_strings: Vec<String> = if let Some(vv) = kv.as_array_vec() {
7724 vv.iter().map(|x| x.to_string()).collect()
7725 } else {
7726 vec![kv.to_string()]
7727 };
7728 for k in key_strings {
7729 result.push(h.get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
7730 }
7731 }
7732 StrykeValue::array(result)
7733 }
7734
7735 pub(crate) fn compound_assign_named_hash_slice(
7737 &mut self,
7738 hash: &str,
7739 key_values: Vec<StrykeValue>,
7740 op: BinOp,
7741 rhs: StrykeValue,
7742 line: usize,
7743 ) -> Result<StrykeValue, FlowOrError> {
7744 let old_list = self.hash_slice_named_values(hash, &key_values);
7745 let last_old = old_list
7746 .to_list()
7747 .last()
7748 .cloned()
7749 .unwrap_or(StrykeValue::UNDEF);
7750 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
7751 let mut ks: Vec<String> = Vec::new();
7752 for kv in &key_values {
7753 if let Some(vv) = kv.as_array_vec() {
7754 ks.extend(vv.iter().map(|x| x.to_string()));
7755 } else {
7756 ks.push(kv.to_string());
7757 }
7758 }
7759 if ks.is_empty() {
7760 return Err(StrykeError::runtime("assign to empty hash slice", line).into());
7761 }
7762 let last_key = ks.last().expect("non-empty ks");
7763 let container = StrykeValue::string(hash.to_string());
7764 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7765 Ok(new_val)
7766 }
7767
7768 pub(crate) fn named_hash_slice_inc_dec(
7770 &mut self,
7771 hash: &str,
7772 key_values: Vec<StrykeValue>,
7773 kind: u8,
7774 line: usize,
7775 ) -> Result<StrykeValue, FlowOrError> {
7776 let old_list = self.hash_slice_named_values(hash, &key_values);
7777 let last_old = old_list
7778 .to_list()
7779 .last()
7780 .cloned()
7781 .unwrap_or(StrykeValue::UNDEF);
7782 let new_val = if kind & 1 == 0 {
7783 StrykeValue::integer(last_old.to_int() + 1)
7784 } else {
7785 StrykeValue::integer(last_old.to_int() - 1)
7786 };
7787 let mut ks: Vec<String> = Vec::new();
7788 for kv in &key_values {
7789 if let Some(vv) = kv.as_array_vec() {
7790 ks.extend(vv.iter().map(|x| x.to_string()));
7791 } else {
7792 ks.push(kv.to_string());
7793 }
7794 }
7795 let last_key = ks.last().ok_or_else(|| {
7796 StrykeError::runtime("Hash slice increment needs at least one key", line)
7797 })?;
7798 let container = StrykeValue::string(hash.to_string());
7799 self.assign_hash_slice_one_key(container, last_key, new_val.clone(), line)?;
7800 Ok(if kind < 2 { new_val } else { last_old })
7801 }
7802
7803 fn match_array_pattern_elems(
7804 &mut self,
7805 arr: &[StrykeValue],
7806 elems: &[MatchArrayElem],
7807 line: usize,
7808 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7809 let has_rest = elems
7810 .iter()
7811 .any(|e| matches!(e, MatchArrayElem::Rest | MatchArrayElem::RestBind(_)));
7812 let mut binds: Vec<PatternBinding> = Vec::new();
7813 let mut idx = 0usize;
7814 for (i, elem) in elems.iter().enumerate() {
7815 match elem {
7816 MatchArrayElem::Rest => {
7817 if i != elems.len() - 1 {
7818 return Err(StrykeError::runtime(
7819 "internal: `*` must be last in array match pattern",
7820 line,
7821 )
7822 .into());
7823 }
7824 return Ok(Some(binds));
7825 }
7826 MatchArrayElem::RestBind(name) => {
7827 if i != elems.len() - 1 {
7828 return Err(StrykeError::runtime(
7829 "internal: `@name` rest bind must be last in array match pattern",
7830 line,
7831 )
7832 .into());
7833 }
7834 let tail = arr[idx..].to_vec();
7835 binds.push(PatternBinding::Array(name.clone(), tail));
7836 return Ok(Some(binds));
7837 }
7838 MatchArrayElem::CaptureScalar(name) => {
7839 if idx >= arr.len() {
7840 return Ok(None);
7841 }
7842 binds.push(PatternBinding::Scalar(name.clone(), arr[idx].clone()));
7843 idx += 1;
7844 }
7845 MatchArrayElem::Expr(e) => {
7846 if idx >= arr.len() {
7847 return Ok(None);
7848 }
7849 let expected = self.eval_expr(e)?;
7850 if !self.smartmatch_when(&arr[idx], &expected) {
7851 return Ok(None);
7852 }
7853 idx += 1;
7854 }
7855 }
7856 }
7857 if !has_rest && idx != arr.len() {
7858 return Ok(None);
7859 }
7860 Ok(Some(binds))
7861 }
7862
7863 fn match_hash_pattern_pairs(
7864 &mut self,
7865 h: &IndexMap<String, StrykeValue>,
7866 pairs: &[MatchHashPair],
7867 _line: usize,
7868 ) -> Result<Option<Vec<PatternBinding>>, FlowOrError> {
7869 let mut binds = Vec::new();
7870 for pair in pairs {
7871 match pair {
7872 MatchHashPair::KeyOnly { key } => {
7873 let ks = self.eval_expr(key)?.to_string();
7874 if !h.contains_key(&ks) {
7875 return Ok(None);
7876 }
7877 }
7878 MatchHashPair::Capture { key, name } => {
7879 let ks = self.eval_expr(key)?.to_string();
7880 let Some(v) = h.get(&ks) else {
7881 return Ok(None);
7882 };
7883 binds.push(PatternBinding::Scalar(name.clone(), v.clone()));
7884 }
7885 }
7886 }
7887 Ok(Some(binds))
7888 }
7889
7890 #[inline]
7892 fn block_needs_scope(block: &Block) -> bool {
7893 block.iter().any(|s| match &s.kind {
7894 StmtKind::My(_)
7895 | StmtKind::Our(_)
7896 | StmtKind::Local(_)
7897 | StmtKind::State(_)
7898 | StmtKind::LocalExpr { .. } => true,
7899 StmtKind::StmtGroup(inner) => Self::block_needs_scope(inner),
7900 _ => false,
7901 })
7902 }
7903
7904 #[inline]
7906 pub(crate) fn exec_block_smart(&mut self, block: &Block) -> ExecResult {
7907 if Self::block_needs_scope(block) {
7908 self.exec_block(block)
7909 } else {
7910 self.exec_block_no_scope(block)
7911 }
7912 }
7913
7914 fn exec_statement(&mut self, stmt: &Statement) -> ExecResult {
7915 let t0 = self.profiler.is_some().then(std::time::Instant::now);
7916 let r = self.exec_statement_inner(stmt);
7917 if let (Some(prof), Some(t0)) = (&mut self.profiler, t0) {
7918 prof.on_line(&self.file, stmt.line, t0.elapsed());
7919 }
7920 r
7921 }
7922
7923 fn exec_statement_inner(&mut self, stmt: &Statement) -> ExecResult {
7924 if let Err(e) = crate::perl_signal::poll(self) {
7925 return Err(FlowOrError::Error(e));
7926 }
7927 if let Err(e) = self.drain_pending_destroys(stmt.line) {
7928 return Err(FlowOrError::Error(e));
7929 }
7930 match &stmt.kind {
7931 StmtKind::StmtGroup(block) => self.exec_block_no_scope(block),
7932 StmtKind::Expression(expr) => self.eval_expr_ctx(expr, WantarrayCtx::Void),
7933 StmtKind::If {
7934 condition,
7935 body,
7936 elsifs,
7937 else_block,
7938 } => {
7939 if self.eval_boolean_rvalue_condition(condition)? {
7940 return self.exec_block(body);
7941 }
7942 for (c, b) in elsifs {
7943 if self.eval_boolean_rvalue_condition(c)? {
7944 return self.exec_block(b);
7945 }
7946 }
7947 if let Some(eb) = else_block {
7948 return self.exec_block(eb);
7949 }
7950 Ok(StrykeValue::UNDEF)
7951 }
7952 StmtKind::Unless {
7953 condition,
7954 body,
7955 else_block,
7956 } => {
7957 if !self.eval_boolean_rvalue_condition(condition)? {
7958 return self.exec_block(body);
7959 }
7960 if let Some(eb) = else_block {
7961 return self.exec_block(eb);
7962 }
7963 Ok(StrykeValue::UNDEF)
7964 }
7965 StmtKind::While {
7966 condition,
7967 body,
7968 label,
7969 continue_block,
7970 } => {
7971 'outer: loop {
7972 if !self.eval_boolean_rvalue_condition(condition)? {
7973 break;
7974 }
7975 'inner: loop {
7976 match self.exec_block_smart(body) {
7977 Ok(_) => break 'inner,
7978 Err(FlowOrError::Flow(Flow::Last(ref l)))
7979 if l == label || l.is_none() =>
7980 {
7981 break 'outer;
7982 }
7983 Err(FlowOrError::Flow(Flow::Next(ref l)))
7984 if l == label || l.is_none() =>
7985 {
7986 if let Some(cb) = continue_block {
7987 let _ = self.exec_block_smart(cb);
7988 }
7989 continue 'outer;
7990 }
7991 Err(FlowOrError::Flow(Flow::Redo(ref l)))
7992 if l == label || l.is_none() =>
7993 {
7994 continue 'inner;
7995 }
7996 Err(e) => return Err(e),
7997 }
7998 }
7999 if let Some(cb) = continue_block {
8000 let _ = self.exec_block_smart(cb);
8001 }
8002 }
8003 Ok(StrykeValue::UNDEF)
8004 }
8005 StmtKind::Until {
8006 condition,
8007 body,
8008 label,
8009 continue_block,
8010 } => {
8011 'outer: loop {
8012 if self.eval_boolean_rvalue_condition(condition)? {
8013 break;
8014 }
8015 'inner: loop {
8016 match self.exec_block(body) {
8017 Ok(_) => break 'inner,
8018 Err(FlowOrError::Flow(Flow::Last(ref l)))
8019 if l == label || l.is_none() =>
8020 {
8021 break 'outer;
8022 }
8023 Err(FlowOrError::Flow(Flow::Next(ref l)))
8024 if l == label || l.is_none() =>
8025 {
8026 if let Some(cb) = continue_block {
8027 let _ = self.exec_block_smart(cb);
8028 }
8029 continue 'outer;
8030 }
8031 Err(FlowOrError::Flow(Flow::Redo(ref l)))
8032 if l == label || l.is_none() =>
8033 {
8034 continue 'inner;
8035 }
8036 Err(e) => return Err(e),
8037 }
8038 }
8039 if let Some(cb) = continue_block {
8040 let _ = self.exec_block_smart(cb);
8041 }
8042 }
8043 Ok(StrykeValue::UNDEF)
8044 }
8045 StmtKind::DoWhile { body, condition } => {
8046 loop {
8047 self.exec_block(body)?;
8048 if !self.eval_boolean_rvalue_condition(condition)? {
8049 break;
8050 }
8051 }
8052 Ok(StrykeValue::UNDEF)
8053 }
8054 StmtKind::For {
8055 init,
8056 condition,
8057 step,
8058 body,
8059 label,
8060 continue_block,
8061 } => {
8062 self.scope_push_hook();
8063 if let Some(init) = init {
8064 self.exec_statement(init)?;
8065 }
8066 'outer: loop {
8067 if let Some(cond) = condition {
8068 if !self.eval_boolean_rvalue_condition(cond)? {
8069 break;
8070 }
8071 }
8072 'inner: loop {
8073 match self.exec_block_smart(body) {
8074 Ok(_) => break 'inner,
8075 Err(FlowOrError::Flow(Flow::Last(ref l)))
8076 if l == label || l.is_none() =>
8077 {
8078 break 'outer;
8079 }
8080 Err(FlowOrError::Flow(Flow::Next(ref l)))
8081 if l == label || l.is_none() =>
8082 {
8083 if let Some(cb) = continue_block {
8084 let _ = self.exec_block_smart(cb);
8085 }
8086 if let Some(step) = step {
8087 self.eval_expr(step)?;
8088 }
8089 continue 'outer;
8090 }
8091 Err(FlowOrError::Flow(Flow::Redo(ref l)))
8092 if l == label || l.is_none() =>
8093 {
8094 continue 'inner;
8095 }
8096 Err(e) => {
8097 self.scope_pop_hook();
8098 return Err(e);
8099 }
8100 }
8101 }
8102 if let Some(cb) = continue_block {
8103 let _ = self.exec_block_smart(cb);
8104 }
8105 if let Some(step) = step {
8106 self.eval_expr(step)?;
8107 }
8108 }
8109 self.scope_pop_hook();
8110 Ok(StrykeValue::UNDEF)
8111 }
8112 StmtKind::Foreach {
8113 var,
8114 list,
8115 body,
8116 label,
8117 continue_block,
8118 } => {
8119 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
8120 if list_val.is_iterator() {
8128 let iter = list_val.into_iterator();
8129 self.scope_push_hook();
8130 self.scope.declare_scalar(var, StrykeValue::UNDEF);
8131 self.english_note_lexical_scalar(var);
8132 'outer_iter: loop {
8133 let next = match iter.next_item() {
8134 Some(v) => v,
8135 None => break 'outer_iter,
8136 };
8137 if var == "_" {
8138 self.scope.set_topic(next);
8139 } else {
8140 self.scope
8141 .set_scalar(var, next)
8142 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
8143 }
8144 'inner_iter: loop {
8145 match self.exec_block_smart(body) {
8146 Ok(_) => break 'inner_iter,
8147 Err(FlowOrError::Flow(Flow::Last(ref l)))
8148 if l == label || l.is_none() =>
8149 {
8150 break 'outer_iter;
8151 }
8152 Err(FlowOrError::Flow(Flow::Next(ref l)))
8153 if l == label || l.is_none() =>
8154 {
8155 if let Some(cb) = continue_block {
8156 let _ = self.exec_block_smart(cb);
8157 }
8158 continue 'outer_iter;
8159 }
8160 Err(FlowOrError::Flow(Flow::Redo(ref l)))
8161 if l == label || l.is_none() =>
8162 {
8163 continue 'inner_iter;
8164 }
8165 Err(e) => {
8166 self.scope_pop_hook();
8167 return Err(e);
8168 }
8169 }
8170 }
8171 if let Some(cb) = continue_block {
8172 let _ = self.exec_block_smart(cb);
8173 }
8174 }
8175 self.scope_pop_hook();
8176 return Ok(StrykeValue::UNDEF);
8177 }
8178 let items = list_val.to_list();
8179 self.scope_push_hook();
8180 self.scope.declare_scalar(var, StrykeValue::UNDEF);
8181 self.english_note_lexical_scalar(var);
8182 let mut i = 0usize;
8183 'outer: while i < items.len() {
8184 if var == "_" {
8191 self.scope.set_topic(items[i].clone());
8192 } else {
8193 self.scope
8194 .set_scalar(var, items[i].clone())
8195 .map_err(|e| FlowOrError::Error(e.at_line(stmt.line)))?;
8196 }
8197 'inner: loop {
8198 match self.exec_block_smart(body) {
8199 Ok(_) => break 'inner,
8200 Err(FlowOrError::Flow(Flow::Last(ref l)))
8201 if l == label || l.is_none() =>
8202 {
8203 break 'outer;
8204 }
8205 Err(FlowOrError::Flow(Flow::Next(ref l)))
8206 if l == label || l.is_none() =>
8207 {
8208 if let Some(cb) = continue_block {
8209 let _ = self.exec_block_smart(cb);
8210 }
8211 i += 1;
8212 continue 'outer;
8213 }
8214 Err(FlowOrError::Flow(Flow::Redo(ref l)))
8215 if l == label || l.is_none() =>
8216 {
8217 continue 'inner;
8218 }
8219 Err(e) => {
8220 self.scope_pop_hook();
8221 return Err(e);
8222 }
8223 }
8224 }
8225 if let Some(cb) = continue_block {
8226 let _ = self.exec_block_smart(cb);
8227 }
8228 i += 1;
8229 }
8230 self.scope_pop_hook();
8231 Ok(StrykeValue::UNDEF)
8232 }
8233 StmtKind::SubDecl {
8234 name,
8235 params,
8236 body,
8237 prototype,
8238 } => {
8239 let key = self.qualify_sub_key(name);
8240 let captured = self.scope.capture();
8241 let closure_env = if captured.is_empty() {
8242 None
8243 } else {
8244 Some(captured)
8245 };
8246 let mut sub = StrykeSub {
8247 name: name.clone(),
8248 params: params.clone(),
8249 body: body.clone(),
8250 closure_env,
8251 prototype: prototype.clone(),
8252 fib_like: None,
8253 };
8254 sub.fib_like = crate::fib_like_tail::detect_fib_like_recursive_add(&sub);
8255 self.subs.insert(key, Arc::new(sub));
8256 Ok(StrykeValue::UNDEF)
8257 }
8258 StmtKind::StructDecl { def } => {
8259 if self.struct_defs.contains_key(&def.name) {
8260 return Err(StrykeError::runtime(
8261 format!("duplicate struct `{}`", def.name),
8262 stmt.line,
8263 )
8264 .into());
8265 }
8266 self.struct_defs
8267 .insert(def.name.clone(), Arc::new(def.clone()));
8268 Ok(StrykeValue::UNDEF)
8269 }
8270 StmtKind::EnumDecl { def } => {
8271 if self.enum_defs.contains_key(&def.name) {
8272 return Err(StrykeError::runtime(
8273 format!("duplicate enum `{}`", def.name),
8274 stmt.line,
8275 )
8276 .into());
8277 }
8278 self.enum_defs
8279 .insert(def.name.clone(), Arc::new(def.clone()));
8280 Ok(StrykeValue::UNDEF)
8281 }
8282 StmtKind::ClassDecl { def } => {
8283 if self.class_defs.contains_key(&def.name) {
8284 return Err(StrykeError::runtime(
8285 format!("duplicate class `{}`", def.name),
8286 stmt.line,
8287 )
8288 .into());
8289 }
8290 for parent_name in &def.extends {
8292 if let Some(parent_def) = self.class_defs.get(parent_name) {
8293 if parent_def.is_final {
8294 return Err(StrykeError::runtime(
8295 format!("cannot extend final class `{}`", parent_name),
8296 stmt.line,
8297 )
8298 .into());
8299 }
8300 for m in &def.methods {
8302 if let Some(parent_method) = parent_def.method(&m.name) {
8303 if parent_method.is_final {
8304 return Err(StrykeError::runtime(
8305 format!(
8306 "cannot override final method `{}` from class `{}`",
8307 m.name, parent_name
8308 ),
8309 stmt.line,
8310 )
8311 .into());
8312 }
8313 }
8314 }
8315 }
8316 }
8317 let mut def = def.clone();
8319 for trait_name in &def.implements.clone() {
8320 if let Some(trait_def) = self.trait_defs.get(trait_name).cloned() {
8321 for required in trait_def.required_methods() {
8322 let has_method = def.methods.iter().any(|m| m.name == required.name);
8323 if !has_method {
8324 return Err(StrykeError::runtime(
8325 format!(
8326 "class `{}` implements trait `{}` but does not define required method `{}`",
8327 def.name, trait_name, required.name
8328 ),
8329 stmt.line,
8330 )
8331 .into());
8332 }
8333 }
8334 for tm in &trait_def.methods {
8336 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
8337 def.methods.push(tm.clone());
8338 }
8339 }
8340 }
8341 }
8342 if !def.is_abstract {
8345 for parent_name in &def.extends.clone() {
8346 if let Some(parent_def) = self.class_defs.get(parent_name) {
8347 if parent_def.is_abstract {
8348 for m in &parent_def.methods {
8349 if m.body.is_none()
8350 && !def.methods.iter().any(|dm| dm.name == m.name)
8351 {
8352 return Err(StrykeError::runtime(
8353 format!(
8354 "class `{}` must implement abstract method `{}` from `{}`",
8355 def.name, m.name, parent_name
8356 ),
8357 stmt.line,
8358 )
8359 .into());
8360 }
8361 }
8362 }
8363 }
8364 }
8365 }
8366 for sf in &def.static_fields {
8368 let val = if let Some(ref expr) = sf.default {
8369 self.eval_expr(expr)?
8370 } else {
8371 StrykeValue::UNDEF
8372 };
8373 let key = format!("{}::{}", def.name, sf.name);
8374 self.scope.declare_scalar(&key, val);
8375 }
8376 for m in &def.methods {
8378 if let Some(ref body) = m.body {
8379 let fq = format!("{}::{}", def.name, m.name);
8380 let sub = Arc::new(StrykeSub {
8381 name: fq.clone(),
8382 params: m.params.clone(),
8383 body: body.clone(),
8384 closure_env: None,
8385 prototype: None,
8386 fib_like: None,
8387 });
8388 self.subs.insert(fq, sub);
8389 }
8390 }
8391 if !def.extends.is_empty() {
8393 let isa_key = format!("{}::ISA", def.name);
8394 let parents: Vec<StrykeValue> = def
8395 .extends
8396 .iter()
8397 .map(|p| StrykeValue::string(p.clone()))
8398 .collect();
8399 self.scope.declare_array(&isa_key, parents);
8400 }
8401 let arc_def = Arc::new(def);
8402 self.class_defs
8403 .insert(arc_def.name.clone(), Arc::clone(&arc_def));
8404 crate::serialize_normalize::register_class_def(arc_def);
8408 Ok(StrykeValue::UNDEF)
8409 }
8410 StmtKind::TraitDecl { def } => {
8411 if self.trait_defs.contains_key(&def.name) {
8412 return Err(StrykeError::runtime(
8413 format!("duplicate trait `{}`", def.name),
8414 stmt.line,
8415 )
8416 .into());
8417 }
8418 self.trait_defs
8419 .insert(def.name.clone(), Arc::new(def.clone()));
8420 Ok(StrykeValue::UNDEF)
8421 }
8422 StmtKind::My(decls) | StmtKind::Our(decls) => {
8423 let is_our = matches!(&stmt.kind, StmtKind::Our(_));
8424 if decls.len() > 1 && decls[0].initializer.is_some() {
8427 let val = self.eval_expr_ctx(
8428 decls[0].initializer.as_ref().unwrap(),
8429 WantarrayCtx::List,
8430 )?;
8431 let items = val.to_list();
8432 let mut idx = 0;
8433 for decl in decls {
8434 match decl.sigil {
8435 Sigil::Scalar => {
8436 let v = items.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
8437 let skey = if is_our {
8438 self.stash_scalar_name_for_package(&decl.name)
8439 } else {
8440 decl.name.clone()
8441 };
8442 self.scope.declare_scalar_frozen(
8443 &skey,
8444 v,
8445 decl.frozen,
8446 decl.type_annotation.clone(),
8447 )?;
8448 self.english_note_lexical_scalar(&decl.name);
8449 if is_our {
8450 self.note_our_scalar(&decl.name);
8451 }
8452 idx += 1;
8453 }
8454 Sigil::Array => {
8455 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8457 idx = items.len();
8458 if is_our {
8459 self.record_exporter_our_array_name(&decl.name, &rest);
8460 }
8461 let aname = if is_our {
8462 self.stash_array_full_name_for_package(&decl.name)
8463 } else {
8464 self.stash_array_name_for_package(&decl.name)
8465 };
8466 self.scope.declare_array(&aname, rest);
8467 if is_our {
8468 self.note_our_array(&decl.name);
8469 }
8470 }
8471 Sigil::Hash => {
8472 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8473 idx = items.len();
8474 let mut map = IndexMap::new();
8475 let mut i = 0;
8476 while i + 1 < rest.len() {
8477 map.insert(rest[i].to_string(), rest[i + 1].clone());
8478 i += 2;
8479 }
8480 let hname = if is_our {
8481 self.stash_hash_full_name_for_package(&decl.name)
8482 } else {
8483 decl.name.clone()
8484 };
8485 self.scope.declare_hash(&hname, map);
8486 if is_our {
8487 self.note_our_hash(&decl.name);
8488 }
8489 }
8490 Sigil::Typeglob => {
8491 return Err(StrykeError::runtime(
8492 "list assignment to typeglob (`my (*a,*b)=...`) is not supported",
8493 stmt.line,
8494 )
8495 .into());
8496 }
8497 }
8498 }
8499 } else {
8500 for decl in decls {
8502 let compound_init = decl
8506 .initializer
8507 .as_ref()
8508 .is_some_and(|i| matches!(i.kind, ExprKind::CompoundAssign { .. }));
8509
8510 if compound_init {
8511 match decl.sigil {
8512 Sigil::Typeglob => {
8513 return Err(StrykeError::runtime(
8514 "compound assignment on typeglob declaration is not supported",
8515 stmt.line,
8516 )
8517 .into());
8518 }
8519 Sigil::Scalar => {
8520 let skey = if is_our {
8521 self.stash_scalar_name_for_package(&decl.name)
8522 } else {
8523 decl.name.clone()
8524 };
8525 self.scope.declare_scalar_frozen(
8526 &skey,
8527 StrykeValue::UNDEF,
8528 decl.frozen,
8529 decl.type_annotation.clone(),
8530 )?;
8531 self.english_note_lexical_scalar(&decl.name);
8532 if is_our {
8533 self.note_our_scalar(&decl.name);
8534 }
8535 let init = decl.initializer.as_ref().unwrap();
8536 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8537 }
8538 Sigil::Array => {
8539 let aname = if is_our {
8540 self.stash_array_full_name_for_package(&decl.name)
8541 } else {
8542 self.stash_array_name_for_package(&decl.name)
8543 };
8544 self.scope.declare_array_frozen(&aname, vec![], decl.frozen);
8545 if is_our {
8546 self.note_our_array(&decl.name);
8547 }
8548 let init = decl.initializer.as_ref().unwrap();
8549 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8550 if is_our {
8551 let items = self.scope.get_array(&aname);
8552 self.record_exporter_our_array_name(&decl.name, &items);
8553 }
8554 }
8555 Sigil::Hash => {
8556 let hname = if is_our {
8557 self.stash_hash_full_name_for_package(&decl.name)
8558 } else {
8559 decl.name.clone()
8560 };
8561 self.scope.declare_hash_frozen(
8562 &hname,
8563 IndexMap::new(),
8564 decl.frozen,
8565 );
8566 if is_our {
8567 self.note_our_hash(&decl.name);
8568 }
8569 let init = decl.initializer.as_ref().unwrap();
8570 self.eval_expr_ctx(init, WantarrayCtx::Void)?;
8571 }
8572 }
8573 continue;
8574 }
8575
8576 let val = if let Some(init) = &decl.initializer {
8577 let ctx = match decl.sigil {
8578 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8579 Sigil::Scalar if decl.list_context => WantarrayCtx::List,
8580 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8581 };
8582 let v = self.eval_expr_ctx(init, ctx)?;
8583 if decl.sigil == Sigil::Scalar && decl.list_context {
8585 v.to_list().first().cloned().unwrap_or(StrykeValue::UNDEF)
8586 } else {
8587 v
8588 }
8589 } else {
8590 StrykeValue::UNDEF
8591 };
8592 match decl.sigil {
8593 Sigil::Typeglob => {
8594 return Err(StrykeError::runtime(
8595 "`my *FH` / typeglob declaration is not supported",
8596 stmt.line,
8597 )
8598 .into());
8599 }
8600 Sigil::Scalar => {
8601 let skey = if is_our {
8602 self.stash_scalar_name_for_package(&decl.name)
8603 } else {
8604 decl.name.clone()
8605 };
8606 self.scope.declare_scalar_frozen(
8607 &skey,
8608 val,
8609 decl.frozen,
8610 decl.type_annotation.clone(),
8611 )?;
8612 self.english_note_lexical_scalar(&decl.name);
8613 if is_our {
8614 self.note_our_scalar(&decl.name);
8615 }
8616 }
8617 Sigil::Array => {
8618 let items = val.to_list();
8619 if is_our {
8620 self.record_exporter_our_array_name(&decl.name, &items);
8621 }
8622 let aname = if is_our {
8623 self.stash_array_full_name_for_package(&decl.name)
8624 } else {
8625 self.stash_array_name_for_package(&decl.name)
8626 };
8627 self.scope.declare_array_frozen(&aname, items, decl.frozen);
8628 if is_our {
8629 self.note_our_array(&decl.name);
8630 }
8631 }
8632 Sigil::Hash => {
8633 let items = val.to_list();
8634 let mut map = IndexMap::new();
8635 let mut i = 0;
8636 while i + 1 < items.len() {
8637 let k = items[i].to_string();
8638 let v = items[i + 1].clone();
8639 map.insert(k, v);
8640 i += 2;
8641 }
8642 let hname = if is_our {
8643 self.stash_hash_full_name_for_package(&decl.name)
8644 } else {
8645 decl.name.clone()
8646 };
8647 self.scope.declare_hash_frozen(&hname, map, decl.frozen);
8648 if is_our {
8649 self.note_our_hash(&decl.name);
8650 }
8651 }
8652 }
8653 }
8654 }
8655 Ok(StrykeValue::UNDEF)
8656 }
8657 StmtKind::State(decls) => {
8658 for decl in decls {
8661 let state_key = format!("{}:{}", stmt.line, decl.name);
8662 match decl.sigil {
8663 Sigil::Scalar => {
8664 if let Some(prev) = self.state_vars.get(&state_key).cloned() {
8665 self.scope.declare_scalar(&decl.name, prev);
8667 } else {
8668 let val = if let Some(init) = &decl.initializer {
8670 self.eval_expr(init)?
8671 } else {
8672 StrykeValue::UNDEF
8673 };
8674 self.state_vars.insert(state_key.clone(), val.clone());
8675 self.scope.declare_scalar(&decl.name, val);
8676 }
8677 if let Some(frame) = self.state_bindings_stack.last_mut() {
8679 frame.push((decl.name.clone(), state_key));
8680 }
8681 }
8682 _ => {
8683 let val = if let Some(init) = &decl.initializer {
8685 self.eval_expr(init)?
8686 } else {
8687 StrykeValue::UNDEF
8688 };
8689 match decl.sigil {
8690 Sigil::Array => self.scope.declare_array(&decl.name, val.to_list()),
8691 Sigil::Hash => {
8692 let items = val.to_list();
8693 let mut map = IndexMap::new();
8694 let mut i = 0;
8695 while i + 1 < items.len() {
8696 map.insert(items[i].to_string(), items[i + 1].clone());
8697 i += 2;
8698 }
8699 self.scope.declare_hash(&decl.name, map);
8700 }
8701 _ => {}
8702 }
8703 }
8704 }
8705 }
8706 Ok(StrykeValue::UNDEF)
8707 }
8708 StmtKind::Local(decls) => {
8709 if decls.len() > 1 && decls[0].initializer.is_some() {
8710 let val = self.eval_expr_ctx(
8711 decls[0].initializer.as_ref().unwrap(),
8712 WantarrayCtx::List,
8713 )?;
8714 let items = val.to_list();
8715 let mut idx = 0;
8716 for decl in decls {
8717 match decl.sigil {
8718 Sigil::Scalar => {
8719 let v = items.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
8720 idx += 1;
8721 self.scope.local_set_scalar(&decl.name, v)?;
8722 }
8723 Sigil::Array => {
8724 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8725 idx = items.len();
8726 self.scope.local_set_array(&decl.name, rest)?;
8727 }
8728 Sigil::Hash => {
8729 let rest: Vec<StrykeValue> = items[idx..].to_vec();
8730 idx = items.len();
8731 if decl.name == "ENV" {
8732 self.materialize_env_if_needed();
8733 }
8734 let mut map = IndexMap::new();
8735 let mut i = 0;
8736 while i + 1 < rest.len() {
8737 map.insert(rest[i].to_string(), rest[i + 1].clone());
8738 i += 2;
8739 }
8740 self.scope.local_set_hash(&decl.name, map)?;
8741 }
8742 Sigil::Typeglob => {
8743 return Err(StrykeError::runtime(
8744 "list assignment to typeglob (`local (*a,*b)=...`) is not supported",
8745 stmt.line,
8746 )
8747 .into());
8748 }
8749 }
8750 }
8751 Ok(val)
8752 } else {
8753 let mut last_val = StrykeValue::UNDEF;
8754 for decl in decls {
8755 let val = if let Some(init) = &decl.initializer {
8756 let ctx = match decl.sigil {
8757 Sigil::Array | Sigil::Hash => WantarrayCtx::List,
8758 Sigil::Scalar | Sigil::Typeglob => WantarrayCtx::Scalar,
8759 };
8760 self.eval_expr_ctx(init, ctx)?
8761 } else {
8762 StrykeValue::UNDEF
8763 };
8764 last_val = val.clone();
8765 match decl.sigil {
8766 Sigil::Typeglob => {
8767 let old = self.glob_handle_alias.remove(&decl.name);
8768 if let Some(frame) = self.glob_restore_frames.last_mut() {
8769 frame.push((decl.name.clone(), old));
8770 }
8771 if let Some(init) = &decl.initializer {
8772 if let ExprKind::Typeglob(rhs) = &init.kind {
8773 self.glob_handle_alias
8774 .insert(decl.name.clone(), rhs.clone());
8775 } else {
8776 return Err(StrykeError::runtime(
8777 "local *GLOB = *OTHER — right side must be a typeglob",
8778 stmt.line,
8779 )
8780 .into());
8781 }
8782 }
8783 }
8784 Sigil::Scalar => {
8785 if Self::is_special_scalar_name_for_set(&decl.name) {
8791 let old = self.get_special_var(&decl.name);
8792 if let Some(frame) = self.special_var_restore_frames.last_mut()
8793 {
8794 frame.push((decl.name.clone(), old));
8795 }
8796 self.set_special_var(&decl.name, &val)
8797 .map_err(|e| e.at_line(stmt.line))?;
8798 }
8799 self.scope.local_set_scalar(&decl.name, val)?;
8800 }
8801 Sigil::Array => {
8802 self.scope.local_set_array(&decl.name, val.to_list())?;
8803 }
8804 Sigil::Hash => {
8805 if decl.name == "ENV" {
8806 self.materialize_env_if_needed();
8807 }
8808 let items = val.to_list();
8809 let mut map = IndexMap::new();
8810 let mut i = 0;
8811 while i + 1 < items.len() {
8812 let k = items[i].to_string();
8813 let v = items[i + 1].clone();
8814 map.insert(k, v);
8815 i += 2;
8816 }
8817 self.scope.local_set_hash(&decl.name, map)?;
8818 }
8819 }
8820 }
8821 Ok(last_val)
8822 }
8823 }
8824 StmtKind::LocalExpr {
8825 target,
8826 initializer,
8827 } => {
8828 let rhs_name = |init: &Expr| -> StrykeResult<Option<String>> {
8829 match &init.kind {
8830 ExprKind::Typeglob(rhs) => Ok(Some(rhs.clone())),
8831 _ => Err(StrykeError::runtime(
8832 "local *GLOB = *OTHER — right side must be a typeglob",
8833 stmt.line,
8834 )),
8835 }
8836 };
8837 match &target.kind {
8838 ExprKind::Typeglob(name) => {
8839 let rhs = if let Some(init) = initializer {
8840 rhs_name(init)?
8841 } else {
8842 None
8843 };
8844 self.local_declare_typeglob(name, rhs.as_deref(), stmt.line)?;
8845 return Ok(StrykeValue::UNDEF);
8846 }
8847 ExprKind::Deref {
8848 expr,
8849 kind: Sigil::Typeglob,
8850 } => {
8851 let lhs = self.eval_expr(expr)?.to_string();
8852 let rhs = if let Some(init) = initializer {
8853 rhs_name(init)?
8854 } else {
8855 None
8856 };
8857 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8858 return Ok(StrykeValue::UNDEF);
8859 }
8860 ExprKind::TypeglobExpr(e) => {
8861 let lhs = self.eval_expr(e)?.to_string();
8862 let rhs = if let Some(init) = initializer {
8863 rhs_name(init)?
8864 } else {
8865 None
8866 };
8867 self.local_declare_typeglob(lhs.as_str(), rhs.as_deref(), stmt.line)?;
8868 return Ok(StrykeValue::UNDEF);
8869 }
8870 _ => {}
8871 }
8872 let val = if let Some(init) = initializer {
8873 let ctx = match &target.kind {
8874 ExprKind::HashVar(_) | ExprKind::ArrayVar(_) => WantarrayCtx::List,
8875 _ => WantarrayCtx::Scalar,
8876 };
8877 self.eval_expr_ctx(init, ctx)?
8878 } else {
8879 StrykeValue::UNDEF
8880 };
8881 match &target.kind {
8882 ExprKind::ScalarVar(name) => {
8883 if Self::is_special_scalar_name_for_set(name) {
8886 let old = self.get_special_var(name);
8887 if let Some(frame) = self.special_var_restore_frames.last_mut() {
8888 frame.push((name.clone(), old));
8889 }
8890 self.set_special_var(name, &val)
8891 .map_err(|e| e.at_line(stmt.line))?;
8892 }
8893 self.scope.local_set_scalar(name, val.clone())?;
8894 }
8895 ExprKind::ArrayVar(name) => {
8896 self.scope.local_set_array(name, val.to_list())?;
8897 }
8898 ExprKind::HashVar(name) => {
8899 if name == "ENV" {
8900 self.materialize_env_if_needed();
8901 }
8902 let items = val.to_list();
8903 let mut map = IndexMap::new();
8904 let mut i = 0;
8905 while i + 1 < items.len() {
8906 map.insert(items[i].to_string(), items[i + 1].clone());
8907 i += 2;
8908 }
8909 self.scope.local_set_hash(name, map)?;
8910 }
8911 ExprKind::HashElement { hash, key } => {
8912 let ks = self.eval_expr(key)?.to_string();
8913 self.scope.local_set_hash_element(hash, &ks, val.clone())?;
8914 }
8915 ExprKind::ArrayElement { array, index } => {
8916 self.check_strict_array_var(array, stmt.line)?;
8917 let aname = self.stash_array_name_for_package(array);
8918 let idx = self.eval_expr(index)?.to_int();
8919 self.scope
8920 .local_set_array_element(&aname, idx, val.clone())?;
8921 }
8922 _ => {
8923 return Err(StrykeError::runtime(
8924 format!(
8925 "local on this lvalue is not supported yet ({:?})",
8926 target.kind
8927 ),
8928 stmt.line,
8929 )
8930 .into());
8931 }
8932 }
8933 Ok(val)
8934 }
8935 StmtKind::MySync(decls) => {
8936 for decl in decls {
8937 let val = if let Some(init) = &decl.initializer {
8938 self.eval_expr(init)?
8939 } else {
8940 StrykeValue::UNDEF
8941 };
8942 match decl.sigil {
8943 Sigil::Typeglob => {
8944 return Err(StrykeError::runtime(
8945 "`mysync` does not support typeglob variables",
8946 stmt.line,
8947 )
8948 .into());
8949 }
8950 Sigil::Scalar => {
8951 let stored = if val.is_mysync_deque_or_heap() {
8954 val
8955 } else {
8956 StrykeValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(
8957 val,
8958 )))
8959 };
8960 self.scope.declare_scalar(&decl.name, stored);
8961 }
8962 Sigil::Array => {
8963 self.scope.declare_atomic_array(&decl.name, val.to_list());
8964 }
8965 Sigil::Hash => {
8966 let items = val.to_list();
8967 let mut map = IndexMap::new();
8968 let mut i = 0;
8969 while i + 1 < items.len() {
8970 map.insert(items[i].to_string(), items[i + 1].clone());
8971 i += 2;
8972 }
8973 self.scope.declare_atomic_hash(&decl.name, map);
8974 }
8975 }
8976 }
8977 Ok(StrykeValue::UNDEF)
8978 }
8979 StmtKind::OurSync(decls) => {
8980 for decl in decls {
8987 let val = if let Some(init) = &decl.initializer {
8988 self.eval_expr(init)?
8989 } else {
8990 StrykeValue::UNDEF
8991 };
8992 match decl.sigil {
8993 Sigil::Typeglob => {
8994 return Err(StrykeError::runtime(
8995 "`oursync` does not support typeglob variables",
8996 stmt.line,
8997 )
8998 .into());
8999 }
9000 Sigil::Scalar => {
9001 let stash = self.stash_scalar_name_for_package(&decl.name);
9002 let stored = if val.is_mysync_deque_or_heap() {
9003 val
9004 } else {
9005 StrykeValue::atomic(std::sync::Arc::new(parking_lot::Mutex::new(
9006 val,
9007 )))
9008 };
9009 self.scope.declare_scalar(&stash, stored);
9010 self.english_note_lexical_scalar(&decl.name);
9011 self.note_our_scalar(&decl.name);
9012 }
9013 Sigil::Array => {
9014 let stash = self.stash_array_name_for_package(&decl.name);
9015 self.scope.declare_atomic_array(&stash, val.to_list());
9016 self.english_note_lexical_scalar(&decl.name);
9017 self.note_our_scalar(&decl.name);
9018 }
9019 Sigil::Hash => {
9020 let items = val.to_list();
9021 let mut map = IndexMap::new();
9022 let mut i = 0;
9023 while i + 1 < items.len() {
9024 map.insert(items[i].to_string(), items[i + 1].clone());
9025 i += 2;
9026 }
9027 self.scope.declare_atomic_hash(&decl.name, map);
9030 self.english_note_lexical_scalar(&decl.name);
9031 self.note_our_scalar(&decl.name);
9032 }
9033 }
9034 }
9035 Ok(StrykeValue::UNDEF)
9036 }
9037 StmtKind::Package { name } => {
9038 let _ = self
9040 .scope
9041 .set_scalar("__PACKAGE__", StrykeValue::string(name.clone()));
9042 Ok(StrykeValue::UNDEF)
9043 }
9044 StmtKind::UsePerlVersion { .. } => Ok(StrykeValue::UNDEF),
9045 StmtKind::Use { .. } => {
9046 Ok(StrykeValue::UNDEF)
9048 }
9049 StmtKind::UseOverload { pairs } => {
9050 self.install_use_overload_pairs(pairs);
9051 Ok(StrykeValue::UNDEF)
9052 }
9053 StmtKind::No { .. } => {
9054 Ok(StrykeValue::UNDEF)
9056 }
9057 StmtKind::Return(val) => {
9058 let v = if let Some(e) = val {
9059 self.eval_expr_ctx(e, self.wantarray_kind)?
9063 } else {
9064 StrykeValue::UNDEF
9065 };
9066 Err(Flow::Return(v).into())
9067 }
9068 StmtKind::Last(label) => Err(Flow::Last(label.clone()).into()),
9069 StmtKind::Next(label) => Err(Flow::Next(label.clone()).into()),
9070 StmtKind::Redo(label) => Err(Flow::Redo(label.clone()).into()),
9071 StmtKind::Block(block) => self.exec_block(block),
9072 StmtKind::Begin(_)
9073 | StmtKind::UnitCheck(_)
9074 | StmtKind::Check(_)
9075 | StmtKind::Init(_)
9076 | StmtKind::End(_) => Ok(StrykeValue::UNDEF),
9077 StmtKind::Empty => Ok(StrykeValue::UNDEF),
9078 StmtKind::Goto { target } => {
9079 if let ExprKind::SubroutineRef(name) = &target.kind {
9081 return Err(Flow::GotoSub(name.clone()).into());
9082 }
9083 Err(StrykeError::runtime("goto reached outside goto-aware block", stmt.line).into())
9084 }
9085 StmtKind::EvalTimeout { timeout, body } => {
9086 let secs = self.eval_expr(timeout)?.to_number();
9087 self.eval_timeout_block(body, secs, stmt.line)
9088 }
9089 StmtKind::Tie {
9090 target,
9091 class,
9092 args,
9093 } => {
9094 let kind = match &target {
9095 TieTarget::Scalar(_) => 0u8,
9096 TieTarget::Array(_) => 1u8,
9097 TieTarget::Hash(_) => 2u8,
9098 };
9099 let name = match &target {
9100 TieTarget::Scalar(s) => s.as_str(),
9101 TieTarget::Array(a) => a.as_str(),
9102 TieTarget::Hash(h) => h.as_str(),
9103 };
9104 let mut vals = vec![self.eval_expr(class)?];
9105 for a in args {
9106 vals.push(self.eval_expr(a)?);
9107 }
9108 self.tie_execute(kind, name, vals, stmt.line)
9109 .map_err(Into::into)
9110 }
9111 StmtKind::TryCatch {
9112 try_block,
9113 catch_var,
9114 catch_block,
9115 finally_block,
9116 } => match self.exec_block(try_block) {
9117 Ok(v) => {
9118 if let Some(fb) = finally_block {
9119 self.exec_block(fb)?;
9120 }
9121 Ok(v)
9122 }
9123 Err(FlowOrError::Error(e)) => {
9124 if matches!(e.kind, ErrorKind::Exit(_)) {
9125 return Err(FlowOrError::Error(e));
9126 }
9127 self.scope_push_hook();
9128 self.scope
9129 .declare_scalar(catch_var, StrykeValue::string(e.to_string()));
9130 self.english_note_lexical_scalar(catch_var);
9131 let r = self.exec_block(catch_block);
9132 self.scope_pop_hook();
9133 if let Some(fb) = finally_block {
9134 self.exec_block(fb)?;
9135 }
9136 r
9137 }
9138 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
9139 },
9140 StmtKind::Given { topic, body } => self.exec_given(topic, body),
9141 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => Err(StrykeError::runtime(
9142 "when/default may only appear inside a given block",
9143 stmt.line,
9144 )
9145 .into()),
9146 StmtKind::FormatDecl { .. } => {
9147 Ok(StrykeValue::UNDEF)
9149 }
9150 StmtKind::AdviceDecl {
9151 kind,
9152 pattern,
9153 body,
9154 } => {
9155 let id = self.next_intercept_id;
9161 self.next_intercept_id = id.saturating_add(1);
9162 self.intercepts.push(crate::aop::Intercept {
9163 id,
9164 kind: *kind,
9165 pattern: pattern.clone(),
9166 body: body.clone(),
9167 body_block_idx: u16::MAX,
9168 });
9169 Ok(StrykeValue::UNDEF)
9170 }
9171 StmtKind::Continue(block) => self.exec_block_smart(block),
9172 }
9173 }
9174
9175 #[inline]
9176 pub(crate) fn eval_expr(&mut self, expr: &Expr) -> ExecResult {
9177 self.eval_expr_ctx(expr, WantarrayCtx::Scalar)
9178 }
9179
9180 pub(crate) fn scalar_compound_assign_scalar_target(
9184 &mut self,
9185 name: &str,
9186 op: BinOp,
9187 rhs: StrykeValue,
9188 ) -> Result<StrykeValue, StrykeError> {
9189 if op == BinOp::Concat {
9190 return self.scope.scalar_concat_inplace(name, &rhs);
9191 }
9192 self.scope
9193 .atomic_mutate(name, |old| Self::compound_scalar_binop(old, op, &rhs))
9194 }
9195
9196 fn compound_scalar_binop(old: &StrykeValue, op: BinOp, rhs: &StrykeValue) -> StrykeValue {
9197 match op {
9198 BinOp::Add => {
9199 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9200 StrykeValue::integer(a.wrapping_add(b))
9201 } else {
9202 StrykeValue::float(old.to_number() + rhs.to_number())
9203 }
9204 }
9205 BinOp::Sub => {
9206 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9207 StrykeValue::integer(a.wrapping_sub(b))
9208 } else {
9209 StrykeValue::float(old.to_number() - rhs.to_number())
9210 }
9211 }
9212 BinOp::Mul => {
9213 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
9214 StrykeValue::integer(a.wrapping_mul(b))
9215 } else {
9216 StrykeValue::float(old.to_number() * rhs.to_number())
9217 }
9218 }
9219 BinOp::BitAnd => {
9220 if let Some(s) = crate::value::set_intersection(old, rhs) {
9221 s
9222 } else {
9223 StrykeValue::integer(old.to_int() & rhs.to_int())
9224 }
9225 }
9226 BinOp::BitOr => {
9227 if let Some(s) = crate::value::set_union(old, rhs) {
9228 s
9229 } else {
9230 StrykeValue::integer(old.to_int() | rhs.to_int())
9231 }
9232 }
9233 BinOp::BitXor => StrykeValue::integer(old.to_int() ^ rhs.to_int()),
9234 BinOp::ShiftLeft => StrykeValue::integer(perl_shl_i64(old.to_int(), rhs.to_int())),
9235 BinOp::ShiftRight => StrykeValue::integer(perl_shr_i64(old.to_int(), rhs.to_int())),
9236 BinOp::Div => StrykeValue::float(old.to_number() / rhs.to_number()),
9237 BinOp::Mod => {
9238 let b = rhs.to_int();
9243 if b == 0 {
9244 StrykeValue::integer(0)
9245 } else {
9246 StrykeValue::integer(crate::value::perl_mod_i64(old.to_int(), b))
9247 }
9248 }
9249 BinOp::Pow => StrykeValue::float(old.to_number().powf(rhs.to_number())),
9250 BinOp::LogOr => {
9251 if old.is_true() {
9252 old.clone()
9253 } else {
9254 rhs.clone()
9255 }
9256 }
9257 BinOp::DefinedOr => {
9258 if !old.is_undef() {
9259 old.clone()
9260 } else {
9261 rhs.clone()
9262 }
9263 }
9264 BinOp::LogAnd => {
9265 if old.is_true() {
9266 rhs.clone()
9267 } else {
9268 old.clone()
9269 }
9270 }
9271 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
9272 }
9273 }
9274
9275 fn eval_hash_slice_key_components(
9279 &mut self,
9280 key_expr: &Expr,
9281 ) -> Result<Vec<String>, FlowOrError> {
9282 let v = self.eval_expr_ctx(key_expr, WantarrayCtx::List)?;
9287 if let Some(vv) = v.as_array_vec() {
9288 return Ok(vv.iter().map(|x| x.to_string()).collect());
9289 }
9290 if let Some(r) = v.as_array_ref() {
9291 return Ok(r.read().iter().map(|x| x.to_string()).collect());
9292 }
9293 if v.is_iterator() {
9294 return Ok(v
9295 .into_iterator()
9296 .collect_all()
9297 .iter()
9298 .map(|x| x.to_string())
9299 .collect());
9300 }
9301 Ok(vec![v.to_string()])
9302 }
9303
9304 pub(crate) fn symbolic_deref(
9306 &mut self,
9307 val: StrykeValue,
9308 kind: Sigil,
9309 line: usize,
9310 ) -> ExecResult {
9311 match kind {
9312 Sigil::Scalar => {
9313 if let Some(name) = val.as_scalar_binding_name() {
9314 return Ok(self.get_special_var(&name));
9315 }
9316 if let Some(r) = val.as_scalar_ref() {
9317 return Ok(r.read().clone());
9318 }
9319 if let Some(r) = val.as_array_ref() {
9321 return Ok(StrykeValue::array(r.read().clone()));
9322 }
9323 if let Some(name) = val.as_array_binding_name() {
9324 return Ok(StrykeValue::array(self.scope.get_array(&name)));
9325 }
9326 if let Some(r) = val.as_hash_ref() {
9327 return Ok(StrykeValue::hash(r.read().clone()));
9328 }
9329 if let Some(name) = val.as_hash_binding_name() {
9330 self.touch_env_hash(&name);
9331 return Ok(StrykeValue::hash(self.scope.get_hash(&name)));
9332 }
9333 if let Some(s) = val.as_str() {
9334 if self.strict_refs {
9335 return Err(StrykeError::runtime(
9336 format!(
9337 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
9338 s
9339 ),
9340 line,
9341 )
9342 .into());
9343 }
9344 return Ok(self.get_special_var(&s));
9345 }
9346 Err(StrykeError::runtime("Can't dereference non-reference as scalar", line).into())
9347 }
9348 Sigil::Array => {
9349 if let Some(r) = val.as_array_ref() {
9350 return Ok(StrykeValue::array(r.read().clone()));
9351 }
9352 if let Some(name) = val.as_array_binding_name() {
9353 return Ok(StrykeValue::array(self.scope.get_array(&name)));
9354 }
9355 if val.is_undef() {
9356 if self.strict_refs {
9357 return Err(StrykeError::runtime(
9358 "Can't use an undefined value as an ARRAY reference",
9359 line,
9360 )
9361 .into());
9362 }
9363 return Ok(StrykeValue::array(vec![]));
9364 }
9365 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
9371 let s = val.to_string();
9372 if self.strict_refs {
9373 return Err(StrykeError::runtime(
9374 format!(
9375 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
9376 s
9377 ),
9378 line,
9379 )
9380 .into());
9381 }
9382 return Ok(StrykeValue::array(self.scope.get_array(&s)));
9383 }
9384 Err(StrykeError::runtime("Can't dereference non-reference as array", line).into())
9385 }
9386 Sigil::Hash => {
9387 if let Some(r) = val.as_hash_ref() {
9388 return Ok(StrykeValue::hash(r.read().clone()));
9389 }
9390 if let Some(name) = val.as_hash_binding_name() {
9391 self.touch_env_hash(&name);
9392 return Ok(StrykeValue::hash(self.scope.get_hash(&name)));
9393 }
9394 if let Some(c) = val.as_class_inst() {
9402 let all_fields = self.collect_class_fields_full(&c.def);
9403 let values = c.get_values();
9404 let mut map = IndexMap::new();
9405 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
9406 if let Some(v) = values.get(i) {
9407 map.insert(name.clone(), v.clone());
9408 }
9409 }
9410 return Ok(StrykeValue::hash(map));
9411 }
9412 if let Some(s) = val.as_struct_inst() {
9415 let values = s.get_values();
9416 let mut map = IndexMap::new();
9417 for (i, field) in s.def.fields.iter().enumerate() {
9418 if let Some(v) = values.get(i) {
9419 map.insert(field.name.clone(), v.clone());
9420 }
9421 }
9422 return Ok(StrykeValue::hash(map));
9423 }
9424 if let Some(b) = val.as_blessed_ref() {
9429 let inner = b.data.read().clone();
9430 if let Some(r) = inner.as_hash_ref() {
9431 return Ok(StrykeValue::hash(r.read().clone()));
9432 }
9433 if let Some(h) = inner.as_hash_map() {
9434 return Ok(StrykeValue::hash(h));
9435 }
9436 }
9437 if val.is_undef() {
9438 if self.strict_refs {
9439 return Err(StrykeError::runtime(
9440 "Can't use an undefined value as a HASH reference",
9441 line,
9442 )
9443 .into());
9444 }
9445 return Ok(StrykeValue::hash(IndexMap::new()));
9446 }
9447 if val.is_integer_like() || val.is_float_like() || val.is_string_like() {
9448 let s = val.to_string();
9449 if self.strict_refs {
9450 return Err(StrykeError::runtime(
9451 format!(
9452 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
9453 s
9454 ),
9455 line,
9456 )
9457 .into());
9458 }
9459 self.touch_env_hash(&s);
9460 return Ok(StrykeValue::hash(self.scope.get_hash(&s)));
9461 }
9462 Err(StrykeError::runtime("Can't dereference non-reference as hash", line).into())
9463 }
9464 Sigil::Typeglob => {
9465 if let Some(s) = val.as_str() {
9466 return Ok(StrykeValue::string(self.resolve_io_handle_name(&s)));
9467 }
9468 Err(
9469 StrykeError::runtime("Can't dereference non-reference as typeglob", line)
9470 .into(),
9471 )
9472 }
9473 }
9474 }
9475
9476 #[inline]
9479 pub(crate) fn peel_array_ref_for_list_join(&self, v: StrykeValue) -> StrykeValue {
9480 if let Some(r) = v.as_array_ref() {
9481 return StrykeValue::array(r.read().clone());
9482 }
9483 v
9484 }
9485
9486 pub(crate) fn make_array_ref_alias(&self, val: StrykeValue, line: usize) -> ExecResult {
9488 if let Some(a) = val.as_array_ref() {
9489 return Ok(StrykeValue::array_ref(Arc::clone(&a)));
9490 }
9491 if let Some(name) = val.as_array_binding_name() {
9492 return Ok(StrykeValue::array_binding_ref(name));
9493 }
9494 if let Some(s) = val.as_str() {
9495 if self.strict_refs {
9496 return Err(StrykeError::runtime(
9497 format!(
9498 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
9499 s
9500 ),
9501 line,
9502 )
9503 .into());
9504 }
9505 return Ok(StrykeValue::array_binding_ref(s.to_string()));
9506 }
9507 if let Some(r) = val.as_scalar_ref() {
9508 let inner = r.read().clone();
9509 return self.make_array_ref_alias(inner, line);
9510 }
9511 Err(StrykeError::runtime("Can't make array reference from value", line).into())
9512 }
9513
9514 pub(crate) fn make_hash_ref_alias(&self, val: StrykeValue, line: usize) -> ExecResult {
9516 if let Some(h) = val.as_hash_ref() {
9517 return Ok(StrykeValue::hash_ref(Arc::clone(&h)));
9518 }
9519 if let Some(name) = val.as_hash_binding_name() {
9520 return Ok(StrykeValue::hash_binding_ref(name));
9521 }
9522 if let Some(s) = val.as_str() {
9523 if self.strict_refs {
9524 return Err(StrykeError::runtime(
9525 format!(
9526 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
9527 s
9528 ),
9529 line,
9530 )
9531 .into());
9532 }
9533 return Ok(StrykeValue::hash_binding_ref(s.to_string()));
9534 }
9535 if let Some(r) = val.as_scalar_ref() {
9536 let inner = r.read().clone();
9537 return self.make_hash_ref_alias(inner, line);
9538 }
9539 Err(StrykeError::runtime("Can't make hash reference from value", line).into())
9540 }
9541
9542 pub(crate) fn process_case_escapes(s: &str) -> String {
9545 if !s.contains('\\') {
9547 return s.to_string();
9548 }
9549 let mut result = String::with_capacity(s.len());
9550 let mut chars = s.chars().peekable();
9551 let mut mode: Option<char> = None; let mut next_char_mod: Option<char> = None; while let Some(c) = chars.next() {
9555 if c == '\\' {
9556 match chars.peek() {
9557 Some(&'U') => {
9558 chars.next();
9559 mode = Some('U');
9560 continue;
9561 }
9562 Some(&'L') => {
9563 chars.next();
9564 mode = Some('L');
9565 continue;
9566 }
9567 Some(&'Q') => {
9568 chars.next();
9569 mode = Some('Q');
9570 continue;
9571 }
9572 Some(&'E') => {
9573 chars.next();
9574 mode = None;
9575 next_char_mod = None;
9576 continue;
9577 }
9578 Some(&'u') => {
9579 chars.next();
9580 next_char_mod = Some('u');
9581 continue;
9582 }
9583 Some(&'l') => {
9584 chars.next();
9585 next_char_mod = Some('l');
9586 continue;
9587 }
9588 _ => {}
9589 }
9590 }
9591
9592 let ch = c;
9593
9594 if let Some(m) = next_char_mod.take() {
9596 let transformed = match m {
9597 'u' => ch.to_uppercase().next().unwrap_or(ch),
9598 'l' => ch.to_lowercase().next().unwrap_or(ch),
9599 _ => ch,
9600 };
9601 result.push(transformed);
9602 } else {
9603 match mode {
9605 Some('U') => {
9606 for uc in ch.to_uppercase() {
9607 result.push(uc);
9608 }
9609 }
9610 Some('L') => {
9611 for lc in ch.to_lowercase() {
9612 result.push(lc);
9613 }
9614 }
9615 Some('Q') => {
9616 if !ch.is_ascii_alphanumeric() && ch != '_' {
9617 result.push('\\');
9618 }
9619 result.push(ch);
9620 }
9621 None | Some(_) => {
9622 result.push(ch);
9623 }
9624 }
9625 }
9626 }
9627 result
9628 }
9629
9630 pub(crate) fn eval_expr_ctx(&mut self, expr: &Expr, ctx: WantarrayCtx) -> ExecResult {
9631 let line = expr.line;
9632 match &expr.kind {
9633 ExprKind::Integer(n) => Ok(StrykeValue::integer(*n)),
9634 ExprKind::Float(f) => Ok(StrykeValue::float(*f)),
9635 ExprKind::String(s) => {
9636 let processed = Self::process_case_escapes(s);
9637 Ok(StrykeValue::string(processed))
9638 }
9639 ExprKind::Bareword(s) => {
9640 if s == "__PACKAGE__" {
9641 return Ok(StrykeValue::string(self.current_package()));
9642 }
9643 if let Some(sub) = self.resolve_sub_by_name(s) {
9644 return self.call_sub(&sub, vec![], ctx, line);
9645 }
9646 if let Some(r) = crate::builtins::try_builtin(self, s, &[], line) {
9648 return r.map_err(Into::into);
9649 }
9650 Ok(StrykeValue::string(s.clone()))
9651 }
9652 ExprKind::Undef => Ok(StrykeValue::UNDEF),
9653 ExprKind::MagicConst(MagicConstKind::File) => {
9654 Ok(StrykeValue::string(self.file.clone()))
9655 }
9656 ExprKind::MagicConst(MagicConstKind::Line) => {
9657 Ok(StrykeValue::integer(expr.line as i64))
9658 }
9659 ExprKind::MagicConst(MagicConstKind::Sub) => {
9660 if let Some(sub) = self.current_sub_stack.last().cloned() {
9661 Ok(StrykeValue::code_ref(sub))
9662 } else {
9663 Ok(StrykeValue::UNDEF)
9664 }
9665 }
9666 ExprKind::Regex(pattern, flags) => {
9667 if ctx == WantarrayCtx::Void {
9668 let topic = self.scope.get_scalar("_");
9670 let s = topic.to_string();
9671 self.regex_match_execute(s, pattern, flags, false, "_", line)
9672 } else {
9673 let re = self.compile_regex(pattern, flags, line)?;
9674 Ok(StrykeValue::regex(re, pattern.clone(), flags.clone()))
9675 }
9676 }
9677 ExprKind::QW(words) => Ok(StrykeValue::array(
9678 words
9679 .iter()
9680 .map(|w| StrykeValue::string(w.clone()))
9681 .collect(),
9682 )),
9683
9684 ExprKind::InterpolatedString(parts) => {
9686 let mut raw_result = String::new();
9687 for part in parts {
9688 match part {
9689 StringPart::Literal(s) => raw_result.push_str(s),
9690 StringPart::ScalarVar(name) => {
9691 self.check_strict_scalar_var(name, line)?;
9692 let val = self.get_special_var(name);
9693 let s = self.stringify_value(val, line)?;
9694 raw_result.push_str(&s);
9695 }
9696 StringPart::ArrayVar(name) => {
9697 self.check_strict_array_var(name, line)?;
9698 let aname = self.stash_array_name_for_package(name);
9699 let arr = self.scope.get_array(&aname);
9700 let mut parts = Vec::with_capacity(arr.len());
9701 for v in &arr {
9702 parts.push(self.stringify_value(v.clone(), line)?);
9703 }
9704 let sep = self.list_separator.clone();
9705 raw_result.push_str(&parts.join(&sep));
9706 }
9707 StringPart::Expr(e) => {
9708 if let ExprKind::ArraySlice { array, .. } = &e.kind {
9709 self.check_strict_array_var(array, line)?;
9710 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9711 let val = self.peel_array_ref_for_list_join(val);
9712 let list = val.to_list();
9713 let sep = self.list_separator.clone();
9714 let mut parts = Vec::with_capacity(list.len());
9715 for v in list {
9716 parts.push(self.stringify_value(v, line)?);
9717 }
9718 raw_result.push_str(&parts.join(&sep));
9719 } else if let ExprKind::Deref {
9720 kind: Sigil::Array, ..
9721 } = &e.kind
9722 {
9723 let val = self.eval_expr_ctx(e, WantarrayCtx::List)?;
9724 let val = self.peel_array_ref_for_list_join(val);
9725 let list = val.to_list();
9726 let sep = self.list_separator.clone();
9727 let mut parts = Vec::with_capacity(list.len());
9728 for v in list {
9729 parts.push(self.stringify_value(v, line)?);
9730 }
9731 raw_result.push_str(&parts.join(&sep));
9732 } else {
9733 let val = self.eval_expr(e)?;
9734 let s = self.stringify_value(val, line)?;
9735 raw_result.push_str(&s);
9736 }
9737 }
9738 }
9739 }
9740 let result = Self::process_case_escapes(&raw_result);
9741 Ok(StrykeValue::string(result))
9742 }
9743
9744 ExprKind::ScalarVar(name) => {
9746 self.check_strict_scalar_var(name, line)?;
9747 let stor = self.tree_scalar_storage_name(name);
9748 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
9749 let class = obj
9750 .as_blessed_ref()
9751 .map(|b| b.class.clone())
9752 .unwrap_or_default();
9753 let full = format!("{}::FETCH", class);
9754 if let Some(sub) = self.subs.get(&full).cloned() {
9755 return self.call_sub(&sub, vec![obj], ctx, line);
9756 }
9757 }
9758 Ok(self.get_special_var(&stor))
9759 }
9760 ExprKind::ArrayVar(name) => {
9761 self.check_strict_array_var(name, line)?;
9762 let qualified = self.tree_array_storage_name(name);
9763 let aname = self.stash_array_name_for_package(&qualified);
9764 let arr = self.scope.get_array(&aname);
9765 if ctx == WantarrayCtx::List {
9766 Ok(StrykeValue::array(arr))
9767 } else {
9768 Ok(StrykeValue::integer(arr.len() as i64))
9769 }
9770 }
9771 ExprKind::HashVar(name) => {
9772 self.check_strict_hash_var(name, line)?;
9773 self.touch_env_hash(name);
9774 let hname = self.tree_hash_storage_name(name);
9775 let h = self.scope.get_hash(&hname);
9776 let pv = StrykeValue::hash(h);
9777 if ctx == WantarrayCtx::List {
9778 Ok(pv)
9779 } else {
9780 Ok(pv.scalar_context())
9781 }
9782 }
9783 ExprKind::Typeglob(name) => {
9784 let n = self.resolve_io_handle_name(name);
9785 Ok(StrykeValue::string(n))
9786 }
9787 ExprKind::TypeglobExpr(e) => {
9788 let name = self.eval_expr(e)?.to_string();
9789 let n = self.resolve_io_handle_name(&name);
9790 Ok(StrykeValue::string(n))
9791 }
9792 ExprKind::ArrayElement { array, index } => {
9793 if let Some(real) = array.strip_prefix("__topicstr__") {
9798 let s = self.scope.get_scalar(real).to_string();
9799 if let ExprKind::Range {
9800 from,
9801 to,
9802 exclusive,
9803 step,
9804 } = &index.kind
9805 {
9806 let n = s.chars().count() as i64;
9807 let mut from_i = self.eval_expr(from)?.to_int();
9808 let mut to_i = self.eval_expr(to)?.to_int();
9809 let step_i = match step {
9810 Some(e) => self.eval_expr(e)?.to_int(),
9811 None => 1,
9812 };
9813 if from_i < 0 {
9814 from_i += n
9815 }
9816 if to_i < 0 {
9817 to_i += n
9818 }
9819 if *exclusive {
9820 to_i -= 1
9821 }
9822 let chars: Vec<char> = s.chars().collect();
9823 let mut out = String::new();
9824 if step_i > 0 {
9825 let mut i = from_i;
9826 while i <= to_i && i < n {
9827 if i >= 0 {
9828 out.push(chars[i as usize]);
9829 }
9830 i += step_i;
9831 }
9832 } else if step_i < 0 {
9833 let mut i = from_i;
9834 while i >= to_i && i >= 0 {
9835 if i < n {
9836 out.push(chars[i as usize]);
9837 }
9838 i += step_i;
9839 }
9840 }
9841 return Ok(StrykeValue::string(out));
9842 }
9843 let idx = self.eval_expr(index)?.to_int();
9844 let n = s.chars().count() as i64;
9845 let i = if idx < 0 { idx + n } else { idx };
9846 return Ok(if i >= 0 && i < n {
9847 s.chars()
9848 .nth(i as usize)
9849 .map(|c| StrykeValue::string(c.to_string()))
9850 .unwrap_or(StrykeValue::UNDEF)
9851 } else {
9852 StrykeValue::UNDEF
9853 });
9854 }
9855 self.check_strict_array_var(array, line)?;
9856 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9861 if let ExprKind::Range {
9862 from,
9863 to,
9864 exclusive,
9865 step,
9866 } = &index.kind
9867 {
9868 let aname_check = self.stash_array_name_for_package(array);
9869 let prefer_scalar =
9870 array == "_" || self.scope.get_array(&aname_check).is_empty();
9871 if prefer_scalar {
9872 let s = self.scope.get_scalar(array).to_string();
9873 if !s.is_empty() {
9874 let n = s.chars().count() as i64;
9875 let mut from_i = self.eval_expr(from)?.to_int();
9876 let mut to_i = self.eval_expr(to)?.to_int();
9877 let step_i = match step {
9878 Some(e) => self.eval_expr(e)?.to_int(),
9879 None => 1,
9880 };
9881 if from_i < 0 {
9882 from_i += n
9883 }
9884 if to_i < 0 {
9885 to_i += n
9886 }
9887 if *exclusive {
9888 to_i -= 1
9889 }
9890 let chars: Vec<char> = s.chars().collect();
9891 let mut out = String::new();
9892 if step_i > 0 {
9893 let mut i = from_i;
9894 while i <= to_i && i < n {
9895 if i >= 0 {
9896 out.push(chars[i as usize]);
9897 }
9898 i += step_i;
9899 }
9900 } else if step_i < 0 {
9901 let mut i = from_i;
9902 while i >= to_i && i >= 0 {
9903 if i < n {
9904 out.push(chars[i as usize]);
9905 }
9906 i += step_i;
9907 }
9908 }
9909 return Ok(StrykeValue::string(out));
9910 }
9911 }
9912 }
9913 }
9914 let idx = self.eval_expr(index)?.to_int();
9915 let aname = self.stash_array_name_for_package(array);
9916 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
9917 let class = obj
9918 .as_blessed_ref()
9919 .map(|b| b.class.clone())
9920 .unwrap_or_default();
9921 let full = format!("{}::FETCH", class);
9922 if let Some(sub) = self.subs.get(&full).cloned() {
9923 let arg_vals = vec![obj, StrykeValue::integer(idx)];
9924 return self.call_sub(&sub, arg_vals, ctx, line);
9925 }
9926 }
9927 if !crate::compat_mode() && self.scope.scalar_binding_exists(array) {
9936 let prefer_scalar = self.scope.get_array(&aname).is_empty();
9937 if prefer_scalar {
9938 let s = self.scope.get_scalar(array).to_string();
9939 if !s.is_empty() {
9940 let n = s.chars().count() as i64;
9941 let i = if idx < 0 { idx + n } else { idx };
9942 if i >= 0 && i < n {
9943 if let Some(c) = s.chars().nth(i as usize) {
9944 return Ok(StrykeValue::string(c.to_string()));
9945 }
9946 }
9947 return Ok(StrykeValue::UNDEF);
9948 }
9949 }
9950 }
9951 Ok(self.scope.get_array_element(&aname, idx))
9952 }
9953 ExprKind::HashElement { hash, key } => {
9954 self.check_strict_hash_var(hash, line)?;
9955 let k = self.eval_expr(key)?.to_string();
9956 self.touch_env_hash(hash);
9957 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
9958 let class = obj
9959 .as_blessed_ref()
9960 .map(|b| b.class.clone())
9961 .unwrap_or_default();
9962 let full = format!("{}::FETCH", class);
9963 if let Some(sub) = self.subs.get(&full).cloned() {
9964 let arg_vals = vec![obj, StrykeValue::string(k)];
9965 return self.call_sub(&sub, arg_vals, ctx, line);
9966 }
9967 }
9968 let hname = self.tree_hash_storage_name(hash);
9969 Ok(self.scope.get_hash_element(&hname, &k))
9970 }
9971 ExprKind::ArraySlice { array, indices } => {
9972 self.check_strict_array_var(array, line)?;
9973 let aname = self.stash_array_name_for_package(array);
9974 let flat = self.flatten_array_slice_index_specs(indices)?;
9975 let mut result = Vec::with_capacity(flat.len());
9976 for idx in flat {
9977 result.push(self.scope.get_array_element(&aname, idx));
9978 }
9979 Ok(StrykeValue::array(result))
9980 }
9981 ExprKind::HashSlice { hash, keys } => {
9982 self.check_strict_hash_var(hash, line)?;
9983 self.touch_env_hash(hash);
9984 let mut result = Vec::new();
9985 for key_expr in keys {
9986 for k in self.eval_hash_slice_key_components(key_expr)? {
9987 result.push(self.scope.get_hash_element(hash, &k));
9988 }
9989 }
9990 Ok(StrykeValue::array(result))
9991 }
9992 ExprKind::HashKvSlice { hash, keys } => {
9993 self.check_strict_hash_var(hash, line)?;
9996 self.touch_env_hash(hash);
9997 let mut result = Vec::new();
9998 for key_expr in keys {
9999 for k in self.eval_hash_slice_key_components(key_expr)? {
10000 let v = self.scope.get_hash_element(hash, &k);
10001 result.push(StrykeValue::string(k));
10002 result.push(v);
10003 }
10004 }
10005 Ok(StrykeValue::array(result))
10006 }
10007 ExprKind::HashSliceDeref { container, keys } => {
10008 let hv = self.eval_expr(container)?;
10009 let mut key_vals = Vec::with_capacity(keys.len());
10010 for key_expr in keys {
10011 let v = if matches!(
10012 key_expr.kind,
10013 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
10014 ) {
10015 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
10016 } else {
10017 self.eval_expr(key_expr)?
10018 };
10019 key_vals.push(v);
10020 }
10021 self.hash_slice_deref_values(&hv, &key_vals, line)
10022 }
10023 ExprKind::AnonymousListSlice { source, indices } => {
10024 let list_val = self.eval_expr_ctx(source, WantarrayCtx::List)?;
10025 let items = list_val.to_list();
10026 let flat = self.flatten_array_slice_index_specs(indices)?;
10027 let mut out = Vec::with_capacity(flat.len());
10028 for idx in flat {
10029 let i = if idx < 0 {
10030 (items.len() as i64 + idx) as usize
10031 } else {
10032 idx as usize
10033 };
10034 out.push(items.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
10035 }
10036 let arr = StrykeValue::array(out);
10037 if ctx != WantarrayCtx::List {
10038 let v = arr.to_list();
10039 Ok(v.last().cloned().unwrap_or(StrykeValue::UNDEF))
10040 } else {
10041 Ok(arr)
10042 }
10043 }
10044
10045 ExprKind::ScalarRef(inner) => match &inner.kind {
10047 ExprKind::ScalarVar(name) => Ok(StrykeValue::scalar_binding_ref(name.clone())),
10048 ExprKind::ArrayVar(name) => {
10049 self.check_strict_array_var(name, line)?;
10050 let aname = self.stash_array_name_for_package(name);
10051 let arc = self.scope.promote_array_to_shared(&aname);
10054 Ok(StrykeValue::array_ref(arc))
10055 }
10056 ExprKind::HashVar(name) => {
10057 self.check_strict_hash_var(name, line)?;
10058 let arc = self.scope.promote_hash_to_shared(name);
10059 Ok(StrykeValue::hash_ref(arc))
10060 }
10061 ExprKind::Deref {
10062 expr: e,
10063 kind: Sigil::Array,
10064 } => {
10065 let v = self.eval_expr(e)?;
10066 self.make_array_ref_alias(v, line)
10067 }
10068 ExprKind::Deref {
10069 expr: e,
10070 kind: Sigil::Hash,
10071 } => {
10072 let v = self.eval_expr(e)?;
10073 self.make_hash_ref_alias(v, line)
10074 }
10075 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
10076 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
10077 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(
10078 list.to_list(),
10079 ))))
10080 }
10081 ExprKind::HashSliceDeref { .. } => {
10082 let list = self.eval_expr_ctx(inner, WantarrayCtx::List)?;
10083 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(
10084 list.to_list(),
10085 ))))
10086 }
10087 _ => {
10088 let val = self.eval_expr(inner)?;
10089 Ok(StrykeValue::scalar_ref(Arc::new(RwLock::new(val))))
10090 }
10091 },
10092 ExprKind::ArrayRef(elems) => {
10093 let mut arr = Vec::with_capacity(elems.len());
10097 for e in elems {
10098 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
10099 let v = self.scope.resolve_container_binding_ref(v);
10100 if let Some(vec) = v.as_array_vec() {
10101 arr.extend(vec);
10102 } else {
10103 arr.push(v);
10104 }
10105 }
10106 Ok(StrykeValue::array_ref(Arc::new(RwLock::new(arr))))
10107 }
10108 ExprKind::HashRef(pairs) => {
10109 let mut map = IndexMap::new();
10112 for (k, v) in pairs {
10113 let key_str = self.eval_expr(k)?.to_string();
10114 if key_str == "__HASH_SPREAD__" {
10115 let spread = self.eval_expr_ctx(v, WantarrayCtx::List)?;
10117 let items = spread.to_list();
10118 let mut i = 0;
10119 while i + 1 < items.len() {
10120 map.insert(items[i].to_string(), items[i + 1].clone());
10121 i += 2;
10122 }
10123 } else {
10124 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
10125 map.insert(key_str, val);
10126 }
10127 }
10128 Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map))))
10129 }
10130 ExprKind::CodeRef { params, body } => {
10131 let captured = self.scope.capture();
10132 Ok(StrykeValue::code_ref(Arc::new(StrykeSub {
10133 name: "__ANON__".to_string(),
10134 params: params.clone(),
10135 body: body.clone(),
10136 closure_env: Some(captured),
10137 prototype: None,
10138 fib_like: None,
10139 })))
10140 }
10141 ExprKind::SubroutineRef(name) => self.call_named_sub(name, vec![], line, ctx),
10142 ExprKind::SubroutineCodeRef(name) => {
10143 let sub = self.resolve_sub_by_name(name).ok_or_else(|| {
10144 StrykeError::runtime(self.undefined_subroutine_resolve_message(name), line)
10145 })?;
10146 Ok(StrykeValue::code_ref(sub))
10147 }
10148 ExprKind::DynamicSubCodeRef(expr) => {
10149 let name = self.eval_expr(expr)?.to_string();
10150 let sub = self.resolve_sub_by_name(&name).ok_or_else(|| {
10151 StrykeError::runtime(self.undefined_subroutine_resolve_message(&name), line)
10152 })?;
10153 Ok(StrykeValue::code_ref(sub))
10154 }
10155 ExprKind::Deref { expr, kind } => {
10156 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
10157 let val = self.eval_expr(expr)?;
10158 let n = self.array_deref_len(val, line)?;
10159 return Ok(StrykeValue::integer(n));
10160 }
10161 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
10162 let val = self.eval_expr(expr)?;
10163 let h = self.symbolic_deref(val, Sigil::Hash, line)?;
10164 return Ok(h.scalar_context());
10165 }
10166 let val = self.eval_expr(expr)?;
10167 self.symbolic_deref(val, *kind, line)
10168 }
10169 ExprKind::ArrowDeref { expr, index, kind } => {
10170 match kind {
10171 DerefKind::Array => {
10172 let container = self.eval_arrow_array_base(expr, line)?;
10173 if let ExprKind::List(indices) = &index.kind {
10174 let mut out = Vec::with_capacity(indices.len());
10175 for ix in indices {
10176 let idx = self.eval_expr(ix)?.to_int();
10177 out.push(self.read_arrow_array_element(
10178 container.clone(),
10179 idx,
10180 line,
10181 )?);
10182 }
10183 let arr = StrykeValue::array(out);
10184 if ctx != WantarrayCtx::List {
10185 let v = arr.to_list();
10186 return Ok(v.last().cloned().unwrap_or(StrykeValue::UNDEF));
10187 }
10188 return Ok(arr);
10189 }
10190 let idx = self.eval_expr(index)?.to_int();
10191 self.read_arrow_array_element(container, idx, line)
10192 }
10193 DerefKind::Hash => {
10194 let val = self.eval_arrow_hash_base(expr, line)?;
10195 let key = self.eval_expr(index)?.to_string();
10196 self.read_arrow_hash_element(val, key.as_str(), line)
10197 }
10198 DerefKind::Call => {
10199 let val = self.eval_expr(expr)?;
10208 if let ExprKind::List(ref arg_exprs) = index.kind {
10209 let mut args = Vec::with_capacity(arg_exprs.len());
10210 for a in arg_exprs {
10211 if matches!(a.kind, ExprKind::ArrayVar(_) | ExprKind::HashVar(_)) {
10212 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
10213 if let Some(items) = v.as_array_vec() {
10214 args.extend(items);
10215 } else {
10216 args.push(v);
10217 }
10218 } else {
10219 args.push(self.eval_expr(a)?);
10220 }
10221 }
10222 let callable = if let Some(inner) = val.as_scalar_ref() {
10224 inner.read().clone()
10225 } else {
10226 val
10227 };
10228 if let Some(sub) = callable.as_code_ref() {
10229 return self.call_sub(&sub, args, ctx, line);
10230 }
10231 Err(StrykeError::runtime("Not a code reference", line).into())
10232 } else {
10233 Err(StrykeError::runtime("Invalid call deref", line).into())
10234 }
10235 }
10236 }
10237 }
10238
10239 ExprKind::BinOp { left, op, right } => {
10241 match op {
10243 BinOp::BindMatch => {
10244 let lv = self.eval_expr(left)?;
10245 let rv = self.eval_expr(right)?;
10246 let s = lv.to_string();
10247 let pat = rv.to_string();
10248 return self.regex_match_execute(s, &pat, "", false, "_", line);
10249 }
10250 BinOp::BindNotMatch => {
10251 let lv = self.eval_expr(left)?;
10252 let rv = self.eval_expr(right)?;
10253 let s = lv.to_string();
10254 let pat = rv.to_string();
10255 let m = self.regex_match_execute(s, &pat, "", false, "_", line)?;
10256 return Ok(StrykeValue::integer(if m.is_true() { 0 } else { 1 }));
10257 }
10258 BinOp::LogAnd | BinOp::LogAndWord => {
10259 match &left.kind {
10260 ExprKind::Regex(_, _) => {
10261 if !self.eval_boolean_rvalue_condition(left)? {
10262 return Ok(StrykeValue::string(String::new()));
10263 }
10264 }
10265 _ => {
10266 let lv = self.eval_expr(left)?;
10267 if !lv.is_true() {
10268 return Ok(lv);
10269 }
10270 }
10271 }
10272 return match &right.kind {
10273 ExprKind::Regex(_, _) => Ok(StrykeValue::integer(
10274 if self.eval_boolean_rvalue_condition(right)? {
10275 1
10276 } else {
10277 0
10278 },
10279 )),
10280 _ => self.eval_expr(right),
10281 };
10282 }
10283 BinOp::LogOr | BinOp::LogOrWord => {
10284 match &left.kind {
10285 ExprKind::Regex(_, _) => {
10286 if self.eval_boolean_rvalue_condition(left)? {
10287 return Ok(StrykeValue::integer(1));
10288 }
10289 }
10290 _ => {
10291 let lv = self.eval_expr(left)?;
10292 if lv.is_true() {
10293 return Ok(lv);
10294 }
10295 }
10296 }
10297 return match &right.kind {
10298 ExprKind::Regex(_, _) => Ok(StrykeValue::integer(
10299 if self.eval_boolean_rvalue_condition(right)? {
10300 1
10301 } else {
10302 0
10303 },
10304 )),
10305 _ => self.eval_expr(right),
10306 };
10307 }
10308 BinOp::DefinedOr => {
10309 let lv = self.eval_expr(left)?;
10310 if !lv.is_undef() {
10311 return Ok(lv);
10312 }
10313 return self.eval_expr(right);
10314 }
10315 _ => {}
10316 }
10317 let lv = self.eval_expr(left)?;
10318 let rv = self.eval_expr(right)?;
10319 if let Some(r) = self.try_overload_binop(*op, &lv, &rv, line) {
10320 return r;
10321 }
10322 self.eval_binop(*op, &lv, &rv, line)
10323 }
10324
10325 ExprKind::UnaryOp { op, expr } => match op {
10327 UnaryOp::PreIncrement => {
10328 if let ExprKind::ScalarVar(name) = &expr.kind {
10329 self.check_strict_scalar_var(name, line)?;
10330 let n = self.resolved_scalar_storage_name(name);
10331 return Ok(self
10332 .scope
10333 .atomic_mutate(&n, perl_inc)
10334 .map_err(|e| e.at_line(line))?);
10335 }
10336 if let ExprKind::Deref { kind, .. } = &expr.kind {
10337 if matches!(kind, Sigil::Array | Sigil::Hash) {
10338 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
10339 *kind, true, true, line,
10340 ));
10341 }
10342 }
10343 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
10344 let href = self.eval_expr(container)?;
10345 let mut key_vals = Vec::with_capacity(keys.len());
10346 for key_expr in keys {
10347 key_vals.push(self.eval_expr(key_expr)?);
10348 }
10349 return self.hash_slice_deref_inc_dec(href, key_vals, 0, line);
10350 }
10351 if let ExprKind::ArrowDeref {
10352 expr: arr_expr,
10353 index,
10354 kind: DerefKind::Array,
10355 } = &expr.kind
10356 {
10357 if let ExprKind::List(indices) = &index.kind {
10358 let container = self.eval_arrow_array_base(arr_expr, line)?;
10359 let mut idxs = Vec::with_capacity(indices.len());
10360 for ix in indices {
10361 idxs.push(self.eval_expr(ix)?.to_int());
10362 }
10363 return self.arrow_array_slice_inc_dec(container, idxs, 0, line);
10364 }
10365 }
10366 let val = self.eval_expr(expr)?;
10367 let new_val = perl_inc(&val);
10368 self.assign_value(expr, new_val.clone())?;
10369 Ok(new_val)
10370 }
10371 UnaryOp::PreDecrement => {
10372 if let ExprKind::ScalarVar(name) = &expr.kind {
10373 self.check_strict_scalar_var(name, line)?;
10374 let n = self.resolved_scalar_storage_name(name);
10375 return Ok(self
10376 .scope
10377 .atomic_mutate(&n, |v| StrykeValue::integer(v.to_int() - 1))
10378 .map_err(|e| e.at_line(line))?);
10379 }
10380 if let ExprKind::Deref { kind, .. } = &expr.kind {
10381 if matches!(kind, Sigil::Array | Sigil::Hash) {
10382 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
10383 *kind, true, false, line,
10384 ));
10385 }
10386 }
10387 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
10388 let href = self.eval_expr(container)?;
10389 let mut key_vals = Vec::with_capacity(keys.len());
10390 for key_expr in keys {
10391 key_vals.push(self.eval_expr(key_expr)?);
10392 }
10393 return self.hash_slice_deref_inc_dec(href, key_vals, 1, line);
10394 }
10395 if let ExprKind::ArrowDeref {
10396 expr: arr_expr,
10397 index,
10398 kind: DerefKind::Array,
10399 } = &expr.kind
10400 {
10401 if let ExprKind::List(indices) = &index.kind {
10402 let container = self.eval_arrow_array_base(arr_expr, line)?;
10403 let mut idxs = Vec::with_capacity(indices.len());
10404 for ix in indices {
10405 idxs.push(self.eval_expr(ix)?.to_int());
10406 }
10407 return self.arrow_array_slice_inc_dec(container, idxs, 1, line);
10408 }
10409 }
10410 let val = self.eval_expr(expr)?;
10411 let new_val = StrykeValue::integer(val.to_int() - 1);
10412 self.assign_value(expr, new_val.clone())?;
10413 Ok(new_val)
10414 }
10415 _ => {
10416 match op {
10417 UnaryOp::LogNot | UnaryOp::LogNotWord => {
10418 if let ExprKind::Regex(pattern, flags) = &expr.kind {
10419 let topic = self.scope.get_scalar("_");
10420 let rl = expr.line;
10421 let s = topic.to_string();
10422 let v =
10423 self.regex_match_execute(s, pattern, flags, false, "_", rl)?;
10424 return Ok(StrykeValue::integer(if v.is_true() { 0 } else { 1 }));
10425 }
10426 }
10427 _ => {}
10428 }
10429 let val = self.eval_expr(expr)?;
10430 match op {
10431 UnaryOp::Negate => {
10432 if let Some(r) = self.try_overload_unary_dispatch("neg", &val, line) {
10433 return r;
10434 }
10435 if let Some(n) = val.as_integer() {
10436 Ok(StrykeValue::integer(-n))
10437 } else {
10438 Ok(StrykeValue::float(-val.to_number()))
10439 }
10440 }
10441 UnaryOp::LogNot => {
10442 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
10443 let pv = r?;
10444 return Ok(StrykeValue::integer(if pv.is_true() { 0 } else { 1 }));
10445 }
10446 Ok(StrykeValue::integer(if val.is_true() { 0 } else { 1 }))
10447 }
10448 UnaryOp::BitNot => Ok(StrykeValue::integer(!val.to_int())),
10449 UnaryOp::LogNotWord => {
10450 if let Some(r) = self.try_overload_unary_dispatch("bool", &val, line) {
10451 let pv = r?;
10452 return Ok(StrykeValue::integer(if pv.is_true() { 0 } else { 1 }));
10453 }
10454 Ok(StrykeValue::integer(if val.is_true() { 0 } else { 1 }))
10455 }
10456 UnaryOp::Ref => {
10457 if let ExprKind::ScalarVar(name) = &expr.kind {
10458 return Ok(StrykeValue::scalar_binding_ref(name.clone()));
10459 }
10460 Ok(StrykeValue::scalar_ref(Arc::new(RwLock::new(val))))
10461 }
10462 _ => unreachable!(),
10463 }
10464 }
10465 },
10466
10467 ExprKind::PostfixOp { expr, op } => {
10468 if let ExprKind::ScalarVar(name) = &expr.kind {
10471 self.check_strict_scalar_var(name, line)?;
10472 let n = self.resolved_scalar_storage_name(name);
10473 let f: fn(&StrykeValue) -> StrykeValue = match op {
10474 PostfixOp::Increment => |v| perl_inc(v),
10475 PostfixOp::Decrement => |v| StrykeValue::integer(v.to_int() - 1),
10476 };
10477 return Ok(self
10478 .scope
10479 .atomic_mutate_post(&n, f)
10480 .map_err(|e| e.at_line(line))?);
10481 }
10482 if let ExprKind::Deref { kind, .. } = &expr.kind {
10483 if matches!(kind, Sigil::Array | Sigil::Hash) {
10484 let is_inc = matches!(op, PostfixOp::Increment);
10485 return Err(Self::err_modify_symbolic_aggregate_deref_inc_dec(
10486 *kind, false, is_inc, line,
10487 ));
10488 }
10489 }
10490 if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
10491 let href = self.eval_expr(container)?;
10492 let mut key_vals = Vec::with_capacity(keys.len());
10493 for key_expr in keys {
10494 key_vals.push(self.eval_expr(key_expr)?);
10495 }
10496 let kind_byte = match op {
10497 PostfixOp::Increment => 2u8,
10498 PostfixOp::Decrement => 3u8,
10499 };
10500 return self.hash_slice_deref_inc_dec(href, key_vals, kind_byte, line);
10501 }
10502 if let ExprKind::ArrowDeref {
10503 expr: arr_expr,
10504 index,
10505 kind: DerefKind::Array,
10506 } = &expr.kind
10507 {
10508 if let ExprKind::List(indices) = &index.kind {
10509 let container = self.eval_arrow_array_base(arr_expr, line)?;
10510 let mut idxs = Vec::with_capacity(indices.len());
10511 for ix in indices {
10512 idxs.push(self.eval_expr(ix)?.to_int());
10513 }
10514 let kind_byte = match op {
10515 PostfixOp::Increment => 2u8,
10516 PostfixOp::Decrement => 3u8,
10517 };
10518 return self.arrow_array_slice_inc_dec(container, idxs, kind_byte, line);
10519 }
10520 }
10521 let val = self.eval_expr(expr)?;
10522 let old = val.clone();
10523 let new_val = match op {
10524 PostfixOp::Increment => perl_inc(&val),
10525 PostfixOp::Decrement => StrykeValue::integer(val.to_int() - 1),
10526 };
10527 self.assign_value(expr, new_val)?;
10528 Ok(old)
10529 }
10530
10531 ExprKind::Assign { target, value } => {
10533 if let ExprKind::Typeglob(lhs) = &target.kind {
10534 if let ExprKind::Typeglob(rhs) = &value.kind {
10535 self.copy_typeglob_slots(lhs, rhs, line)?;
10536 return self.eval_expr(value);
10537 }
10538 }
10539 let val = self.eval_expr_ctx(value, assign_rhs_wantarray(target))?;
10540 self.assign_value(target, val.clone())?;
10541 Ok(val)
10542 }
10543 ExprKind::CompoundAssign { target, op, value } => {
10544 if let ExprKind::ScalarVar(name) = &target.kind {
10547 self.check_strict_scalar_var(name, line)?;
10548 let n = self.resolved_scalar_storage_name(name);
10549 let op = *op;
10550 let rhs = match op {
10551 BinOp::LogOr => {
10552 let old = self.scope.get_scalar(&n);
10553 if old.is_true() {
10554 return Ok(old);
10555 }
10556 self.eval_expr(value)?
10557 }
10558 BinOp::DefinedOr => {
10559 let old = self.scope.get_scalar(&n);
10560 if !old.is_undef() {
10561 return Ok(old);
10562 }
10563 self.eval_expr(value)?
10564 }
10565 BinOp::LogAnd => {
10566 let old = self.scope.get_scalar(&n);
10567 if !old.is_true() {
10568 return Ok(old);
10569 }
10570 self.eval_expr(value)?
10571 }
10572 _ => self.eval_expr(value)?,
10573 };
10574 return Ok(self.scalar_compound_assign_scalar_target(&n, op, rhs)?);
10575 }
10576 let rhs = self.eval_expr(value)?;
10577 if let ExprKind::HashElement { hash, key } = &target.kind {
10579 self.check_strict_hash_var(hash, line)?;
10580 let k = self.eval_expr(key)?.to_string();
10581 let op = *op;
10582 return Ok(self.scope.atomic_hash_mutate(hash, &k, |old| match op {
10583 BinOp::Add => {
10584 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10585 StrykeValue::integer(a.wrapping_add(b))
10586 } else {
10587 StrykeValue::float(old.to_number() + rhs.to_number())
10588 }
10589 }
10590 BinOp::Sub => {
10591 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10592 StrykeValue::integer(a.wrapping_sub(b))
10593 } else {
10594 StrykeValue::float(old.to_number() - rhs.to_number())
10595 }
10596 }
10597 BinOp::Concat => {
10598 let mut s = old.to_string();
10599 rhs.append_to(&mut s);
10600 StrykeValue::string(s)
10601 }
10602 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
10603 })?);
10604 }
10605 if let ExprKind::ArrayElement { array, index } = &target.kind {
10607 self.check_strict_array_var(array, line)?;
10608 let idx = self.eval_expr(index)?.to_int();
10609 let op = *op;
10610 return Ok(self.scope.atomic_array_mutate(array, idx, |old| match op {
10611 BinOp::Add => {
10612 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10613 StrykeValue::integer(a.wrapping_add(b))
10614 } else {
10615 StrykeValue::float(old.to_number() + rhs.to_number())
10616 }
10617 }
10618 BinOp::Sub => {
10619 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10620 StrykeValue::integer(a.wrapping_sub(b))
10621 } else {
10622 StrykeValue::float(old.to_number() - rhs.to_number())
10623 }
10624 }
10625 BinOp::Mul => {
10626 if let (Some(a), Some(b)) = (old.as_integer(), rhs.as_integer()) {
10627 StrykeValue::integer(a.wrapping_mul(b))
10628 } else {
10629 StrykeValue::float(old.to_number() * rhs.to_number())
10630 }
10631 }
10632 BinOp::Div => StrykeValue::float(old.to_number() / rhs.to_number()),
10633 BinOp::Mod => {
10634 let a = old.to_int();
10639 let b = rhs.to_int();
10640 if b == 0 {
10641 StrykeValue::integer(0)
10642 } else {
10643 StrykeValue::integer(crate::value::perl_mod_i64(a, b))
10644 }
10645 }
10646 BinOp::Concat => {
10647 let mut s = old.to_string();
10648 rhs.append_to(&mut s);
10649 StrykeValue::string(s)
10650 }
10651 BinOp::Pow => StrykeValue::float(old.to_number().powf(rhs.to_number())),
10652 BinOp::BitAnd => StrykeValue::integer(old.to_int() & rhs.to_int()),
10653 BinOp::BitOr => StrykeValue::integer(old.to_int() | rhs.to_int()),
10654 BinOp::BitXor => StrykeValue::integer(old.to_int() ^ rhs.to_int()),
10655 BinOp::ShiftLeft => {
10656 StrykeValue::integer(perl_shl_i64(old.to_int(), rhs.to_int()))
10657 }
10658 BinOp::ShiftRight => {
10659 StrykeValue::integer(perl_shr_i64(old.to_int(), rhs.to_int()))
10660 }
10661 _ => StrykeValue::float(old.to_number() + rhs.to_number()),
10662 })?);
10663 }
10664 if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
10665 let href = self.eval_expr(container)?;
10666 let mut key_vals = Vec::with_capacity(keys.len());
10667 for key_expr in keys {
10668 key_vals.push(self.eval_expr(key_expr)?);
10669 }
10670 return self.compound_assign_hash_slice_deref(href, key_vals, *op, rhs, line);
10671 }
10672 if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
10673 if let ExprKind::Deref {
10674 expr: inner,
10675 kind: Sigil::Array,
10676 } = &source.kind
10677 {
10678 let container = self.eval_arrow_array_base(inner, line)?;
10679 let idxs = self.flatten_array_slice_index_specs(indices)?;
10680 return self
10681 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
10682 }
10683 }
10684 if let ExprKind::ArrowDeref {
10685 expr: arr_expr,
10686 index,
10687 kind: DerefKind::Array,
10688 } = &target.kind
10689 {
10690 if let ExprKind::List(indices) = &index.kind {
10691 let container = self.eval_arrow_array_base(arr_expr, line)?;
10692 let mut idxs = Vec::with_capacity(indices.len());
10693 for ix in indices {
10694 idxs.push(self.eval_expr(ix)?.to_int());
10695 }
10696 return self
10697 .compound_assign_arrow_array_slice(container, idxs, *op, rhs, line);
10698 }
10699 }
10700 let old = self.eval_expr(target)?;
10701 let new_val = self.eval_binop(*op, &old, &rhs, line)?;
10702 self.assign_value(target, new_val.clone())?;
10703 Ok(new_val)
10704 }
10705
10706 ExprKind::Ternary {
10710 condition,
10711 then_expr,
10712 else_expr,
10713 } => {
10714 if self.eval_boolean_rvalue_condition(condition)? {
10715 self.eval_expr_ctx(then_expr, ctx)
10716 } else {
10717 self.eval_expr_ctx(else_expr, ctx)
10718 }
10719 }
10720
10721 ExprKind::Range {
10723 from,
10724 to,
10725 exclusive,
10726 step,
10727 } => {
10728 if ctx == WantarrayCtx::List {
10729 let f = self.eval_expr(from)?;
10730 let t = self.eval_expr(to)?;
10731 if let Some(s) = step {
10732 let step_val = self.eval_expr(s)?.to_int();
10733 let from_i = f.to_int();
10734 let to_i = t.to_int();
10735 let list = if step_val == 0 {
10736 vec![]
10737 } else if step_val > 0 {
10738 (from_i..=to_i)
10739 .step_by(step_val as usize)
10740 .map(StrykeValue::integer)
10741 .collect()
10742 } else {
10743 std::iter::successors(Some(from_i), |&x| {
10744 let next = x - step_val.abs();
10745 if next >= to_i {
10746 Some(next)
10747 } else {
10748 None
10749 }
10750 })
10751 .map(StrykeValue::integer)
10752 .collect()
10753 };
10754 Ok(StrykeValue::array(list))
10755 } else {
10756 let list = perl_list_range_expand(f, t);
10757 Ok(StrykeValue::array(list))
10758 }
10759 } else {
10760 let key = std::ptr::from_ref(expr) as usize;
10761 match (&from.kind, &to.kind) {
10762 (
10763 ExprKind::Regex(left_pat, left_flags),
10764 ExprKind::Regex(right_pat, right_flags),
10765 ) => {
10766 let dot = self.scalar_flipflop_dot_line();
10767 let subject = self.scope.get_scalar("_").to_string();
10768 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10769 |e| match e {
10770 FlowOrError::Error(err) => err,
10771 FlowOrError::Flow(_) => StrykeError::runtime(
10772 "unexpected flow in regex flip-flop",
10773 line,
10774 ),
10775 },
10776 )?;
10777 let right_re = self
10778 .compile_regex(right_pat, right_flags, line)
10779 .map_err(|e| match e {
10780 FlowOrError::Error(err) => err,
10781 FlowOrError::Flow(_) => StrykeError::runtime(
10782 "unexpected flow in regex flip-flop",
10783 line,
10784 ),
10785 })?;
10786 let left_m = left_re.is_match(&subject);
10787 let right_m = right_re.is_match(&subject);
10788 let st = self.flip_flop_tree.entry(key).or_default();
10789 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10790 &mut st.active,
10791 &mut st.exclusive_left_line,
10792 *exclusive,
10793 dot,
10794 left_m,
10795 right_m,
10796 )))
10797 }
10798 (ExprKind::Regex(left_pat, left_flags), ExprKind::Eof(None)) => {
10799 let dot = self.scalar_flipflop_dot_line();
10800 let subject = self.scope.get_scalar("_").to_string();
10801 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10802 |e| match e {
10803 FlowOrError::Error(err) => err,
10804 FlowOrError::Flow(_) => StrykeError::runtime(
10805 "unexpected flow in regex/eof flip-flop",
10806 line,
10807 ),
10808 },
10809 )?;
10810 let left_m = left_re.is_match(&subject);
10811 let right_m = self.eof_without_arg_is_true();
10812 let st = self.flip_flop_tree.entry(key).or_default();
10813 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10814 &mut st.active,
10815 &mut st.exclusive_left_line,
10816 *exclusive,
10817 dot,
10818 left_m,
10819 right_m,
10820 )))
10821 }
10822 (
10823 ExprKind::Regex(left_pat, left_flags),
10824 ExprKind::Integer(_) | ExprKind::Float(_),
10825 ) => {
10826 let dot = self.scalar_flipflop_dot_line();
10827 let right = self.eval_expr(to)?.to_int();
10828 let subject = self.scope.get_scalar("_").to_string();
10829 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10830 |e| match e {
10831 FlowOrError::Error(err) => err,
10832 FlowOrError::Flow(_) => StrykeError::runtime(
10833 "unexpected flow in regex flip-flop",
10834 line,
10835 ),
10836 },
10837 )?;
10838 let left_m = left_re.is_match(&subject);
10839 let right_m = dot == right;
10840 let st = self.flip_flop_tree.entry(key).or_default();
10841 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10842 &mut st.active,
10843 &mut st.exclusive_left_line,
10844 *exclusive,
10845 dot,
10846 left_m,
10847 right_m,
10848 )))
10849 }
10850 (ExprKind::Regex(left_pat, left_flags), _) => {
10851 if let ExprKind::Eof(Some(_)) = &to.kind {
10852 return Err(FlowOrError::Error(StrykeError::runtime(
10853 "regex flip-flop with eof(HANDLE) is not supported",
10854 line,
10855 )));
10856 }
10857 let dot = self.scalar_flipflop_dot_line();
10858 let subject = self.scope.get_scalar("_").to_string();
10859 let left_re = self.compile_regex(left_pat, left_flags, line).map_err(
10860 |e| match e {
10861 FlowOrError::Error(err) => err,
10862 FlowOrError::Flow(_) => StrykeError::runtime(
10863 "unexpected flow in regex flip-flop",
10864 line,
10865 ),
10866 },
10867 )?;
10868 let left_m = left_re.is_match(&subject);
10869 let right_m = self.eval_boolean_rvalue_condition(to)?;
10870 let st = self.flip_flop_tree.entry(key).or_default();
10871 Ok(StrykeValue::integer(Self::regex_flip_flop_transition(
10872 &mut st.active,
10873 &mut st.exclusive_left_line,
10874 *exclusive,
10875 dot,
10876 left_m,
10877 right_m,
10878 )))
10879 }
10880 _ => {
10881 let left = self.eval_expr(from)?.to_int();
10882 let right = self.eval_expr(to)?.to_int();
10883 let dot = self.scalar_flipflop_dot_line();
10884 let st = self.flip_flop_tree.entry(key).or_default();
10885 if !st.active {
10886 if dot == left {
10887 st.active = true;
10888 if *exclusive {
10889 st.exclusive_left_line = Some(dot);
10890 } else {
10891 st.exclusive_left_line = None;
10892 if dot == right {
10893 st.active = false;
10894 }
10895 }
10896 return Ok(StrykeValue::integer(1));
10897 }
10898 return Ok(StrykeValue::integer(0));
10899 }
10900 if let Some(ll) = st.exclusive_left_line {
10901 if dot == right && dot > ll {
10902 st.active = false;
10903 st.exclusive_left_line = None;
10904 }
10905 } else if dot == right {
10906 st.active = false;
10907 }
10908 Ok(StrykeValue::integer(1))
10909 }
10910 }
10911 }
10912 }
10913
10914 ExprKind::SliceRange { from, to, step } => {
10920 let f = match from {
10921 Some(e) => self.eval_expr(e)?,
10922 None => {
10923 return Err(StrykeError::runtime(
10924 "open-ended slice range cannot be evaluated outside slice subscript",
10925 line,
10926 )
10927 .into());
10928 }
10929 };
10930 let t = match to {
10931 Some(e) => self.eval_expr(e)?,
10932 None => {
10933 return Err(StrykeError::runtime(
10934 "open-ended slice range cannot be evaluated outside slice subscript",
10935 line,
10936 )
10937 .into());
10938 }
10939 };
10940 let list = if let Some(s) = step {
10941 let sv = self.eval_expr(s)?;
10942 crate::value::perl_list_range_expand_stepped(f, t, sv)
10943 } else {
10944 perl_list_range_expand(f, t)
10945 };
10946 Ok(StrykeValue::array(list))
10947 }
10948
10949 ExprKind::Repeat {
10951 expr,
10952 count,
10953 list_repeat,
10954 } => {
10955 let n = self.eval_expr(count)?.to_int().max(0) as usize;
10956 if *list_repeat {
10957 let saved = self.wantarray_kind;
10959 self.wantarray_kind = WantarrayCtx::List;
10960 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
10961 self.wantarray_kind = saved;
10962 let items: Vec<StrykeValue> = val.as_array_vec().unwrap_or_else(|| vec![val]);
10963 let mut result = Vec::with_capacity(items.len() * n);
10964 for _ in 0..n {
10965 result.extend(items.iter().cloned());
10966 }
10967 Ok(StrykeValue::array(result))
10968 } else {
10969 let val = self.eval_expr(expr)?;
10971 Ok(StrykeValue::string(val.to_string().repeat(n)))
10972 }
10973 }
10974
10975 ExprKind::MyExpr { keyword, decls } => {
10980 let stmt_kind = match keyword.as_str() {
10983 "my" => StmtKind::My(decls.clone()),
10984 "our" => StmtKind::Our(decls.clone()),
10985 "state" => StmtKind::State(decls.clone()),
10986 "local" => StmtKind::Local(decls.clone()),
10987 _ => StmtKind::My(decls.clone()),
10988 };
10989 let stmt = Statement {
10990 label: None,
10991 kind: stmt_kind,
10992 line,
10993 };
10994 self.exec_statement(&stmt)?;
10995 let first = decls.first().ok_or_else(|| {
10999 FlowOrError::Error(StrykeError::runtime("MyExpr: empty decl list", line))
11000 })?;
11001 Ok(match first.sigil {
11002 Sigil::Scalar => self.scope.get_scalar(&first.name),
11003 Sigil::Array => StrykeValue::array(self.scope.get_array(&first.name)),
11004 Sigil::Hash => {
11005 let h = self.scope.get_hash(&first.name);
11006 let mut flat: Vec<StrykeValue> = Vec::with_capacity(h.len() * 2);
11007 for (k, v) in h {
11008 flat.push(StrykeValue::string(k));
11009 flat.push(v);
11010 }
11011 StrykeValue::array(flat)
11012 }
11013 Sigil::Typeglob => StrykeValue::UNDEF,
11014 })
11015 }
11016
11017 ExprKind::FuncCall { name, args } => {
11019 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
11022 if matches!(dispatch_name, "read") && args.len() >= 3 {
11024 let fh_val = self.eval_expr(&args[0])?;
11025 let fh = fh_val
11026 .as_io_handle_name()
11027 .unwrap_or_else(|| fh_val.to_string());
11028 let len = self.eval_expr(&args[2])?.to_int().max(0) as usize;
11029 let offset = if args.len() > 3 {
11030 self.eval_expr(&args[3])?.to_int().max(0) as usize
11031 } else {
11032 0
11033 };
11034 let var_name = match &args[1].kind {
11036 ExprKind::ScalarVar(n) => n.clone(),
11037 _ => self.eval_expr(&args[1])?.to_string(),
11038 };
11039 let mut buf = vec![0u8; len];
11040 let n = if let Some(slot) = self.io_file_slots.get(&fh).cloned() {
11041 slot.lock().read(&mut buf).unwrap_or(0)
11042 } else if fh == "STDIN" {
11043 std::io::stdin().read(&mut buf).unwrap_or(0)
11044 } else {
11045 return Err(StrykeError::runtime(
11046 format!("read: unopened handle {}", fh),
11047 line,
11048 )
11049 .into());
11050 };
11051 buf.truncate(n);
11052 let read_str = crate::perl_fs::decode_utf8_or_latin1(&buf);
11053 if offset > 0 {
11054 let mut existing = self.scope.get_scalar(&var_name).to_string();
11055 while existing.len() < offset {
11056 existing.push('\0');
11057 }
11058 existing.push_str(&read_str);
11059 let _ = self
11060 .scope
11061 .set_scalar(&var_name, StrykeValue::string(existing));
11062 } else {
11063 let _ = self
11064 .scope
11065 .set_scalar(&var_name, StrykeValue::string(read_str));
11066 }
11067 return Ok(StrykeValue::integer(n as i64));
11068 }
11069 if matches!(dispatch_name, "group_by" | "chunk_by") {
11070 if args.len() != 2 {
11071 return Err(StrykeError::runtime(
11072 "group_by/chunk_by: expected { BLOCK } or EXPR, LIST",
11073 line,
11074 )
11075 .into());
11076 }
11077 return self.eval_chunk_by_builtin(&args[0], &args[1], ctx, line);
11078 }
11079 if matches!(dispatch_name, "puniq" | "pfirst" | "pany") {
11080 let mut arg_vals = Vec::with_capacity(args.len());
11081 for a in args {
11082 arg_vals.push(self.eval_expr(a)?);
11083 }
11084 let saved_wa = self.wantarray_kind;
11085 self.wantarray_kind = ctx;
11086 let r = self.eval_par_list_call(dispatch_name, &arg_vals, ctx, line);
11087 self.wantarray_kind = saved_wa;
11088 return r.map_err(Into::into);
11089 }
11090 let arg_vals = if matches!(dispatch_name, "any" | "all" | "none" | "first")
11091 || matches!(
11092 dispatch_name,
11093 "take_while"
11094 | "drop_while"
11095 | "skip_while"
11096 | "reject"
11097 | "grepv"
11098 | "tap"
11099 | "peek"
11100 )
11101 || matches!(
11102 dispatch_name,
11103 "partition" | "min_by" | "max_by" | "zip_with" | "count_by"
11104 ) {
11105 if args.len() != 2 {
11106 return Err(StrykeError::runtime(
11107 format!("{}: expected BLOCK, LIST", name),
11108 line,
11109 )
11110 .into());
11111 }
11112 let cr = self.eval_expr(&args[0])?;
11113 let list_src = self.eval_expr_ctx(&args[1], WantarrayCtx::List)?;
11114 let mut v = vec![cr];
11115 v.extend(list_src.to_list());
11116 v
11117 } else if matches!(
11118 dispatch_name,
11119 "zip"
11120 | "zip_longest"
11121 | "zip_shortest"
11122 | "mesh"
11123 | "mesh_longest"
11124 | "mesh_shortest"
11125 ) {
11126 let mut v = Vec::with_capacity(args.len());
11127 for a in args {
11128 v.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
11129 }
11130 v
11131 } else if matches!(
11132 dispatch_name,
11133 "count" | "size" | "cnt" | "len" | "list_count" | "list_size"
11134 ) {
11135 let mut list_out = Vec::new();
11142 if args.len() == 1 {
11143 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
11144 } else {
11145 for a in args {
11146 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
11147 }
11148 }
11149 list_out
11150 } else if matches!(
11151 dispatch_name,
11152 "uniq"
11153 | "distinct"
11154 | "uniqstr"
11155 | "uniqint"
11156 | "uniqnum"
11157 | "flatten"
11158 | "set"
11159 | "with_index"
11160 | "shuffle"
11161 | "sum"
11162 | "sum0"
11163 | "product"
11164 | "min"
11165 | "max"
11166 | "minstr"
11167 | "maxstr"
11168 | "mean"
11169 | "median"
11170 | "mode"
11171 | "stddev"
11172 | "variance"
11173 | "pairs"
11174 | "unpairs"
11175 | "pairkeys"
11176 | "pairvalues"
11177 ) {
11178 let mut list_out = Vec::new();
11182 if args.len() == 1 {
11183 list_out = self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list();
11184 } else {
11185 for a in args {
11186 list_out.extend(self.eval_expr_ctx(a, WantarrayCtx::List)?.to_list());
11187 }
11188 }
11189 list_out
11190 } else if matches!(dispatch_name, "take" | "head" | "tail" | "drop") {
11191 if args.is_empty() {
11192 return Err(StrykeError::runtime(
11193 "take/head/tail/drop: need LIST..., N or unary N",
11194 line,
11195 )
11196 .into());
11197 }
11198 let mut arg_vals = Vec::with_capacity(args.len());
11199 if args.len() == 1 {
11200 arg_vals.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
11202 } else {
11203 for a in &args[..args.len() - 1] {
11204 arg_vals.push(self.eval_expr_ctx(a, WantarrayCtx::List)?);
11205 }
11206 arg_vals.push(self.eval_expr(&args[args.len() - 1])?);
11207 }
11208 arg_vals
11209 } else if matches!(dispatch_name, "chunked" | "windowed") {
11210 let mut list_out = Vec::new();
11211 match args.len() {
11212 0 => {
11213 return Err(StrykeError::runtime(
11214 format!("{name}: expected (LIST, N) or unary N after |>"),
11215 line,
11216 )
11217 .into());
11218 }
11219 1 => {
11220 list_out.push(self.eval_expr_ctx(&args[0], WantarrayCtx::List)?);
11222 }
11223 2 => {
11224 list_out.extend(
11225 self.eval_expr_ctx(&args[0], WantarrayCtx::List)?.to_list(),
11226 );
11227 list_out.push(self.eval_expr(&args[1])?);
11228 }
11229 _ => {
11230 return Err(StrykeError::runtime(
11231 format!(
11232 "{name}: expected exactly (LIST, N); use one list expression then size"
11233 ),
11234 line,
11235 )
11236 .into());
11237 }
11238 }
11239 list_out
11240 } else {
11241 let mut arg_vals = Vec::with_capacity(args.len());
11244 for a in args {
11245 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
11246 if let Some(items) = v.as_array_vec() {
11247 arg_vals.extend(items);
11248 } else {
11249 arg_vals.push(v);
11250 }
11251 }
11252 arg_vals
11253 };
11254 let saved_wa = self.wantarray_kind;
11256 self.wantarray_kind = ctx;
11257 if !crate::compat_mode() {
11260 if matches!(
11261 dispatch_name,
11262 "take_while"
11263 | "drop_while"
11264 | "skip_while"
11265 | "reject"
11266 | "grepv"
11267 | "tap"
11268 | "peek"
11269 ) {
11270 let r =
11271 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
11272 self.wantarray_kind = saved_wa;
11273 return r.map_err(Into::into);
11274 }
11275 if let Some(r) =
11276 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
11277 {
11278 self.wantarray_kind = saved_wa;
11279 return r.map_err(Into::into);
11280 }
11281 }
11282 if let Some(sub) = self.resolve_sub_by_name(name) {
11283 self.wantarray_kind = saved_wa;
11284 let args = self.with_topic_default_args(arg_vals);
11285 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
11286 return self.call_sub_with_package(&sub, args, ctx, line, pkg);
11287 }
11288 if crate::compat_mode() {
11290 if matches!(
11291 dispatch_name,
11292 "take_while"
11293 | "drop_while"
11294 | "skip_while"
11295 | "reject"
11296 | "grepv"
11297 | "tap"
11298 | "peek"
11299 ) {
11300 let r =
11301 self.list_higher_order_block_builtin(dispatch_name, &arg_vals, line);
11302 self.wantarray_kind = saved_wa;
11303 return r.map_err(Into::into);
11304 }
11305 if let Some(r) =
11306 crate::builtins::try_builtin(self, dispatch_name, &arg_vals, line)
11307 {
11308 self.wantarray_kind = saved_wa;
11309 return r.map_err(Into::into);
11310 }
11311 }
11312 self.wantarray_kind = saved_wa;
11313 self.call_named_sub(name, arg_vals, line, ctx)
11314 }
11315 ExprKind::IndirectCall {
11316 target,
11317 args,
11318 ampersand: _,
11319 pass_caller_arglist,
11320 } => {
11321 let tval = self.eval_expr(target)?;
11322 let arg_vals = if *pass_caller_arglist {
11323 self.scope.get_array("_")
11324 } else {
11325 let mut v = Vec::with_capacity(args.len());
11333 for a in args {
11334 if matches!(a.kind, ExprKind::ArrayVar(_) | ExprKind::HashVar(_)) {
11335 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
11336 if let Some(items) = val.as_array_vec() {
11337 v.extend(items);
11338 } else {
11339 v.push(val);
11340 }
11341 } else {
11342 v.push(self.eval_expr(a)?);
11343 }
11344 }
11345 v
11346 };
11347 self.dispatch_indirect_call(tval, arg_vals, ctx, line)
11348 }
11349 ExprKind::MethodCall {
11350 object,
11351 method,
11352 args,
11353 super_call,
11354 } => {
11355 let obj = self.eval_expr(object)?;
11356 let mut arg_vals = vec![obj.clone()];
11357 for a in args {
11358 arg_vals.push(self.eval_expr(a)?);
11359 }
11360 if let Some(r) =
11361 crate::pchannel::dispatch_method(&obj, method, &arg_vals[1..], line)
11362 {
11363 return r.map_err(Into::into);
11364 }
11365 if let Some(r) = self.try_native_method(&obj, method, &arg_vals[1..], line) {
11366 return r.map_err(Into::into);
11367 }
11368 let class = if let Some(b) = obj.as_blessed_ref() {
11370 b.class.clone()
11371 } else if let Some(s) = obj.as_str() {
11372 s } else {
11374 return Err(
11375 StrykeError::runtime("Can't call method on non-object", line).into(),
11376 );
11377 };
11378 if method == "VERSION" && !*super_call {
11379 if let Some(ver) = self.package_version_scalar(class.as_str())? {
11380 return Ok(ver);
11381 }
11382 }
11383 if !*super_call {
11385 match method.as_str() {
11386 "isa" => {
11387 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
11388 let mro = self.mro_linearize(&class);
11389 let result = mro.iter().any(|c| c == &target);
11390 return Ok(StrykeValue::integer(if result { 1 } else { 0 }));
11391 }
11392 "can" => {
11393 let target_method =
11394 arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
11395 let found = self
11396 .resolve_method_full_name(&class, &target_method, false)
11397 .and_then(|fq| self.subs.get(&fq))
11398 .is_some();
11399 if found {
11400 return Ok(StrykeValue::code_ref(Arc::new(StrykeSub {
11401 name: target_method,
11402 params: vec![],
11403 body: vec![],
11404 closure_env: None,
11405 prototype: None,
11406 fib_like: None,
11407 })));
11408 } else {
11409 return Ok(StrykeValue::UNDEF);
11410 }
11411 }
11412 "DOES" => {
11413 let target = arg_vals.get(1).map(|v| v.to_string()).unwrap_or_default();
11414 let mro = self.mro_linearize(&class);
11415 let result = mro.iter().any(|c| c == &target);
11416 return Ok(StrykeValue::integer(if result { 1 } else { 0 }));
11417 }
11418 _ => {}
11419 }
11420 }
11421 let full_name = self
11422 .resolve_method_full_name(&class, method, *super_call)
11423 .ok_or_else(|| {
11424 StrykeError::runtime(
11425 format!(
11426 "Can't locate method \"{}\" for invocant \"{}\"",
11427 method, class
11428 ),
11429 line,
11430 )
11431 })?;
11432 if let Some(sub) = self.subs.get(&full_name).cloned() {
11433 self.call_sub(&sub, arg_vals, ctx, line)
11434 } else if method == "new" && !*super_call {
11435 self.builtin_new(&class, arg_vals, line)
11437 } else if let Some(r) =
11438 self.try_autoload_call(&full_name, arg_vals, line, ctx, Some(&class))
11439 {
11440 r
11441 } else {
11442 Err(StrykeError::runtime(
11443 format!(
11444 "Can't locate method \"{}\" in package \"{}\"",
11445 method, class
11446 ),
11447 line,
11448 )
11449 .into())
11450 }
11451 }
11452
11453 ExprKind::Print { handle, args } => {
11455 self.exec_print(handle.as_deref(), args, false, line)
11456 }
11457 ExprKind::Say { handle, args } => self.exec_print(handle.as_deref(), args, true, line),
11458 ExprKind::Printf { handle, args } => self.exec_printf(handle.as_deref(), args, line),
11459 ExprKind::Die(args) => {
11460 if args.is_empty() {
11461 let current = self.scope.get_scalar("@");
11463 let msg = if current.is_undef() || current.to_string().is_empty() {
11464 let mut m = "Died".to_string();
11465 m.push_str(&self.die_warn_at_suffix(line));
11466 m.push('\n');
11467 m
11468 } else {
11469 current.to_string()
11470 };
11471 self.fire_pseudosig_die(&msg, line)?;
11472 return Err(StrykeError::die(msg, line).into());
11473 }
11474 if args.len() == 1 {
11476 let v = self.eval_expr(&args[0])?;
11477 if v.as_hash_ref().is_some()
11478 || v.as_blessed_ref().is_some()
11479 || v.as_array_ref().is_some()
11480 || v.as_code_ref().is_some()
11481 {
11482 let msg = v.to_string();
11483 self.fire_pseudosig_die(&msg, line)?;
11484 return Err(StrykeError::die_with_value(v, msg, line).into());
11485 }
11486 }
11487 let mut msg = String::new();
11488 for a in args {
11489 let v = self.eval_expr(a)?;
11490 msg.push_str(&v.to_string());
11491 }
11492 if msg.is_empty() {
11493 msg = "Died".to_string();
11494 }
11495 if !msg.ends_with('\n') {
11496 msg.push_str(&self.die_warn_at_suffix(line));
11497 msg.push('\n');
11498 }
11499 self.fire_pseudosig_die(&msg, line)?;
11500 Err(StrykeError::die(msg, line).into())
11501 }
11502 ExprKind::Warn(args) => {
11503 let mut msg = String::new();
11504 for a in args {
11505 let v = self.eval_expr(a)?;
11506 msg.push_str(&v.to_string());
11507 }
11508 if msg.is_empty() {
11509 msg = "Warning: something's wrong".to_string();
11510 }
11511 if !msg.ends_with('\n') {
11512 msg.push_str(&self.die_warn_at_suffix(line));
11513 msg.push('\n');
11514 }
11515 self.fire_pseudosig_warn(&msg, line)?;
11516 Ok(StrykeValue::integer(1))
11517 }
11518
11519 ExprKind::Match {
11521 expr,
11522 pattern,
11523 flags,
11524 scalar_g,
11525 delim: _,
11526 } => {
11527 let val = self.eval_expr(expr)?;
11528 if val.is_iterator() {
11529 let source = crate::map_stream::into_pull_iter(val);
11530 let re = self.compile_regex(pattern, flags, line)?;
11531 let global = flags.contains('g');
11532 if global {
11533 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11534 crate::map_stream::MatchGlobalStreamIterator::new(source, re),
11535 )));
11536 } else {
11537 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11538 crate::map_stream::MatchStreamIterator::new(source, re),
11539 )));
11540 }
11541 }
11542 let s = val.to_string();
11543 let pos_key = match &expr.kind {
11544 ExprKind::ScalarVar(n) => n.as_str(),
11545 _ => "_",
11546 };
11547 self.regex_match_execute(s, pattern, flags, *scalar_g, pos_key, line)
11548 }
11549 ExprKind::Substitution {
11550 expr,
11551 pattern,
11552 replacement,
11553 flags,
11554 delim: _,
11555 } => {
11556 let val = self.eval_expr(expr)?;
11557 if val.is_iterator() {
11558 let source = crate::map_stream::into_pull_iter(val);
11559 let re = self.compile_regex(pattern, flags, line)?;
11560 let global = flags.contains('g');
11561 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11562 crate::map_stream::SubstStreamIterator::new(
11563 source,
11564 re,
11565 normalize_replacement_backrefs(replacement),
11566 global,
11567 ),
11568 )));
11569 }
11570 let s = val.to_string();
11571 self.regex_subst_execute(
11572 s,
11573 pattern,
11574 replacement.as_str(),
11575 flags.as_str(),
11576 expr,
11577 line,
11578 )
11579 }
11580 ExprKind::Transliterate {
11581 expr,
11582 from,
11583 to,
11584 flags,
11585 delim: _,
11586 } => {
11587 let val = self.eval_expr(expr)?;
11588 if val.is_iterator() {
11589 let source = crate::map_stream::into_pull_iter(val);
11590 return Ok(StrykeValue::iterator(std::sync::Arc::new(
11591 crate::map_stream::TransliterateStreamIterator::new(
11592 source, from, to, flags,
11593 ),
11594 )));
11595 }
11596 let s = val.to_string();
11597 self.regex_transliterate_execute(
11598 s,
11599 from.as_str(),
11600 to.as_str(),
11601 flags.as_str(),
11602 expr,
11603 line,
11604 )
11605 }
11606
11607 ExprKind::MapExpr {
11609 block,
11610 list,
11611 flatten_array_refs,
11612 stream,
11613 } => {
11614 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11615 if *stream {
11616 let out =
11617 self.map_stream_block_output(list_val, block, *flatten_array_refs, line)?;
11618 if ctx == WantarrayCtx::List {
11619 return Ok(out);
11620 }
11621 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11622 }
11623 let items = list_val.to_list();
11624 if items.len() == 1 {
11625 if let Some(p) = items[0].as_pipeline() {
11626 if *flatten_array_refs {
11627 return Err(StrykeError::runtime(
11628 "flat_map onto a pipeline value is not supported in this form — use a pipeline ->map stage",
11629 line,
11630 )
11631 .into());
11632 }
11633 let sub = self.anon_coderef_from_block(block);
11634 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
11635 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
11636 }
11637 }
11638 let saved_chain = self.scope.save_topic_chain();
11653 let mut result = Vec::new();
11654 for item in items {
11655 self.scope.set_topic(item);
11656 let val = self.exec_block_with_tail(block, WantarrayCtx::List)?;
11657 result.extend(val.map_flatten_outputs(*flatten_array_refs));
11658 }
11659 self.scope.restore_topic_chain(saved_chain);
11660 if ctx == WantarrayCtx::List {
11661 Ok(StrykeValue::array(result))
11662 } else {
11663 Ok(StrykeValue::integer(result.len() as i64))
11664 }
11665 }
11666 ExprKind::ForEachExpr { block, list } => {
11667 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11668 if list_val.is_iterator() {
11670 let iter = list_val.into_iterator();
11671 let mut count = 0i64;
11672 while let Some(item) = iter.next_item() {
11673 count += 1;
11674 self.scope.set_topic(item);
11675 self.exec_block(block)?;
11676 }
11677 return Ok(StrykeValue::integer(count));
11678 }
11679 let items = list_val.to_list();
11680 let count = items.len();
11681 for item in items {
11682 self.scope.set_topic(item);
11683 self.exec_block(block)?;
11684 }
11685 Ok(StrykeValue::integer(count as i64))
11686 }
11687 ExprKind::MapExprComma {
11688 expr,
11689 list,
11690 flatten_array_refs,
11691 stream,
11692 } => {
11693 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11694 if *stream {
11695 let out =
11696 self.map_stream_expr_output(list_val, expr, *flatten_array_refs, line)?;
11697 if ctx == WantarrayCtx::List {
11698 return Ok(out);
11699 }
11700 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11701 }
11702 let items = list_val.to_list();
11703 let mut result = Vec::new();
11704 for item in items {
11705 self.scope.set_topic_local(item.clone());
11711 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11712 let val = if !crate::compat_mode() {
11716 if let Some(sub) = val.as_code_ref() {
11717 let sub = sub.clone();
11718 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::List, line)?
11719 } else {
11720 val
11721 }
11722 } else {
11723 val
11724 };
11725 result.extend(val.map_flatten_outputs(*flatten_array_refs));
11726 }
11727 if ctx == WantarrayCtx::List {
11728 Ok(StrykeValue::array(result))
11729 } else {
11730 Ok(StrykeValue::integer(result.len() as i64))
11731 }
11732 }
11733 ExprKind::GrepExpr {
11734 block,
11735 list,
11736 keyword,
11737 } => {
11738 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11739 if keyword.is_stream() {
11740 let out = self.filter_stream_block_output(list_val, block, line)?;
11741 if ctx == WantarrayCtx::List {
11742 return Ok(out);
11743 }
11744 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11745 }
11746 let items = list_val.to_list();
11747 if items.len() == 1 {
11748 if let Some(p) = items[0].as_pipeline() {
11749 let sub = self.anon_coderef_from_block(block);
11750 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
11751 return Ok(StrykeValue::pipeline(Arc::clone(&p)));
11752 }
11753 }
11754 let mut result = Vec::new();
11755 for item in items {
11756 self.scope.set_topic(item.clone());
11757 let val = self.exec_block(block)?;
11758 let keep = if let Some(re) = val.as_regex() {
11761 re.is_match(&item.to_string())
11762 } else {
11763 val.is_true()
11764 };
11765 if keep {
11766 result.push(item);
11767 }
11768 }
11769 if ctx == WantarrayCtx::List {
11770 Ok(StrykeValue::array(result))
11771 } else {
11772 Ok(StrykeValue::integer(result.len() as i64))
11773 }
11774 }
11775 ExprKind::GrepExprComma {
11776 expr,
11777 list,
11778 keyword,
11779 } => {
11780 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11781 if keyword.is_stream() {
11782 let out = self.filter_stream_expr_output(list_val, expr, line)?;
11783 if ctx == WantarrayCtx::List {
11784 return Ok(out);
11785 }
11786 return Ok(StrykeValue::integer(out.to_list().len() as i64));
11787 }
11788 let items = list_val.to_list();
11789 let mut result = Vec::new();
11790 for item in items {
11791 self.scope.set_topic_local(item.clone());
11795 let val = self.eval_expr(expr)?;
11796 let val = if !crate::compat_mode() {
11800 if let Some(sub) = val.as_code_ref() {
11801 let sub = sub.clone();
11802 self.call_sub(&sub, vec![item.clone()], WantarrayCtx::Scalar, line)?
11803 } else {
11804 val
11805 }
11806 } else {
11807 val
11808 };
11809 let keep = if let Some(re) = val.as_regex() {
11810 re.is_match(&item.to_string())
11811 } else {
11812 val.is_true()
11813 };
11814 if keep {
11815 result.push(item);
11816 }
11817 }
11818 if ctx == WantarrayCtx::List {
11819 Ok(StrykeValue::array(result))
11820 } else {
11821 Ok(StrykeValue::integer(result.len() as i64))
11822 }
11823 }
11824 ExprKind::SortExpr { cmp, list } => {
11825 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11826 let mut items = list_val.to_list();
11827 match cmp {
11828 Some(SortComparator::Code(code_expr)) => {
11829 let sub = self.eval_expr(code_expr)?;
11830 let Some(sub) = sub.as_code_ref() else {
11831 return Err(StrykeError::runtime(
11832 "sort: comparator must be a code reference",
11833 line,
11834 )
11835 .into());
11836 };
11837 let saved_topic = self.scope.save_topic_chain();
11840 let sub = sub.clone();
11841 items.sort_by(|a, b| {
11842 self.scope.set_sort_pair(a.clone(), b.clone());
11847 match self.call_sub(&sub, vec![a.clone(), b.clone()], ctx, line) {
11848 Ok(v) => {
11849 let n = v.to_int();
11850 if n < 0 {
11851 Ordering::Less
11852 } else if n > 0 {
11853 Ordering::Greater
11854 } else {
11855 Ordering::Equal
11856 }
11857 }
11858 Err(_) => Ordering::Equal,
11859 }
11860 });
11861 self.scope.restore_topic_chain(saved_topic);
11862 }
11863 Some(SortComparator::Block(cmp_block)) => {
11864 if let Some(mode) = detect_sort_block_fast(cmp_block) {
11865 items.sort_by(|a, b| sort_magic_cmp(a, b, mode));
11866 } else {
11867 let saved_topic = self.scope.save_topic_chain();
11870 let cmp_block = cmp_block.clone();
11871 items.sort_by(|a, b| {
11872 self.scope.set_sort_pair(a.clone(), b.clone());
11873 match self.exec_block(&cmp_block) {
11874 Ok(v) => {
11875 let n = v.to_int();
11876 if n < 0 {
11877 Ordering::Less
11878 } else if n > 0 {
11879 Ordering::Greater
11880 } else {
11881 Ordering::Equal
11882 }
11883 }
11884 Err(_) => Ordering::Equal,
11885 }
11886 });
11887 self.scope.restore_topic_chain(saved_topic);
11888 }
11889 }
11890 None => {
11891 items.sort_by_key(|a| a.to_string());
11892 }
11893 }
11894 Ok(StrykeValue::array(items))
11895 }
11896 ExprKind::Rev(expr) => {
11897 let val = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
11899 if val.is_iterator() {
11900 return Ok(StrykeValue::iterator(Arc::new(
11901 crate::value::RevIterator::new(val.into_iterator()),
11902 )));
11903 }
11904 if let Some(s) = crate::value::set_payload(&val) {
11905 let mut out = crate::value::PerlSet::new();
11906 for (k, v) in s.iter().rev() {
11907 out.insert(k.clone(), v.clone());
11908 }
11909 return Ok(StrykeValue::set(Arc::new(out)));
11910 }
11911 if let Some(ar) = val.as_array_ref() {
11912 let items: Vec<_> = ar.read().iter().rev().cloned().collect();
11913 return Ok(StrykeValue::array_ref(Arc::new(parking_lot::RwLock::new(
11914 items,
11915 ))));
11916 }
11917 if let Some(hr) = val.as_hash_ref() {
11918 let mut out: indexmap::IndexMap<String, StrykeValue> =
11919 indexmap::IndexMap::new();
11920 for (k, v) in hr.read().iter() {
11921 out.insert(v.to_string(), StrykeValue::string(k.clone()));
11922 }
11923 return Ok(StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(
11924 out,
11925 ))));
11926 }
11927 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
11929 if let Some(hm) = val.as_hash_map() {
11930 let mut out: indexmap::IndexMap<String, StrykeValue> =
11931 indexmap::IndexMap::new();
11932 for (k, v) in hm.iter() {
11933 out.insert(v.to_string(), StrykeValue::string(k.clone()));
11934 }
11935 return Ok(StrykeValue::hash(out));
11936 }
11937 if val.as_array_vec().is_some() {
11938 let mut items = val.to_list();
11939 items.reverse();
11940 Ok(StrykeValue::array(items))
11941 } else {
11942 let items = val.to_list();
11943 if items.len() > 1 {
11944 let mut items = items;
11945 items.reverse();
11946 Ok(StrykeValue::array(items))
11947 } else {
11948 let s = val.to_string();
11949 Ok(StrykeValue::string(s.chars().rev().collect()))
11950 }
11951 }
11952 }
11953 ExprKind::ReverseExpr(list) => {
11954 let val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
11955 match ctx {
11956 WantarrayCtx::List => {
11957 let mut items = val.to_list();
11958 items.reverse();
11959 Ok(StrykeValue::array(items))
11960 }
11961 _ => {
11962 let items = val.to_list();
11963 let s: String = items.iter().map(|v| v.to_string()).collect();
11964 Ok(StrykeValue::string(s.chars().rev().collect()))
11965 }
11966 }
11967 }
11968
11969 ExprKind::ParLinesExpr {
11971 path,
11972 callback,
11973 progress,
11974 } => self.eval_par_lines_expr(
11975 path.as_ref(),
11976 callback.as_ref(),
11977 progress.as_deref(),
11978 line,
11979 ),
11980 ExprKind::ParWalkExpr {
11981 path,
11982 callback,
11983 progress,
11984 } => {
11985 self.eval_par_walk_expr(path.as_ref(), callback.as_ref(), progress.as_deref(), line)
11986 }
11987 ExprKind::PwatchExpr { path, callback } => {
11988 self.eval_pwatch_expr(path.as_ref(), callback.as_ref(), line)
11989 }
11990 ExprKind::PMapExpr {
11991 block,
11992 list,
11993 progress,
11994 flat_outputs,
11995 on_cluster,
11996 stream,
11997 } => {
11998 let show_progress = progress
11999 .as_ref()
12000 .map(|p| self.eval_expr(p))
12001 .transpose()?
12002 .map(|v| v.is_true())
12003 .unwrap_or(false);
12004 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12011 if let Some(cluster_e) = on_cluster {
12012 let cluster_val = self.eval_expr(cluster_e.as_ref())?;
12013 return self.eval_pmap_remote(
12014 cluster_val,
12015 list_val,
12016 show_progress,
12017 block,
12018 *flat_outputs,
12019 line,
12020 );
12021 }
12022 if *stream {
12023 let source = crate::map_stream::into_pull_iter(list_val);
12024 let sub = self.anon_coderef_from_block(block);
12025 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
12026 return Ok(StrykeValue::iterator(Arc::new(
12027 crate::map_stream::PMapStreamIterator::new(
12028 source,
12029 sub,
12030 self.subs.clone(),
12031 capture,
12032 atomic_arrays,
12033 atomic_hashes,
12034 *flat_outputs,
12035 ),
12036 )));
12037 }
12038 let items = list_val.to_list();
12039 let block = block.clone();
12040 let subs = self.subs.clone();
12041 let (scope_capture, atomic_arrays, atomic_hashes) =
12042 self.scope.capture_with_atomics();
12043 let pmap_progress = PmapProgress::new(show_progress, items.len());
12044
12045 if *flat_outputs {
12046 let mut indexed: Vec<(usize, Vec<StrykeValue>)> = items
12047 .into_par_iter()
12048 .enumerate()
12049 .map(|(i, item)| {
12050 let mut local_interp = VMHelper::new();
12051 local_interp.subs = subs.clone();
12052 local_interp.scope.restore_capture(&scope_capture);
12053 local_interp
12054 .scope
12055 .restore_atomics(&atomic_arrays, &atomic_hashes);
12056 local_interp.enable_parallel_guard();
12057 local_interp.scope.set_topic(item);
12058 let val = match local_interp.exec_block(&block) {
12059 Ok(val) => val,
12060 Err(_) => StrykeValue::UNDEF,
12061 };
12062 let chunk = val.map_flatten_outputs(true);
12063 pmap_progress.tick();
12064 (i, chunk)
12065 })
12066 .collect();
12067 pmap_progress.finish();
12068 indexed.sort_by_key(|(i, _)| *i);
12069 let results: Vec<StrykeValue> =
12070 indexed.into_iter().flat_map(|(_, v)| v).collect();
12071 Ok(StrykeValue::array(results))
12072 } else {
12073 let results: Vec<StrykeValue> = items
12074 .into_par_iter()
12075 .map(|item| {
12076 let mut local_interp = VMHelper::new();
12077 local_interp.subs = subs.clone();
12078 local_interp.scope.restore_capture(&scope_capture);
12079 local_interp
12080 .scope
12081 .restore_atomics(&atomic_arrays, &atomic_hashes);
12082 local_interp.enable_parallel_guard();
12083 local_interp.scope.set_topic(item);
12084 let val = match local_interp.exec_block(&block) {
12085 Ok(val) => val,
12086 Err(_) => StrykeValue::UNDEF,
12087 };
12088 pmap_progress.tick();
12089 val
12090 })
12091 .collect();
12092 pmap_progress.finish();
12093 Ok(StrykeValue::array(results))
12094 }
12095 }
12096 ExprKind::PMapChunkedExpr {
12097 chunk_size,
12098 block,
12099 list,
12100 progress,
12101 } => {
12102 let show_progress = progress
12103 .as_ref()
12104 .map(|p| self.eval_expr(p))
12105 .transpose()?
12106 .map(|v| v.is_true())
12107 .unwrap_or(false);
12108 let chunk_n = self.eval_expr(chunk_size)?.to_int().max(1) as usize;
12109 let list_val = self.eval_expr(list)?;
12110 let items = list_val.to_list();
12111 let block = block.clone();
12112 let subs = self.subs.clone();
12113 let (scope_capture, atomic_arrays, atomic_hashes) =
12114 self.scope.capture_with_atomics();
12115
12116 let indexed_chunks: Vec<(usize, Vec<StrykeValue>)> = items
12117 .chunks(chunk_n)
12118 .enumerate()
12119 .map(|(i, c)| (i, c.to_vec()))
12120 .collect();
12121
12122 let n_chunks = indexed_chunks.len();
12123 let pmap_progress = PmapProgress::new(show_progress, n_chunks);
12124
12125 let mut chunk_results: Vec<(usize, Vec<StrykeValue>)> = indexed_chunks
12126 .into_par_iter()
12127 .map(|(chunk_idx, chunk)| {
12128 let mut local_interp = VMHelper::new();
12129 local_interp.subs = subs.clone();
12130 local_interp.scope.restore_capture(&scope_capture);
12131 local_interp
12132 .scope
12133 .restore_atomics(&atomic_arrays, &atomic_hashes);
12134 local_interp.enable_parallel_guard();
12135 let mut out = Vec::with_capacity(chunk.len());
12136 for item in chunk {
12137 local_interp.scope.set_topic(item);
12138 match local_interp.exec_block(&block) {
12139 Ok(val) => out.push(val),
12140 Err(_) => out.push(StrykeValue::UNDEF),
12141 }
12142 }
12143 pmap_progress.tick();
12144 (chunk_idx, out)
12145 })
12146 .collect();
12147
12148 pmap_progress.finish();
12149 chunk_results.sort_by_key(|(i, _)| *i);
12150 let results: Vec<StrykeValue> =
12151 chunk_results.into_iter().flat_map(|(_, v)| v).collect();
12152 Ok(StrykeValue::array(results))
12153 }
12154 ExprKind::PGrepExpr {
12155 block,
12156 list,
12157 progress,
12158 stream,
12159 } => {
12160 let show_progress = progress
12161 .as_ref()
12162 .map(|p| self.eval_expr(p))
12163 .transpose()?
12164 .map(|v| v.is_true())
12165 .unwrap_or(false);
12166 let list_val = self.eval_expr(list)?;
12167 if *stream {
12168 let source = crate::map_stream::into_pull_iter(list_val);
12169 let sub = self.anon_coderef_from_block(block);
12170 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
12171 return Ok(StrykeValue::iterator(Arc::new(
12172 crate::map_stream::PGrepStreamIterator::new(
12173 source,
12174 sub,
12175 self.subs.clone(),
12176 capture,
12177 atomic_arrays,
12178 atomic_hashes,
12179 ),
12180 )));
12181 }
12182 let items = list_val.to_list();
12183 let block = block.clone();
12184 let subs = self.subs.clone();
12185 let (scope_capture, atomic_arrays, atomic_hashes) =
12186 self.scope.capture_with_atomics();
12187 let pmap_progress = PmapProgress::new(show_progress, items.len());
12188
12189 let results: Vec<StrykeValue> = items
12190 .into_par_iter()
12191 .filter_map(|item| {
12192 let mut local_interp = VMHelper::new();
12193 local_interp.subs = subs.clone();
12194 local_interp.scope.restore_capture(&scope_capture);
12195 local_interp
12196 .scope
12197 .restore_atomics(&atomic_arrays, &atomic_hashes);
12198 local_interp.enable_parallel_guard();
12199 local_interp.scope.set_topic(item.clone());
12200 let keep = match local_interp.exec_block(&block) {
12201 Ok(val) => val.is_true(),
12202 Err(_) => false,
12203 };
12204 pmap_progress.tick();
12205 if keep {
12206 Some(item)
12207 } else {
12208 None
12209 }
12210 })
12211 .collect();
12212 pmap_progress.finish();
12213 Ok(StrykeValue::array(results))
12214 }
12215 ExprKind::ParExpr { block, list } => {
12216 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12231 let n_threads = rayon::current_num_threads().clamp(1, 8);
12232 let chunks = par_chunk_value(&list_val, n_threads);
12233 if chunks.len() < 2 {
12234 self.scope.set_topic(list_val);
12237 let v = self.exec_block(block)?;
12238 return Ok(v);
12239 }
12240 let block_clone = block.clone();
12241 let subs = self.subs.clone();
12242 let (scope_capture, atomic_arrays, atomic_hashes) =
12243 self.scope.capture_with_atomics();
12244 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
12245 let err_w = Arc::clone(&first_err);
12246 let per_chunk: Vec<Vec<StrykeValue>> = chunks
12247 .into_par_iter()
12248 .map(|chunk| {
12249 if err_w.lock().is_some() {
12250 return Vec::new();
12251 }
12252 let mut local_interp = VMHelper::new();
12253 local_interp.subs = subs.clone();
12254 local_interp.scope.restore_capture(&scope_capture);
12255 local_interp
12256 .scope
12257 .restore_atomics(&atomic_arrays, &atomic_hashes);
12258 local_interp.enable_parallel_guard();
12259 local_interp.scope.set_topic(chunk);
12260 match local_interp.exec_block(&block_clone) {
12261 Ok(v) => v.map_flatten_outputs(true),
12262 Err(e) => {
12263 let mut g = err_w.lock();
12264 if g.is_none() {
12265 *g = Some(format!("par: {:?}", e));
12266 }
12267 Vec::new()
12268 }
12269 }
12270 })
12271 .collect();
12272 if let Some(msg) = first_err.lock().take() {
12273 return Err(FlowOrError::Error(StrykeError::runtime(msg, line)));
12274 }
12275 let total: usize = per_chunk.iter().map(|v| v.len()).sum();
12276 let mut out = Vec::with_capacity(total);
12277 for v in per_chunk {
12278 out.extend(v);
12279 }
12280 Ok(StrykeValue::array(out))
12281 }
12282 ExprKind::ParReduceExpr {
12283 extract_block,
12284 reduce_block,
12285 list,
12286 } => {
12287 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12298 let n_threads = rayon::current_num_threads().clamp(1, 8);
12299 let chunks = par_chunk_value(&list_val, n_threads);
12300 if chunks.len() < 2 {
12301 let chunk_arr = match list_val.as_array_vec() {
12309 Some(arr) => arr,
12310 None => vec![list_val.clone()],
12311 };
12312 let first = chunk_arr.first().cloned().unwrap_or(StrykeValue::UNDEF);
12313 self.scope.declare_array("_", chunk_arr);
12314 self.scope.set_topic(first);
12315 return self.exec_block_with_tail(extract_block, WantarrayCtx::List);
12316 }
12317 let extract = extract_block.clone();
12318 let subs = self.subs.clone();
12319 let (scope_capture, atomic_arrays, atomic_hashes) =
12320 self.scope.capture_with_atomics();
12321 let first_err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
12322 let err_w = Arc::clone(&first_err);
12323 let per_chunk: Vec<StrykeValue> = chunks
12324 .into_par_iter()
12325 .map(|chunk| {
12326 if err_w.lock().is_some() {
12327 return StrykeValue::UNDEF;
12328 }
12329 let mut local = VMHelper::new();
12330 local.subs = subs.clone();
12331 local.scope.restore_capture(&scope_capture);
12332 local.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
12333 local.enable_parallel_guard();
12334 let chunk_arr = match chunk.as_array_vec() {
12338 Some(arr) => arr,
12339 None => vec![chunk.clone()],
12340 };
12341 let first = chunk_arr.first().cloned().unwrap_or(StrykeValue::UNDEF);
12342 local.scope.declare_array("_", chunk_arr);
12343 local.scope.set_topic(first);
12344 match local.exec_block_with_tail(&extract, WantarrayCtx::List) {
12345 Ok(v) => v,
12346 Err(e) => {
12347 let mut g = err_w.lock();
12348 if g.is_none() {
12349 *g = Some(format!("par_reduce: {:?}", e));
12350 }
12351 StrykeValue::UNDEF
12352 }
12353 }
12354 })
12355 .collect();
12356 if let Some(msg) = first_err.lock().take() {
12357 return Err(FlowOrError::Error(StrykeError::runtime(msg, line)));
12358 }
12359 if per_chunk.is_empty() {
12360 return Ok(StrykeValue::UNDEF);
12361 }
12362 if let Some(rb) = reduce_block {
12364 let mut acc = per_chunk[0].clone();
12365 for v in per_chunk.into_iter().skip(1) {
12366 self.scope.declare_scalar("a", acc.clone());
12367 self.scope.declare_scalar("b", v);
12368 acc = self.exec_block(rb)?;
12369 }
12370 return Ok(acc);
12371 }
12372 Ok(par_reduce_auto_merge(per_chunk))
12374 }
12375 ExprKind::DistReduceExpr {
12376 cluster,
12377 extract_block,
12378 list,
12379 } => {
12380 let cluster_pv = self.eval_expr(cluster)?;
12392 let Some(remote_cluster) = cluster_pv.as_remote_cluster() else {
12393 return Err(StrykeError::runtime(
12394 "~d>: expected cluster(...) value after `on`",
12395 line,
12396 )
12397 .into());
12398 };
12399 let list_val = self.eval_expr_ctx(list, WantarrayCtx::List)?;
12400 let items_flat = list_val.to_list();
12401 if items_flat.is_empty() {
12402 return Ok(StrykeValue::array(vec![]));
12403 }
12404 let (scope_capture, atomic_arrays, atomic_hashes) =
12405 self.scope.capture_with_atomics();
12406 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
12407 return Err(StrykeError::runtime(
12408 "~d>: mysync/atomic capture is not supported for remote workers",
12409 line,
12410 )
12411 .into());
12412 }
12413 let n_slots = remote_cluster.slots.len().max(1);
12418 let target_chunks = (n_slots * 4).min(items_flat.len()).max(1);
12419 let chunk_size = items_flat.len().div_ceil(target_chunks);
12420 let mut chunk_items: Vec<serde_json::Value> = Vec::new();
12421 let mut chunk_jsons_buf: Vec<StrykeValue> = Vec::new();
12422 for item in items_flat.into_iter() {
12423 chunk_jsons_buf.push(item);
12424 if chunk_jsons_buf.len() >= chunk_size {
12425 let drained: Vec<StrykeValue> = std::mem::take(&mut chunk_jsons_buf);
12426 let as_array = StrykeValue::array(drained);
12427 chunk_items.push(
12428 crate::remote_wire::perl_to_json_value(&as_array)
12429 .map_err(|e| StrykeError::runtime(e, line))?,
12430 );
12431 }
12432 }
12433 if !chunk_jsons_buf.is_empty() {
12434 let as_array = StrykeValue::array(chunk_jsons_buf);
12435 chunk_items.push(
12436 crate::remote_wire::perl_to_json_value(&as_array)
12437 .map_err(|e| StrykeError::runtime(e, line))?,
12438 );
12439 }
12440 let cap_json: Vec<(String, serde_json::Value)> = scope_capture
12448 .iter()
12449 .filter_map(|(k, v)| {
12450 crate::remote_wire::perl_to_json_value(v)
12451 .ok()
12452 .map(|j| (k.clone(), j))
12453 })
12454 .collect();
12455 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
12456 let user_block_src = crate::fmt::format_block(extract_block);
12468 let block_src = format!("@_ = $_;\n[ {user_block_src} ]");
12469 let result_values = crate::cluster::run_cluster(
12470 &remote_cluster,
12471 subs_prelude,
12472 block_src,
12473 cap_json,
12474 chunk_items,
12475 )
12476 .map_err(|e| StrykeError::runtime(format!("~d> remote: {e}"), line))?;
12477 let mut merged: Vec<StrykeValue> = Vec::new();
12481 for v in result_values {
12482 if let Some(items) = v.as_array_vec() {
12483 merged.extend(items);
12484 } else if let Some(ar) = v.as_array_ref() {
12485 merged.extend(ar.read().iter().cloned());
12486 } else {
12487 merged.push(v);
12488 }
12489 }
12490 Ok(StrykeValue::array(merged))
12491 }
12492 ExprKind::PForExpr {
12493 block,
12494 list,
12495 progress,
12496 } => {
12497 let show_progress = progress
12498 .as_ref()
12499 .map(|p| self.eval_expr(p))
12500 .transpose()?
12501 .map(|v| v.is_true())
12502 .unwrap_or(false);
12503 let list_val = self.eval_expr(list)?;
12504 let items = list_val.to_list();
12505 let block = block.clone();
12506 let subs = self.subs.clone();
12507 let (scope_capture, atomic_arrays, atomic_hashes) =
12508 self.scope.capture_with_atomics();
12509
12510 let pmap_progress = PmapProgress::new(show_progress, items.len());
12511 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
12512 items.into_par_iter().for_each(|item| {
12513 if first_err.lock().is_some() {
12514 return;
12515 }
12516 let mut local_interp = VMHelper::new();
12517 local_interp.subs = subs.clone();
12518 local_interp.scope.restore_capture(&scope_capture);
12519 local_interp
12520 .scope
12521 .restore_atomics(&atomic_arrays, &atomic_hashes);
12522 local_interp.enable_parallel_guard();
12523 local_interp.scope.set_topic(item);
12524 match local_interp.exec_block(&block) {
12525 Ok(_) => {}
12526 Err(e) => {
12527 let stryke = match e {
12528 FlowOrError::Error(stryke) => stryke,
12529 FlowOrError::Flow(_) => StrykeError::runtime(
12530 "return/last/next/redo not supported inside pfor block",
12531 line,
12532 ),
12533 };
12534 let mut g = first_err.lock();
12535 if g.is_none() {
12536 *g = Some(stryke);
12537 }
12538 }
12539 }
12540 pmap_progress.tick();
12541 });
12542 pmap_progress.finish();
12543 if let Some(e) = first_err.lock().take() {
12544 return Err(FlowOrError::Error(e));
12545 }
12546 Ok(StrykeValue::UNDEF)
12547 }
12548 ExprKind::FanExpr {
12549 count,
12550 block,
12551 progress,
12552 capture,
12553 } => {
12554 let show_progress = progress
12555 .as_ref()
12556 .map(|p| self.eval_expr(p))
12557 .transpose()?
12558 .map(|v| v.is_true())
12559 .unwrap_or(false);
12560 let n = match count {
12561 Some(c) => self.eval_expr(c)?.to_int().max(0) as usize,
12562 None => self.parallel_thread_count(),
12563 };
12564 let block = block.clone();
12565 let subs = self.subs.clone();
12566 let (scope_capture, atomic_arrays, atomic_hashes) =
12567 self.scope.capture_with_atomics();
12568
12569 let fan_progress = FanProgress::new(show_progress, n);
12570 if *capture {
12571 if n == 0 {
12572 return Ok(StrykeValue::array(Vec::new()));
12573 }
12574 let pairs: Vec<(usize, ExecResult)> = (0..n)
12575 .into_par_iter()
12576 .map(|i| {
12577 fan_progress.start_worker(i);
12578 let mut local_interp = VMHelper::new();
12579 local_interp.subs = subs.clone();
12580 local_interp.suppress_stdout = show_progress;
12581 local_interp.scope.restore_capture(&scope_capture);
12582 local_interp
12583 .scope
12584 .restore_atomics(&atomic_arrays, &atomic_hashes);
12585 local_interp.enable_parallel_guard();
12586 local_interp.scope.set_topic(StrykeValue::integer(i as i64));
12587 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
12588 let res = local_interp.exec_block(&block);
12589 crate::parallel_trace::fan_worker_set_index(None);
12590 fan_progress.finish_worker(i);
12591 (i, res)
12592 })
12593 .collect();
12594 fan_progress.finish();
12595 let mut pairs = pairs;
12596 pairs.sort_by_key(|(i, _)| *i);
12597 let mut out = Vec::with_capacity(n);
12598 for (_, r) in pairs {
12599 match r {
12600 Ok(v) => out.push(v),
12601 Err(e) => return Err(e),
12602 }
12603 }
12604 return Ok(StrykeValue::array(out));
12605 }
12606 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
12607 (0..n).into_par_iter().for_each(|i| {
12608 if first_err.lock().is_some() {
12609 return;
12610 }
12611 fan_progress.start_worker(i);
12612 let mut local_interp = VMHelper::new();
12613 local_interp.subs = subs.clone();
12614 local_interp.suppress_stdout = show_progress;
12615 local_interp.scope.restore_capture(&scope_capture);
12616 local_interp
12617 .scope
12618 .restore_atomics(&atomic_arrays, &atomic_hashes);
12619 local_interp.enable_parallel_guard();
12620 local_interp.scope.set_topic(StrykeValue::integer(i as i64));
12621 crate::parallel_trace::fan_worker_set_index(Some(i as i64));
12622 match local_interp.exec_block(&block) {
12623 Ok(_) => {}
12624 Err(e) => {
12625 let stryke = match e {
12626 FlowOrError::Error(stryke) => stryke,
12627 FlowOrError::Flow(_) => StrykeError::runtime(
12628 "return/last/next/redo not supported inside fan block",
12629 line,
12630 ),
12631 };
12632 let mut g = first_err.lock();
12633 if g.is_none() {
12634 *g = Some(stryke);
12635 }
12636 }
12637 }
12638 crate::parallel_trace::fan_worker_set_index(None);
12639 fan_progress.finish_worker(i);
12640 });
12641 fan_progress.finish();
12642 if let Some(e) = first_err.lock().take() {
12643 return Err(FlowOrError::Error(e));
12644 }
12645 Ok(StrykeValue::UNDEF)
12646 }
12647 ExprKind::RetryBlock {
12648 body,
12649 times,
12650 backoff,
12651 } => self.eval_retry_block(body, times, *backoff, line),
12652 ExprKind::RateLimitBlock {
12653 slot,
12654 max,
12655 window,
12656 body,
12657 } => self.eval_rate_limit_block(*slot, max, window, body, line),
12658 ExprKind::EveryBlock { interval, body } => self.eval_every_block(interval, body, line),
12659 ExprKind::GenBlock { body } => {
12660 let g = Arc::new(PerlGenerator {
12661 block: body.clone(),
12662 pc: Mutex::new(0),
12663 scope_started: Mutex::new(false),
12664 exhausted: Mutex::new(false),
12665 });
12666 Ok(StrykeValue::generator(g))
12667 }
12668 ExprKind::Yield(e) => {
12669 if !self.in_generator {
12670 return Err(StrykeError::runtime("yield outside gen block", line).into());
12671 }
12672 let v = self.eval_expr(e)?;
12673 Err(FlowOrError::Flow(Flow::Yield(v)))
12674 }
12675 ExprKind::AlgebraicMatch { subject, arms } => {
12676 self.eval_algebraic_match(subject, arms, line)
12677 }
12678 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
12679 Ok(self.spawn_async_block(body))
12680 }
12681 ExprKind::Trace { body } => {
12682 crate::parallel_trace::trace_enter();
12683 let out = self.exec_block(body);
12684 crate::parallel_trace::trace_leave();
12685 out
12686 }
12687 ExprKind::Spinner { message, body } => {
12688 use std::io::Write as _;
12689 let msg = self.eval_expr(message)?.to_string();
12690 let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
12691 let done2 = done.clone();
12692 let handle = std::thread::spawn(move || {
12693 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12694 let mut i = 0;
12695 let stderr = std::io::stderr();
12696 while !done2.load(std::sync::atomic::Ordering::Relaxed) {
12697 {
12698 let stdout = std::io::stdout();
12699 let _stdout_lock = stdout.lock();
12700 let mut err = stderr.lock();
12701 let _ = write!(
12702 err,
12703 "\r\x1b[2K\x1b[36m{}\x1b[0m {} ",
12704 frames[i % frames.len()],
12705 msg
12706 );
12707 let _ = err.flush();
12708 }
12709 std::thread::sleep(std::time::Duration::from_millis(80));
12710 i += 1;
12711 }
12712 let mut err = stderr.lock();
12713 let _ = write!(err, "\r\x1b[2K");
12714 let _ = err.flush();
12715 });
12716 let result = self.exec_block(body);
12717 done.store(true, std::sync::atomic::Ordering::Relaxed);
12718 let _ = handle.join();
12719 result
12720 }
12721 ExprKind::Timer { body } => {
12722 let start = std::time::Instant::now();
12723 self.exec_block(body)?;
12724 let ms = start.elapsed().as_secs_f64() * 1000.0;
12725 Ok(StrykeValue::float(ms))
12726 }
12727 ExprKind::Bench { body, times } => {
12728 let n = self.eval_expr(times)?.to_int();
12729 if n < 0 {
12730 return Err(StrykeError::runtime(
12731 "bench: iteration count must be non-negative",
12732 line,
12733 )
12734 .into());
12735 }
12736 self.run_bench_block(body, n as usize, line)
12737 }
12738 ExprKind::Await(expr) => {
12739 let v = self.eval_expr(expr)?;
12740 if let Some(t) = v.as_async_task() {
12741 t.await_result().map_err(FlowOrError::from)
12742 } else {
12743 Ok(v)
12744 }
12745 }
12746 ExprKind::Slurp(e) => {
12747 let path = self.eval_expr(e)?.to_string();
12748 let path = self.resolve_stryke_path_string(&path);
12749 crate::perl_fs::read_bytes_or_glob(&path)
12750 .map(StrykeValue::bytes)
12751 .map_err(|e| {
12752 FlowOrError::Error(StrykeError::runtime(format!("slurp: {}", e), line))
12753 })
12754 }
12755 ExprKind::Swallow(e) => {
12756 let path = self.eval_expr(e)?.to_string();
12757 let path = self.resolve_stryke_path_string(&path);
12758 crate::perl_fs::swallow_to_hash(&path).map_err(|e| {
12759 FlowOrError::Error(StrykeError::runtime(format!("swallow: {}", e), line))
12760 })
12761 }
12762 ExprKind::Ingest(e) => {
12763 let path = self.eval_expr(e)?.to_string();
12764 let path = self.resolve_stryke_path_string(&path);
12765 crate::perl_fs::ingest_iterator(&path).map_err(|e| {
12766 FlowOrError::Error(StrykeError::runtime(format!("ingest: {}", e), line))
12767 })
12768 }
12769 ExprKind::Burp(e) => {
12770 let v = self.eval_expr(e)?;
12771 crate::perl_fs::burp_hash_to_disk(&v)
12772 .map(StrykeValue::integer)
12773 .map_err(|e| {
12774 FlowOrError::Error(StrykeError::runtime(format!("burp: {}", e), line))
12775 })
12776 }
12777 ExprKind::God(e) => {
12778 let v = self.eval_expr(e)?;
12779 Ok(StrykeValue::string(crate::god::god_dump(&v)))
12780 }
12781 ExprKind::Capture(e) => {
12782 let cmd = self.eval_expr(e)?.to_string();
12783 let output = Command::new("sh")
12784 .arg("-c")
12785 .arg(&cmd)
12786 .output()
12787 .map_err(|e| {
12788 FlowOrError::Error(StrykeError::runtime(format!("capture: {}", e), line))
12789 })?;
12790 self.record_child_exit_status(output.status);
12791 let exitcode = output.status.code().unwrap_or(-1) as i64;
12792 let stdout = decode_utf8_or_latin1(&output.stdout);
12793 let stderr = decode_utf8_or_latin1(&output.stderr);
12794 Ok(StrykeValue::capture(Arc::new(CaptureResult {
12795 stdout,
12796 stderr,
12797 exitcode,
12798 })))
12799 }
12800 ExprKind::Qx(e) => {
12801 let cmd = self.eval_expr(e)?.to_string();
12802 let raw =
12803 crate::capture::run_readpipe(self, &cmd, line).map_err(FlowOrError::Error)?;
12804 if ctx == WantarrayCtx::List {
12805 let s = raw.to_string();
12809 if s.is_empty() {
12810 return Ok(StrykeValue::array(Vec::new()));
12811 }
12812 let mut lines = Vec::new();
12813 let mut buf = String::new();
12814 for c in s.chars() {
12815 buf.push(c);
12816 if c == '\n' {
12817 lines.push(StrykeValue::string(std::mem::take(&mut buf)));
12818 }
12819 }
12820 if !buf.is_empty() {
12821 lines.push(StrykeValue::string(buf));
12822 }
12823 Ok(StrykeValue::array(lines))
12824 } else {
12825 Ok(raw)
12826 }
12827 }
12828 ExprKind::FetchUrl(e) => {
12829 let url = self.eval_expr(e)?.to_string();
12830 ureq::get(&url)
12831 .call()
12832 .map_err(|e| {
12833 FlowOrError::Error(StrykeError::runtime(format!("fetch_url: {}", e), line))
12834 })
12835 .and_then(|r| {
12836 r.into_string().map(StrykeValue::string).map_err(|e| {
12837 FlowOrError::Error(StrykeError::runtime(
12838 format!("fetch_url: {}", e),
12839 line,
12840 ))
12841 })
12842 })
12843 }
12844 ExprKind::Pchannel { capacity } => {
12845 if let Some(c) = capacity {
12846 let n = self.eval_expr(c)?.to_int().max(1) as usize;
12847 Ok(crate::pchannel::create_bounded_pair(n))
12848 } else {
12849 Ok(crate::pchannel::create_pair())
12850 }
12851 }
12852 ExprKind::PSortExpr {
12853 cmp,
12854 list,
12855 progress,
12856 } => {
12857 let show_progress = progress
12858 .as_ref()
12859 .map(|p| self.eval_expr(p))
12860 .transpose()?
12861 .map(|v| v.is_true())
12862 .unwrap_or(false);
12863 let list_val = self.eval_expr(list)?;
12864 let mut items = list_val.to_list();
12865 let pmap_progress = PmapProgress::new(show_progress, 2);
12866 pmap_progress.tick();
12867 if let Some(cmp_block) = cmp {
12868 if let Some(mode) = detect_sort_block_fast(cmp_block) {
12869 items.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
12870 } else {
12871 let cmp_block = cmp_block.clone();
12872 let subs = self.subs.clone();
12873 let scope_capture = self.scope.capture();
12874 items.par_sort_by(|a, b| {
12875 let mut local_interp = VMHelper::new();
12876 local_interp.subs = subs.clone();
12877 local_interp.scope.restore_capture(&scope_capture);
12878 local_interp.scope.set_sort_pair(a.clone(), b.clone());
12879 match local_interp.exec_block(&cmp_block) {
12880 Ok(v) => {
12881 let n = v.to_int();
12882 if n < 0 {
12883 std::cmp::Ordering::Less
12884 } else if n > 0 {
12885 std::cmp::Ordering::Greater
12886 } else {
12887 std::cmp::Ordering::Equal
12888 }
12889 }
12890 Err(_) => std::cmp::Ordering::Equal,
12891 }
12892 });
12893 }
12894 } else {
12895 items.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
12896 }
12897 pmap_progress.tick();
12898 pmap_progress.finish();
12899 Ok(StrykeValue::array(items))
12900 }
12901
12902 ExprKind::ReduceExpr { block, list } => {
12903 let list_val = self.eval_expr(list)?;
12904 let items = list_val.to_list();
12905 if items.is_empty() {
12906 return Ok(StrykeValue::UNDEF);
12907 }
12908 if items.len() == 1 {
12909 return Ok(items.into_iter().next().unwrap());
12910 }
12911 let block = block.clone();
12912 let subs = self.subs.clone();
12913 let scope_capture = self.scope.capture();
12914 let mut acc = items[0].clone();
12915 for b in items.into_iter().skip(1) {
12916 let mut local_interp = VMHelper::new();
12917 local_interp.subs = subs.clone();
12918 local_interp.scope.restore_capture(&scope_capture);
12919 local_interp.scope.set_sort_pair(acc, b);
12920 acc = match local_interp.exec_block(&block) {
12921 Ok(val) => val,
12922 Err(_) => StrykeValue::UNDEF,
12923 };
12924 }
12925 Ok(acc)
12926 }
12927
12928 ExprKind::PReduceExpr {
12929 block,
12930 list,
12931 progress,
12932 } => {
12933 let show_progress = progress
12934 .as_ref()
12935 .map(|p| self.eval_expr(p))
12936 .transpose()?
12937 .map(|v| v.is_true())
12938 .unwrap_or(false);
12939 let list_val = self.eval_expr(list)?;
12940 let items = list_val.to_list();
12941 if items.is_empty() {
12942 return Ok(StrykeValue::UNDEF);
12943 }
12944 if items.len() == 1 {
12945 return Ok(items.into_iter().next().unwrap());
12946 }
12947 let block = block.clone();
12948 let subs = self.subs.clone();
12949 let scope_capture = self.scope.capture();
12950 let pmap_progress = PmapProgress::new(show_progress, items.len());
12951
12952 let result = items
12953 .into_par_iter()
12954 .map(|x| {
12955 pmap_progress.tick();
12956 x
12957 })
12958 .reduce_with(|a, b| {
12959 let mut local_interp = VMHelper::new();
12960 local_interp.subs = subs.clone();
12961 local_interp.scope.restore_capture(&scope_capture);
12962 local_interp.scope.set_sort_pair(a, b);
12963 match local_interp.exec_block(&block) {
12964 Ok(val) => val,
12965 Err(_) => StrykeValue::UNDEF,
12966 }
12967 });
12968 pmap_progress.finish();
12969 Ok(result.unwrap_or(StrykeValue::UNDEF))
12970 }
12971
12972 ExprKind::PReduceInitExpr {
12973 init,
12974 block,
12975 list,
12976 progress,
12977 } => {
12978 let show_progress = progress
12979 .as_ref()
12980 .map(|p| self.eval_expr(p))
12981 .transpose()?
12982 .map(|v| v.is_true())
12983 .unwrap_or(false);
12984 let init_val = self.eval_expr(init)?;
12985 let list_val = self.eval_expr(list)?;
12986 let items = list_val.to_list();
12987 if items.is_empty() {
12988 return Ok(init_val);
12989 }
12990 let block = block.clone();
12991 let subs = self.subs.clone();
12992 let scope_capture = self.scope.capture();
12993 let cap: &[(String, StrykeValue)] = scope_capture.as_slice();
12994 if items.len() == 1 {
12995 return Ok(fold_preduce_init_step(
12996 &subs,
12997 cap,
12998 &block,
12999 preduce_init_fold_identity(&init_val),
13000 items.into_iter().next().unwrap(),
13001 ));
13002 }
13003 let pmap_progress = PmapProgress::new(show_progress, items.len());
13004 let result = items
13005 .into_par_iter()
13006 .fold(
13007 || preduce_init_fold_identity(&init_val),
13008 |acc, item| {
13009 pmap_progress.tick();
13010 fold_preduce_init_step(&subs, cap, &block, acc, item)
13011 },
13012 )
13013 .reduce(
13014 || preduce_init_fold_identity(&init_val),
13015 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
13016 );
13017 pmap_progress.finish();
13018 Ok(result)
13019 }
13020
13021 ExprKind::PMapReduceExpr {
13022 map_block,
13023 reduce_block,
13024 list,
13025 progress,
13026 } => {
13027 let show_progress = progress
13028 .as_ref()
13029 .map(|p| self.eval_expr(p))
13030 .transpose()?
13031 .map(|v| v.is_true())
13032 .unwrap_or(false);
13033 let list_val = self.eval_expr(list)?;
13034 let items = list_val.to_list();
13035 if items.is_empty() {
13036 return Ok(StrykeValue::UNDEF);
13037 }
13038 let map_block = map_block.clone();
13039 let reduce_block = reduce_block.clone();
13040 let subs = self.subs.clone();
13041 let scope_capture = self.scope.capture();
13042 if items.len() == 1 {
13043 let mut local_interp = VMHelper::new();
13044 local_interp.subs = subs.clone();
13045 local_interp.scope.restore_capture(&scope_capture);
13046 local_interp.scope.set_topic(items[0].clone());
13047 return match local_interp.exec_block_no_scope(&map_block) {
13048 Ok(v) => Ok(v),
13049 Err(_) => Ok(StrykeValue::UNDEF),
13050 };
13051 }
13052 let pmap_progress = PmapProgress::new(show_progress, items.len());
13053 let result = items
13054 .into_par_iter()
13055 .map(|item| {
13056 let mut local_interp = VMHelper::new();
13057 local_interp.subs = subs.clone();
13058 local_interp.scope.restore_capture(&scope_capture);
13059 local_interp.scope.set_topic(item);
13060 let val = match local_interp.exec_block_no_scope(&map_block) {
13061 Ok(val) => val,
13062 Err(_) => StrykeValue::UNDEF,
13063 };
13064 pmap_progress.tick();
13065 val
13066 })
13067 .reduce_with(|a, b| {
13068 let mut local_interp = VMHelper::new();
13069 local_interp.subs = subs.clone();
13070 local_interp.scope.restore_capture(&scope_capture);
13071 local_interp.scope.set_sort_pair(a, b);
13072 match local_interp.exec_block_no_scope(&reduce_block) {
13073 Ok(val) => val,
13074 Err(_) => StrykeValue::UNDEF,
13075 }
13076 });
13077 pmap_progress.finish();
13078 Ok(result.unwrap_or(StrykeValue::UNDEF))
13079 }
13080
13081 ExprKind::PcacheExpr {
13082 block,
13083 list,
13084 progress,
13085 } => {
13086 let show_progress = progress
13087 .as_ref()
13088 .map(|p| self.eval_expr(p))
13089 .transpose()?
13090 .map(|v| v.is_true())
13091 .unwrap_or(false);
13092 let list_val = self.eval_expr(list)?;
13093 let items = list_val.to_list();
13094 let block = block.clone();
13095 let subs = self.subs.clone();
13096 let scope_capture = self.scope.capture();
13097 let cache = &*crate::pcache::GLOBAL_PCACHE;
13098 let pmap_progress = PmapProgress::new(show_progress, items.len());
13099 let results: Vec<StrykeValue> = items
13100 .into_par_iter()
13101 .map(|item| {
13102 let k = crate::pcache::cache_key(&item);
13103 if let Some(v) = cache.get(&k) {
13104 pmap_progress.tick();
13105 return v.clone();
13106 }
13107 let mut local_interp = VMHelper::new();
13108 local_interp.subs = subs.clone();
13109 local_interp.scope.restore_capture(&scope_capture);
13110 local_interp.scope.set_topic(item.clone());
13111 let val = match local_interp.exec_block_no_scope(&block) {
13112 Ok(v) => v,
13113 Err(_) => StrykeValue::UNDEF,
13114 };
13115 cache.insert(k, val.clone());
13116 pmap_progress.tick();
13117 val
13118 })
13119 .collect();
13120 pmap_progress.finish();
13121 Ok(StrykeValue::array(results))
13122 }
13123
13124 ExprKind::PselectExpr { receivers, timeout } => {
13125 let mut rx_vals = Vec::with_capacity(receivers.len());
13126 for r in receivers {
13127 rx_vals.push(self.eval_expr(r)?);
13128 }
13129 let dur = if let Some(t) = timeout.as_ref() {
13130 Some(std::time::Duration::from_secs_f64(
13131 self.eval_expr(t)?.to_number().max(0.0),
13132 ))
13133 } else {
13134 None
13135 };
13136 Ok(crate::pchannel::pselect_recv_with_optional_timeout(
13137 &rx_vals, dur, line,
13138 )?)
13139 }
13140
13141 ExprKind::Push { array, values } => {
13143 self.eval_push_expr(array.as_ref(), values.as_slice(), line)
13144 }
13145 ExprKind::Pop(array) => self.eval_pop_expr(array.as_ref(), line),
13146 ExprKind::Shift(array) => self.eval_shift_expr(array.as_ref(), line),
13147 ExprKind::Unshift { array, values } => {
13148 self.eval_unshift_expr(array.as_ref(), values.as_slice(), line)
13149 }
13150 ExprKind::Splice {
13151 array,
13152 offset,
13153 length,
13154 replacement,
13155 } => self.eval_splice_expr(
13156 array.as_ref(),
13157 offset.as_deref(),
13158 length.as_deref(),
13159 replacement.as_slice(),
13160 ctx,
13161 line,
13162 ),
13163 ExprKind::Delete(expr) => self.eval_delete_operand(expr.as_ref(), line),
13164 ExprKind::Exists(expr) => self.eval_exists_operand(expr.as_ref(), line),
13165 ExprKind::Keys(expr) => {
13166 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
13167 let keys = Self::keys_from_value(val, line)?;
13168 if ctx == WantarrayCtx::List {
13169 Ok(keys)
13170 } else {
13171 let n = keys.as_array_vec().map(|a| a.len()).unwrap_or(0);
13172 Ok(StrykeValue::integer(n as i64))
13173 }
13174 }
13175 ExprKind::Values(expr) => {
13176 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
13177 let vals = Self::values_from_value(val, line)?;
13178 if ctx == WantarrayCtx::List {
13179 Ok(vals)
13180 } else {
13181 let n = vals.as_array_vec().map(|a| a.len()).unwrap_or(0);
13182 Ok(StrykeValue::integer(n as i64))
13183 }
13184 }
13185 ExprKind::Each(_) => {
13186 Ok(StrykeValue::array(vec![]))
13188 }
13189
13190 ExprKind::Chomp(expr) => {
13192 let val = self.eval_expr(expr)?;
13193 self.chomp_inplace_execute(val, expr)
13194 }
13195 ExprKind::Chop(expr) => {
13196 let val = self.eval_expr(expr)?;
13197 self.chop_inplace_execute(val, expr)
13198 }
13199 ExprKind::Length(expr) => {
13200 let val = self.eval_expr(expr)?;
13201 Ok(if let Some(a) = val.as_array_vec() {
13202 StrykeValue::integer(a.len() as i64)
13203 } else if let Some(h) = val.as_hash_map() {
13204 StrykeValue::integer(h.len() as i64)
13205 } else if let Some(b) = val.as_bytes_arc() {
13206 StrykeValue::integer(b.len() as i64)
13208 } else {
13209 let s = val.to_string();
13210 let n = if self.utf8_pragma {
13211 s.chars().count()
13212 } else {
13213 s.len()
13214 };
13215 StrykeValue::integer(n as i64)
13216 })
13217 }
13218 ExprKind::Substr {
13219 string,
13220 offset,
13221 length,
13222 replacement,
13223 } => self.eval_substr_expr(
13224 string.as_ref(),
13225 offset.as_ref(),
13226 length.as_deref(),
13227 replacement.as_deref(),
13228 line,
13229 ),
13230 ExprKind::Index {
13231 string,
13232 substr,
13233 position,
13234 } => {
13235 let s = self.eval_expr(string)?.to_string();
13236 let sub = self.eval_expr(substr)?.to_string();
13237 let pos = if let Some(p) = position {
13240 let raw = self.eval_expr(p)?.to_int();
13241 if raw < 0 {
13242 0usize
13243 } else {
13244 (raw as usize).min(s.len())
13245 }
13246 } else {
13247 0
13248 };
13249 let result = s[pos..].find(&sub).map(|i| (i + pos) as i64).unwrap_or(-1);
13250 Ok(StrykeValue::integer(result))
13251 }
13252 ExprKind::Rindex {
13253 string,
13254 substr,
13255 position,
13256 } => {
13257 let s = self.eval_expr(string)?.to_string();
13258 let sub = self.eval_expr(substr)?.to_string();
13259 let result = if let Some(p) = position {
13262 let raw = self.eval_expr(p)?.to_int();
13263 if raw < 0 {
13264 -1
13265 } else {
13266 let end = (raw as usize).saturating_add(sub.len()).min(s.len());
13267 s[..end].rfind(&sub).map(|i| i as i64).unwrap_or(-1)
13268 }
13269 } else {
13270 s.rfind(&sub).map(|i| i as i64).unwrap_or(-1)
13271 };
13272 Ok(StrykeValue::integer(result))
13273 }
13274 ExprKind::Sprintf { format, args } => {
13275 let fmt = self.eval_expr(format)?.to_string();
13276 let mut arg_vals = Vec::new();
13279 for a in args {
13280 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
13281 if let Some(items) = v.as_array_vec() {
13282 arg_vals.extend(items);
13283 } else {
13284 arg_vals.push(v);
13285 }
13286 }
13287 let s = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
13288 Ok(StrykeValue::string(s))
13289 }
13290 ExprKind::JoinExpr { separator, list } => {
13291 let sep = self.eval_expr(separator)?.to_string();
13292 let items = if let ExprKind::List(exprs) = &list.kind {
13296 let saved = self.wantarray_kind;
13297 self.wantarray_kind = WantarrayCtx::List;
13298 let mut vals = Vec::new();
13299 for e in exprs {
13300 let v = self.eval_expr_ctx(e, self.wantarray_kind)?;
13301 if let Some(items) = v.as_array_vec() {
13302 vals.extend(items);
13303 } else if v.is_iterator() {
13304 vals.extend(v.into_iterator().collect_all());
13308 } else {
13309 vals.push(v);
13310 }
13311 }
13312 self.wantarray_kind = saved;
13313 vals
13314 } else {
13315 let saved = self.wantarray_kind;
13316 self.wantarray_kind = WantarrayCtx::List;
13317 let v = self.eval_expr_ctx(list, WantarrayCtx::List)?;
13318 self.wantarray_kind = saved;
13319 if let Some(items) = v.as_array_vec() {
13320 items
13321 } else if v.is_iterator() {
13322 v.into_iterator().collect_all()
13326 } else {
13327 vec![v]
13328 }
13329 };
13330 let mut strs = Vec::with_capacity(items.len());
13331 for v in &items {
13332 strs.push(self.stringify_value(v.clone(), line)?);
13333 }
13334 Ok(StrykeValue::string(strs.join(&sep)))
13335 }
13336 ExprKind::SplitExpr {
13337 pattern,
13338 string,
13339 limit,
13340 } => {
13341 let pat_val = self.eval_expr(pattern)?;
13342 let pat = pat_val
13346 .regex_src_and_flags()
13347 .map(|(s, _)| s)
13348 .unwrap_or_else(|| pat_val.to_string());
13349 let s = self.eval_expr(string)?.to_string();
13350 if s.is_empty() {
13351 return Ok(StrykeValue::array(vec![]));
13352 }
13353 let lim_opt: Option<i64> = limit
13362 .as_ref()
13363 .map(|l| self.eval_expr(l).map(|v| v.to_int()))
13364 .transpose()?;
13365 let re = self.compile_regex(&pat, "", line)?;
13366 let mut parts: Vec<String> = match lim_opt {
13367 Some(l) if l > 0 => re.splitn_strings(&s, l as usize),
13368 _ => re.split_strings(&s),
13369 };
13370
13371 if pat.is_empty() && parts.first().is_some_and(|p| p.is_empty()) {
13377 parts.remove(0);
13378 }
13379 let strip_trailing = matches!(lim_opt, None | Some(0));
13383 if strip_trailing {
13384 while parts.last().is_some_and(|p| p.is_empty()) {
13385 parts.pop();
13386 }
13387 }
13388
13389 Ok(StrykeValue::array(
13390 parts.into_iter().map(StrykeValue::string).collect(),
13391 ))
13392 }
13393
13394 ExprKind::Abs(expr) => {
13396 let val = self.eval_expr(expr)?;
13397 if let Some(r) = self.try_overload_unary_dispatch("abs", &val, line) {
13398 return r;
13399 }
13400 Ok(StrykeValue::float(val.to_number().abs()))
13401 }
13402 ExprKind::Int(expr) => {
13403 let val = self.eval_expr(expr)?;
13404 Ok(StrykeValue::integer(val.to_number() as i64))
13405 }
13406 ExprKind::Sqrt(expr) => {
13407 let val = self.eval_expr(expr)?;
13408 Ok(StrykeValue::float(val.to_number().sqrt()))
13409 }
13410 ExprKind::Sin(expr) => {
13411 let val = self.eval_expr(expr)?;
13412 Ok(StrykeValue::float(val.to_number().sin()))
13413 }
13414 ExprKind::Cos(expr) => {
13415 let val = self.eval_expr(expr)?;
13416 Ok(StrykeValue::float(val.to_number().cos()))
13417 }
13418 ExprKind::Atan2 { y, x } => {
13419 let yv = self.eval_expr(y)?.to_number();
13420 let xv = self.eval_expr(x)?.to_number();
13421 Ok(StrykeValue::float(yv.atan2(xv)))
13422 }
13423 ExprKind::Exp(expr) => {
13424 let val = self.eval_expr(expr)?;
13425 Ok(StrykeValue::float(val.to_number().exp()))
13426 }
13427 ExprKind::Log(expr) => {
13428 let val = self.eval_expr(expr)?;
13429 Ok(StrykeValue::float(val.to_number().ln()))
13430 }
13431 ExprKind::Rand(upper) => {
13432 let u = match upper {
13433 Some(e) => self.eval_expr(e)?.to_number(),
13434 None => 1.0,
13435 };
13436 Ok(StrykeValue::float(self.perl_rand(u)))
13437 }
13438 ExprKind::Srand(seed) => {
13439 let s = match seed {
13440 Some(e) => Some(self.eval_expr(e)?.to_number()),
13441 None => None,
13442 };
13443 Ok(StrykeValue::integer(self.perl_srand(s)))
13444 }
13445 ExprKind::Hex(expr) => {
13446 let val = self.eval_expr(expr)?.to_string();
13447 let clean = val.trim().trim_start_matches("0x").trim_start_matches("0X");
13448 let n = i64::from_str_radix(clean, 16).unwrap_or(0);
13449 Ok(StrykeValue::integer(n))
13450 }
13451 ExprKind::Oct(expr) => {
13452 let val = self.eval_expr(expr)?.to_string();
13453 let s = val.trim();
13454 let n = if s.starts_with("0x") || s.starts_with("0X") {
13455 i64::from_str_radix(&s[2..], 16).unwrap_or(0)
13456 } else if s.starts_with("0b") || s.starts_with("0B") {
13457 i64::from_str_radix(&s[2..], 2).unwrap_or(0)
13458 } else if s.starts_with("0o") || s.starts_with("0O") {
13459 i64::from_str_radix(&s[2..], 8).unwrap_or(0)
13460 } else {
13461 i64::from_str_radix(s.trim_start_matches('0'), 8).unwrap_or(0)
13462 };
13463 Ok(StrykeValue::integer(n))
13464 }
13465
13466 ExprKind::Lc(expr) => Ok(StrykeValue::string(
13468 self.eval_expr(expr)?.to_string().to_lowercase(),
13469 )),
13470 ExprKind::Uc(expr) => Ok(StrykeValue::string(
13471 self.eval_expr(expr)?.to_string().to_uppercase(),
13472 )),
13473 ExprKind::Lcfirst(expr) => {
13474 let s = self.eval_expr(expr)?.to_string();
13475 let mut chars = s.chars();
13476 let result = match chars.next() {
13477 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
13478 None => String::new(),
13479 };
13480 Ok(StrykeValue::string(result))
13481 }
13482 ExprKind::Ucfirst(expr) => {
13483 let s = self.eval_expr(expr)?.to_string();
13484 let mut chars = s.chars();
13485 let result = match chars.next() {
13486 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
13487 None => String::new(),
13488 };
13489 Ok(StrykeValue::string(result))
13490 }
13491 ExprKind::Fc(expr) => Ok(StrykeValue::string(default_case_fold_str(
13492 &self.eval_expr(expr)?.to_string(),
13493 ))),
13494 ExprKind::Quotemeta(expr) => Ok(StrykeValue::string(crate::perl_regex::perl_quotemeta(
13495 &self.eval_expr(expr)?.to_string(),
13496 ))),
13497 ExprKind::Crypt { plaintext, salt } => {
13498 let p = self.eval_expr(plaintext)?.to_string();
13499 let sl = self.eval_expr(salt)?.to_string();
13500 Ok(StrykeValue::string(perl_crypt(&p, &sl)))
13501 }
13502 ExprKind::Pos(e) => {
13503 let key = match e {
13504 None => "_".to_string(),
13505 Some(expr) => match &expr.kind {
13506 ExprKind::ScalarVar(n) => n.clone(),
13507 _ => self.eval_expr(expr)?.to_string(),
13508 },
13509 };
13510 Ok(self
13511 .regex_pos
13512 .get(&key)
13513 .copied()
13514 .flatten()
13515 .map(|p| StrykeValue::integer(p as i64))
13516 .unwrap_or(StrykeValue::UNDEF))
13517 }
13518 ExprKind::Study(expr) => {
13519 let s = self.eval_expr(expr)?.to_string();
13520 Ok(Self::study_return_value(&s))
13521 }
13522
13523 ExprKind::Defined(expr) => {
13525 if let ExprKind::SubroutineRef(name) = &expr.kind {
13527 let exists = self.resolve_sub_by_name(name).is_some();
13528 return Ok(StrykeValue::integer(if exists { 1 } else { 0 }));
13529 }
13530 let val = self.eval_expr(expr)?;
13531 Ok(StrykeValue::integer(if val.is_undef() { 0 } else { 1 }))
13532 }
13533 ExprKind::Ref(expr) => {
13534 let val = self.eval_expr(expr)?;
13535 Ok(val.ref_type())
13536 }
13537 ExprKind::ScalarContext(expr) => {
13538 let v = self.eval_expr_ctx(expr, WantarrayCtx::Scalar)?;
13539 Ok(v.scalar_context())
13540 }
13541
13542 ExprKind::Chr(expr) => {
13544 let n = self.eval_expr(expr)?.to_int() as u32;
13545 Ok(StrykeValue::string(
13546 char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(),
13547 ))
13548 }
13549 ExprKind::Ord(expr) => {
13550 let s = self.eval_expr(expr)?.to_string();
13551 Ok(StrykeValue::integer(
13552 s.chars().next().map(|c| c as i64).unwrap_or(0),
13553 ))
13554 }
13555
13556 ExprKind::OpenMyHandle { .. } => Err(StrykeError::runtime(
13558 "internal: `open my $fh` handle used outside open()",
13559 line,
13560 )
13561 .into()),
13562 ExprKind::OpendirMyHandle { .. } => Err(StrykeError::runtime(
13563 "internal: `opendir my $dh` handle used outside opendir()",
13564 line,
13565 )
13566 .into()),
13567 ExprKind::Open { handle, mode, file } => {
13568 if let ExprKind::OpenMyHandle { name } = &handle.kind {
13569 self.scope
13570 .declare_scalar_frozen(name, StrykeValue::UNDEF, false, None)?;
13571 self.english_note_lexical_scalar(name);
13572 let mode_s = self.eval_expr(mode)?.to_string();
13573 let file_opt = if let Some(f) = file {
13574 Some(self.eval_expr(f)?.to_string())
13575 } else {
13576 None
13577 };
13578 let ret = self.open_builtin_execute(name.clone(), mode_s, file_opt, line)?;
13579 self.scope.set_scalar(name, ret.clone())?;
13580 return Ok(ret);
13581 }
13582 let handle_s = self.eval_expr(handle)?.to_string();
13583 let handle_name = self.resolve_io_handle_name(&handle_s);
13584 let mode_s = self.eval_expr(mode)?.to_string();
13585 let file_opt = if let Some(f) = file {
13586 Some(self.eval_expr(f)?.to_string())
13587 } else {
13588 None
13589 };
13590 self.open_builtin_execute(handle_name, mode_s, file_opt, line)
13591 .map_err(Into::into)
13592 }
13593 ExprKind::Close(expr) => {
13594 let s = self.eval_expr(expr)?.to_string();
13595 let name = self.resolve_io_handle_name(&s);
13596 self.close_builtin_execute(name).map_err(Into::into)
13597 }
13598 ExprKind::ReadLine(handle) => if ctx == WantarrayCtx::List {
13599 self.readline_builtin_execute_list(handle.as_deref())
13600 } else {
13601 self.readline_builtin_execute(handle.as_deref())
13602 }
13603 .map_err(Into::into),
13604 ExprKind::Eof(expr) => match expr {
13605 None => self.eof_builtin_execute(&[], line).map_err(Into::into),
13606 Some(e) => {
13607 let name = self.eval_expr(e)?;
13608 self.eof_builtin_execute(&[name], line).map_err(Into::into)
13609 }
13610 },
13611
13612 ExprKind::Opendir { handle, path } => {
13613 if let ExprKind::OpendirMyHandle { name } = &handle.kind {
13614 self.scope
13615 .declare_scalar_frozen(name, StrykeValue::UNDEF, false, None)?;
13616 self.english_note_lexical_scalar(name);
13617 let p = self.eval_expr(path)?.to_string();
13618 let ret = self.opendir_handle(name, &p);
13619 self.scope.set_scalar(name, ret.clone())?;
13620 return Ok(ret);
13621 }
13622 let h = self.eval_expr(handle)?.to_string();
13623 let p = self.eval_expr(path)?.to_string();
13624 Ok(self.opendir_handle(&h, &p))
13625 }
13626 ExprKind::Readdir(e) => {
13627 let h = self.eval_expr(e)?.to_string();
13628 Ok(if ctx == WantarrayCtx::List {
13629 self.readdir_handle_list(&h)
13630 } else {
13631 self.readdir_handle(&h)
13632 })
13633 }
13634 ExprKind::Closedir(e) => {
13635 let h = self.eval_expr(e)?.to_string();
13636 Ok(self.closedir_handle(&h))
13637 }
13638 ExprKind::Rewinddir(e) => {
13639 let h = self.eval_expr(e)?.to_string();
13640 Ok(self.rewinddir_handle(&h))
13641 }
13642 ExprKind::Telldir(e) => {
13643 let h = self.eval_expr(e)?.to_string();
13644 Ok(self.telldir_handle(&h))
13645 }
13646 ExprKind::Seekdir { handle, position } => {
13647 let h = self.eval_expr(handle)?.to_string();
13648 let pos = self.eval_expr(position)?.to_int().max(0) as usize;
13649 Ok(self.seekdir_handle(&h, pos))
13650 }
13651
13652 ExprKind::FileTest { op, expr } => {
13654 let raw = self.eval_expr(expr)?.to_string();
13655 let path = self.resolve_stryke_path_string(&raw);
13656 if matches!(op, 'M' | 'A' | 'C') {
13658 #[cfg(unix)]
13659 {
13660 return match crate::perl_fs::filetest_age_days(&path, *op) {
13661 Some(days) => Ok(StrykeValue::float(days)),
13662 None => Ok(StrykeValue::UNDEF),
13663 };
13664 }
13665 #[cfg(not(unix))]
13666 return Ok(StrykeValue::UNDEF);
13667 }
13668 if *op == 's' {
13670 return match std::fs::metadata(&path) {
13671 Ok(m) => Ok(StrykeValue::integer(m.len() as i64)),
13672 Err(_) => Ok(StrykeValue::UNDEF),
13673 };
13674 }
13675 let result = match op {
13676 'e' => std::path::Path::new(&path).exists(),
13677 'f' => std::path::Path::new(&path).is_file(),
13678 'd' => std::path::Path::new(&path).is_dir(),
13679 'l' => std::path::Path::new(&path).is_symlink(),
13680 #[cfg(unix)]
13681 'r' => crate::perl_fs::filetest_effective_access(&path, 4),
13682 #[cfg(not(unix))]
13683 'r' => std::fs::metadata(&path).is_ok(),
13684 #[cfg(unix)]
13685 'w' => crate::perl_fs::filetest_effective_access(&path, 2),
13686 #[cfg(not(unix))]
13687 'w' => std::fs::metadata(&path).is_ok(),
13688 #[cfg(unix)]
13689 'x' => crate::perl_fs::filetest_effective_access(&path, 1),
13690 #[cfg(not(unix))]
13691 'x' => false,
13692 #[cfg(unix)]
13693 'o' => crate::perl_fs::filetest_owned_effective(&path),
13694 #[cfg(not(unix))]
13695 'o' => false,
13696 #[cfg(unix)]
13697 'R' => crate::perl_fs::filetest_real_access(&path, libc::R_OK),
13698 #[cfg(not(unix))]
13699 'R' => false,
13700 #[cfg(unix)]
13701 'W' => crate::perl_fs::filetest_real_access(&path, libc::W_OK),
13702 #[cfg(not(unix))]
13703 'W' => false,
13704 #[cfg(unix)]
13705 'X' => crate::perl_fs::filetest_real_access(&path, libc::X_OK),
13706 #[cfg(not(unix))]
13707 'X' => false,
13708 #[cfg(unix)]
13709 'O' => crate::perl_fs::filetest_owned_real(&path),
13710 #[cfg(not(unix))]
13711 'O' => false,
13712 'z' => std::fs::metadata(&path)
13713 .map(|m| m.len() == 0)
13714 .unwrap_or(true),
13715 't' => crate::perl_fs::filetest_is_tty(&path),
13716 #[cfg(unix)]
13717 'p' => crate::perl_fs::filetest_is_pipe(&path),
13718 #[cfg(not(unix))]
13719 'p' => false,
13720 #[cfg(unix)]
13721 'S' => crate::perl_fs::filetest_is_socket(&path),
13722 #[cfg(not(unix))]
13723 'S' => false,
13724 #[cfg(unix)]
13725 'b' => crate::perl_fs::filetest_is_block_device(&path),
13726 #[cfg(not(unix))]
13727 'b' => false,
13728 #[cfg(unix)]
13729 'c' => crate::perl_fs::filetest_is_char_device(&path),
13730 #[cfg(not(unix))]
13731 'c' => false,
13732 #[cfg(unix)]
13733 'u' => crate::perl_fs::filetest_is_setuid(&path),
13734 #[cfg(not(unix))]
13735 'u' => false,
13736 #[cfg(unix)]
13737 'g' => crate::perl_fs::filetest_is_setgid(&path),
13738 #[cfg(not(unix))]
13739 'g' => false,
13740 #[cfg(unix)]
13741 'k' => crate::perl_fs::filetest_is_sticky(&path),
13742 #[cfg(not(unix))]
13743 'k' => false,
13744 'T' => crate::perl_fs::filetest_is_text(&path),
13745 'B' => crate::perl_fs::filetest_is_binary(&path),
13746 _ => false,
13747 };
13748 Ok(StrykeValue::integer(if result { 1 } else { 0 }))
13749 }
13750
13751 ExprKind::System(args) => {
13753 let mut cmd_args = Vec::new();
13760 for a in args {
13761 cmd_args.push(self.eval_expr(a)?.to_string());
13762 }
13763 if cmd_args.is_empty() {
13764 self.child_exit_status = -1;
13765 return Ok(StrykeValue::integer(-1));
13766 }
13767 let status = if cmd_args.len() == 1 {
13768 Command::new("sh").arg("-c").arg(&cmd_args[0]).status()
13769 } else {
13770 Command::new(&cmd_args[0]).args(&cmd_args[1..]).status()
13771 };
13772 match status {
13773 Ok(s) => {
13774 self.record_child_exit_status(s);
13775 Ok(StrykeValue::integer(self.child_exit_status))
13776 }
13777 Err(e) => {
13778 self.apply_io_error_to_errno(&e);
13779 self.child_exit_status = -1;
13780 Ok(StrykeValue::integer(-1))
13781 }
13782 }
13783 }
13784 ExprKind::Exec(args) => {
13785 let mut cmd_args = Vec::new();
13786 for a in args {
13787 cmd_args.push(self.eval_expr(a)?.to_string());
13788 }
13789 if cmd_args.is_empty() {
13790 return Ok(StrykeValue::integer(-1));
13791 }
13792 let status = Command::new("sh")
13793 .arg("-c")
13794 .arg(cmd_args.join(" "))
13795 .status();
13796 match status {
13797 Ok(s) => std::process::exit(s.code().unwrap_or(-1)),
13798 Err(e) => {
13799 self.apply_io_error_to_errno(&e);
13800 Ok(StrykeValue::integer(-1))
13801 }
13802 }
13803 }
13804 ExprKind::Eval(expr) => {
13805 self.eval_nesting += 1;
13806 let out = match &expr.kind {
13807 ExprKind::CodeRef { body, .. } => match self.exec_block_with_tail(body, ctx) {
13808 Ok(v) => {
13809 self.clear_eval_error();
13810 Ok(v)
13811 }
13812 Err(FlowOrError::Error(e)) => {
13813 self.set_eval_error_from_perl_error(&e);
13814 Ok(StrykeValue::UNDEF)
13815 }
13816 Err(FlowOrError::Flow(f)) => Err(FlowOrError::Flow(f)),
13817 },
13818 _ => {
13819 let code = self.eval_expr(expr)?.to_string();
13820 match crate::parse_and_run_string(&code, self) {
13822 Ok(v) => {
13823 self.clear_eval_error();
13824 Ok(v)
13825 }
13826 Err(e) => {
13827 self.set_eval_error(e.to_string());
13828 Ok(StrykeValue::UNDEF)
13829 }
13830 }
13831 }
13832 };
13833 self.eval_nesting -= 1;
13834 out
13835 }
13836 ExprKind::Do(expr) => match &expr.kind {
13837 ExprKind::CodeRef { body, .. } => self.exec_block_with_tail(body, ctx),
13838 _ => {
13839 let val = self.eval_expr(expr)?;
13840 let filename = val.to_string();
13841 match read_file_text_perl_compat(&filename) {
13842 Ok(code) => {
13843 let code = crate::data_section::strip_perl_end_marker(&code);
13844 match crate::parse_and_run_string_in_file(code, self, &filename) {
13845 Ok(v) => Ok(v),
13846 Err(e) => {
13847 self.set_eval_error(e.to_string());
13848 Ok(StrykeValue::UNDEF)
13849 }
13850 }
13851 }
13852 Err(e) => {
13853 self.apply_io_error_to_errno(&e);
13854 Ok(StrykeValue::UNDEF)
13855 }
13856 }
13857 }
13858 },
13859 ExprKind::Require(expr) => {
13860 let spec = self.eval_expr(expr)?.to_string();
13861 self.require_execute(&spec, line)
13862 .map_err(FlowOrError::Error)
13863 }
13864 ExprKind::Exit(code) => {
13865 let c = if let Some(e) = code {
13866 self.eval_expr(e)?.to_int() as i32
13867 } else {
13868 0
13869 };
13870 Err(StrykeError::new(ErrorKind::Exit(c), "", line, &self.file).into())
13871 }
13872 ExprKind::Chdir(expr) => {
13873 let path = self.eval_expr(expr)?.to_string();
13874 match std::env::set_current_dir(&path) {
13875 Ok(_) => {
13876 if let Ok(c) = std::env::current_dir() {
13877 self.stryke_pwd = std::fs::canonicalize(&c).unwrap_or(c);
13878 }
13879 Ok(StrykeValue::integer(1))
13880 }
13881 Err(e) => {
13882 self.apply_io_error_to_errno(&e);
13883 Ok(StrykeValue::integer(0))
13884 }
13885 }
13886 }
13887 ExprKind::Mkdir { path, mode: _ } => {
13888 let raw = self.eval_expr(path)?.to_string();
13889 let p = self.resolve_stryke_path_string(&raw);
13890 match std::fs::create_dir(&p) {
13891 Ok(_) => Ok(StrykeValue::integer(1)),
13892 Err(e) => {
13893 self.apply_io_error_to_errno(&e);
13894 Ok(StrykeValue::integer(0))
13895 }
13896 }
13897 }
13898 ExprKind::Unlink(args) => {
13899 let mut count = 0i64;
13900 for a in args {
13901 let raw = self.eval_expr(a)?.to_string();
13902 let path = self.resolve_stryke_path_string(&raw);
13903 if std::fs::remove_file(&path).is_ok() {
13904 count += 1;
13905 }
13906 }
13907 Ok(StrykeValue::integer(count))
13908 }
13909 ExprKind::Rename { old, new } => {
13910 let o_raw = self.eval_expr(old)?.to_string();
13911 let n_raw = self.eval_expr(new)?.to_string();
13912 let o = self.resolve_stryke_path_string(&o_raw);
13913 let n = self.resolve_stryke_path_string(&n_raw);
13914 Ok(crate::perl_fs::rename_paths(&o, &n))
13915 }
13916 ExprKind::Chmod(args) => {
13917 let mode = self.eval_expr(&args[0])?.to_int();
13918 let mut paths = Vec::new();
13919 for a in &args[1..] {
13920 let raw = self.eval_expr(a)?.to_string();
13921 paths.push(self.resolve_stryke_path_string(&raw));
13922 }
13923 Ok(StrykeValue::integer(crate::perl_fs::chmod_paths(
13924 &paths, mode,
13925 )))
13926 }
13927 ExprKind::Chown(args) => {
13928 let uid = self.eval_expr(&args[0])?.to_int();
13929 let gid = self.eval_expr(&args[1])?.to_int();
13930 let mut paths = Vec::new();
13931 for a in &args[2..] {
13932 let raw = self.eval_expr(a)?.to_string();
13933 paths.push(self.resolve_stryke_path_string(&raw));
13934 }
13935 Ok(StrykeValue::integer(crate::perl_fs::chown_paths(
13936 &paths, uid, gid,
13937 )))
13938 }
13939 ExprKind::Stat(e) => {
13940 let raw = self.eval_expr(e)?.to_string();
13941 let path = self.resolve_stryke_path_string(&raw);
13942 Ok(crate::perl_fs::stat_path(&path, false))
13943 }
13944 ExprKind::Lstat(e) => {
13945 let raw = self.eval_expr(e)?.to_string();
13946 let path = self.resolve_stryke_path_string(&raw);
13947 Ok(crate::perl_fs::stat_path(&path, true))
13948 }
13949 ExprKind::Link { old, new } => {
13950 let o_raw = self.eval_expr(old)?.to_string();
13951 let n_raw = self.eval_expr(new)?.to_string();
13952 let o = self.resolve_stryke_path_string(&o_raw);
13953 let n = self.resolve_stryke_path_string(&n_raw);
13954 Ok(crate::perl_fs::link_hard(&o, &n))
13955 }
13956 ExprKind::Symlink { old, new } => {
13957 let o = self.eval_expr(old)?.to_string();
13958 let n_raw = self.eval_expr(new)?.to_string();
13959 let n = self.resolve_stryke_path_string(&n_raw);
13960 Ok(crate::perl_fs::link_sym(&o, &n))
13961 }
13962 ExprKind::Readlink(e) => {
13963 let raw = self.eval_expr(e)?.to_string();
13964 let path = self.resolve_stryke_path_string(&raw);
13965 Ok(crate::perl_fs::read_link(&path))
13966 }
13967 ExprKind::Files(args) => {
13968 let dir_raw = if args.is_empty() {
13969 ".".to_string()
13970 } else {
13971 self.eval_expr(&args[0])?.to_string()
13972 };
13973 let dir = self.resolve_stryke_path_string(&dir_raw);
13974 Ok(crate::perl_fs::list_files(&dir))
13975 }
13976 ExprKind::Filesf(args) => {
13977 let dir_raw = if args.is_empty() {
13978 ".".to_string()
13979 } else {
13980 self.eval_expr(&args[0])?.to_string()
13981 };
13982 let dir = self.resolve_stryke_path_string(&dir_raw);
13983 Ok(crate::perl_fs::list_filesf(&dir))
13984 }
13985 ExprKind::FilesfRecursive(args) => {
13986 let dir_raw = if args.is_empty() {
13987 ".".to_string()
13988 } else {
13989 self.eval_expr(&args[0])?.to_string()
13990 };
13991 let dir = self.resolve_stryke_path_string(&dir_raw);
13992 Ok(StrykeValue::iterator(Arc::new(
13993 crate::value::FsWalkIterator::new(&dir, true),
13994 )))
13995 }
13996 ExprKind::Dirs(args) => {
13997 let dir_raw = if args.is_empty() {
13998 ".".to_string()
13999 } else {
14000 self.eval_expr(&args[0])?.to_string()
14001 };
14002 let dir = self.resolve_stryke_path_string(&dir_raw);
14003 Ok(crate::perl_fs::list_dirs(&dir))
14004 }
14005 ExprKind::DirsRecursive(args) => {
14006 let dir_raw = if args.is_empty() {
14007 ".".to_string()
14008 } else {
14009 self.eval_expr(&args[0])?.to_string()
14010 };
14011 let dir = self.resolve_stryke_path_string(&dir_raw);
14012 Ok(StrykeValue::iterator(Arc::new(
14013 crate::value::FsWalkIterator::new(&dir, false),
14014 )))
14015 }
14016 ExprKind::SymLinks(args) => {
14017 let dir_raw = if args.is_empty() {
14018 ".".to_string()
14019 } else {
14020 self.eval_expr(&args[0])?.to_string()
14021 };
14022 let dir = self.resolve_stryke_path_string(&dir_raw);
14023 Ok(crate::perl_fs::list_sym_links(&dir))
14024 }
14025 ExprKind::Sockets(args) => {
14026 let dir_raw = if args.is_empty() {
14027 ".".to_string()
14028 } else {
14029 self.eval_expr(&args[0])?.to_string()
14030 };
14031 let dir = self.resolve_stryke_path_string(&dir_raw);
14032 Ok(crate::perl_fs::list_sockets(&dir))
14033 }
14034 ExprKind::Pipes(args) => {
14035 let dir_raw = if args.is_empty() {
14036 ".".to_string()
14037 } else {
14038 self.eval_expr(&args[0])?.to_string()
14039 };
14040 let dir = self.resolve_stryke_path_string(&dir_raw);
14041 Ok(crate::perl_fs::list_pipes(&dir))
14042 }
14043 ExprKind::BlockDevices(args) => {
14044 let dir_raw = if args.is_empty() {
14045 ".".to_string()
14046 } else {
14047 self.eval_expr(&args[0])?.to_string()
14048 };
14049 let dir = self.resolve_stryke_path_string(&dir_raw);
14050 Ok(crate::perl_fs::list_block_devices(&dir))
14051 }
14052 ExprKind::CharDevices(args) => {
14053 let dir_raw = if args.is_empty() {
14054 ".".to_string()
14055 } else {
14056 self.eval_expr(&args[0])?.to_string()
14057 };
14058 let dir = self.resolve_stryke_path_string(&dir_raw);
14059 Ok(crate::perl_fs::list_char_devices(&dir))
14060 }
14061 ExprKind::Executables(args) => {
14062 let dir_raw = if args.is_empty() {
14063 ".".to_string()
14064 } else {
14065 self.eval_expr(&args[0])?.to_string()
14066 };
14067 let dir = self.resolve_stryke_path_string(&dir_raw);
14068 Ok(crate::perl_fs::list_executables(&dir))
14069 }
14070 ExprKind::Glob(args) => {
14071 let mut pats = Vec::new();
14078 for a in args {
14079 pats.push(self.eval_expr(a)?.to_string());
14080 }
14081 Ok(crate::perl_fs::glob_patterns(&pats))
14082 }
14083 ExprKind::GlobPar { args, progress } => {
14084 let mut pats = Vec::new();
14085 for a in args {
14086 pats.push(self.eval_expr(a)?.to_string());
14087 }
14088 let show_progress = progress
14089 .as_ref()
14090 .map(|p| self.eval_expr(p))
14091 .transpose()?
14092 .map(|v| v.is_true())
14093 .unwrap_or(false);
14094 if show_progress {
14095 Ok(crate::perl_fs::glob_par_patterns_with_progress(&pats, true))
14096 } else {
14097 Ok(crate::perl_fs::glob_par_patterns(&pats))
14098 }
14099 }
14100 ExprKind::ParSed { args, progress } => {
14101 let has_progress = progress.is_some();
14102 let mut vals: Vec<StrykeValue> = Vec::new();
14103 for a in args {
14104 vals.push(self.eval_expr(a)?);
14105 }
14106 if let Some(p) = progress {
14107 vals.push(self.eval_expr(p.as_ref())?);
14108 }
14109 Ok(self.builtin_par_sed(&vals, line, has_progress)?)
14110 }
14111 ExprKind::Bless { ref_expr, class } => {
14112 let val = self.eval_expr(ref_expr)?;
14113 let class_name = if let Some(c) = class {
14114 self.eval_expr(c)?.to_string()
14115 } else {
14116 self.scope.get_scalar("__PACKAGE__").to_string()
14117 };
14118 Ok(StrykeValue::blessed(Arc::new(
14119 crate::value::BlessedRef::new_blessed(class_name, val),
14120 )))
14121 }
14122 ExprKind::Caller(_) => {
14123 let sub_name = self
14129 .current_sub_stack
14130 .last()
14131 .map(|s| StrykeValue::string(s.name.clone()))
14132 .unwrap_or(StrykeValue::UNDEF);
14133 let pkg = self.current_package();
14134 Ok(StrykeValue::array(vec![
14135 StrykeValue::string(pkg),
14136 StrykeValue::string(self.file.clone()),
14137 StrykeValue::integer(line as i64),
14138 sub_name,
14139 ]))
14140 }
14141 ExprKind::Wantarray => Ok(match self.wantarray_kind {
14142 WantarrayCtx::Void => StrykeValue::UNDEF,
14143 WantarrayCtx::Scalar => StrykeValue::integer(0),
14144 WantarrayCtx::List => StrykeValue::integer(1),
14145 }),
14146
14147 ExprKind::List(exprs) => {
14148 if ctx == WantarrayCtx::Scalar {
14150 if let Some(last) = exprs.last() {
14151 for e in &exprs[..exprs.len() - 1] {
14153 self.eval_expr(e)?;
14154 }
14155 return self.eval_expr(last);
14156 } else {
14157 return Ok(StrykeValue::UNDEF);
14158 }
14159 }
14160 let mut vals = Vec::new();
14161 for e in exprs {
14162 let v = self.eval_expr_ctx(e, WantarrayCtx::List)?;
14163 if let Some(items) = v.as_array_vec() {
14164 vals.extend(items);
14165 } else {
14166 vals.push(v);
14167 }
14168 }
14169 if vals.len() == 1 {
14170 Ok(vals.pop().unwrap())
14171 } else {
14172 Ok(StrykeValue::array(vals))
14173 }
14174 }
14175
14176 ExprKind::PostfixIf { expr, condition } => {
14178 if self.eval_postfix_condition(condition)? {
14179 self.eval_expr(expr)
14180 } else {
14181 Ok(StrykeValue::UNDEF)
14182 }
14183 }
14184 ExprKind::PostfixUnless { expr, condition } => {
14185 if !self.eval_postfix_condition(condition)? {
14186 self.eval_expr(expr)
14187 } else {
14188 Ok(StrykeValue::UNDEF)
14189 }
14190 }
14191 ExprKind::PostfixWhile { expr, condition } => {
14192 let is_do_block = matches!(
14195 &expr.kind,
14196 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
14197 );
14198 let mut last = StrykeValue::UNDEF;
14199 if is_do_block {
14200 loop {
14201 last = self.eval_expr(expr)?;
14202 if !self.eval_postfix_condition(condition)? {
14203 break;
14204 }
14205 }
14206 } else {
14207 loop {
14208 if !self.eval_postfix_condition(condition)? {
14209 break;
14210 }
14211 last = self.eval_expr(expr)?;
14212 }
14213 }
14214 Ok(last)
14215 }
14216 ExprKind::PostfixUntil { expr, condition } => {
14217 let is_do_block = matches!(
14218 &expr.kind,
14219 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
14220 );
14221 let mut last = StrykeValue::UNDEF;
14222 if is_do_block {
14223 loop {
14224 last = self.eval_expr(expr)?;
14225 if self.eval_postfix_condition(condition)? {
14226 break;
14227 }
14228 }
14229 } else {
14230 loop {
14231 if self.eval_postfix_condition(condition)? {
14232 break;
14233 }
14234 last = self.eval_expr(expr)?;
14235 }
14236 }
14237 Ok(last)
14238 }
14239 ExprKind::PostfixForeach { expr, list } => {
14240 let items = self.eval_expr_ctx(list, WantarrayCtx::List)?.to_list();
14241 let mut last = StrykeValue::UNDEF;
14242 for item in items {
14243 self.scope.set_topic(item);
14244 last = self.eval_expr(expr)?;
14245 }
14246 Ok(last)
14247 }
14248 }
14249 }
14250
14251 fn overload_key_for_binop(op: BinOp) -> Option<&'static str> {
14254 match op {
14255 BinOp::Add => Some("+"),
14256 BinOp::Sub => Some("-"),
14257 BinOp::Mul => Some("*"),
14258 BinOp::Div => Some("/"),
14259 BinOp::Mod => Some("%"),
14260 BinOp::Pow => Some("**"),
14261 BinOp::Concat => Some("."),
14262 BinOp::StrEq => Some("eq"),
14263 BinOp::NumEq => Some("=="),
14264 BinOp::StrNe => Some("ne"),
14265 BinOp::NumNe => Some("!="),
14266 BinOp::StrLt => Some("lt"),
14267 BinOp::StrGt => Some("gt"),
14268 BinOp::StrLe => Some("le"),
14269 BinOp::StrGe => Some("ge"),
14270 BinOp::NumLt => Some("<"),
14271 BinOp::NumGt => Some(">"),
14272 BinOp::NumLe => Some("<="),
14273 BinOp::NumGe => Some(">="),
14274 BinOp::Spaceship => Some("<=>"),
14275 BinOp::StrCmp => Some("cmp"),
14276 _ => None,
14277 }
14278 }
14279
14280 fn overload_stringify_method(map: &HashMap<String, String>) -> Option<&String> {
14282 map.get("").or_else(|| map.get("\"\""))
14283 }
14284
14285 pub(crate) fn stringify_value(
14287 &mut self,
14288 v: StrykeValue,
14289 line: usize,
14290 ) -> Result<String, FlowOrError> {
14291 if let Some(r) = self.try_overload_stringify(&v, line) {
14292 let pv = r?;
14293 return Ok(pv.to_string());
14294 }
14295 Ok(v.to_string())
14296 }
14297
14298 pub(crate) fn perl_sprintf_stringify(
14300 &mut self,
14301 fmt: &str,
14302 args: &[StrykeValue],
14303 line: usize,
14304 ) -> Result<String, FlowOrError> {
14305 let (out, pending_n) = {
14307 let mut stringify = |v: &StrykeValue| -> Result<String, FlowOrError> {
14308 self.stringify_value(v.clone(), line)
14309 };
14310 perl_sprintf_format_full(fmt, args, &mut stringify)?
14311 };
14312 for (target, count) in pending_n {
14314 self.assign_scalar_ref_deref(target, StrykeValue::integer(count), line)?;
14315 }
14316 Ok(out)
14317 }
14318
14319 pub(crate) fn render_format_template(
14321 &mut self,
14322 tmpl: &crate::format::FormatTemplate,
14323 line: usize,
14324 ) -> Result<String, FlowOrError> {
14325 use crate::format::{FormatRecord, PictureSegment};
14326 let mut buf = String::new();
14327 for rec in &tmpl.records {
14328 match rec {
14329 FormatRecord::Literal(s) => {
14330 buf.push_str(s);
14331 buf.push('\n');
14332 }
14333 FormatRecord::Picture { segments, exprs } => {
14334 let mut vals: Vec<String> = Vec::new();
14335 for e in exprs {
14336 let v = self.eval_expr(e)?;
14337 vals.push(self.stringify_value(v, line)?);
14338 }
14339 let mut vi = 0usize;
14340 let mut line_out = String::new();
14341 for seg in segments {
14342 match seg {
14343 PictureSegment::Literal(t) => line_out.push_str(t),
14344 PictureSegment::Field {
14345 width,
14346 align,
14347 kind: _,
14348 } => {
14349 let s = vals.get(vi).map(|s| s.as_str()).unwrap_or("");
14350 vi += 1;
14351 line_out.push_str(&crate::format::pad_field(s, *width, *align));
14352 }
14353 }
14354 }
14355 buf.push_str(line_out.trim_end());
14356 buf.push('\n');
14357 }
14358 }
14359 }
14360 Ok(buf)
14361 }
14362
14363 pub(crate) fn resolve_write_output_handle(
14365 &self,
14366 v: &StrykeValue,
14367 line: usize,
14368 ) -> StrykeResult<String> {
14369 if let Some(n) = v.as_io_handle_name() {
14370 let n = self.resolve_io_handle_name(&n);
14371 if self.is_bound_handle(&n) {
14372 return Ok(n);
14373 }
14374 }
14375 if let Some(s) = v.as_str() {
14376 if self.is_bound_handle(&s) {
14377 return Ok(self.resolve_io_handle_name(&s));
14378 }
14379 }
14380 let s = v.to_string();
14381 if self.is_bound_handle(&s) {
14382 return Ok(self.resolve_io_handle_name(&s));
14383 }
14384 Err(StrykeError::runtime(
14385 format!("write: invalid or unopened filehandle {}", s),
14386 line,
14387 ))
14388 }
14389
14390 pub(crate) fn write_format_execute(
14394 &mut self,
14395 args: &[StrykeValue],
14396 line: usize,
14397 ) -> StrykeResult<StrykeValue> {
14398 let handle_name = match args.len() {
14399 0 => self.default_print_handle.clone(),
14400 1 => self.resolve_write_output_handle(&args[0], line)?,
14401 _ => {
14402 return Err(StrykeError::runtime("write: too many arguments", line));
14403 }
14404 };
14405 let pkg = self.current_package();
14406 let mut fmt_name = self.scope.get_scalar("~").to_string();
14407 if fmt_name.is_empty() {
14408 fmt_name = "STDOUT".to_string();
14409 }
14410 let key = format!("{}::{}", pkg, fmt_name);
14411 let tmpl = self
14412 .format_templates
14413 .get(&key)
14414 .map(Arc::clone)
14415 .ok_or_else(|| {
14416 StrykeError::runtime(
14417 format!("Unknown format `{}` in package `{}`", fmt_name, pkg),
14418 line,
14419 )
14420 })?;
14421 let out = self
14422 .render_format_template(&tmpl, line)
14423 .map_err(|e| match e {
14424 FlowOrError::Error(e) => e,
14425 FlowOrError::Flow(_) => {
14426 StrykeError::runtime("write: unexpected control flow", line)
14427 }
14428 })?;
14429 self.write_formatted_print(handle_name.as_str(), &out, line)?;
14430 Ok(StrykeValue::integer(1))
14431 }
14432
14433 pub(crate) fn try_overload_stringify(
14434 &mut self,
14435 v: &StrykeValue,
14436 line: usize,
14437 ) -> Option<ExecResult> {
14438 if let Some(c) = v.as_class_inst() {
14440 let method_name = c
14441 .def
14442 .method("stringify")
14443 .or_else(|| c.def.method("\"\""))
14444 .filter(|m| m.body.is_some())?;
14445 let body = method_name.body.clone().unwrap();
14446 let params = method_name.params.clone();
14447 return Some(self.call_class_method(&body, ¶ms, vec![v.clone()], line));
14448 }
14449 let br = v.as_blessed_ref()?;
14450 let class = br.class.clone();
14451 let map = self.overload_table.get(&class)?;
14452 let sub_short = Self::overload_stringify_method(map)?;
14453 let fq = format!("{}::{}", class, sub_short);
14454 let sub = self.subs.get(&fq)?.clone();
14455 Some(self.call_sub(&sub, vec![v.clone()], WantarrayCtx::Scalar, line))
14456 }
14457
14458 fn overload_method_name_for_key(key: &str) -> Option<&'static str> {
14460 match key {
14461 "+" => Some("op_add"),
14462 "-" => Some("op_sub"),
14463 "*" => Some("op_mul"),
14464 "/" => Some("op_div"),
14465 "%" => Some("op_mod"),
14466 "**" => Some("op_pow"),
14467 "." => Some("op_concat"),
14468 "==" => Some("op_eq"),
14469 "!=" => Some("op_ne"),
14470 "<" => Some("op_lt"),
14471 ">" => Some("op_gt"),
14472 "<=" => Some("op_le"),
14473 ">=" => Some("op_ge"),
14474 "<=>" => Some("op_spaceship"),
14475 "eq" => Some("op_str_eq"),
14476 "ne" => Some("op_str_ne"),
14477 "lt" => Some("op_str_lt"),
14478 "gt" => Some("op_str_gt"),
14479 "le" => Some("op_str_le"),
14480 "ge" => Some("op_str_ge"),
14481 "cmp" => Some("op_cmp"),
14482 _ => None,
14483 }
14484 }
14485
14486 pub(crate) fn try_overload_binop(
14487 &mut self,
14488 op: BinOp,
14489 lv: &StrykeValue,
14490 rv: &StrykeValue,
14491 line: usize,
14492 ) -> Option<ExecResult> {
14493 let key = Self::overload_key_for_binop(op)?;
14494 let (ci_def, invocant, other) = if let Some(c) = lv.as_class_inst() {
14496 (Some(c.def.clone()), lv.clone(), rv.clone())
14497 } else if let Some(c) = rv.as_class_inst() {
14498 (Some(c.def.clone()), rv.clone(), lv.clone())
14499 } else {
14500 (None, lv.clone(), rv.clone())
14501 };
14502 if let Some(ref def) = ci_def {
14503 if let Some(method_name) = Self::overload_method_name_for_key(key) {
14504 if let Some((m, _)) = self.find_class_method(def, method_name) {
14505 if let Some(ref body) = m.body {
14506 let params = m.params.clone();
14507 return Some(self.call_class_method(
14508 body,
14509 ¶ms,
14510 vec![invocant, other],
14511 line,
14512 ));
14513 }
14514 }
14515 }
14516 }
14517 let (class, invocant, other) = if let Some(br) = lv.as_blessed_ref() {
14519 (br.class.clone(), lv.clone(), rv.clone())
14520 } else if let Some(br) = rv.as_blessed_ref() {
14521 (br.class.clone(), rv.clone(), lv.clone())
14522 } else {
14523 return None;
14524 };
14525 let map = self.overload_table.get(&class)?;
14526 let sub_short = if let Some(s) = map.get(key) {
14527 s.clone()
14528 } else if let Some(nm) = map.get("nomethod") {
14529 let fq = format!("{}::{}", class, nm);
14530 let sub = self.subs.get(&fq)?.clone();
14531 return Some(self.call_sub(
14532 &sub,
14533 vec![invocant, other, StrykeValue::string(key.to_string())],
14534 WantarrayCtx::Scalar,
14535 line,
14536 ));
14537 } else {
14538 return None;
14539 };
14540 let fq = format!("{}::{}", class, sub_short);
14541 let sub = self.subs.get(&fq)?.clone();
14542 Some(self.call_sub(&sub, vec![invocant, other], WantarrayCtx::Scalar, line))
14543 }
14544
14545 pub(crate) fn try_overload_unary_dispatch(
14547 &mut self,
14548 op_key: &str,
14549 val: &StrykeValue,
14550 line: usize,
14551 ) -> Option<ExecResult> {
14552 if let Some(c) = val.as_class_inst() {
14554 let method_name = match op_key {
14555 "neg" => "op_neg",
14556 "bool" => "op_bool",
14557 "abs" => "op_abs",
14558 "0+" => "op_numify",
14559 _ => return None,
14560 };
14561 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
14562 if let Some(ref body) = m.body {
14563 let params = m.params.clone();
14564 return Some(self.call_class_method(body, ¶ms, vec![val.clone()], line));
14565 }
14566 }
14567 return None;
14568 }
14569 let br = val.as_blessed_ref()?;
14571 let class = br.class.clone();
14572 let map = self.overload_table.get(&class)?;
14573 if let Some(s) = map.get(op_key) {
14574 let fq = format!("{}::{}", class, s);
14575 let sub = self.subs.get(&fq)?.clone();
14576 return Some(self.call_sub(&sub, vec![val.clone()], WantarrayCtx::Scalar, line));
14577 }
14578 if let Some(nm) = map.get("nomethod") {
14579 let fq = format!("{}::{}", class, nm);
14580 let sub = self.subs.get(&fq)?.clone();
14581 return Some(self.call_sub(
14582 &sub,
14583 vec![val.clone(), StrykeValue::string(op_key.to_string())],
14584 WantarrayCtx::Scalar,
14585 line,
14586 ));
14587 }
14588 None
14589 }
14590
14591 #[inline]
14592 fn eval_binop(
14593 &mut self,
14594 op: BinOp,
14595 lv: &StrykeValue,
14596 rv: &StrykeValue,
14597 _line: usize,
14598 ) -> ExecResult {
14599 Ok(match op {
14600 BinOp::Add => {
14603 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14604 StrykeValue::integer(a.wrapping_add(b))
14605 } else {
14606 StrykeValue::float(lv.to_number() + rv.to_number())
14607 }
14608 }
14609 BinOp::Sub => {
14610 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14611 StrykeValue::integer(a.wrapping_sub(b))
14612 } else {
14613 StrykeValue::float(lv.to_number() - rv.to_number())
14614 }
14615 }
14616 BinOp::Mul => {
14617 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14618 StrykeValue::integer(a.wrapping_mul(b))
14619 } else {
14620 StrykeValue::float(lv.to_number() * rv.to_number())
14621 }
14622 }
14623 BinOp::Div => {
14624 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14625 if b == 0 {
14626 return Err(StrykeError::division_by_zero(
14627 "Illegal division by zero",
14628 _line,
14629 )
14630 .into());
14631 }
14632 if a % b == 0 {
14633 StrykeValue::integer(a / b)
14634 } else {
14635 StrykeValue::float(a as f64 / b as f64)
14636 }
14637 } else {
14638 let d = rv.to_number();
14639 if d == 0.0 {
14640 return Err(StrykeError::division_by_zero(
14641 "Illegal division by zero",
14642 _line,
14643 )
14644 .into());
14645 }
14646 StrykeValue::float(lv.to_number() / d)
14647 }
14648 }
14649 BinOp::Mod => {
14650 let d = rv.to_int();
14651 if d == 0 {
14652 return Err(StrykeError::division_by_zero("Illegal modulus zero", _line).into());
14653 }
14654 StrykeValue::integer(crate::value::perl_mod_i64(lv.to_int(), d))
14655 }
14656 BinOp::Pow => {
14657 if crate::compat_mode() || crate::bigint_pragma() {
14661 crate::value::compat_pow(lv, rv)
14662 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14663 let int_pow = (b >= 0)
14664 .then(|| u32::try_from(b).ok())
14665 .flatten()
14666 .and_then(|bu| a.checked_pow(bu))
14667 .map(StrykeValue::integer);
14668 int_pow
14669 .unwrap_or_else(|| StrykeValue::float(lv.to_number().powf(rv.to_number())))
14670 } else {
14671 StrykeValue::float(lv.to_number().powf(rv.to_number()))
14672 }
14673 }
14674 BinOp::Concat => {
14675 let mut s = String::new();
14676 lv.append_to(&mut s);
14677 rv.append_to(&mut s);
14678 StrykeValue::string(s)
14679 }
14680 BinOp::NumEq => {
14681 if let (Some(a), Some(b)) = (lv.as_struct_inst(), rv.as_struct_inst()) {
14683 if a.def.name != b.def.name {
14684 StrykeValue::integer(0)
14685 } else {
14686 let av = a.get_values();
14687 let bv = b.get_values();
14688 let eq = av.len() == bv.len()
14689 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y));
14690 StrykeValue::integer(if eq { 1 } else { 0 })
14691 }
14692 } else if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14693 StrykeValue::integer(if a == b { 1 } else { 0 })
14694 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
14695 StrykeValue::integer(if lv.to_string() == rv.to_string() {
14701 1
14702 } else {
14703 0
14704 })
14705 } else {
14706 StrykeValue::integer(if lv.to_number() == rv.to_number() {
14707 1
14708 } else {
14709 0
14710 })
14711 }
14712 }
14713 BinOp::NumNe => {
14714 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14715 StrykeValue::integer(if a != b { 1 } else { 0 })
14716 } else if !crate::compat_mode() && both_non_numeric_strings_iv(lv, rv) {
14717 StrykeValue::integer(if lv.to_string() != rv.to_string() {
14718 1
14719 } else {
14720 0
14721 })
14722 } else {
14723 StrykeValue::integer(if lv.to_number() != rv.to_number() {
14724 1
14725 } else {
14726 0
14727 })
14728 }
14729 }
14730 BinOp::NumLt => {
14731 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14732 StrykeValue::integer(if a < b { 1 } else { 0 })
14733 } else {
14734 StrykeValue::integer(if lv.to_number() < rv.to_number() {
14735 1
14736 } else {
14737 0
14738 })
14739 }
14740 }
14741 BinOp::NumGt => {
14742 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14743 StrykeValue::integer(if a > b { 1 } else { 0 })
14744 } else {
14745 StrykeValue::integer(if lv.to_number() > rv.to_number() {
14746 1
14747 } else {
14748 0
14749 })
14750 }
14751 }
14752 BinOp::NumLe => {
14753 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14754 StrykeValue::integer(if a <= b { 1 } else { 0 })
14755 } else {
14756 StrykeValue::integer(if lv.to_number() <= rv.to_number() {
14757 1
14758 } else {
14759 0
14760 })
14761 }
14762 }
14763 BinOp::NumGe => {
14764 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14765 StrykeValue::integer(if a >= b { 1 } else { 0 })
14766 } else {
14767 StrykeValue::integer(if lv.to_number() >= rv.to_number() {
14768 1
14769 } else {
14770 0
14771 })
14772 }
14773 }
14774 BinOp::Spaceship => {
14775 if let (Some(a), Some(b)) = (lv.as_integer(), rv.as_integer()) {
14776 StrykeValue::integer(if a < b {
14777 -1
14778 } else if a > b {
14779 1
14780 } else {
14781 0
14782 })
14783 } else {
14784 let a = lv.to_number();
14785 let b = rv.to_number();
14786 StrykeValue::integer(if a < b {
14787 -1
14788 } else if a > b {
14789 1
14790 } else {
14791 0
14792 })
14793 }
14794 }
14795 BinOp::StrEq => StrykeValue::integer(if lv.to_string() == rv.to_string() {
14796 1
14797 } else {
14798 0
14799 }),
14800 BinOp::StrNe => StrykeValue::integer(if lv.to_string() != rv.to_string() {
14801 1
14802 } else {
14803 0
14804 }),
14805 BinOp::StrLt => StrykeValue::integer(if lv.to_string() < rv.to_string() {
14806 1
14807 } else {
14808 0
14809 }),
14810 BinOp::StrGt => StrykeValue::integer(if lv.to_string() > rv.to_string() {
14811 1
14812 } else {
14813 0
14814 }),
14815 BinOp::StrLe => StrykeValue::integer(if lv.to_string() <= rv.to_string() {
14816 1
14817 } else {
14818 0
14819 }),
14820 BinOp::StrGe => StrykeValue::integer(if lv.to_string() >= rv.to_string() {
14821 1
14822 } else {
14823 0
14824 }),
14825 BinOp::StrCmp => {
14826 let cmp = lv.to_string().cmp(&rv.to_string());
14827 StrykeValue::integer(match cmp {
14828 std::cmp::Ordering::Less => -1,
14829 std::cmp::Ordering::Greater => 1,
14830 std::cmp::Ordering::Equal => 0,
14831 })
14832 }
14833 BinOp::BitAnd => {
14834 if let Some(s) = crate::value::set_intersection(lv, rv) {
14835 s
14836 } else {
14837 StrykeValue::integer(lv.to_int() & rv.to_int())
14838 }
14839 }
14840 BinOp::BitOr => {
14841 if let Some(s) = crate::value::set_union(lv, rv) {
14842 s
14843 } else {
14844 StrykeValue::integer(lv.to_int() | rv.to_int())
14845 }
14846 }
14847 BinOp::BitXor => StrykeValue::integer(lv.to_int() ^ rv.to_int()),
14848 BinOp::ShiftLeft => StrykeValue::integer(perl_shl_i64(lv.to_int(), rv.to_int())),
14849 BinOp::ShiftRight => StrykeValue::integer(perl_shr_i64(lv.to_int(), rv.to_int())),
14850 BinOp::LogAnd
14852 | BinOp::LogOr
14853 | BinOp::DefinedOr
14854 | BinOp::LogAndWord
14855 | BinOp::LogOrWord => unreachable!(),
14856 BinOp::BindMatch | BinOp::BindNotMatch => {
14857 unreachable!("regex bind handled in eval_expr BinOp arm")
14858 }
14859 })
14860 }
14861
14862 fn err_modify_symbolic_aggregate_deref_inc_dec(
14866 kind: Sigil,
14867 is_pre: bool,
14868 is_inc: bool,
14869 line: usize,
14870 ) -> FlowOrError {
14871 let agg = match kind {
14872 Sigil::Array => "array",
14873 Sigil::Hash => "hash",
14874 _ => unreachable!("expected symbolic @{{}} or %{{}} deref"),
14875 };
14876 let op = match (is_pre, is_inc) {
14877 (true, true) => "preincrement (++)",
14878 (true, false) => "predecrement (--)",
14879 (false, true) => "postincrement (++)",
14880 (false, false) => "postdecrement (--)",
14881 };
14882 FlowOrError::Error(StrykeError::runtime(
14883 format!("Can't modify {agg} dereference in {op}"),
14884 line,
14885 ))
14886 }
14887
14888 pub(crate) fn symbolic_scalar_ref_postfix(
14890 &mut self,
14891 ref_val: StrykeValue,
14892 decrement: bool,
14893 line: usize,
14894 ) -> Result<StrykeValue, FlowOrError> {
14895 let old = self.symbolic_deref(ref_val.clone(), Sigil::Scalar, line)?;
14896 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
14897 self.assign_scalar_ref_deref(ref_val, new_val, line)?;
14898 Ok(old)
14899 }
14900
14901 pub(crate) fn assign_scalar_ref_deref(
14904 &mut self,
14905 ref_val: StrykeValue,
14906 val: StrykeValue,
14907 line: usize,
14908 ) -> ExecResult {
14909 if let Some(name) = ref_val.as_scalar_binding_name() {
14910 self.set_special_var(&name, &val)
14911 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14912 return Ok(StrykeValue::UNDEF);
14913 }
14914 if let Some(r) = ref_val.as_scalar_ref() {
14915 *r.write() = val;
14916 return Ok(StrykeValue::UNDEF);
14917 }
14918 if ref_val.is_integer_like() || ref_val.is_float_like() || ref_val.is_string_like() {
14921 let s = ref_val.to_string();
14922 if self.strict_refs {
14923 return Err(StrykeError::runtime(
14924 format!(
14925 "Can't use string (\"{}\") as a SCALAR ref while \"strict refs\" in use",
14926 s
14927 ),
14928 line,
14929 )
14930 .into());
14931 }
14932 self.set_special_var(&s, &val)
14933 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14934 return Ok(StrykeValue::UNDEF);
14935 }
14936 Err(StrykeError::runtime("Can't assign to non-scalar reference", line).into())
14937 }
14938
14939 pub(crate) fn assign_symbolic_array_ref_deref(
14941 &mut self,
14942 ref_val: StrykeValue,
14943 val: StrykeValue,
14944 line: usize,
14945 ) -> ExecResult {
14946 if let Some(a) = ref_val.as_array_ref() {
14947 *a.write() = val.to_list();
14948 return Ok(StrykeValue::UNDEF);
14949 }
14950 if let Some(name) = ref_val.as_array_binding_name() {
14951 self.scope
14952 .set_array(&name, val.to_list())
14953 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14954 return Ok(StrykeValue::UNDEF);
14955 }
14956 if let Some(s) = ref_val.as_str() {
14957 if self.strict_refs {
14958 return Err(StrykeError::runtime(
14959 format!(
14960 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
14961 s
14962 ),
14963 line,
14964 )
14965 .into());
14966 }
14967 self.scope
14968 .set_array(&s, val.to_list())
14969 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
14970 return Ok(StrykeValue::UNDEF);
14971 }
14972 Err(StrykeError::runtime("Can't assign to non-array reference", line).into())
14973 }
14974
14975 pub(crate) fn assign_symbolic_typeglob_ref_deref(
14978 &mut self,
14979 ref_val: StrykeValue,
14980 val: StrykeValue,
14981 line: usize,
14982 ) -> ExecResult {
14983 let lhs_name = if let Some(s) = ref_val.as_str() {
14984 if self.strict_refs {
14985 return Err(StrykeError::runtime(
14986 format!(
14987 "Can't use string (\"{}\") as a symbol ref while \"strict refs\" in use",
14988 s
14989 ),
14990 line,
14991 )
14992 .into());
14993 }
14994 s.to_string()
14995 } else {
14996 return Err(
14997 StrykeError::runtime("Can't assign to non-glob symbolic reference", line).into(),
14998 );
14999 };
15000 let is_coderef = val.as_code_ref().is_some()
15001 || val
15002 .as_scalar_ref()
15003 .map(|r| r.read().as_code_ref().is_some())
15004 .unwrap_or(false);
15005 if is_coderef {
15006 return self.assign_typeglob_value(&lhs_name, val, line);
15007 }
15008 let rhs_key = val.to_string();
15009 self.copy_typeglob_slots(&lhs_name, &rhs_key, line)
15010 .map_err(FlowOrError::Error)?;
15011 Ok(StrykeValue::UNDEF)
15012 }
15013
15014 pub(crate) fn assign_symbolic_hash_ref_deref(
15016 &mut self,
15017 ref_val: StrykeValue,
15018 val: StrykeValue,
15019 line: usize,
15020 ) -> ExecResult {
15021 let items = val.to_list();
15022 let mut map = IndexMap::new();
15023 let mut i = 0;
15024 while i + 1 < items.len() {
15025 map.insert(items[i].to_string(), items[i + 1].clone());
15026 i += 2;
15027 }
15028 if let Some(h) = ref_val.as_hash_ref() {
15029 *h.write() = map;
15030 return Ok(StrykeValue::UNDEF);
15031 }
15032 if let Some(name) = ref_val.as_hash_binding_name() {
15033 self.touch_env_hash(&name);
15034 self.scope
15035 .set_hash(&name, map)
15036 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15037 return Ok(StrykeValue::UNDEF);
15038 }
15039 if let Some(s) = ref_val.as_str() {
15040 if self.strict_refs {
15041 return Err(StrykeError::runtime(
15042 format!(
15043 "Can't use string (\"{}\") as a HASH ref while \"strict refs\" in use",
15044 s
15045 ),
15046 line,
15047 )
15048 .into());
15049 }
15050 self.touch_env_hash(&s);
15051 self.scope
15052 .set_hash(&s, map)
15053 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15054 return Ok(StrykeValue::UNDEF);
15055 }
15056 Err(StrykeError::runtime("Can't assign to non-hash reference", line).into())
15057 }
15058
15059 pub(crate) fn assign_arrow_hash_deref(
15061 &mut self,
15062 container: StrykeValue,
15063 key: String,
15064 val: StrykeValue,
15065 line: usize,
15066 ) -> ExecResult {
15067 if let Some(b) = container.as_blessed_ref() {
15068 let mut data = b.data.write();
15069 if let Some(r) = data.as_hash_ref() {
15070 r.write().insert(key, val);
15071 return Ok(StrykeValue::UNDEF);
15072 }
15073 if let Some(mut map) = data.as_hash_map() {
15074 map.insert(key, val);
15075 *data = StrykeValue::hash(map);
15076 return Ok(StrykeValue::UNDEF);
15077 }
15078 return Err(
15079 StrykeError::runtime("Can't assign into non-hash blessed ref", line).into(),
15080 );
15081 }
15082 if let Some(r) = container.as_hash_ref() {
15083 r.write().insert(key, val);
15084 return Ok(StrykeValue::UNDEF);
15085 }
15086 if let Some(name) = container.as_hash_binding_name() {
15087 self.touch_env_hash(&name);
15088 self.scope
15089 .set_hash_element(&name, &key, val)
15090 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15091 return Ok(StrykeValue::UNDEF);
15092 }
15093 Err(StrykeError::runtime("Can't assign to arrow hash deref on non-hash(-ref)", line).into())
15094 }
15095
15096 pub(crate) fn eval_arrow_array_base(
15099 &mut self,
15100 expr: &Expr,
15101 _line: usize,
15102 ) -> Result<StrykeValue, FlowOrError> {
15103 match &expr.kind {
15104 ExprKind::Deref {
15105 expr: inner,
15106 kind: Sigil::Array | Sigil::Scalar,
15107 } => self.eval_expr(inner),
15108 _ => self.eval_expr(expr),
15109 }
15110 }
15111
15112 pub(crate) fn eval_arrow_hash_base(
15114 &mut self,
15115 expr: &Expr,
15116 _line: usize,
15117 ) -> Result<StrykeValue, FlowOrError> {
15118 match &expr.kind {
15119 ExprKind::Deref {
15120 expr: inner,
15121 kind: Sigil::Scalar,
15122 } => self.eval_expr(inner),
15123 _ => self.eval_expr(expr),
15124 }
15125 }
15126
15127 pub(crate) fn read_arrow_array_element(
15129 &self,
15130 container: StrykeValue,
15131 idx: i64,
15132 line: usize,
15133 ) -> Result<StrykeValue, FlowOrError> {
15134 if let Some(a) = container.as_array_ref() {
15135 let arr = a.read();
15136 let i = if idx < 0 {
15137 (arr.len() as i64 + idx) as usize
15138 } else {
15139 idx as usize
15140 };
15141 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
15142 }
15143 if let Some(name) = container.as_array_binding_name() {
15144 return Ok(self.scope.get_array_element(&name, idx));
15145 }
15146 if let Some(arr) = container.as_array_vec() {
15147 let i = if idx < 0 {
15148 (arr.len() as i64 + idx) as usize
15149 } else {
15150 idx as usize
15151 };
15152 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
15153 }
15154 if let Some(b) = container.as_blessed_ref() {
15157 let inner = b.data.read().clone();
15158 if let Some(a) = inner.as_array_ref() {
15159 let arr = a.read();
15160 let i = if idx < 0 {
15161 (arr.len() as i64 + idx) as usize
15162 } else {
15163 idx as usize
15164 };
15165 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
15166 }
15167 }
15168 Err(StrykeError::runtime("Can't use arrow deref on non-array-ref", line).into())
15169 }
15170
15171 pub(crate) fn read_arrow_hash_element(
15173 &mut self,
15174 container: StrykeValue,
15175 key: &str,
15176 line: usize,
15177 ) -> Result<StrykeValue, FlowOrError> {
15178 if let Some(r) = container.as_hash_ref() {
15179 let h = r.read();
15180 return Ok(h.get(key).cloned().unwrap_or(StrykeValue::UNDEF));
15181 }
15182 if let Some(name) = container.as_hash_binding_name() {
15183 self.touch_env_hash(&name);
15184 return Ok(self.scope.get_hash_element(&name, key));
15185 }
15186 if let Some(b) = container.as_blessed_ref() {
15187 let data = b.data.read();
15188 if let Some(v) = data.hash_get(key) {
15189 return Ok(v);
15190 }
15191 if let Some(r) = data.as_hash_ref() {
15192 let h = r.read();
15193 return Ok(h.get(key).cloned().unwrap_or(StrykeValue::UNDEF));
15194 }
15195 return Err(StrykeError::runtime(
15196 "Can't access hash field on non-hash blessed ref",
15197 line,
15198 )
15199 .into());
15200 }
15201 if let Some(s) = container.as_struct_inst() {
15203 if let Some(idx) = s.def.field_index(key) {
15204 return Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF));
15205 }
15206 return Err(StrykeError::runtime(
15207 format!("struct {} has no field `{}`", s.def.name, key),
15208 line,
15209 )
15210 .into());
15211 }
15212 if let Some(c) = container.as_class_inst() {
15214 if let Some(idx) = c.def.field_index(key) {
15215 return Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF));
15216 }
15217 return Err(StrykeError::runtime(
15218 format!("class {} has no field `{}`", c.def.name, key),
15219 line,
15220 )
15221 .into());
15222 }
15223 Err(StrykeError::runtime("Can't use arrow deref on non-hash-ref", line).into())
15224 }
15225
15226 pub(crate) fn arrow_array_postfix(
15228 &mut self,
15229 container: StrykeValue,
15230 idx: i64,
15231 decrement: bool,
15232 line: usize,
15233 ) -> Result<StrykeValue, FlowOrError> {
15234 let old = self.read_arrow_array_element(container.clone(), idx, line)?;
15235 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
15236 self.assign_arrow_array_deref(container, idx, new_val, line)?;
15237 Ok(old)
15238 }
15239
15240 pub(crate) fn arrow_hash_postfix(
15242 &mut self,
15243 container: StrykeValue,
15244 key: String,
15245 decrement: bool,
15246 line: usize,
15247 ) -> Result<StrykeValue, FlowOrError> {
15248 let old = self.read_arrow_hash_element(container.clone(), key.as_str(), line)?;
15249 let new_val = StrykeValue::integer(old.to_int() + if decrement { -1 } else { 1 });
15250 self.assign_arrow_hash_deref(container, key, new_val, line)?;
15251 Ok(old)
15252 }
15253
15254 pub(crate) fn resolve_bareword_rvalue(
15262 &mut self,
15263 name: &str,
15264 want: WantarrayCtx,
15265 line: usize,
15266 ) -> Result<StrykeValue, FlowOrError> {
15267 if name == "__PACKAGE__" {
15268 return Ok(StrykeValue::string(self.current_package()));
15269 }
15270 if let Some(sub) = self.resolve_sub_by_name(name) {
15271 return self.call_sub(&sub, vec![], want, line);
15272 }
15273 if let Some(r) = crate::builtins::try_builtin(self, name, &[], line) {
15275 return r.map_err(Into::into);
15276 }
15277 Ok(StrykeValue::string(name.to_string()))
15278 }
15279
15280 pub(crate) fn arrow_array_slice_values(
15284 &mut self,
15285 container: StrykeValue,
15286 indices: &[i64],
15287 line: usize,
15288 ) -> Result<StrykeValue, FlowOrError> {
15289 let mut out = Vec::with_capacity(indices.len());
15290 for &idx in indices {
15291 let v = self.read_arrow_array_element(container.clone(), idx, line)?;
15292 out.push(v);
15293 }
15294 Ok(StrykeValue::array(out))
15295 }
15296
15297 pub(crate) fn assign_arrow_array_slice(
15301 &mut self,
15302 container: StrykeValue,
15303 indices: Vec<i64>,
15304 val: StrykeValue,
15305 line: usize,
15306 ) -> Result<StrykeValue, FlowOrError> {
15307 if indices.is_empty() {
15308 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15309 }
15310 let vals = val.to_list();
15311 for (i, idx) in indices.iter().enumerate() {
15312 let v = vals.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15313 self.assign_arrow_array_deref(container.clone(), *idx, v, line)?;
15314 }
15315 Ok(StrykeValue::UNDEF)
15316 }
15317
15318 pub(crate) fn flatten_array_slice_index_specs(
15320 &mut self,
15321 indices: &[Expr],
15322 ) -> Result<Vec<i64>, FlowOrError> {
15323 let mut out = Vec::new();
15324 for idx_expr in indices {
15325 let v = if matches!(
15326 idx_expr.kind,
15327 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
15328 ) {
15329 self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?
15330 } else {
15331 self.eval_expr(idx_expr)?
15332 };
15333 if let Some(list) = v.as_array_vec() {
15334 for idx in list {
15335 out.push(idx.to_int());
15336 }
15337 } else {
15338 out.push(v.to_int());
15339 }
15340 }
15341 Ok(out)
15342 }
15343
15344 pub(crate) fn assign_named_array_slice(
15346 &mut self,
15347 stash_array_name: &str,
15348 indices: Vec<i64>,
15349 val: StrykeValue,
15350 line: usize,
15351 ) -> Result<StrykeValue, FlowOrError> {
15352 if indices.is_empty() {
15353 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15354 }
15355 let vals = val.to_list();
15356 for (i, idx) in indices.iter().enumerate() {
15357 let v = vals.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15358 self.scope
15359 .set_array_element(stash_array_name, *idx, v)
15360 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15361 }
15362 Ok(StrykeValue::UNDEF)
15363 }
15364
15365 pub(crate) fn compound_assign_arrow_array_slice(
15368 &mut self,
15369 container: StrykeValue,
15370 indices: Vec<i64>,
15371 op: BinOp,
15372 rhs: StrykeValue,
15373 line: usize,
15374 ) -> Result<StrykeValue, FlowOrError> {
15375 if indices.is_empty() {
15376 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15377 }
15378 let last_idx = *indices.last().expect("non-empty indices");
15379 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
15380 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
15381 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
15382 Ok(new_val)
15383 }
15384
15385 pub(crate) fn arrow_array_slice_inc_dec(
15390 &mut self,
15391 container: StrykeValue,
15392 indices: Vec<i64>,
15393 kind: u8,
15394 line: usize,
15395 ) -> Result<StrykeValue, FlowOrError> {
15396 if indices.is_empty() {
15397 return Err(StrykeError::runtime(
15398 "array slice increment needs at least one index",
15399 line,
15400 )
15401 .into());
15402 }
15403 let last_idx = *indices.last().expect("non-empty indices");
15404 let last_old = self.read_arrow_array_element(container.clone(), last_idx, line)?;
15405 let new_val = if kind & 1 == 0 {
15406 StrykeValue::integer(last_old.to_int() + 1)
15407 } else {
15408 StrykeValue::integer(last_old.to_int() - 1)
15409 };
15410 self.assign_arrow_array_deref(container, last_idx, new_val.clone(), line)?;
15411 Ok(if kind < 2 { new_val } else { last_old })
15412 }
15413
15414 pub(crate) fn named_array_slice_inc_dec(
15417 &mut self,
15418 stash_array_name: &str,
15419 indices: Vec<i64>,
15420 kind: u8,
15421 line: usize,
15422 ) -> Result<StrykeValue, FlowOrError> {
15423 let last_idx = *indices.last().ok_or_else(|| {
15424 StrykeError::runtime("array slice increment needs at least one index", line)
15425 })?;
15426 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
15427 let new_val = if kind & 1 == 0 {
15428 StrykeValue::integer(last_old.to_int() + 1)
15429 } else {
15430 StrykeValue::integer(last_old.to_int() - 1)
15431 };
15432 self.scope
15433 .set_array_element(stash_array_name, last_idx, new_val.clone())
15434 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15435 Ok(if kind < 2 { new_val } else { last_old })
15436 }
15437
15438 pub(crate) fn compound_assign_named_array_slice(
15440 &mut self,
15441 stash_array_name: &str,
15442 indices: Vec<i64>,
15443 op: BinOp,
15444 rhs: StrykeValue,
15445 line: usize,
15446 ) -> Result<StrykeValue, FlowOrError> {
15447 if indices.is_empty() {
15448 return Err(StrykeError::runtime("assign to empty array slice", line).into());
15449 }
15450 let last_idx = *indices.last().expect("non-empty indices");
15451 let last_old = self.scope.get_array_element(stash_array_name, last_idx);
15452 let new_val = self.eval_binop(op, &last_old, &rhs, line)?;
15453 self.scope
15454 .set_array_element(stash_array_name, last_idx, new_val.clone())
15455 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15456 Ok(new_val)
15457 }
15458
15459 pub(crate) fn assign_arrow_array_deref(
15461 &mut self,
15462 container: StrykeValue,
15463 idx: i64,
15464 val: StrykeValue,
15465 line: usize,
15466 ) -> ExecResult {
15467 if let Some(a) = container.as_array_ref() {
15468 let mut arr = a.write();
15469 let i = if idx < 0 {
15470 (arr.len() as i64 + idx) as usize
15471 } else {
15472 idx as usize
15473 };
15474 if i >= arr.len() {
15475 arr.resize(i + 1, StrykeValue::UNDEF);
15476 }
15477 arr[i] = val;
15478 return Ok(StrykeValue::UNDEF);
15479 }
15480 if let Some(name) = container.as_array_binding_name() {
15481 self.scope
15482 .set_array_element(&name, idx, val)
15483 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
15484 return Ok(StrykeValue::UNDEF);
15485 }
15486 Err(StrykeError::runtime("Can't assign to arrow array deref on non-array-ref", line).into())
15487 }
15488
15489 pub(crate) fn assign_typeglob_value(
15491 &mut self,
15492 name: &str,
15493 val: StrykeValue,
15494 line: usize,
15495 ) -> ExecResult {
15496 let sub = if let Some(c) = val.as_code_ref() {
15497 Some(c)
15498 } else if let Some(r) = val.as_scalar_ref() {
15499 r.read().as_code_ref().map(|c| Arc::clone(&c))
15500 } else {
15501 None
15502 };
15503 if let Some(sub) = sub {
15504 let lhs_sub = self.qualify_typeglob_sub_key(name);
15505 self.subs.insert(lhs_sub, sub);
15506 return Ok(StrykeValue::UNDEF);
15507 }
15508 Err(StrykeError::runtime(
15509 "typeglob assignment requires a subroutine reference (e.g. *foo = \\&bar) or another typeglob (*foo = *bar)",
15510 line,
15511 )
15512 .into())
15513 }
15514
15515 fn assign_value(&mut self, target: &Expr, val: StrykeValue) -> ExecResult {
15516 match &target.kind {
15517 ExprKind::Substr {
15523 string,
15524 offset,
15525 length,
15526 replacement: None,
15527 } => {
15528 let s = self.eval_expr(string)?.to_string();
15529 let off = self.eval_expr(offset)?.to_int();
15530 let start = if off < 0 {
15531 (s.len() as i64 + off).max(0) as usize
15532 } else {
15533 (off as usize).min(s.len())
15534 };
15535 let len = if let Some(l) = length {
15536 let lv = self.eval_expr(l)?.to_int();
15537 if lv < 0 {
15538 let remaining = s.len().saturating_sub(start) as i64;
15539 (remaining + lv).max(0) as usize
15540 } else {
15541 lv as usize
15542 }
15543 } else {
15544 s.len().saturating_sub(start)
15545 };
15546 let end = start.saturating_add(len).min(s.len());
15547 let mut new_s = String::with_capacity(s.len());
15548 new_s.push_str(&s[..start]);
15549 new_s.push_str(&val.to_string());
15550 new_s.push_str(&s[end..]);
15551 self.assign_value(string, StrykeValue::string(new_s))?;
15552 Ok(StrykeValue::UNDEF)
15553 }
15554 ExprKind::MyExpr { decls, .. } => {
15561 let first = decls.first().ok_or_else(|| {
15562 FlowOrError::Error(StrykeError::runtime(
15563 "assign_value: empty MyExpr decl list",
15564 target.line,
15565 ))
15566 })?;
15567 match first.sigil {
15568 Sigil::Scalar => {
15569 let stor = self.tree_scalar_storage_name(&first.name);
15570 self.set_special_var(&stor, &val)
15571 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
15572 Ok(StrykeValue::UNDEF)
15573 }
15574 Sigil::Array => {
15575 self.scope.set_array(&first.name, val.to_list())?;
15576 Ok(StrykeValue::UNDEF)
15577 }
15578 Sigil::Hash => {
15579 let items = val.to_list();
15580 let mut map = IndexMap::new();
15581 let mut i = 0;
15582 while i + 1 < items.len() {
15583 map.insert(items[i].to_string(), items[i + 1].clone());
15584 i += 2;
15585 }
15586 self.scope.set_hash(&first.name, map)?;
15587 Ok(StrykeValue::UNDEF)
15588 }
15589 Sigil::Typeglob => Ok(StrykeValue::UNDEF),
15590 }
15591 }
15592 ExprKind::ScalarVar(name) => {
15593 let stor = self.tree_scalar_storage_name(name);
15594 if self.scope.is_scalar_frozen(&stor) {
15595 return Err(FlowOrError::Error(StrykeError::runtime(
15596 format!("Modification of a frozen value: ${}", name),
15597 target.line,
15598 )));
15599 }
15600 if let Some(obj) = self.tied_scalars.get(&stor).cloned() {
15601 let class = obj
15602 .as_blessed_ref()
15603 .map(|b| b.class.clone())
15604 .unwrap_or_default();
15605 let full = format!("{}::STORE", class);
15606 if let Some(sub) = self.subs.get(&full).cloned() {
15607 let arg_vals = vec![obj, val];
15608 return match self.call_sub(
15609 &sub,
15610 arg_vals,
15611 WantarrayCtx::Scalar,
15612 target.line,
15613 ) {
15614 Ok(_) => Ok(StrykeValue::UNDEF),
15615 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15616 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15617 };
15618 }
15619 }
15620 self.set_special_var(&stor, &val)
15621 .map_err(|e| FlowOrError::Error(e.at_line(target.line)))?;
15622 Ok(StrykeValue::UNDEF)
15623 }
15624 ExprKind::ArrayVar(name) => {
15625 if self.scope.is_array_frozen(name) {
15626 return Err(StrykeError::runtime(
15627 format!("Modification of a frozen value: @{}", name),
15628 target.line,
15629 )
15630 .into());
15631 }
15632 if self.strict_vars
15633 && !name.contains("::")
15634 && !self.scope.array_binding_exists(name)
15635 {
15636 return Err(StrykeError::runtime(
15637 format!(
15638 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
15639 name, name
15640 ),
15641 target.line,
15642 )
15643 .into());
15644 }
15645 self.scope.set_array(name, val.to_list())?;
15646 Ok(StrykeValue::UNDEF)
15647 }
15648 ExprKind::HashVar(name) => {
15649 if self.strict_vars && !name.contains("::") && !self.scope.hash_binding_exists(name)
15650 {
15651 return Err(StrykeError::runtime(
15652 format!(
15653 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15654 name, name
15655 ),
15656 target.line,
15657 )
15658 .into());
15659 }
15660 let items = val.to_list();
15661 let mut map = IndexMap::new();
15662 let mut i = 0;
15663 while i + 1 < items.len() {
15664 map.insert(items[i].to_string(), items[i + 1].clone());
15665 i += 2;
15666 }
15667 self.scope.set_hash(name, map)?;
15668 Ok(StrykeValue::UNDEF)
15669 }
15670 ExprKind::ArrayElement { array, index } => {
15671 if self.strict_vars
15672 && !array.contains("::")
15673 && !self.scope.array_binding_exists(array)
15674 {
15675 return Err(StrykeError::runtime(
15676 format!(
15677 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
15678 array, array
15679 ),
15680 target.line,
15681 )
15682 .into());
15683 }
15684 if self.scope.is_array_frozen(array) {
15685 return Err(StrykeError::runtime(
15686 format!("Modification of a frozen value: @{}", array),
15687 target.line,
15688 )
15689 .into());
15690 }
15691 let idx = self.eval_expr(index)?.to_int();
15692 let aname = self.stash_array_name_for_package(array);
15693 if let Some(obj) = self.tied_arrays.get(&aname).cloned() {
15694 let class = obj
15695 .as_blessed_ref()
15696 .map(|b| b.class.clone())
15697 .unwrap_or_default();
15698 let full = format!("{}::STORE", class);
15699 if let Some(sub) = self.subs.get(&full).cloned() {
15700 let arg_vals = vec![obj, StrykeValue::integer(idx), val];
15701 return match self.call_sub(
15702 &sub,
15703 arg_vals,
15704 WantarrayCtx::Scalar,
15705 target.line,
15706 ) {
15707 Ok(_) => Ok(StrykeValue::UNDEF),
15708 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15709 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15710 };
15711 }
15712 }
15713 self.scope.set_array_element(&aname, idx, val)?;
15714 Ok(StrykeValue::UNDEF)
15715 }
15716 ExprKind::ArraySlice { array, indices } => {
15717 if indices.is_empty() {
15718 return Err(
15719 StrykeError::runtime("assign to empty array slice", target.line).into(),
15720 );
15721 }
15722 self.check_strict_array_var(array, target.line)?;
15723 if self.scope.is_array_frozen(array) {
15724 return Err(StrykeError::runtime(
15725 format!("Modification of a frozen value: @{}", array),
15726 target.line,
15727 )
15728 .into());
15729 }
15730 let aname = self.stash_array_name_for_package(array);
15731 let flat = self.flatten_array_slice_index_specs(indices)?;
15732 self.assign_named_array_slice(&aname, flat, val, target.line)
15733 }
15734 ExprKind::HashElement { hash, key } => {
15735 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
15736 {
15737 return Err(StrykeError::runtime(
15738 format!(
15739 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15740 hash, hash
15741 ),
15742 target.line,
15743 )
15744 .into());
15745 }
15746 if self.scope.is_hash_frozen(hash) {
15747 return Err(StrykeError::runtime(
15748 format!("Modification of a frozen value: %%{}", hash),
15749 target.line,
15750 )
15751 .into());
15752 }
15753 let k = self.eval_expr(key)?.to_string();
15754 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
15755 let class = obj
15756 .as_blessed_ref()
15757 .map(|b| b.class.clone())
15758 .unwrap_or_default();
15759 let full = format!("{}::STORE", class);
15760 if let Some(sub) = self.subs.get(&full).cloned() {
15761 let arg_vals = vec![obj, StrykeValue::string(k), val];
15762 return match self.call_sub(
15763 &sub,
15764 arg_vals,
15765 WantarrayCtx::Scalar,
15766 target.line,
15767 ) {
15768 Ok(_) => Ok(StrykeValue::UNDEF),
15769 Err(FlowOrError::Flow(_)) => Ok(StrykeValue::UNDEF),
15770 Err(FlowOrError::Error(e)) => Err(FlowOrError::Error(e)),
15771 };
15772 }
15773 }
15774 let hname = self.tree_hash_storage_name(hash);
15775 self.scope.set_hash_element(&hname, &k, val)?;
15776 Ok(StrykeValue::UNDEF)
15777 }
15778 ExprKind::HashSlice { hash, keys } => {
15779 if keys.is_empty() {
15780 return Err(
15781 StrykeError::runtime("assign to empty hash slice", target.line).into(),
15782 );
15783 }
15784 if self.strict_vars && !hash.contains("::") && !self.scope.hash_binding_exists(hash)
15785 {
15786 return Err(StrykeError::runtime(
15787 format!(
15788 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
15789 hash, hash
15790 ),
15791 target.line,
15792 )
15793 .into());
15794 }
15795 if self.scope.is_hash_frozen(hash) {
15796 return Err(StrykeError::runtime(
15797 format!("Modification of a frozen value: %%{}", hash),
15798 target.line,
15799 )
15800 .into());
15801 }
15802 let mut key_vals = Vec::with_capacity(keys.len());
15803 for key_expr in keys {
15804 let v = if matches!(
15805 key_expr.kind,
15806 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
15807 ) {
15808 self.eval_expr_ctx(key_expr, WantarrayCtx::List)?
15809 } else {
15810 self.eval_expr(key_expr)?
15811 };
15812 key_vals.push(v);
15813 }
15814 self.assign_named_hash_slice(hash, key_vals, val, target.line)
15815 }
15816 ExprKind::Typeglob(name) => self.assign_typeglob_value(name, val, target.line),
15817 ExprKind::TypeglobExpr(e) => {
15818 let name = self.eval_expr(e)?.to_string();
15819 let synthetic = Expr {
15820 kind: ExprKind::Typeglob(name),
15821 line: target.line,
15822 };
15823 self.assign_value(&synthetic, val)
15824 }
15825 ExprKind::AnonymousListSlice { source, indices } => {
15826 if let ExprKind::Deref {
15827 expr: inner,
15828 kind: Sigil::Array,
15829 } = &source.kind
15830 {
15831 let container = self.eval_arrow_array_base(inner, target.line)?;
15832 let vals = val.to_list();
15833 let n = indices.len().min(vals.len());
15834 for i in 0..n {
15835 let idx = self.eval_expr(&indices[i])?.to_int();
15836 self.assign_arrow_array_deref(
15837 container.clone(),
15838 idx,
15839 vals[i].clone(),
15840 target.line,
15841 )?;
15842 }
15843 return Ok(StrykeValue::UNDEF);
15844 }
15845 Err(
15846 StrykeError::runtime("assign to list slice: unsupported base", target.line)
15847 .into(),
15848 )
15849 }
15850 ExprKind::ArrowDeref {
15851 expr,
15852 index,
15853 kind: DerefKind::Hash,
15854 } => {
15855 let key = self.eval_expr(index)?.to_string();
15856 let container = self.eval_expr(expr)?;
15857 self.assign_arrow_hash_deref(container, key, val, target.line)
15858 }
15859 ExprKind::ArrowDeref {
15860 expr,
15861 index,
15862 kind: DerefKind::Array,
15863 } => {
15864 let container = self.eval_arrow_array_base(expr, target.line)?;
15865 if let ExprKind::List(indices) = &index.kind {
15866 let vals = val.to_list();
15867 let n = indices.len().min(vals.len());
15868 for i in 0..n {
15869 let idx = self.eval_expr(&indices[i])?.to_int();
15870 self.assign_arrow_array_deref(
15871 container.clone(),
15872 idx,
15873 vals[i].clone(),
15874 target.line,
15875 )?;
15876 }
15877 return Ok(StrykeValue::UNDEF);
15878 }
15879 let idx = self.eval_expr(index)?.to_int();
15880 self.assign_arrow_array_deref(container, idx, val, target.line)
15881 }
15882 ExprKind::HashSliceDeref { container, keys } => {
15883 let href = self.eval_expr(container)?;
15884 let mut key_vals = Vec::with_capacity(keys.len());
15885 for key_expr in keys {
15886 key_vals.push(self.eval_expr(key_expr)?);
15887 }
15888 self.assign_hash_slice_deref(href, key_vals, val, target.line)
15889 }
15890 ExprKind::Deref {
15891 expr,
15892 kind: Sigil::Scalar,
15893 } => {
15894 let ref_val = self.eval_expr(expr)?;
15895 self.assign_scalar_ref_deref(ref_val, val, target.line)
15896 }
15897 ExprKind::Deref {
15898 expr,
15899 kind: Sigil::Array,
15900 } => {
15901 let ref_val = self.eval_expr(expr)?;
15902 self.assign_symbolic_array_ref_deref(ref_val, val, target.line)
15903 }
15904 ExprKind::Deref {
15905 expr,
15906 kind: Sigil::Hash,
15907 } => {
15908 let ref_val = self.eval_expr(expr)?;
15909 self.assign_symbolic_hash_ref_deref(ref_val, val, target.line)
15910 }
15911 ExprKind::Deref {
15912 expr,
15913 kind: Sigil::Typeglob,
15914 } => {
15915 let ref_val = self.eval_expr(expr)?;
15916 self.assign_symbolic_typeglob_ref_deref(ref_val, val, target.line)
15917 }
15918 ExprKind::Pos(inner) => {
15919 let key = match inner {
15920 None => "_".to_string(),
15921 Some(expr) => match &expr.kind {
15922 ExprKind::ScalarVar(n) => n.clone(),
15923 _ => self.eval_expr(expr)?.to_string(),
15924 },
15925 };
15926 if val.is_undef() {
15927 self.regex_pos.insert(key, None);
15928 } else {
15929 let u = val.to_int().max(0) as usize;
15930 self.regex_pos.insert(key, Some(u));
15931 }
15932 Ok(StrykeValue::UNDEF)
15933 }
15934 ExprKind::List(targets) => {
15937 let items = val.to_list();
15938 for (i, t) in targets.iter().enumerate() {
15939 let v = items.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
15940 self.assign_value(t, v)?;
15941 }
15942 Ok(StrykeValue::UNDEF)
15943 }
15944 ExprKind::Assign { target, .. } => self.assign_value(target, val),
15947 _ => Ok(StrykeValue::UNDEF),
15948 }
15949 }
15950
15951 pub(crate) fn is_special_scalar_name_for_get(name: &str) -> bool {
15953 let name = crate::scope::strip_main_prefix(name).unwrap_or(name);
15958 (name.starts_with('#') && name.len() > 1)
15959 || name.starts_with('^')
15960 || matches!(
15961 name,
15962 "$$" | "0"
15963 | "!"
15964 | "@"
15965 | "/"
15966 | "\\"
15967 | ","
15968 | "."
15969 | "]"
15970 | ";"
15971 | "ARGV"
15972 | "^I"
15973 | "^D"
15974 | "^P"
15975 | "^S"
15976 | "^W"
15977 | "^O"
15978 | "^T"
15979 | "^V"
15980 | "^E"
15981 | "^H"
15982 | "^WARNING_BITS"
15983 | "^GLOBAL_PHASE"
15984 | "^MATCH"
15985 | "^PREMATCH"
15986 | "^POSTMATCH"
15987 | "^LAST_SUBMATCH_RESULT"
15988 | "<"
15989 | ">"
15990 | "("
15991 | ")"
15992 | "?"
15993 | "|"
15994 | "\""
15995 | "+"
15996 | "%"
15997 | "="
15998 | "-"
15999 | ":"
16000 | "*"
16001 | "INC"
16002 )
16003 || crate::english::is_known_alias(name)
16004 }
16005
16006 #[inline]
16011 pub(crate) fn resolved_scalar_storage_name(&self, name: &str) -> String {
16016 self.tree_scalar_storage_name(self.english_scalar_name(name))
16017 }
16018
16019 pub(crate) fn english_scalar_name<'a>(&self, name: &'a str) -> &'a str {
16020 if !self.english_enabled {
16021 return name;
16022 }
16023 if self
16024 .english_lexical_scalars
16025 .iter()
16026 .any(|s| s.contains(name))
16027 {
16028 return name;
16029 }
16030 if let Some(short) = crate::english::scalar_alias(name, self.english_no_match_vars) {
16031 return short;
16032 }
16033 name
16034 }
16035
16036 pub(crate) fn is_special_scalar_name_for_set(name: &str) -> bool {
16038 (name.starts_with('#') && name.len() > 1)
16043 || name.starts_with('^')
16044 || matches!(
16045 name,
16046 "0" | "/"
16047 | "\\"
16048 | ","
16049 | ";"
16050 | "\""
16051 | "%"
16052 | "="
16053 | "-"
16054 | ":"
16055 | "*"
16056 | "INC"
16057 | "^I"
16058 | "^D"
16059 | "^P"
16060 | "^W"
16061 | "^H"
16062 | "^WARNING_BITS"
16063 | "$$"
16064 | "]"
16065 | "^S"
16066 | "ARGV"
16067 | "|"
16068 | "+"
16069 | "?"
16070 | "!"
16071 | "@"
16072 | "."
16073 )
16074 || crate::english::is_known_alias(name)
16075 }
16076
16077 pub(crate) fn get_special_var(&self, name: &str) -> StrykeValue {
16078 let name = crate::scope::strip_main_prefix(name).unwrap_or(name);
16082 let name = if !crate::compat_mode() {
16084 match name {
16085 "NR" => {
16090 return StrykeValue::integer(self.nr);
16091 }
16092 "RS" => "/",
16093 "OFS" => ",",
16094 "ORS" => "\\",
16095 "NF" => {
16096 let len = self.scope.array_len("F");
16097 return StrykeValue::integer(len as i64);
16098 }
16099 _ => self.english_scalar_name(name),
16100 }
16101 } else {
16102 self.english_scalar_name(name)
16103 };
16104 match name {
16105 "$$" => StrykeValue::integer(std::process::id() as i64),
16106 "_" => self.scope.get_scalar("_"),
16107 "^MATCH" => StrykeValue::string(self.last_match.clone()),
16108 "^PREMATCH" => StrykeValue::string(self.prematch.clone()),
16109 "^POSTMATCH" => StrykeValue::string(self.postmatch.clone()),
16110 "^LAST_SUBMATCH_RESULT" => StrykeValue::string(self.last_paren_match.clone()),
16111 "0" => StrykeValue::string(self.program_name.clone()),
16112 "!" => StrykeValue::errno_dual(self.errno_code, self.errno.clone()),
16113 "@" => {
16114 if let Some(ref v) = self.eval_error_value {
16115 v.clone()
16116 } else {
16117 StrykeValue::errno_dual(self.eval_error_code, self.eval_error.clone())
16118 }
16119 }
16120 "/" => match &self.irs {
16121 Some(s) => StrykeValue::string(s.clone()),
16122 None => StrykeValue::UNDEF,
16123 },
16124 "\\" => StrykeValue::string(self.ors.clone()),
16125 "," => StrykeValue::string(self.ofs.clone()),
16126 "." => {
16127 if self.last_readline_handle.is_empty() {
16129 if self.line_number == 0 {
16130 StrykeValue::UNDEF
16131 } else {
16132 StrykeValue::integer(self.line_number)
16133 }
16134 } else {
16135 StrykeValue::integer(
16136 *self
16137 .handle_line_numbers
16138 .get(&self.last_readline_handle)
16139 .unwrap_or(&0),
16140 )
16141 }
16142 }
16143 "]" => StrykeValue::float(perl_bracket_version()),
16144 ";" => StrykeValue::string(self.subscript_sep.clone()),
16145 "ARGV" => StrykeValue::string(self.argv_current_file.clone()),
16146 "^I" => StrykeValue::string(self.inplace_edit.clone()),
16147 "^D" => StrykeValue::integer(self.debug_flags),
16148 "^P" => StrykeValue::integer(self.perl_debug_flags),
16149 "^S" => StrykeValue::integer(if self.eval_nesting > 0 { 1 } else { 0 }),
16150 "^W" => StrykeValue::integer(if self.warnings { 1 } else { 0 }),
16151 "^O" => StrykeValue::string(perl_osname()),
16152 "^T" => StrykeValue::integer(self.script_start_time),
16153 "^V" => StrykeValue::string(perl_version_v_string()),
16154 "^E" => StrykeValue::string(extended_os_error_string()),
16155 "^H" => StrykeValue::integer(self.compile_hints),
16156 "^WARNING_BITS" => StrykeValue::integer(self.warning_bits),
16157 "^GLOBAL_PHASE" => StrykeValue::string(self.global_phase.clone()),
16158 "<" | ">" => StrykeValue::integer(unix_id_for_special(name)),
16159 "(" | ")" => StrykeValue::string(unix_group_list_for_special(name)),
16160 "?" => StrykeValue::integer(self.child_exit_status),
16161 "|" => StrykeValue::integer(if self.output_autoflush { 1 } else { 0 }),
16162 "\"" => StrykeValue::string(self.list_separator.clone()),
16163 "+" => StrykeValue::string(self.last_paren_match.clone()),
16164 "%" => StrykeValue::integer(self.format_page_number),
16165 "=" => StrykeValue::integer(self.format_lines_per_page),
16166 "-" => StrykeValue::integer(self.format_lines_left),
16167 ":" => StrykeValue::string(self.format_line_break_chars.clone()),
16168 "*" => StrykeValue::integer(if self.multiline_match { 1 } else { 0 }),
16169 "^" => StrykeValue::string(self.format_top_name.clone()),
16170 "INC" => StrykeValue::integer(self.inc_hook_index),
16171 "^A" => StrykeValue::string(self.accumulator_format.clone()),
16172 "^C" => StrykeValue::integer(if self.sigint_pending_caret.replace(false) {
16173 1
16174 } else {
16175 0
16176 }),
16177 "^F" => StrykeValue::integer(self.max_system_fd),
16178 "^L" => StrykeValue::string(self.formfeed_string.clone()),
16179 "^M" => StrykeValue::string(self.emergency_memory.clone()),
16180 "^N" => StrykeValue::string(self.last_subpattern_name.clone()),
16181 "^X" => StrykeValue::string(self.executable_path.clone()),
16182 "^TAINT" | "^TAINTED" => StrykeValue::integer(0),
16184 "^UNICODE" => StrykeValue::integer(if self.utf8_pragma { 1 } else { 0 }),
16185 "^OPEN" => StrykeValue::integer(if self.open_pragma_utf8 { 1 } else { 0 }),
16186 "^UTF8LOCALE" => StrykeValue::integer(0),
16187 "^UTF8CACHE" => StrykeValue::integer(-1),
16188 _ if name.starts_with('^') && name.len() > 1 => self
16189 .special_caret_scalars
16190 .get(name)
16191 .cloned()
16192 .unwrap_or(StrykeValue::UNDEF),
16193 _ if name.starts_with('#') && name.len() > 1 => {
16194 let arr = &name[1..];
16195 let aname = self.stash_array_name_for_package(arr);
16196 let len = self.scope.array_len(&aname);
16197 StrykeValue::integer(len as i64 - 1)
16198 }
16199 _ => self.scope.get_scalar(name),
16200 }
16201 }
16202
16203 pub(crate) fn set_special_var(
16204 &mut self,
16205 name: &str,
16206 val: &StrykeValue,
16207 ) -> Result<(), StrykeError> {
16208 let name = self.english_scalar_name(name);
16209 match name {
16210 "!" => {
16211 let code = val.to_int() as i32;
16212 self.errno_code = code;
16213 self.errno = if code == 0 {
16214 String::new()
16215 } else {
16216 std::io::Error::from_raw_os_error(code).to_string()
16217 };
16218 }
16219 "@" => {
16220 if let Some((code, msg)) = val.errno_dual_parts() {
16221 self.eval_error_code = code;
16222 self.eval_error = msg;
16223 } else {
16224 self.eval_error = val.to_string();
16225 let mut code = val.to_int() as i32;
16226 if code == 0 && !self.eval_error.is_empty() {
16227 code = 1;
16228 }
16229 self.eval_error_code = code;
16230 }
16231 }
16232 "." => {
16233 let n = val.to_int();
16236 if self.last_readline_handle.is_empty() {
16237 self.line_number = n;
16238 } else {
16239 self.handle_line_numbers
16240 .insert(self.last_readline_handle.clone(), n);
16241 }
16242 }
16243 "0" => self.program_name = val.to_string(),
16244 "/" => {
16245 self.irs = if val.is_undef() {
16246 None
16247 } else {
16248 Some(val.to_string())
16249 }
16250 }
16251 "\\" => self.ors = val.to_string(),
16252 "," => self.ofs = val.to_string(),
16253 ";" => self.subscript_sep = val.to_string(),
16254 "\"" => self.list_separator = val.to_string(),
16255 "%" => self.format_page_number = val.to_int(),
16256 "=" => self.format_lines_per_page = val.to_int(),
16257 "-" => self.format_lines_left = val.to_int(),
16258 ":" => self.format_line_break_chars = val.to_string(),
16259 "*" => self.multiline_match = val.to_int() != 0,
16260 "^" => self.format_top_name = val.to_string(),
16261 "INC" => self.inc_hook_index = val.to_int(),
16262 "^A" => self.accumulator_format = val.to_string(),
16263 "^F" => self.max_system_fd = val.to_int(),
16264 "^L" => self.formfeed_string = val.to_string(),
16265 "^M" => self.emergency_memory = val.to_string(),
16266 "^I" => self.inplace_edit = val.to_string(),
16267 "^D" => self.debug_flags = val.to_int(),
16268 "^P" => self.perl_debug_flags = val.to_int(),
16269 "^W" => self.warnings = val.to_int() != 0,
16270 "^H" => self.compile_hints = val.to_int(),
16271 "^WARNING_BITS" => self.warning_bits = val.to_int(),
16272 "|" => {
16273 self.output_autoflush = val.to_int() != 0;
16274 if self.output_autoflush {
16275 let _ = io::stdout().flush();
16276 }
16277 }
16278 "$$"
16280 | "]"
16281 | "^S"
16282 | "ARGV"
16283 | "?"
16284 | "^O"
16285 | "^T"
16286 | "^V"
16287 | "^E"
16288 | "^GLOBAL_PHASE"
16289 | "^MATCH"
16290 | "^PREMATCH"
16291 | "^POSTMATCH"
16292 | "^LAST_SUBMATCH_RESULT"
16293 | "^C"
16294 | "^N"
16295 | "^X"
16296 | "^TAINT"
16297 | "^TAINTED"
16298 | "^UNICODE"
16299 | "^UTF8LOCALE"
16300 | "^UTF8CACHE"
16301 | "+"
16302 | "<"
16303 | ">"
16304 | "("
16305 | ")" => {}
16306 _ if name.starts_with('^') && name.len() > 1 => {
16307 self.special_caret_scalars
16308 .insert(name.to_string(), val.clone());
16309 }
16310 _ if name.starts_with('#') && name.len() > 1 => {
16311 let arr = &name[1..];
16314 let aname = self.stash_array_name_for_package(arr);
16315 let new_last = val.to_int();
16316 let new_len = if new_last < 0 {
16317 0
16318 } else {
16319 (new_last as usize) + 1
16320 };
16321 let mut current = self.scope.get_array(&aname);
16322 current.resize(new_len, StrykeValue::UNDEF);
16323 self.scope.set_array(&aname, current)?;
16324 }
16325 _ => self.scope.set_scalar(name, val.clone())?,
16326 }
16327 Ok(())
16328 }
16329
16330 fn extract_array_name(&self, expr: &Expr) -> Result<String, FlowOrError> {
16331 match &expr.kind {
16332 ExprKind::ArrayVar(name) => Ok(self.tree_array_storage_name(name)),
16336 ExprKind::ScalarVar(name) => Ok(name.clone()), _ => Err(StrykeError::runtime("Expected array", expr.line).into()),
16338 }
16339 }
16340
16341 fn peel_array_builtin_operand(expr: &Expr) -> &Expr {
16343 match &expr.kind {
16344 ExprKind::ScalarContext(inner) => Self::peel_array_builtin_operand(inner),
16345 ExprKind::List(es) if es.len() == 1 => Self::peel_array_builtin_operand(&es[0]),
16346 _ => expr,
16347 }
16348 }
16349
16350 fn try_eval_array_deref_container(
16352 &mut self,
16353 expr: &Expr,
16354 ) -> Result<Option<StrykeValue>, FlowOrError> {
16355 let e = Self::peel_array_builtin_operand(expr);
16356 if let ExprKind::Deref {
16357 expr: inner,
16358 kind: Sigil::Array,
16359 } = &e.kind
16360 {
16361 return Ok(Some(self.eval_or_autoviv_array_ref(inner)?));
16362 }
16363 Ok(None)
16364 }
16365
16366 fn eval_or_autoviv_array_ref(&mut self, inner: &Expr) -> Result<StrykeValue, FlowOrError> {
16370 let line = inner.line;
16371 let val = self.eval_expr(inner)?;
16372 if !val.is_undef() {
16373 return Ok(val);
16374 }
16375 let new_ref = StrykeValue::array_ref(Arc::new(RwLock::new(Vec::new())));
16376 match &inner.kind {
16377 ExprKind::ScalarVar(name) => {
16378 self.scope
16379 .set_scalar(name, new_ref.clone())
16380 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
16381 Ok(new_ref)
16382 }
16383 ExprKind::HashElement { hash, key } => {
16384 let k = self.eval_expr(key)?.to_string();
16385 self.scope
16386 .set_hash_element(hash, &k, new_ref.clone())
16387 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
16388 Ok(new_ref)
16389 }
16390 ExprKind::ArrayElement { array, index } => {
16391 let i = self.eval_expr(index)?.to_int();
16392 self.scope
16393 .set_array_element(array, i, new_ref.clone())
16394 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
16395 Ok(new_ref)
16396 }
16397 _ => Ok(val),
16398 }
16399 }
16400
16401 pub(crate) fn current_package(&self) -> String {
16403 let s = self.scope.get_scalar("__PACKAGE__").to_string();
16404 if s.is_empty() {
16405 "main".to_string()
16406 } else {
16407 s
16408 }
16409 }
16410
16411 pub(crate) fn package_version_scalar(
16414 &mut self,
16415 package: &str,
16416 ) -> StrykeResult<Option<StrykeValue>> {
16417 let saved_pkg = self.scope.get_scalar("__PACKAGE__");
16418 let _ = self
16419 .scope
16420 .set_scalar("__PACKAGE__", StrykeValue::string(package.to_string()));
16421 let ver = self.get_special_var("VERSION");
16422 let _ = self.scope.set_scalar("__PACKAGE__", saved_pkg);
16423 Ok(if ver.is_undef() { None } else { Some(ver) })
16424 }
16425
16426 pub(crate) fn resolve_autoload_sub(&self, start_package: &str) -> Option<Arc<StrykeSub>> {
16428 let root = if start_package.is_empty() {
16429 "main"
16430 } else {
16431 start_package
16432 };
16433 for pkg in self.mro_linearize(root) {
16434 let key = if pkg == "main" {
16435 "AUTOLOAD".to_string()
16436 } else {
16437 format!("{}::AUTOLOAD", pkg)
16438 };
16439 if let Some(s) = self.subs.get(&key) {
16440 return Some(s.clone());
16441 }
16442 }
16443 None
16444 }
16445
16446 pub(crate) fn try_autoload_call(
16451 &mut self,
16452 missing_name: &str,
16453 args: Vec<StrykeValue>,
16454 line: usize,
16455 want: WantarrayCtx,
16456 method_invocant_class: Option<&str>,
16457 ) -> Option<ExecResult> {
16458 let pkg = self.current_package();
16459 let full = if missing_name.contains("::") {
16460 missing_name.to_string()
16461 } else {
16462 format!("{}::{}", pkg, missing_name)
16463 };
16464 let start_pkg = method_invocant_class.unwrap_or_else(|| {
16465 full.rsplit_once("::")
16466 .map(|(p, _)| p)
16467 .filter(|p| !p.is_empty())
16468 .unwrap_or("main")
16469 });
16470 let sub = self.resolve_autoload_sub(start_pkg)?;
16471 if let Err(e) = self
16472 .scope
16473 .set_scalar("AUTOLOAD", StrykeValue::string(full.clone()))
16474 {
16475 return Some(Err(e.into()));
16476 }
16477 Some(self.call_sub(&sub, args, want, line))
16478 }
16479
16480 pub(crate) fn with_topic_default_args(&self, args: Vec<StrykeValue>) -> Vec<StrykeValue> {
16481 if args.is_empty() {
16482 vec![self.scope.get_scalar("_").clone()]
16483 } else {
16484 args
16485 }
16486 }
16487
16488 pub(crate) fn dispatch_indirect_call(
16491 &mut self,
16492 target: StrykeValue,
16493 arg_vals: Vec<StrykeValue>,
16494 want: WantarrayCtx,
16495 line: usize,
16496 ) -> ExecResult {
16497 if let Some(sub) = target.as_code_ref() {
16498 return self.call_sub(&sub, arg_vals, want, line);
16499 }
16500 if let Some(name) = target.as_str() {
16501 return self.call_named_sub(&name, arg_vals, line, want);
16502 }
16503 Err(StrykeError::runtime("Can't use non-code reference as a subroutine", line).into())
16504 }
16505
16506 pub(crate) fn call_bare_list_builtin(
16511 &mut self,
16512 name: &str,
16513 args: Vec<StrykeValue>,
16514 line: usize,
16515 want: WantarrayCtx,
16516 ) -> ExecResult {
16517 let canonical = match name {
16518 "distinct" | "uq" => "uniq",
16519 "shuf" => "shuffle",
16520 "chk" => "chunked",
16521 "win" => "windowed",
16522 "zp" => "zip",
16523 "fst" => "first",
16524 "rd" => "reduce",
16525 "med" => "median",
16526 "std" => "stddev",
16527 "var" => "variance",
16528 other => other,
16529 };
16530 match crate::list_builtins::dispatch_by_name(self, canonical, &args, want) {
16535 Some(r) => r,
16536 None => Err(StrykeError::runtime(
16537 format!("internal: not a stryke list builtin: {name}"),
16538 line,
16539 )
16540 .into()),
16541 }
16542 }
16543
16544 fn call_named_sub(
16545 &mut self,
16546 name: &str,
16547 args: Vec<StrykeValue>,
16548 line: usize,
16549 want: WantarrayCtx,
16550 ) -> ExecResult {
16551 if let Some(sub) = self.resolve_sub_by_name(name) {
16552 let args = self.with_topic_default_args(args);
16553 let pkg = name.rsplit_once("::").map(|(p, _)| p.to_string());
16557 return self.call_sub_with_package(&sub, args, want, line, pkg);
16558 }
16559 match name {
16560 "uniq" | "distinct" | "uq" | "uniqstr" | "uniqint" | "uniqnum" | "shuffle" | "shuf"
16561 | "sample" | "chunked" | "chk" | "windowed" | "win" | "zip" | "zp" | "zip_shortest"
16562 | "zip_longest" | "mesh" | "mesh_shortest" | "mesh_longest" | "any" | "all"
16563 | "none" | "notall" | "first" | "fst" | "find_index" | "firstidx" | "first_index"
16564 | "reduce" | "rd" | "reductions" | "sum" | "sum0" | "product" | "min" | "max"
16565 | "minstr" | "maxstr" | "mean" | "median" | "med" | "mode" | "stddev" | "std"
16566 | "variance" | "var" | "pairs" | "unpairs" | "pairkeys" | "pairvalues" | "pairgrep"
16567 | "pairmap" | "pairfirst" | "blessed" | "refaddr" | "reftype" | "looks_like_number"
16568 | "weaken" | "unweaken" | "isweak" | "set_subname" | "subname"
16569 | "unicode_to_native" => self.call_bare_list_builtin(name, args, line, want),
16570 "deque" => {
16571 if !args.is_empty() {
16572 return Err(StrykeError::runtime("deque() takes no arguments", line).into());
16573 }
16574 Ok(StrykeValue::deque(Arc::new(Mutex::new(VecDeque::new()))))
16575 }
16576 "defer__internal" => {
16577 if args.len() != 1 {
16578 return Err(StrykeError::runtime(
16579 "defer__internal expects one coderef argument",
16580 line,
16581 )
16582 .into());
16583 }
16584 self.scope.push_defer(args[0].clone());
16585 Ok(StrykeValue::UNDEF)
16586 }
16587 "heap" => {
16588 if args.len() != 1 {
16589 return Err(
16590 StrykeError::runtime("heap() expects one comparator sub", line).into(),
16591 );
16592 }
16593 if let Some(sub) = args[0].as_code_ref() {
16594 Ok(StrykeValue::heap(Arc::new(Mutex::new(PerlHeap {
16595 items: Vec::new(),
16596 cmp: Arc::clone(&sub),
16597 }))))
16598 } else {
16599 Err(StrykeError::runtime("heap() requires a code reference", line).into())
16600 }
16601 }
16602 "pipeline" => {
16603 let mut items = Vec::new();
16604 for v in args {
16605 if let Some(a) = v.as_array_vec() {
16606 items.extend(a);
16607 } else {
16608 items.push(v);
16609 }
16610 }
16611 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
16612 source: items,
16613 ops: Vec::new(),
16614 has_scalar_terminal: false,
16615 par_stream: false,
16616 streaming: false,
16617 streaming_workers: 0,
16618 streaming_buffer: 256,
16619 }))))
16620 }
16621 "par_pipeline" => {
16622 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
16623 return crate::par_pipeline::run_par_pipeline(self, &args, line)
16624 .map_err(Into::into);
16625 }
16626 Ok(self.builtin_par_pipeline_stream(&args, line)?)
16627 }
16628 "par_pipeline_stream" => {
16629 if crate::par_pipeline::is_named_par_pipeline_args(&args) {
16630 return crate::par_pipeline::run_par_pipeline_streaming(self, &args, line)
16631 .map_err(Into::into);
16632 }
16633 Ok(self.builtin_par_pipeline_stream_new(&args, line)?)
16634 }
16635 "ppool" => {
16636 if args.len() != 1 {
16637 return Err(StrykeError::runtime(
16638 "ppool() expects one argument (worker count)",
16639 line,
16640 )
16641 .into());
16642 }
16643 crate::ppool::create_pool(args[0].to_int().max(0) as usize).map_err(Into::into)
16644 }
16645 "barrier" => {
16646 if args.len() != 1 {
16647 return Err(StrykeError::runtime(
16648 "barrier() expects one argument (party count)",
16649 line,
16650 )
16651 .into());
16652 }
16653 let n = args[0].to_int().max(1) as usize;
16654 Ok(StrykeValue::barrier(PerlBarrier(Arc::new(Barrier::new(n)))))
16655 }
16656 "cluster" => {
16657 let items = if args.len() == 1 {
16658 args[0].to_list()
16659 } else {
16660 args.to_vec()
16661 };
16662 let c = RemoteCluster::from_list_args(&items)
16663 .map_err(|msg| StrykeError::runtime(msg, line))?;
16664 Ok(StrykeValue::remote_cluster(Arc::new(c)))
16665 }
16666 _ => {
16667 if let Some(method_name) = name.strip_prefix("static::") {
16669 let self_val = self.scope.get_scalar("self");
16670 if let Some(c) = self_val.as_class_inst() {
16671 if let Some((m, _)) = self.find_class_method(&c.def, method_name) {
16672 if let Some(ref body) = m.body {
16673 let params = m.params.clone();
16674 let mut call_args = vec![self_val.clone()];
16675 call_args.extend(args);
16676 return match self.call_class_method(body, ¶ms, call_args, line)
16677 {
16678 Ok(v) => Ok(v),
16679 Err(FlowOrError::Error(e)) => Err(e.into()),
16680 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16681 Err(e) => Err(e),
16682 };
16683 }
16684 }
16685 return Err(StrykeError::runtime(
16686 format!(
16687 "static::{} — method not found on class {}",
16688 method_name, c.def.name
16689 ),
16690 line,
16691 )
16692 .into());
16693 }
16694 return Err(StrykeError::runtime(
16695 "static:: can only be used inside a class method",
16696 line,
16697 )
16698 .into());
16699 }
16700 if let Some(def) = self.struct_defs.get(name).cloned() {
16702 return self.struct_construct(&def, args, line);
16703 }
16704 if let Some(def) = self.class_defs.get(name).cloned() {
16706 return self.class_construct(&def, args, line);
16707 }
16708 if let Some((enum_name, variant_name)) = name.rsplit_once("::") {
16710 if let Some(def) = self.enum_defs.get(enum_name).cloned() {
16711 return self.enum_construct(&def, variant_name, args, line);
16712 }
16713 }
16714 if let Some((class_name, member_name)) = name.rsplit_once("::") {
16716 if let Some(def) = self.class_defs.get(class_name).cloned() {
16717 if let Some(m) = def.method(member_name) {
16719 if m.is_static {
16720 if let Some(ref body) = m.body {
16721 let params = m.params.clone();
16722 return match self.call_static_class_method(
16723 body,
16724 ¶ms,
16725 args.clone(),
16726 line,
16727 ) {
16728 Ok(v) => Ok(v),
16729 Err(FlowOrError::Error(e)) => Err(e.into()),
16730 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
16731 Err(e) => Err(e),
16732 };
16733 }
16734 }
16735 }
16736 if def.static_fields.iter().any(|sf| sf.name == member_name) {
16738 let key = format!("{}::{}", class_name, member_name);
16739 match args.len() {
16740 0 => {
16741 let val = self.scope.get_scalar(&key);
16742 return Ok(val);
16743 }
16744 1 => {
16745 let _ = self.scope.set_scalar(&key, args[0].clone());
16746 return Ok(args[0].clone());
16747 }
16748 _ => {
16749 return Err(StrykeError::runtime(
16750 format!(
16751 "static field `{}::{}` takes 0 or 1 arguments",
16752 class_name, member_name
16753 ),
16754 line,
16755 )
16756 .into());
16757 }
16758 }
16759 }
16760 }
16761 }
16762 let args = self.with_topic_default_args(args);
16763 if let Some(r) = self.try_autoload_call(name, args, line, want, None) {
16764 return r;
16765 }
16766 Err(StrykeError::runtime(self.undefined_subroutine_call_message(name), line).into())
16767 }
16768 }
16769 }
16770
16771 pub(crate) fn struct_construct(
16773 &mut self,
16774 def: &Arc<StructDef>,
16775 args: Vec<StrykeValue>,
16776 line: usize,
16777 ) -> ExecResult {
16778 let is_named = args.len() >= 2
16781 && args.len().is_multiple_of(2)
16782 && args.iter().step_by(2).all(|v| {
16783 let s = v.to_string();
16784 def.field_index(&s).is_some()
16785 });
16786
16787 let provided = if is_named {
16788 let mut pairs = Vec::new();
16790 let mut i = 0;
16791 while i + 1 < args.len() {
16792 let k = args[i].to_string();
16793 let v = args[i + 1].clone();
16794 pairs.push((k, v));
16795 i += 2;
16796 }
16797 pairs
16798 } else {
16799 def.fields
16801 .iter()
16802 .zip(args.iter())
16803 .map(|(f, v)| (f.name.clone(), v.clone()))
16804 .collect()
16805 };
16806
16807 let mut defaults = Vec::with_capacity(def.fields.len());
16809 for field in &def.fields {
16810 if let Some(ref expr) = field.default {
16811 let val = self.eval_expr(expr)?;
16812 defaults.push(Some(val));
16813 } else {
16814 defaults.push(None);
16815 }
16816 }
16817
16818 Ok(crate::native_data::struct_new_with_defaults(
16819 def, &provided, &defaults, line,
16820 )?)
16821 }
16822
16823 pub(crate) fn class_construct(
16825 &mut self,
16826 def: &Arc<ClassDef>,
16827 args: Vec<StrykeValue>,
16828 _line: usize,
16829 ) -> ExecResult {
16830 use crate::value::ClassInstance;
16831
16832 if def.is_abstract {
16834 return Err(StrykeError::runtime(
16835 format!("cannot instantiate abstract class `{}`", def.name),
16836 _line,
16837 )
16838 .into());
16839 }
16840
16841 let all_fields = self.collect_class_fields(def);
16843
16844 let is_named = args.len() >= 2
16846 && args.len().is_multiple_of(2)
16847 && args.iter().step_by(2).all(|v| {
16848 let s = v.to_string();
16849 all_fields.iter().any(|(name, _, _)| name == &s)
16850 });
16851
16852 let provided: Vec<(String, StrykeValue)> = if is_named {
16853 let mut pairs = Vec::new();
16854 let mut i = 0;
16855 while i + 1 < args.len() {
16856 let k = args[i].to_string();
16857 let v = args[i + 1].clone();
16858 pairs.push((k, v));
16859 i += 2;
16860 }
16861 pairs
16862 } else {
16863 all_fields
16864 .iter()
16865 .zip(args.iter())
16866 .map(|((name, _, _), v)| (name.clone(), v.clone()))
16867 .collect()
16868 };
16869
16870 let mut values = Vec::with_capacity(all_fields.len());
16872 for (name, default, ty) in &all_fields {
16873 let val = if let Some((_, val)) = provided.iter().find(|(k, _)| k == name) {
16874 val.clone()
16875 } else if let Some(ref expr) = default {
16876 self.eval_expr(expr)?
16877 } else {
16878 StrykeValue::UNDEF
16879 };
16880 ty.check_value(&val).map_err(|msg| {
16881 StrykeError::type_error(
16882 format!("class {} field `{}`: {}", def.name, name, msg),
16883 _line,
16884 )
16885 })?;
16886 values.push(val);
16887 }
16888
16889 let isa_chain = self.mro_linearize(&def.name);
16891 let instance = StrykeValue::class_inst(Arc::new(ClassInstance::new_with_isa(
16892 Arc::clone(def),
16893 values,
16894 isa_chain,
16895 )));
16896
16897 let build_chain = self.collect_build_chain(def);
16899 if !build_chain.is_empty() {
16900 for (body, params) in &build_chain {
16901 let call_args = vec![instance.clone()];
16902 match self.call_class_method(body, params, call_args, _line) {
16903 Ok(_) => {}
16904 Err(FlowOrError::Flow(Flow::Return(_))) => {}
16905 Err(e) => return Err(e),
16906 }
16907 }
16908 }
16909
16910 Ok(instance)
16911 }
16912
16913 fn collect_build_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
16915 let mut chain = Vec::new();
16916 for parent_name in &def.extends {
16918 if let Some(parent_def) = self.class_defs.get(parent_name) {
16919 chain.extend(self.collect_build_chain(parent_def));
16920 }
16921 }
16922 if let Some(m) = def.method("BUILD") {
16924 if let Some(ref body) = m.body {
16925 chain.push((body.clone(), m.params.clone()));
16926 }
16927 }
16928 chain
16929 }
16930
16931 pub(crate) fn deep_to_hash_value(&self, v: &StrykeValue) -> StrykeValue {
16937 if let Some(c) = v.as_class_inst() {
16939 let all_fields = self.collect_class_fields_full(&c.def);
16940 let values = c.get_values();
16941 let mut map = IndexMap::new();
16942 for (i, (name, _, _, _, _)) in all_fields.iter().enumerate() {
16943 if let Some(elem) = values.get(i) {
16944 map.insert(name.clone(), self.deep_to_hash_value(elem));
16945 }
16946 }
16947 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16948 }
16949 if let Some(s) = v.as_struct_inst() {
16951 let values = s.get_values();
16952 let mut map = IndexMap::new();
16953 for (i, field) in s.def.fields.iter().enumerate() {
16954 if let Some(elem) = values.get(i) {
16955 map.insert(field.name.clone(), self.deep_to_hash_value(elem));
16956 }
16957 }
16958 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16959 }
16960 if let Some(r) = v.as_hash_ref() {
16962 let inner = r.read().clone();
16963 let mut map = IndexMap::new();
16964 for (k, val) in inner.into_iter() {
16965 map.insert(k, self.deep_to_hash_value(&val));
16966 }
16967 return StrykeValue::hash_ref(Arc::new(RwLock::new(map)));
16968 }
16969 if let Some(r) = v.as_array_ref() {
16971 let inner = r.read().clone();
16972 let out: Vec<StrykeValue> = inner.iter().map(|e| self.deep_to_hash_value(e)).collect();
16973 return StrykeValue::array_ref(Arc::new(RwLock::new(out)));
16974 }
16975 v.clone()
16979 }
16980
16981 fn collect_class_fields(
16984 &self,
16985 def: &ClassDef,
16986 ) -> Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> {
16987 self.collect_class_fields_full(def)
16988 .into_iter()
16989 .map(|(name, default, ty, _, _)| (name, default, ty))
16990 .collect()
16991 }
16992
16993 fn collect_class_fields_full(
16995 &self,
16996 def: &ClassDef,
16997 ) -> Vec<(
16998 String,
16999 Option<Expr>,
17000 crate::ast::PerlTypeName,
17001 crate::ast::Visibility,
17002 String,
17003 )> {
17004 let mut all_fields = Vec::new();
17005
17006 for parent_name in &def.extends {
17007 if let Some(parent_def) = self.class_defs.get(parent_name) {
17008 let parent_fields = self.collect_class_fields_full(parent_def);
17009 all_fields.extend(parent_fields);
17010 }
17011 }
17012
17013 for field in &def.fields {
17014 all_fields.push((
17015 field.name.clone(),
17016 field.default.clone(),
17017 field.ty.clone(),
17018 field.visibility,
17019 def.name.clone(),
17020 ));
17021 }
17022
17023 all_fields
17024 }
17025
17026 fn collect_class_method_names(&self, def: &ClassDef, names: &mut Vec<String>) {
17028 for parent_name in &def.extends {
17030 if let Some(parent_def) = self.class_defs.get(parent_name) {
17031 self.collect_class_method_names(parent_def, names);
17032 }
17033 }
17034 for m in &def.methods {
17036 if !m.is_static && !names.contains(&m.name) {
17037 names.push(m.name.clone());
17038 }
17039 }
17040 }
17041
17042 fn collect_destroy_chain(&self, def: &ClassDef) -> Vec<(Block, Vec<SubSigParam>)> {
17044 let mut chain = Vec::new();
17045 if let Some(m) = def.method("DESTROY") {
17047 if let Some(ref body) = m.body {
17048 chain.push((body.clone(), m.params.clone()));
17049 }
17050 }
17051 for parent_name in &def.extends {
17053 if let Some(parent_def) = self.class_defs.get(parent_name) {
17054 chain.extend(self.collect_destroy_chain(parent_def));
17055 }
17056 }
17057 chain
17058 }
17059
17060 fn class_inherits_from(&self, child: &str, ancestor: &str) -> bool {
17062 if let Some(def) = self.class_defs.get(child) {
17063 for parent in &def.extends {
17064 if parent == ancestor || self.class_inherits_from(parent, ancestor) {
17065 return true;
17066 }
17067 }
17068 }
17069 false
17070 }
17071
17072 fn find_class_method(&self, def: &ClassDef, method: &str) -> Option<(ClassMethod, String)> {
17074 if let Some(m) = def.method(method) {
17076 return Some((m.clone(), def.name.clone()));
17077 }
17078 for parent_name in &def.extends {
17080 if let Some(parent_def) = self.class_defs.get(parent_name) {
17081 if let Some(result) = self.find_class_method(parent_def, method) {
17082 return Some(result);
17083 }
17084 }
17085 }
17086 None
17087 }
17088
17089 pub(crate) fn enum_construct(
17091 &mut self,
17092 def: &Arc<EnumDef>,
17093 variant_name: &str,
17094 args: Vec<StrykeValue>,
17095 line: usize,
17096 ) -> ExecResult {
17097 let variant_idx = def.variant_index(variant_name).ok_or_else(|| {
17098 FlowOrError::Error(StrykeError::runtime(
17099 format!("unknown variant `{}` for enum `{}`", variant_name, def.name),
17100 line,
17101 ))
17102 })?;
17103 let variant = &def.variants[variant_idx];
17104 let data = if variant.ty.is_some() {
17105 if args.is_empty() {
17106 return Err(StrykeError::runtime(
17107 format!(
17108 "enum variant `{}::{}` requires data",
17109 def.name, variant_name
17110 ),
17111 line,
17112 )
17113 .into());
17114 }
17115 if args.len() == 1 {
17116 args.into_iter().next().unwrap()
17117 } else {
17118 StrykeValue::array(args)
17119 }
17120 } else {
17121 if !args.is_empty() {
17122 return Err(StrykeError::runtime(
17123 format!(
17124 "enum variant `{}::{}` does not take data",
17125 def.name, variant_name
17126 ),
17127 line,
17128 )
17129 .into());
17130 }
17131 StrykeValue::UNDEF
17132 };
17133 let inst = crate::value::EnumInstance::new(Arc::clone(def), variant_idx, data);
17134 Ok(StrykeValue::enum_inst(Arc::new(inst)))
17135 }
17136
17137 pub(crate) fn is_bound_handle(&self, name: &str) -> bool {
17139 matches!(name, "STDIN" | "STDOUT" | "STDERR")
17140 || self.input_handles.contains_key(name)
17141 || self.output_handles.contains_key(name)
17142 || self.io_file_slots.contains_key(name)
17143 || self.pipe_children.contains_key(name)
17144 }
17145
17146 pub(crate) fn io_handle_method(
17148 &mut self,
17149 name: &str,
17150 method: &str,
17151 args: &[StrykeValue],
17152 line: usize,
17153 ) -> StrykeResult<StrykeValue> {
17154 match method {
17155 "print" => self.io_handle_print(name, args, false, line),
17156 "say" => self.io_handle_print(name, args, true, line),
17157 "printf" => self.io_handle_printf(name, args, line),
17158 "getline" | "readline" => {
17159 if !args.is_empty() {
17160 return Err(StrykeError::runtime(
17161 format!("{}: too many arguments", method),
17162 line,
17163 ));
17164 }
17165 self.readline_builtin_execute(Some(name))
17166 }
17167 "close" => {
17168 if !args.is_empty() {
17169 return Err(StrykeError::runtime("close: too many arguments", line));
17170 }
17171 self.close_builtin_execute(name.to_string())
17172 }
17173 "eof" => {
17174 if !args.is_empty() {
17175 return Err(StrykeError::runtime("eof: too many arguments", line));
17176 }
17177 let at_eof = !self.has_input_handle(name);
17178 Ok(StrykeValue::integer(if at_eof { 1 } else { 0 }))
17179 }
17180 "getc" => {
17181 if !args.is_empty() {
17182 return Err(StrykeError::runtime("getc: too many arguments", line));
17183 }
17184 match crate::builtins::try_builtin(
17185 self,
17186 "getc",
17187 &[StrykeValue::string(name.to_string())],
17188 line,
17189 ) {
17190 Some(r) => r,
17191 None => Err(StrykeError::runtime("getc: not available", line)),
17192 }
17193 }
17194 "binmode" => match crate::builtins::try_builtin(
17195 self,
17196 "binmode",
17197 &[StrykeValue::string(name.to_string())],
17198 line,
17199 ) {
17200 Some(r) => r,
17201 None => Err(StrykeError::runtime("binmode: not available", line)),
17202 },
17203 "fileno" => match crate::builtins::try_builtin(
17204 self,
17205 "fileno",
17206 &[StrykeValue::string(name.to_string())],
17207 line,
17208 ) {
17209 Some(r) => r,
17210 None => Err(StrykeError::runtime("fileno: not available", line)),
17211 },
17212 "flush" => {
17213 if !args.is_empty() {
17214 return Err(StrykeError::runtime("flush: too many arguments", line));
17215 }
17216 self.io_handle_flush(name, line)
17217 }
17218 _ => Err(StrykeError::runtime(
17219 format!("Unknown method for filehandle: {}", method),
17220 line,
17221 )),
17222 }
17223 }
17224
17225 fn io_handle_flush(&mut self, handle_name: &str, line: usize) -> StrykeResult<StrykeValue> {
17226 match handle_name {
17227 "STDOUT" => {
17228 let _ = IoWrite::flush(&mut io::stdout());
17229 }
17230 "STDERR" => {
17231 let _ = IoWrite::flush(&mut io::stderr());
17232 }
17233 name => {
17234 if let Some(writer) = self.output_handles.get_mut(name) {
17235 let _ = IoWrite::flush(&mut *writer);
17236 } else {
17237 return Err(StrykeError::runtime(
17238 format!("flush on unopened filehandle {}", name),
17239 line,
17240 ));
17241 }
17242 }
17243 }
17244 Ok(StrykeValue::integer(1))
17245 }
17246
17247 fn io_handle_print(
17248 &mut self,
17249 handle_name: &str,
17250 args: &[StrykeValue],
17251 newline: bool,
17252 line: usize,
17253 ) -> StrykeResult<StrykeValue> {
17254 if newline && (self.feature_bits & FEAT_SAY) == 0 {
17255 return Err(StrykeError::runtime(
17256 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
17257 line,
17258 ));
17259 }
17260 let mut output = String::new();
17261 if args.is_empty() {
17262 output.push_str(&self.scope.get_scalar("_").to_string());
17264 } else {
17265 for (i, val) in args.iter().enumerate() {
17266 if i > 0 && !self.ofs.is_empty() {
17267 output.push_str(&self.ofs);
17268 }
17269 output.push_str(&val.to_string());
17270 }
17271 }
17272 if newline {
17273 output.push('\n');
17274 }
17275 output.push_str(&self.ors);
17276
17277 self.write_formatted_print(handle_name, &output, line)?;
17278 Ok(StrykeValue::integer(1))
17279 }
17280
17281 pub(crate) fn write_formatted_print(
17284 &mut self,
17285 handle_name: &str,
17286 output: &str,
17287 line: usize,
17288 ) -> StrykeResult<()> {
17289 match handle_name {
17290 "STDOUT" => {
17291 if !self.suppress_stdout {
17292 print!("{}", output);
17293 if self.output_autoflush {
17294 let _ = io::stdout().flush();
17295 }
17296 }
17297 }
17298 "STDERR" => {
17299 eprint!("{}", output);
17300 let _ = io::stderr().flush();
17301 }
17302 name => {
17303 if let Some(writer) = self.output_handles.get_mut(name) {
17304 let _ = writer.write_all(output.as_bytes());
17305 if self.output_autoflush {
17306 let _ = writer.flush();
17307 }
17308 } else {
17309 return Err(StrykeError::runtime(
17310 format!("print on unopened filehandle {}", name),
17311 line,
17312 ));
17313 }
17314 }
17315 }
17316 Ok(())
17317 }
17318
17319 fn io_handle_printf(
17320 &mut self,
17321 handle_name: &str,
17322 args: &[StrykeValue],
17323 line: usize,
17324 ) -> StrykeResult<StrykeValue> {
17325 let (fmt, rest): (String, &[StrykeValue]) = if args.is_empty() {
17326 let s = match self.stringify_value(self.scope.get_scalar("_").clone(), line) {
17327 Ok(s) => s,
17328 Err(FlowOrError::Error(e)) => return Err(e),
17329 Err(FlowOrError::Flow(_)) => {
17330 return Err(StrykeError::runtime(
17331 "printf: unexpected control flow in sprintf",
17332 line,
17333 ));
17334 }
17335 };
17336 (s, &[])
17337 } else {
17338 (args[0].to_string(), &args[1..])
17339 };
17340 let output = match self.perl_sprintf_stringify(&fmt, rest, line) {
17341 Ok(s) => s,
17342 Err(FlowOrError::Error(e)) => return Err(e),
17343 Err(FlowOrError::Flow(_)) => {
17344 return Err(StrykeError::runtime(
17345 "printf: unexpected control flow in sprintf",
17346 line,
17347 ));
17348 }
17349 };
17350 match handle_name {
17351 "STDOUT" => {
17352 if !self.suppress_stdout {
17353 print!("{}", output);
17354 if self.output_autoflush {
17355 let _ = IoWrite::flush(&mut io::stdout());
17356 }
17357 }
17358 }
17359 "STDERR" => {
17360 eprint!("{}", output);
17361 let _ = IoWrite::flush(&mut io::stderr());
17362 }
17363 name => {
17364 if let Some(writer) = self.output_handles.get_mut(name) {
17365 let _ = writer.write_all(output.as_bytes());
17366 if self.output_autoflush {
17367 let _ = writer.flush();
17368 }
17369 } else {
17370 return Err(StrykeError::runtime(
17371 format!("printf on unopened filehandle {}", name),
17372 line,
17373 ));
17374 }
17375 }
17376 }
17377 Ok(StrykeValue::integer(1))
17378 }
17379
17380 pub(crate) fn try_native_method(
17382 &mut self,
17383 receiver: &StrykeValue,
17384 method: &str,
17385 args: &[StrykeValue],
17386 line: usize,
17387 ) -> Option<StrykeResult<StrykeValue>> {
17388 if let Some(name) = receiver.as_io_handle_name() {
17389 return Some(self.io_handle_method(&name, method, args, line));
17390 }
17391 if let Some(ref s) = receiver.as_str() {
17392 if self.is_bound_handle(s) {
17393 return Some(self.io_handle_method(s, method, args, line));
17394 }
17395 }
17396 if let Some(c) = receiver.as_sqlite_conn() {
17397 return Some(crate::native_data::sqlite_dispatch(&c, method, args, line));
17398 }
17399 if let Some(s) = receiver.as_struct_inst() {
17400 if let Some(idx) = s.def.field_index(method) {
17402 match args.len() {
17403 0 => {
17404 return Some(Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF)));
17405 }
17406 1 => {
17407 let field = &s.def.fields[idx];
17408 let new_val = args[0].clone();
17409 if let Err(msg) = field.ty.check_value(&new_val) {
17410 return Some(Err(StrykeError::type_error(
17411 format!("struct {} field `{}`: {}", s.def.name, field.name, msg),
17412 line,
17413 )));
17414 }
17415 s.set_field(idx, new_val.clone());
17416 return Some(Ok(new_val));
17417 }
17418 _ => {
17419 return Some(Err(StrykeError::runtime(
17420 format!(
17421 "struct field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
17422 method,
17423 args.len()
17424 ),
17425 line,
17426 )));
17427 }
17428 }
17429 }
17430 match method {
17432 "with" => {
17433 let mut new_values = s.get_values();
17435 let mut i = 0;
17436 while i + 1 < args.len() {
17437 let k = args[i].to_string();
17438 let v = args[i + 1].clone();
17439 if let Some(idx) = s.def.field_index(&k) {
17440 let field = &s.def.fields[idx];
17441 if let Err(msg) = field.ty.check_value(&v) {
17442 return Some(Err(StrykeError::type_error(
17443 format!(
17444 "struct {} field `{}`: {}",
17445 s.def.name, field.name, msg
17446 ),
17447 line,
17448 )));
17449 }
17450 new_values[idx] = v;
17451 } else {
17452 return Some(Err(StrykeError::runtime(
17453 format!("struct {}: unknown field `{}`", s.def.name, k),
17454 line,
17455 )));
17456 }
17457 i += 2;
17458 }
17459 return Some(Ok(StrykeValue::struct_inst(Arc::new(
17460 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
17461 ))));
17462 }
17463 "to_hash" => {
17464 if !args.is_empty() {
17466 return Some(Err(StrykeError::runtime(
17467 "struct to_hash takes no arguments",
17468 line,
17469 )));
17470 }
17471 let mut map = IndexMap::new();
17472 let values = s.get_values();
17473 for (i, field) in s.def.fields.iter().enumerate() {
17474 map.insert(field.name.clone(), values[i].clone());
17475 }
17476 return Some(Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map)))));
17477 }
17478 "to_hash_rec" | "to_hash_deep" => {
17479 if !args.is_empty() {
17482 return Some(Err(StrykeError::runtime(
17483 "struct to_hash_rec takes no arguments",
17484 line,
17485 )));
17486 }
17487 return Some(Ok(self.deep_to_hash_value(receiver)));
17488 }
17489 "fields" => {
17490 if !args.is_empty() {
17492 return Some(Err(StrykeError::runtime(
17493 "struct fields takes no arguments",
17494 line,
17495 )));
17496 }
17497 let names: Vec<StrykeValue> = s
17498 .def
17499 .fields
17500 .iter()
17501 .map(|f| StrykeValue::string(f.name.clone()))
17502 .collect();
17503 return Some(Ok(StrykeValue::array(names)));
17504 }
17505 "clone" => {
17506 if !args.is_empty() {
17508 return Some(Err(StrykeError::runtime(
17509 "struct clone takes no arguments",
17510 line,
17511 )));
17512 }
17513 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
17514 return Some(Ok(StrykeValue::struct_inst(Arc::new(
17515 crate::value::StructInstance::new(Arc::clone(&s.def), new_values),
17516 ))));
17517 }
17518 _ => {}
17519 }
17520 if let Some(m) = s.def.method(method) {
17522 let body = m.body.clone();
17523 let params = m.params.clone();
17524 let mut call_args = vec![receiver.clone()];
17526 call_args.extend(args.iter().cloned());
17527 return Some(
17528 match self.call_struct_method(&body, ¶ms, call_args, line) {
17529 Ok(v) => Ok(v),
17530 Err(FlowOrError::Error(e)) => Err(e),
17531 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17532 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
17533 "unexpected control flow in struct method",
17534 line,
17535 )),
17536 },
17537 );
17538 }
17539 return None;
17540 }
17541 if let Some(c) = receiver.as_class_inst() {
17543 let all_fields_full = self.collect_class_fields_full(&c.def);
17545 let all_fields: Vec<(String, Option<Expr>, crate::ast::PerlTypeName)> = all_fields_full
17546 .iter()
17547 .map(|(n, d, t, _, _)| (n.clone(), d.clone(), t.clone()))
17548 .collect();
17549
17550 if let Some(idx) = all_fields_full
17552 .iter()
17553 .position(|(name, _, _, _, _)| name == method)
17554 {
17555 let (_, _, ref ty, vis, ref owner_class) = all_fields_full[idx];
17556
17557 match vis {
17559 crate::ast::Visibility::Private => {
17560 let caller_class = self
17562 .scope
17563 .get_scalar("self")
17564 .as_class_inst()
17565 .map(|ci| ci.def.name.clone());
17566 if caller_class.as_deref() != Some(owner_class.as_str()) {
17567 return Some(Err(StrykeError::runtime(
17568 format!("field `{}` of class {} is private", method, owner_class),
17569 line,
17570 )));
17571 }
17572 }
17573 crate::ast::Visibility::Protected => {
17574 let caller_class = self
17576 .scope
17577 .get_scalar("self")
17578 .as_class_inst()
17579 .map(|ci| ci.def.name.clone());
17580 let allowed = caller_class.as_deref().is_some_and(|caller| {
17581 caller == owner_class || self.class_inherits_from(caller, owner_class)
17582 });
17583 if !allowed {
17584 return Some(Err(StrykeError::runtime(
17585 format!("field `{}` of class {} is protected", method, owner_class),
17586 line,
17587 )));
17588 }
17589 }
17590 crate::ast::Visibility::Public => {}
17591 }
17592
17593 match args.len() {
17594 0 => {
17595 return Some(Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF)));
17596 }
17597 1 => {
17598 let new_val = args[0].clone();
17599 if let Err(msg) = ty.check_value(&new_val) {
17600 return Some(Err(StrykeError::type_error(
17601 format!("class {} field `{}`: {}", c.def.name, method, msg),
17602 line,
17603 )));
17604 }
17605 c.set_field(idx, new_val.clone());
17606 return Some(Ok(new_val));
17607 }
17608 _ => {
17609 return Some(Err(StrykeError::runtime(
17610 format!(
17611 "class field `{}` takes 0 arguments (getter) or 1 argument (setter), got {}",
17612 method,
17613 args.len()
17614 ),
17615 line,
17616 )));
17617 }
17618 }
17619 }
17620 match method {
17622 "with" => {
17623 let mut new_values = c.get_values();
17624 let mut i = 0;
17625 while i + 1 < args.len() {
17626 let k = args[i].to_string();
17627 let v = args[i + 1].clone();
17628 if let Some(idx) = all_fields.iter().position(|(name, _, _)| name == &k) {
17629 let (_, _, ref ty) = all_fields[idx];
17630 if let Err(msg) = ty.check_value(&v) {
17631 return Some(Err(StrykeError::type_error(
17632 format!("class {} field `{}`: {}", c.def.name, k, msg),
17633 line,
17634 )));
17635 }
17636 new_values[idx] = v;
17637 } else {
17638 return Some(Err(StrykeError::runtime(
17639 format!("class {}: unknown field `{}`", c.def.name, k),
17640 line,
17641 )));
17642 }
17643 i += 2;
17644 }
17645 return Some(Ok(StrykeValue::class_inst(Arc::new(
17646 crate::value::ClassInstance::new_with_isa(
17647 Arc::clone(&c.def),
17648 new_values,
17649 c.isa_chain.clone(),
17650 ),
17651 ))));
17652 }
17653 "to_hash" => {
17654 if !args.is_empty() {
17655 return Some(Err(StrykeError::runtime(
17656 "class to_hash takes no arguments",
17657 line,
17658 )));
17659 }
17660 let mut map = IndexMap::new();
17661 let values = c.get_values();
17662 for (i, (name, _, _)) in all_fields.iter().enumerate() {
17663 if let Some(v) = values.get(i) {
17664 map.insert(name.clone(), v.clone());
17665 }
17666 }
17667 return Some(Ok(StrykeValue::hash_ref(Arc::new(RwLock::new(map)))));
17668 }
17669 "to_hash_rec" | "to_hash_deep" => {
17670 if !args.is_empty() {
17675 return Some(Err(StrykeError::runtime(
17676 "class to_hash_rec takes no arguments",
17677 line,
17678 )));
17679 }
17680 return Some(Ok(self.deep_to_hash_value(receiver)));
17681 }
17682 "fields" => {
17683 if !args.is_empty() {
17684 return Some(Err(StrykeError::runtime(
17685 "class fields takes no arguments",
17686 line,
17687 )));
17688 }
17689 let names: Vec<StrykeValue> = all_fields
17690 .iter()
17691 .map(|(name, _, _)| StrykeValue::string(name.clone()))
17692 .collect();
17693 return Some(Ok(StrykeValue::array(names)));
17694 }
17695 "clone" => {
17696 if !args.is_empty() {
17697 return Some(Err(StrykeError::runtime(
17698 "class clone takes no arguments",
17699 line,
17700 )));
17701 }
17702 let new_values = c.get_values().iter().map(|v| v.deep_clone()).collect();
17703 return Some(Ok(StrykeValue::class_inst(Arc::new(
17704 crate::value::ClassInstance::new_with_isa(
17705 Arc::clone(&c.def),
17706 new_values,
17707 c.isa_chain.clone(),
17708 ),
17709 ))));
17710 }
17711 "isa" => {
17712 if args.len() != 1 {
17713 return Some(Err(StrykeError::runtime("isa requires one argument", line)));
17714 }
17715 let class_name = args[0].to_string();
17716 let is_a = c.isa(&class_name);
17717 return Some(Ok(if is_a {
17718 StrykeValue::integer(1)
17719 } else {
17720 StrykeValue::string(String::new())
17721 }));
17722 }
17723 "does" => {
17724 if args.len() != 1 {
17725 return Some(Err(StrykeError::runtime(
17726 "does requires one argument",
17727 line,
17728 )));
17729 }
17730 let trait_name = args[0].to_string();
17731 let implements = c.def.implements.contains(&trait_name);
17732 return Some(Ok(if implements {
17733 StrykeValue::integer(1)
17734 } else {
17735 StrykeValue::string(String::new())
17736 }));
17737 }
17738 "methods" => {
17739 if !args.is_empty() {
17740 return Some(Err(StrykeError::runtime(
17741 "methods takes no arguments",
17742 line,
17743 )));
17744 }
17745 let mut names = Vec::new();
17746 self.collect_class_method_names(&c.def, &mut names);
17747 let values: Vec<StrykeValue> =
17748 names.into_iter().map(StrykeValue::string).collect();
17749 return Some(Ok(StrykeValue::array(values)));
17750 }
17751 "superclass" => {
17752 if !args.is_empty() {
17753 return Some(Err(StrykeError::runtime(
17754 "superclass takes no arguments",
17755 line,
17756 )));
17757 }
17758 let parents: Vec<StrykeValue> = c
17759 .def
17760 .extends
17761 .iter()
17762 .map(|s| StrykeValue::string(s.clone()))
17763 .collect();
17764 return Some(Ok(StrykeValue::array(parents)));
17765 }
17766 "destroy" => {
17767 let destroy_chain = self.collect_destroy_chain(&c.def);
17769 for (body, params) in &destroy_chain {
17770 let call_args = vec![receiver.clone()];
17771 match self.call_class_method(body, params, call_args, line) {
17772 Ok(_) => {}
17773 Err(FlowOrError::Flow(Flow::Return(_))) => {}
17774 Err(FlowOrError::Error(e)) => return Some(Err(e)),
17775 Err(_) => {}
17776 }
17777 }
17778 return Some(Ok(StrykeValue::UNDEF));
17779 }
17780 _ => {}
17781 }
17782 if let Some((m, ref owner_class)) = self.find_class_method(&c.def, method) {
17784 match m.visibility {
17786 crate::ast::Visibility::Private => {
17787 let caller_class = self
17788 .scope
17789 .get_scalar("self")
17790 .as_class_inst()
17791 .map(|ci| ci.def.name.clone());
17792 if caller_class.as_deref() != Some(owner_class.as_str()) {
17793 return Some(Err(StrykeError::runtime(
17794 format!("method `{}` of class {} is private", method, owner_class),
17795 line,
17796 )));
17797 }
17798 }
17799 crate::ast::Visibility::Protected => {
17800 let caller_class = self
17801 .scope
17802 .get_scalar("self")
17803 .as_class_inst()
17804 .map(|ci| ci.def.name.clone());
17805 let allowed = caller_class.as_deref().is_some_and(|caller| {
17806 caller == owner_class.as_str()
17807 || self.class_inherits_from(caller, owner_class)
17808 });
17809 if !allowed {
17810 return Some(Err(StrykeError::runtime(
17811 format!(
17812 "method `{}` of class {} is protected",
17813 method, owner_class
17814 ),
17815 line,
17816 )));
17817 }
17818 }
17819 crate::ast::Visibility::Public => {}
17820 }
17821 if let Some(ref body) = m.body {
17822 let params = m.params.clone();
17823 let mut call_args = vec![receiver.clone()];
17824 call_args.extend(args.iter().cloned());
17825 return Some(
17826 match self.call_class_method(body, ¶ms, call_args, line) {
17827 Ok(v) => Ok(v),
17828 Err(FlowOrError::Error(e)) => Err(e),
17829 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
17830 Err(FlowOrError::Flow(_)) => Err(StrykeError::runtime(
17831 "unexpected control flow in class method",
17832 line,
17833 )),
17834 },
17835 );
17836 }
17837 }
17838 return None;
17839 }
17840 if let Some(d) = receiver.as_dataframe() {
17841 return Some(self.dataframe_method(d, method, args, line));
17842 }
17843 if let Some(s) = crate::value::set_payload(receiver) {
17844 return Some(self.set_method(s, method, args, line));
17845 }
17846 if let Some(d) = receiver.as_deque() {
17847 return Some(self.deque_method(d, method, args, line));
17848 }
17849 if let Some(h) = receiver.as_heap_pq() {
17850 return Some(self.heap_method(h, method, args, line));
17851 }
17852 if let Some(p) = receiver.as_pipeline() {
17853 return Some(self.pipeline_method(p, method, args, line));
17854 }
17855 if let Some(c) = receiver.as_capture() {
17856 return Some(self.capture_method(c, method, args, line));
17857 }
17858 if let Some(p) = receiver.as_ppool() {
17859 return Some(self.ppool_method(p, method, args, line));
17860 }
17861 if let Some(b) = receiver.as_barrier() {
17862 return Some(self.barrier_method(b, method, args, line));
17863 }
17864 if let Some(g) = receiver.as_generator() {
17865 if method == "next" {
17866 if !args.is_empty() {
17867 return Some(Err(StrykeError::runtime(
17868 "generator->next takes no arguments",
17869 line,
17870 )));
17871 }
17872 return Some(self.generator_next(&g));
17873 }
17874 return None;
17875 }
17876 if receiver.is_iterator() {
17882 if method == "next" {
17883 if !args.is_empty() {
17884 return Some(Err(StrykeError::runtime(
17885 "iterator->next takes no arguments",
17886 line,
17887 )));
17888 }
17889 let it = receiver.clone().into_iterator();
17890 let (value, more) = match it.next_item() {
17891 Some(v) => (v, 1i64),
17892 None => (StrykeValue::UNDEF, 0i64),
17893 };
17894 let pair = vec![value, StrykeValue::integer(more)];
17895 return Some(Ok(StrykeValue::array_ref(Arc::new(RwLock::new(pair)))));
17896 }
17897 return None;
17898 }
17899 if let Some(arc) = receiver.as_atomic_arc() {
17900 let inner = arc.lock().clone();
17901 if let Some(d) = inner.as_deque() {
17902 return Some(self.deque_method(d, method, args, line));
17903 }
17904 if let Some(h) = inner.as_heap_pq() {
17905 return Some(self.heap_method(h, method, args, line));
17906 }
17907 }
17908 None
17909 }
17910
17911 fn dataframe_method(
17913 &mut self,
17914 d: Arc<Mutex<PerlDataFrame>>,
17915 method: &str,
17916 args: &[StrykeValue],
17917 line: usize,
17918 ) -> StrykeResult<StrykeValue> {
17919 match method {
17920 "nrow" | "nrows" => {
17921 if !args.is_empty() {
17922 return Err(StrykeError::runtime(
17923 format!("dataframe {} takes no arguments", method),
17924 line,
17925 ));
17926 }
17927 Ok(StrykeValue::integer(d.lock().nrows() as i64))
17928 }
17929 "ncol" | "ncols" => {
17930 if !args.is_empty() {
17931 return Err(StrykeError::runtime(
17932 format!("dataframe {} takes no arguments", method),
17933 line,
17934 ));
17935 }
17936 Ok(StrykeValue::integer(d.lock().ncols() as i64))
17937 }
17938 "filter" => {
17939 if args.len() != 1 {
17940 return Err(StrykeError::runtime(
17941 "dataframe filter expects 1 argument (sub)",
17942 line,
17943 ));
17944 }
17945 let Some(sub) = args[0].as_code_ref() else {
17946 return Err(StrykeError::runtime(
17947 "dataframe filter expects a code reference",
17948 line,
17949 ));
17950 };
17951 let df_guard = d.lock();
17952 let n = df_guard.nrows();
17953 let mut keep = vec![false; n];
17954 for (r, row_keep) in keep.iter_mut().enumerate().take(n) {
17955 let row = df_guard.row_hashref(r);
17956 self.scope_push_hook();
17957 self.scope.set_topic(row);
17958 if let Some(ref env) = sub.closure_env {
17959 self.scope.restore_capture(env);
17960 }
17961 let pass = match self.exec_block_no_scope(&sub.body) {
17962 Ok(v) => v.is_true(),
17963 Err(_) => false,
17964 };
17965 self.scope_pop_hook();
17966 *row_keep = pass;
17967 }
17968 let columns = df_guard.columns.clone();
17969 let cols: Vec<Vec<StrykeValue>> = (0..df_guard.ncols())
17970 .map(|i| {
17971 let mut out = Vec::new();
17972 for (r, pass_row) in keep.iter().enumerate().take(n) {
17973 if *pass_row {
17974 out.push(df_guard.cols[i][r].clone());
17975 }
17976 }
17977 out
17978 })
17979 .collect();
17980 let group_by = df_guard.group_by.clone();
17981 drop(df_guard);
17982 let new_df = PerlDataFrame {
17983 columns,
17984 cols,
17985 group_by,
17986 };
17987 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(new_df))))
17988 }
17989 "group_by" => {
17990 if args.len() != 1 {
17991 return Err(StrykeError::runtime(
17992 "dataframe group_by expects 1 column name",
17993 line,
17994 ));
17995 }
17996 let key = args[0].to_string();
17997 let inner = d.lock();
17998 if inner.col_index(&key).is_none() {
17999 return Err(StrykeError::runtime(
18000 format!("dataframe group_by: unknown column \"{}\"", key),
18001 line,
18002 ));
18003 }
18004 let new_df = PerlDataFrame {
18005 columns: inner.columns.clone(),
18006 cols: inner.cols.clone(),
18007 group_by: Some(key),
18008 };
18009 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(new_df))))
18010 }
18011 "sum" => {
18012 if args.len() != 1 {
18013 return Err(StrykeError::runtime(
18014 "dataframe sum expects 1 column name",
18015 line,
18016 ));
18017 }
18018 let col_name = args[0].to_string();
18019 let inner = d.lock();
18020 let val_idx = inner.col_index(&col_name).ok_or_else(|| {
18021 StrykeError::runtime(
18022 format!("dataframe sum: unknown column \"{}\"", col_name),
18023 line,
18024 )
18025 })?;
18026 match &inner.group_by {
18027 Some(gcol) => {
18028 let gi = inner.col_index(gcol).ok_or_else(|| {
18029 StrykeError::runtime(
18030 format!("dataframe sum: unknown group column \"{}\"", gcol),
18031 line,
18032 )
18033 })?;
18034 let mut acc: IndexMap<String, f64> = IndexMap::new();
18035 for r in 0..inner.nrows() {
18036 let k = inner.cols[gi][r].to_string();
18037 let v = inner.cols[val_idx][r].to_number();
18038 *acc.entry(k).or_insert(0.0) += v;
18039 }
18040 let keys: Vec<String> = acc.keys().cloned().collect();
18041 let sums: Vec<f64> = acc.values().copied().collect();
18042 let cols = vec![
18043 keys.into_iter().map(StrykeValue::string).collect(),
18044 sums.into_iter().map(StrykeValue::float).collect(),
18045 ];
18046 let columns = vec![gcol.clone(), format!("sum_{}", col_name)];
18047 let out = PerlDataFrame {
18048 columns,
18049 cols,
18050 group_by: None,
18051 };
18052 Ok(StrykeValue::dataframe(Arc::new(Mutex::new(out))))
18053 }
18054 None => {
18055 let total: f64 = (0..inner.nrows())
18056 .map(|r| inner.cols[val_idx][r].to_number())
18057 .sum();
18058 Ok(StrykeValue::float(total))
18059 }
18060 }
18061 }
18062 _ => Err(StrykeError::runtime(
18063 format!("Unknown method for dataframe: {}", method),
18064 line,
18065 )),
18066 }
18067 }
18068
18069 fn set_method(
18071 &self,
18072 s: Arc<crate::value::PerlSet>,
18073 method: &str,
18074 args: &[StrykeValue],
18075 line: usize,
18076 ) -> StrykeResult<StrykeValue> {
18077 match method {
18078 "has" | "contains" | "member" => {
18079 if args.len() != 1 {
18080 return Err(StrykeError::runtime(
18081 "set->has expects one argument (element)",
18082 line,
18083 ));
18084 }
18085 let k = crate::value::set_member_key(&args[0]);
18086 Ok(StrykeValue::integer(if s.contains_key(&k) { 1 } else { 0 }))
18087 }
18088 "size" | "len" | "count" => {
18089 if !args.is_empty() {
18090 return Err(StrykeError::runtime("set->size takes no arguments", line));
18091 }
18092 Ok(StrykeValue::integer(s.len() as i64))
18093 }
18094 "values" | "list" | "elements" => {
18095 if !args.is_empty() {
18096 return Err(StrykeError::runtime("set->values takes no arguments", line));
18097 }
18098 Ok(StrykeValue::array(s.values().cloned().collect()))
18099 }
18100 _ => Err(StrykeError::runtime(
18101 format!("Unknown method for set: {}", method),
18102 line,
18103 )),
18104 }
18105 }
18106
18107 fn deque_method(
18108 &mut self,
18109 d: Arc<Mutex<VecDeque<StrykeValue>>>,
18110 method: &str,
18111 args: &[StrykeValue],
18112 line: usize,
18113 ) -> StrykeResult<StrykeValue> {
18114 match method {
18115 "push_back" => {
18116 if args.len() != 1 {
18117 return Err(StrykeError::runtime("push_back expects 1 argument", line));
18118 }
18119 d.lock().push_back(args[0].clone());
18120 Ok(StrykeValue::integer(d.lock().len() as i64))
18121 }
18122 "push_front" => {
18123 if args.len() != 1 {
18124 return Err(StrykeError::runtime("push_front expects 1 argument", line));
18125 }
18126 d.lock().push_front(args[0].clone());
18127 Ok(StrykeValue::integer(d.lock().len() as i64))
18128 }
18129 "pop_back" => Ok(d.lock().pop_back().unwrap_or(StrykeValue::UNDEF)),
18130 "pop_front" => Ok(d.lock().pop_front().unwrap_or(StrykeValue::UNDEF)),
18131 "size" | "len" => Ok(StrykeValue::integer(d.lock().len() as i64)),
18132 _ => Err(StrykeError::runtime(
18133 format!("Unknown method for deque: {}", method),
18134 line,
18135 )),
18136 }
18137 }
18138
18139 fn heap_method(
18140 &mut self,
18141 h: Arc<Mutex<PerlHeap>>,
18142 method: &str,
18143 args: &[StrykeValue],
18144 line: usize,
18145 ) -> StrykeResult<StrykeValue> {
18146 match method {
18147 "push" => {
18148 if args.len() != 1 {
18149 return Err(StrykeError::runtime("heap push expects 1 argument", line));
18150 }
18151 let mut g = h.lock();
18152 let n = g.items.len();
18153 g.items.push(args[0].clone());
18154 let cmp = g.cmp.clone();
18155 drop(g);
18156 let mut g = h.lock();
18157 self.heap_sift_up(&mut g.items, &cmp, n);
18158 Ok(StrykeValue::integer(g.items.len() as i64))
18159 }
18160 "pop" => {
18161 let mut g = h.lock();
18162 if g.items.is_empty() {
18163 return Ok(StrykeValue::UNDEF);
18164 }
18165 let cmp = g.cmp.clone();
18166 let n = g.items.len();
18167 g.items.swap(0, n - 1);
18168 let v = g.items.pop().unwrap();
18169 if !g.items.is_empty() {
18170 self.heap_sift_down(&mut g.items, &cmp, 0);
18171 }
18172 Ok(v)
18173 }
18174 "peek" => Ok(h
18175 .lock()
18176 .items
18177 .first()
18178 .cloned()
18179 .unwrap_or(StrykeValue::UNDEF)),
18180 _ => Err(StrykeError::runtime(
18181 format!("Unknown method for heap: {}", method),
18182 line,
18183 )),
18184 }
18185 }
18186
18187 fn ppool_method(
18188 &mut self,
18189 pool: PerlPpool,
18190 method: &str,
18191 args: &[StrykeValue],
18192 line: usize,
18193 ) -> StrykeResult<StrykeValue> {
18194 match method {
18195 "submit" => pool.submit(self, args, line),
18196 "collect" => {
18197 if !args.is_empty() {
18198 return Err(StrykeError::runtime("collect() takes no arguments", line));
18199 }
18200 pool.collect(line)
18201 }
18202 _ => Err(StrykeError::runtime(
18203 format!("Unknown method for ppool: {}", method),
18204 line,
18205 )),
18206 }
18207 }
18208
18209 fn barrier_method(
18210 &self,
18211 barrier: PerlBarrier,
18212 method: &str,
18213 args: &[StrykeValue],
18214 line: usize,
18215 ) -> StrykeResult<StrykeValue> {
18216 match method {
18217 "wait" => {
18218 if !args.is_empty() {
18219 return Err(StrykeError::runtime("wait() takes no arguments", line));
18220 }
18221 let _ = barrier.0.wait();
18222 Ok(StrykeValue::integer(1))
18223 }
18224 _ => Err(StrykeError::runtime(
18225 format!("Unknown method for barrier: {}", method),
18226 line,
18227 )),
18228 }
18229 }
18230
18231 fn capture_method(
18232 &self,
18233 c: Arc<CaptureResult>,
18234 method: &str,
18235 args: &[StrykeValue],
18236 line: usize,
18237 ) -> StrykeResult<StrykeValue> {
18238 if !args.is_empty() {
18239 return Err(StrykeError::runtime(
18240 format!("capture: {} takes no arguments", method),
18241 line,
18242 ));
18243 }
18244 match method {
18245 "stdout" => Ok(StrykeValue::string(c.stdout.clone())),
18246 "stderr" => Ok(StrykeValue::string(c.stderr.clone())),
18247 "exitcode" => Ok(StrykeValue::integer(c.exitcode)),
18248 "failed" => Ok(StrykeValue::integer(if c.exitcode != 0 { 1 } else { 0 })),
18249 _ => Err(StrykeError::runtime(
18250 format!("Unknown method for capture: {}", method),
18251 line,
18252 )),
18253 }
18254 }
18255
18256 pub(crate) fn builtin_par_pipeline_stream(
18257 &mut self,
18258 args: &[StrykeValue],
18259 _line: usize,
18260 ) -> StrykeResult<StrykeValue> {
18261 let mut items = Vec::new();
18262 for v in args {
18263 if let Some(a) = v.as_array_vec() {
18264 items.extend(a);
18265 } else {
18266 items.push(v.clone());
18267 }
18268 }
18269 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
18270 source: items,
18271 ops: Vec::new(),
18272 has_scalar_terminal: false,
18273 par_stream: true,
18274 streaming: false,
18275 streaming_workers: 0,
18276 streaming_buffer: 256,
18277 }))))
18278 }
18279
18280 pub(crate) fn builtin_par_pipeline_stream_new(
18283 &mut self,
18284 args: &[StrykeValue],
18285 _line: usize,
18286 ) -> StrykeResult<StrykeValue> {
18287 let mut items = Vec::new();
18288 let mut workers: usize = 0;
18289 let mut buffer: usize = 256;
18290 let mut i = 0;
18292 while i < args.len() {
18293 let s = args[i].to_string();
18294 if (s == "workers" || s == "buffer") && i + 1 < args.len() {
18295 let val = args[i + 1].to_int().max(1) as usize;
18296 if s == "workers" {
18297 workers = val;
18298 } else {
18299 buffer = val;
18300 }
18301 i += 2;
18302 } else if let Some(a) = args[i].as_array_vec() {
18303 items.extend(a);
18304 i += 1;
18305 } else {
18306 items.push(args[i].clone());
18307 i += 1;
18308 }
18309 }
18310 Ok(StrykeValue::pipeline(Arc::new(Mutex::new(PipelineInner {
18311 source: items,
18312 ops: Vec::new(),
18313 has_scalar_terminal: false,
18314 par_stream: false,
18315 streaming: true,
18316 streaming_workers: workers,
18317 streaming_buffer: buffer,
18318 }))))
18319 }
18320
18321 pub(crate) fn pipeline_int_mul_sub(k: i64) -> Arc<StrykeSub> {
18323 let line = 1usize;
18324 let body = vec![Statement {
18325 label: None,
18326 kind: StmtKind::Expression(Expr {
18327 kind: ExprKind::BinOp {
18328 left: Box::new(Expr {
18329 kind: ExprKind::ScalarVar("_".into()),
18330 line,
18331 }),
18332 op: BinOp::Mul,
18333 right: Box::new(Expr {
18334 kind: ExprKind::Integer(k),
18335 line,
18336 }),
18337 },
18338 line,
18339 }),
18340 line,
18341 }];
18342 Arc::new(StrykeSub {
18343 name: "__pipeline_int_mul__".into(),
18344 params: vec![],
18345 body,
18346 closure_env: None,
18347 prototype: None,
18348 fib_like: None,
18349 })
18350 }
18351
18352 pub(crate) fn anon_coderef_from_block(&mut self, block: &Block) -> Arc<StrykeSub> {
18353 let captured = self.scope.capture();
18354 Arc::new(StrykeSub {
18355 name: "__ANON__".into(),
18356 params: vec![],
18357 body: block.clone(),
18358 closure_env: Some(captured),
18359 prototype: None,
18360 fib_like: None,
18361 })
18362 }
18363
18364 pub(crate) fn builtin_collect_execute(
18365 &mut self,
18366 args: &[StrykeValue],
18367 line: usize,
18368 ) -> StrykeResult<StrykeValue> {
18369 if args.is_empty() {
18370 return Err(StrykeError::runtime(
18371 "collect() expects at least one argument",
18372 line,
18373 ));
18374 }
18375 if args.len() == 1 {
18378 if let Some(p) = args[0].as_pipeline() {
18379 return self.pipeline_collect(&p, line);
18380 }
18381 return Ok(StrykeValue::array(args[0].to_list()));
18382 }
18383 Ok(StrykeValue::array(args.to_vec()))
18384 }
18385
18386 pub(crate) fn pipeline_push(
18387 &self,
18388 p: &Arc<Mutex<PipelineInner>>,
18389 op: PipelineOp,
18390 line: usize,
18391 ) -> StrykeResult<()> {
18392 let mut g = p.lock();
18393 if g.has_scalar_terminal {
18394 return Err(StrykeError::runtime(
18395 "pipeline: cannot chain after preduce / preduce_init / pmap_reduce (must be last before collect)",
18396 line,
18397 ));
18398 }
18399 if matches!(
18400 &op,
18401 PipelineOp::PReduce { .. }
18402 | PipelineOp::PReduceInit { .. }
18403 | PipelineOp::PMapReduce { .. }
18404 ) {
18405 g.has_scalar_terminal = true;
18406 }
18407 g.ops.push(op);
18408 Ok(())
18409 }
18410
18411 fn pipeline_parse_sub_progress(
18412 args: &[StrykeValue],
18413 line: usize,
18414 name: &str,
18415 ) -> StrykeResult<(Arc<StrykeSub>, bool)> {
18416 if args.is_empty() {
18417 return Err(StrykeError::runtime(
18418 format!("pipeline {}: expects at least 1 argument (code ref)", name),
18419 line,
18420 ));
18421 }
18422 let Some(sub) = args[0].as_code_ref() else {
18423 return Err(StrykeError::runtime(
18424 format!("pipeline {}: first argument must be a code reference", name),
18425 line,
18426 ));
18427 };
18428 let progress = args.get(1).map(|x| x.is_true()).unwrap_or(false);
18429 if args.len() > 2 {
18430 return Err(StrykeError::runtime(
18431 format!(
18432 "pipeline {}: at most 2 arguments (sub, optional progress flag)",
18433 name
18434 ),
18435 line,
18436 ));
18437 }
18438 Ok((sub, progress))
18439 }
18440
18441 pub(crate) fn pipeline_method(
18442 &mut self,
18443 p: Arc<Mutex<PipelineInner>>,
18444 method: &str,
18445 args: &[StrykeValue],
18446 line: usize,
18447 ) -> StrykeResult<StrykeValue> {
18448 match method {
18449 "filter" | "f" | "grep" => {
18450 if args.len() != 1 {
18451 return Err(StrykeError::runtime(
18452 "pipeline filter/grep expects 1 argument (sub)",
18453 line,
18454 ));
18455 }
18456 let Some(sub) = args[0].as_code_ref() else {
18457 return Err(StrykeError::runtime(
18458 "pipeline filter/grep expects a code reference",
18459 line,
18460 ));
18461 };
18462 self.pipeline_push(&p, PipelineOp::Filter(sub), line)?;
18463 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18464 }
18465 "map" => {
18466 if args.len() != 1 {
18467 return Err(StrykeError::runtime(
18468 "pipeline map expects 1 argument (sub)",
18469 line,
18470 ));
18471 }
18472 let Some(sub) = args[0].as_code_ref() else {
18473 return Err(StrykeError::runtime(
18474 "pipeline map expects a code reference",
18475 line,
18476 ));
18477 };
18478 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
18479 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18480 }
18481 "tap" | "peek" => {
18482 if args.len() != 1 {
18483 return Err(StrykeError::runtime(
18484 "pipeline tap/peek expects 1 argument (sub)",
18485 line,
18486 ));
18487 }
18488 let Some(sub) = args[0].as_code_ref() else {
18489 return Err(StrykeError::runtime(
18490 "pipeline tap/peek expects a code reference",
18491 line,
18492 ));
18493 };
18494 self.pipeline_push(&p, PipelineOp::Tap(sub), line)?;
18495 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18496 }
18497 "take" => {
18498 if args.len() != 1 {
18499 return Err(StrykeError::runtime(
18500 "pipeline take expects 1 argument",
18501 line,
18502 ));
18503 }
18504 let n = args[0].to_int();
18505 self.pipeline_push(&p, PipelineOp::Take(n), line)?;
18506 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18507 }
18508 "pmap" => {
18509 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pmap")?;
18510 self.pipeline_push(&p, PipelineOp::PMap { sub, progress }, line)?;
18511 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18512 }
18513 "pgrep" => {
18514 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pgrep")?;
18515 self.pipeline_push(&p, PipelineOp::PGrep { sub, progress }, line)?;
18516 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18517 }
18518 "pfor" => {
18519 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pfor")?;
18520 self.pipeline_push(&p, PipelineOp::PFor { sub, progress }, line)?;
18521 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18522 }
18523 "pmap_chunked" => {
18524 if args.len() < 2 {
18525 return Err(StrykeError::runtime(
18526 "pipeline pmap_chunked expects chunk size and a code reference",
18527 line,
18528 ));
18529 }
18530 let chunk = args[0].to_int().max(1);
18531 let Some(sub) = args[1].as_code_ref() else {
18532 return Err(StrykeError::runtime(
18533 "pipeline pmap_chunked: second argument must be a code reference",
18534 line,
18535 ));
18536 };
18537 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
18538 if args.len() > 3 {
18539 return Err(StrykeError::runtime(
18540 "pipeline pmap_chunked: chunk, sub, optional progress (at most 3 args)",
18541 line,
18542 ));
18543 }
18544 self.pipeline_push(
18545 &p,
18546 PipelineOp::PMapChunked {
18547 chunk,
18548 sub,
18549 progress,
18550 },
18551 line,
18552 )?;
18553 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18554 }
18555 "psort" => {
18556 let (cmp, progress) = match args.len() {
18557 0 => (None, false),
18558 1 => {
18559 if let Some(s) = args[0].as_code_ref() {
18560 (Some(s), false)
18561 } else {
18562 (None, args[0].is_true())
18563 }
18564 }
18565 2 => {
18566 let Some(s) = args[0].as_code_ref() else {
18567 return Err(StrykeError::runtime(
18568 "pipeline psort: with two arguments, the first must be a comparator sub",
18569 line,
18570 ));
18571 };
18572 (Some(s), args[1].is_true())
18573 }
18574 _ => {
18575 return Err(StrykeError::runtime(
18576 "pipeline psort: 0 args, 1 (sub or progress), or 2 (sub, progress)",
18577 line,
18578 ));
18579 }
18580 };
18581 self.pipeline_push(&p, PipelineOp::PSort { cmp, progress }, line)?;
18582 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18583 }
18584 "pcache" => {
18585 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "pcache")?;
18586 self.pipeline_push(&p, PipelineOp::PCache { sub, progress }, line)?;
18587 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18588 }
18589 "preduce" => {
18590 let (sub, progress) = Self::pipeline_parse_sub_progress(args, line, "preduce")?;
18591 self.pipeline_push(&p, PipelineOp::PReduce { sub, progress }, line)?;
18592 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18593 }
18594 "preduce_init" => {
18595 if args.len() < 2 {
18596 return Err(StrykeError::runtime(
18597 "pipeline preduce_init expects init value and a code reference",
18598 line,
18599 ));
18600 }
18601 let init = args[0].clone();
18602 let Some(sub) = args[1].as_code_ref() else {
18603 return Err(StrykeError::runtime(
18604 "pipeline preduce_init: second argument must be a code reference",
18605 line,
18606 ));
18607 };
18608 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
18609 if args.len() > 3 {
18610 return Err(StrykeError::runtime(
18611 "pipeline preduce_init: init, sub, optional progress (at most 3 args)",
18612 line,
18613 ));
18614 }
18615 self.pipeline_push(
18616 &p,
18617 PipelineOp::PReduceInit {
18618 init,
18619 sub,
18620 progress,
18621 },
18622 line,
18623 )?;
18624 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18625 }
18626 "pmap_reduce" => {
18627 if args.len() < 2 {
18628 return Err(StrykeError::runtime(
18629 "pipeline pmap_reduce expects map sub and reduce sub",
18630 line,
18631 ));
18632 }
18633 let Some(map) = args[0].as_code_ref() else {
18634 return Err(StrykeError::runtime(
18635 "pipeline pmap_reduce: first argument must be a code reference (map)",
18636 line,
18637 ));
18638 };
18639 let Some(reduce) = args[1].as_code_ref() else {
18640 return Err(StrykeError::runtime(
18641 "pipeline pmap_reduce: second argument must be a code reference (reduce)",
18642 line,
18643 ));
18644 };
18645 let progress = args.get(2).map(|x| x.is_true()).unwrap_or(false);
18646 if args.len() > 3 {
18647 return Err(StrykeError::runtime(
18648 "pipeline pmap_reduce: map, reduce, optional progress (at most 3 args)",
18649 line,
18650 ));
18651 }
18652 self.pipeline_push(
18653 &p,
18654 PipelineOp::PMapReduce {
18655 map,
18656 reduce,
18657 progress,
18658 },
18659 line,
18660 )?;
18661 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18662 }
18663 "collect" => {
18664 if !args.is_empty() {
18665 return Err(StrykeError::runtime(
18666 "pipeline collect takes no arguments",
18667 line,
18668 ));
18669 }
18670 self.pipeline_collect(&p, line)
18671 }
18672 _ => {
18673 if let Some(sub) = self.resolve_sub_by_name(method) {
18676 if !args.is_empty() {
18677 return Err(StrykeError::runtime(
18678 format!(
18679 "pipeline ->{}: resolved subroutine takes no arguments; use a no-arg call or built-in ->map(sub {{ ... }}) / ->filter(sub {{ ... }})",
18680 method
18681 ),
18682 line,
18683 ));
18684 }
18685 self.pipeline_push(&p, PipelineOp::Map(sub), line)?;
18686 Ok(StrykeValue::pipeline(Arc::clone(&p)))
18687 } else {
18688 Err(StrykeError::runtime(
18689 format!("Unknown method for pipeline: {}", method),
18690 line,
18691 ))
18692 }
18693 }
18694 }
18695 }
18696
18697 fn pipeline_parallel_map(
18698 &mut self,
18699 items: Vec<StrykeValue>,
18700 sub: &Arc<StrykeSub>,
18701 progress: bool,
18702 ) -> Vec<StrykeValue> {
18703 let subs = self.subs.clone();
18704 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18705 let pmap_progress = PmapProgress::new(progress, items.len());
18706 let results: Vec<StrykeValue> = items
18707 .into_par_iter()
18708 .map(|item| {
18709 let mut local_interp = VMHelper::new();
18710 local_interp.subs = subs.clone();
18711 local_interp.scope.restore_capture(&scope_capture);
18712 local_interp
18713 .scope
18714 .restore_atomics(&atomic_arrays, &atomic_hashes);
18715 local_interp.enable_parallel_guard();
18716 local_interp.scope.set_topic(item);
18717 local_interp.scope_push_hook();
18718 let val = match local_interp.exec_block_no_scope(&sub.body) {
18719 Ok(val) => val,
18720 Err(_) => StrykeValue::UNDEF,
18721 };
18722 local_interp.scope_pop_hook();
18723 pmap_progress.tick();
18724 val
18725 })
18726 .collect();
18727 pmap_progress.finish();
18728 results
18729 }
18730
18731 fn pipeline_par_stream_filter(
18733 &mut self,
18734 items: Vec<StrykeValue>,
18735 sub: &Arc<StrykeSub>,
18736 ) -> Vec<StrykeValue> {
18737 if items.is_empty() {
18738 return items;
18739 }
18740 let subs = self.subs.clone();
18741 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18742 let indexed: Vec<(usize, StrykeValue)> = items.into_iter().enumerate().collect();
18743 let mut kept: Vec<(usize, StrykeValue)> = indexed
18744 .into_par_iter()
18745 .filter_map(|(i, item)| {
18746 let mut local_interp = VMHelper::new();
18747 local_interp.subs = subs.clone();
18748 local_interp.scope.restore_capture(&scope_capture);
18749 local_interp
18750 .scope
18751 .restore_atomics(&atomic_arrays, &atomic_hashes);
18752 local_interp.enable_parallel_guard();
18753 local_interp.scope.set_topic(item.clone());
18754 local_interp.scope_push_hook();
18755 let keep = match local_interp.exec_block_no_scope(&sub.body) {
18756 Ok(val) => val.is_true(),
18757 Err(_) => false,
18758 };
18759 local_interp.scope_pop_hook();
18760 if keep {
18761 Some((i, item))
18762 } else {
18763 None
18764 }
18765 })
18766 .collect();
18767 kept.sort_by_key(|(i, _)| *i);
18768 kept.into_iter().map(|(_, x)| x).collect()
18769 }
18770
18771 fn pipeline_par_stream_map(
18773 &mut self,
18774 items: Vec<StrykeValue>,
18775 sub: &Arc<StrykeSub>,
18776 ) -> Vec<StrykeValue> {
18777 if items.is_empty() {
18778 return items;
18779 }
18780 let subs = self.subs.clone();
18781 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
18782 let indexed: Vec<(usize, StrykeValue)> = items.into_iter().enumerate().collect();
18783 let mut mapped: Vec<(usize, StrykeValue)> = indexed
18784 .into_par_iter()
18785 .map(|(i, item)| {
18786 let mut local_interp = VMHelper::new();
18787 local_interp.subs = subs.clone();
18788 local_interp.scope.restore_capture(&scope_capture);
18789 local_interp
18790 .scope
18791 .restore_atomics(&atomic_arrays, &atomic_hashes);
18792 local_interp.enable_parallel_guard();
18793 local_interp.scope.set_topic(item);
18794 local_interp.scope_push_hook();
18795 let val = match local_interp.exec_block_no_scope(&sub.body) {
18796 Ok(val) => val,
18797 Err(_) => StrykeValue::UNDEF,
18798 };
18799 local_interp.scope_pop_hook();
18800 (i, val)
18801 })
18802 .collect();
18803 mapped.sort_by_key(|(i, _)| *i);
18804 mapped.into_iter().map(|(_, x)| x).collect()
18805 }
18806
18807 fn pipeline_collect(
18808 &mut self,
18809 p: &Arc<Mutex<PipelineInner>>,
18810 line: usize,
18811 ) -> StrykeResult<StrykeValue> {
18812 let (mut v, ops, par_stream, streaming, streaming_workers, streaming_buffer) = {
18813 let g = p.lock();
18814 (
18815 g.source.clone(),
18816 g.ops.clone(),
18817 g.par_stream,
18818 g.streaming,
18819 g.streaming_workers,
18820 g.streaming_buffer,
18821 )
18822 };
18823 if streaming {
18824 return self.pipeline_collect_streaming(
18825 v,
18826 &ops,
18827 streaming_workers,
18828 streaming_buffer,
18829 line,
18830 );
18831 }
18832 for op in ops {
18833 match op {
18834 PipelineOp::Filter(sub) => {
18835 if par_stream {
18836 v = self.pipeline_par_stream_filter(v, &sub);
18837 } else {
18838 let mut out = Vec::new();
18839 for item in v {
18840 self.scope_push_hook();
18841 self.scope.set_topic(item.clone());
18842 if let Some(ref env) = sub.closure_env {
18843 self.scope.restore_capture(env);
18844 }
18845 let keep = match self.exec_block_no_scope(&sub.body) {
18846 Ok(val) => val.is_true(),
18847 Err(_) => false,
18848 };
18849 self.scope_pop_hook();
18850 if keep {
18851 out.push(item);
18852 }
18853 }
18854 v = out;
18855 }
18856 }
18857 PipelineOp::Map(sub) => {
18858 if par_stream {
18859 v = self.pipeline_par_stream_map(v, &sub);
18860 } else {
18861 let mut out = Vec::new();
18862 for item in v {
18863 self.scope_push_hook();
18864 self.scope.set_topic(item);
18865 if let Some(ref env) = sub.closure_env {
18866 self.scope.restore_capture(env);
18867 }
18868 let mapped = match self.exec_block_no_scope(&sub.body) {
18869 Ok(val) => val,
18870 Err(_) => StrykeValue::UNDEF,
18871 };
18872 self.scope_pop_hook();
18873 out.push(mapped);
18874 }
18875 v = out;
18876 }
18877 }
18878 PipelineOp::Tap(sub) => {
18879 match self.call_sub(&sub, v.clone(), WantarrayCtx::Void, line) {
18880 Ok(_) => {}
18881 Err(FlowOrError::Error(e)) => return Err(e),
18882 Err(FlowOrError::Flow(_)) => {
18883 return Err(StrykeError::runtime(
18884 "tap: unsupported control flow in block",
18885 line,
18886 ));
18887 }
18888 }
18889 }
18890 PipelineOp::Take(n) => {
18891 let n = n.max(0) as usize;
18892 if v.len() > n {
18893 v.truncate(n);
18894 }
18895 }
18896 PipelineOp::PMap { sub, progress } => {
18897 v = self.pipeline_parallel_map(v, &sub, progress);
18898 }
18899 PipelineOp::PGrep { sub, progress } => {
18900 let subs = self.subs.clone();
18901 let (scope_capture, atomic_arrays, atomic_hashes) =
18902 self.scope.capture_with_atomics();
18903 let pmap_progress = PmapProgress::new(progress, v.len());
18904 v = v
18905 .into_par_iter()
18906 .filter_map(|item| {
18907 let mut local_interp = VMHelper::new();
18908 local_interp.subs = subs.clone();
18909 local_interp.scope.restore_capture(&scope_capture);
18910 local_interp
18911 .scope
18912 .restore_atomics(&atomic_arrays, &atomic_hashes);
18913 local_interp.enable_parallel_guard();
18914 local_interp.scope.set_topic(item.clone());
18915 local_interp.scope_push_hook();
18916 let keep = match local_interp.exec_block_no_scope(&sub.body) {
18917 Ok(val) => val.is_true(),
18918 Err(_) => false,
18919 };
18920 local_interp.scope_pop_hook();
18921 pmap_progress.tick();
18922 if keep {
18923 Some(item)
18924 } else {
18925 None
18926 }
18927 })
18928 .collect();
18929 pmap_progress.finish();
18930 }
18931 PipelineOp::PFor { sub, progress } => {
18932 let subs = self.subs.clone();
18933 let (scope_capture, atomic_arrays, atomic_hashes) =
18934 self.scope.capture_with_atomics();
18935 let pmap_progress = PmapProgress::new(progress, v.len());
18936 let first_err: Arc<Mutex<Option<StrykeError>>> = Arc::new(Mutex::new(None));
18937 v.clone().into_par_iter().for_each(|item| {
18938 if first_err.lock().is_some() {
18939 return;
18940 }
18941 let mut local_interp = VMHelper::new();
18942 local_interp.subs = subs.clone();
18943 local_interp.scope.restore_capture(&scope_capture);
18944 local_interp
18945 .scope
18946 .restore_atomics(&atomic_arrays, &atomic_hashes);
18947 local_interp.enable_parallel_guard();
18948 local_interp.scope.set_topic(item);
18949 local_interp.scope_push_hook();
18950 match local_interp.exec_block_no_scope(&sub.body) {
18951 Ok(_) => {}
18952 Err(e) => {
18953 let stryke = match e {
18954 FlowOrError::Error(stryke) => stryke,
18955 FlowOrError::Flow(_) => StrykeError::runtime(
18956 "return/last/next/redo not supported inside pipeline pfor block",
18957 line,
18958 ),
18959 };
18960 let mut g = first_err.lock();
18961 if g.is_none() {
18962 *g = Some(stryke);
18963 }
18964 }
18965 }
18966 local_interp.scope_pop_hook();
18967 pmap_progress.tick();
18968 });
18969 pmap_progress.finish();
18970 let pfor_err = first_err.lock().take();
18971 if let Some(e) = pfor_err {
18972 return Err(e);
18973 }
18974 }
18975 PipelineOp::PMapChunked {
18976 chunk,
18977 sub,
18978 progress,
18979 } => {
18980 let chunk_n = chunk.max(1) as usize;
18981 let subs = self.subs.clone();
18982 let (scope_capture, atomic_arrays, atomic_hashes) =
18983 self.scope.capture_with_atomics();
18984 let indexed_chunks: Vec<(usize, Vec<StrykeValue>)> = v
18985 .chunks(chunk_n)
18986 .enumerate()
18987 .map(|(i, c)| (i, c.to_vec()))
18988 .collect();
18989 let n_chunks = indexed_chunks.len();
18990 let pmap_progress = PmapProgress::new(progress, n_chunks);
18991 let mut chunk_results: Vec<(usize, Vec<StrykeValue>)> = indexed_chunks
18992 .into_par_iter()
18993 .map(|(chunk_idx, chunk)| {
18994 let mut local_interp = VMHelper::new();
18995 local_interp.subs = subs.clone();
18996 local_interp.scope.restore_capture(&scope_capture);
18997 local_interp
18998 .scope
18999 .restore_atomics(&atomic_arrays, &atomic_hashes);
19000 local_interp.enable_parallel_guard();
19001 let mut out = Vec::with_capacity(chunk.len());
19002 for item in chunk {
19003 local_interp.scope.set_topic(item);
19004 local_interp.scope_push_hook();
19005 match local_interp.exec_block_no_scope(&sub.body) {
19006 Ok(val) => {
19007 local_interp.scope_pop_hook();
19008 out.push(val);
19009 }
19010 Err(_) => {
19011 local_interp.scope_pop_hook();
19012 out.push(StrykeValue::UNDEF);
19013 }
19014 }
19015 }
19016 pmap_progress.tick();
19017 (chunk_idx, out)
19018 })
19019 .collect();
19020 pmap_progress.finish();
19021 chunk_results.sort_by_key(|(i, _)| *i);
19022 v = chunk_results.into_iter().flat_map(|(_, x)| x).collect();
19023 }
19024 PipelineOp::PSort { cmp, progress } => {
19025 let pmap_progress = PmapProgress::new(progress, 2);
19026 pmap_progress.tick();
19027 match cmp {
19028 Some(cmp_block) => {
19029 if let Some(mode) = detect_sort_block_fast(&cmp_block.body) {
19030 v.par_sort_by(|a, b| sort_magic_cmp(a, b, mode));
19031 } else {
19032 let subs = self.subs.clone();
19033 let scope_capture = self.scope.capture();
19034 v.par_sort_by(|a, b| {
19035 let mut local_interp = VMHelper::new();
19036 local_interp.subs = subs.clone();
19037 local_interp.scope.restore_capture(&scope_capture);
19038 local_interp.enable_parallel_guard();
19039 local_interp.scope.set_sort_pair(a.clone(), b.clone());
19040 local_interp.scope_push_hook();
19041 let ord =
19042 match local_interp.exec_block_no_scope(&cmp_block.body) {
19043 Ok(v) => {
19044 let n = v.to_int();
19045 if n < 0 {
19046 std::cmp::Ordering::Less
19047 } else if n > 0 {
19048 std::cmp::Ordering::Greater
19049 } else {
19050 std::cmp::Ordering::Equal
19051 }
19052 }
19053 Err(_) => std::cmp::Ordering::Equal,
19054 };
19055 local_interp.scope_pop_hook();
19056 ord
19057 });
19058 }
19059 }
19060 None => {
19061 v.par_sort_by(|a, b| a.to_string().cmp(&b.to_string()));
19062 }
19063 }
19064 pmap_progress.tick();
19065 pmap_progress.finish();
19066 }
19067 PipelineOp::PCache { sub, progress } => {
19068 let subs = self.subs.clone();
19069 let scope_capture = self.scope.capture();
19070 let cache = &*crate::pcache::GLOBAL_PCACHE;
19071 let pmap_progress = PmapProgress::new(progress, v.len());
19072 v = v
19073 .into_par_iter()
19074 .map(|item| {
19075 let k = crate::pcache::cache_key(&item);
19076 if let Some(cached) = cache.get(&k) {
19077 pmap_progress.tick();
19078 return cached.clone();
19079 }
19080 let mut local_interp = VMHelper::new();
19081 local_interp.subs = subs.clone();
19082 local_interp.scope.restore_capture(&scope_capture);
19083 local_interp.enable_parallel_guard();
19084 local_interp.scope.set_topic(item.clone());
19085 local_interp.scope_push_hook();
19086 let val = match local_interp.exec_block_no_scope(&sub.body) {
19087 Ok(v) => v,
19088 Err(_) => StrykeValue::UNDEF,
19089 };
19090 local_interp.scope_pop_hook();
19091 cache.insert(k, val.clone());
19092 pmap_progress.tick();
19093 val
19094 })
19095 .collect();
19096 pmap_progress.finish();
19097 }
19098 PipelineOp::PReduce { sub, progress } => {
19099 if v.is_empty() {
19100 return Ok(StrykeValue::UNDEF);
19101 }
19102 if v.len() == 1 {
19103 return Ok(v.into_iter().next().unwrap());
19104 }
19105 let block = sub.body.clone();
19106 let subs = self.subs.clone();
19107 let scope_capture = self.scope.capture();
19108 let pmap_progress = PmapProgress::new(progress, v.len());
19109 let result = v
19110 .into_par_iter()
19111 .map(|x| {
19112 pmap_progress.tick();
19113 x
19114 })
19115 .reduce_with(|a, b| {
19116 let mut local_interp = VMHelper::new();
19117 local_interp.subs = subs.clone();
19118 local_interp.scope.restore_capture(&scope_capture);
19119 local_interp.enable_parallel_guard();
19120 local_interp.scope.set_sort_pair(a, b);
19121 match local_interp.exec_block(&block) {
19122 Ok(val) => val,
19123 Err(_) => StrykeValue::UNDEF,
19124 }
19125 });
19126 pmap_progress.finish();
19127 return Ok(result.unwrap_or(StrykeValue::UNDEF));
19128 }
19129 PipelineOp::PReduceInit {
19130 init,
19131 sub,
19132 progress,
19133 } => {
19134 if v.is_empty() {
19135 return Ok(init);
19136 }
19137 let block = sub.body.clone();
19138 let subs = self.subs.clone();
19139 let scope_capture = self.scope.capture();
19140 let cap: &[(String, StrykeValue)] = scope_capture.as_slice();
19141 if v.len() == 1 {
19142 return Ok(fold_preduce_init_step(
19143 &subs,
19144 cap,
19145 &block,
19146 preduce_init_fold_identity(&init),
19147 v.into_iter().next().unwrap(),
19148 ));
19149 }
19150 let pmap_progress = PmapProgress::new(progress, v.len());
19151 let result = v
19152 .into_par_iter()
19153 .fold(
19154 || preduce_init_fold_identity(&init),
19155 |acc, item| {
19156 pmap_progress.tick();
19157 fold_preduce_init_step(&subs, cap, &block, acc, item)
19158 },
19159 )
19160 .reduce(
19161 || preduce_init_fold_identity(&init),
19162 |a, b| merge_preduce_init_partials(a, b, &block, &subs, cap),
19163 );
19164 pmap_progress.finish();
19165 return Ok(result);
19166 }
19167 PipelineOp::PMapReduce {
19168 map,
19169 reduce,
19170 progress,
19171 } => {
19172 if v.is_empty() {
19173 return Ok(StrykeValue::UNDEF);
19174 }
19175 let map_block = map.body.clone();
19176 let reduce_block = reduce.body.clone();
19177 let subs = self.subs.clone();
19178 let scope_capture = self.scope.capture();
19179 if v.len() == 1 {
19180 let mut local_interp = VMHelper::new();
19181 local_interp.subs = subs.clone();
19182 local_interp.scope.restore_capture(&scope_capture);
19183 local_interp.scope.set_topic(v[0].clone());
19184 return match local_interp.exec_block_no_scope(&map_block) {
19185 Ok(val) => Ok(val),
19186 Err(_) => Ok(StrykeValue::UNDEF),
19187 };
19188 }
19189 let pmap_progress = PmapProgress::new(progress, v.len());
19190 let result = v
19191 .into_par_iter()
19192 .map(|item| {
19193 let mut local_interp = VMHelper::new();
19194 local_interp.subs = subs.clone();
19195 local_interp.scope.restore_capture(&scope_capture);
19196 local_interp.scope.set_topic(item);
19197 let val = match local_interp.exec_block_no_scope(&map_block) {
19198 Ok(val) => val,
19199 Err(_) => StrykeValue::UNDEF,
19200 };
19201 pmap_progress.tick();
19202 val
19203 })
19204 .reduce_with(|a, b| {
19205 let mut local_interp = VMHelper::new();
19206 local_interp.subs = subs.clone();
19207 local_interp.scope.restore_capture(&scope_capture);
19208 local_interp.scope.set_sort_pair(a, b);
19209 match local_interp.exec_block_no_scope(&reduce_block) {
19210 Ok(val) => val,
19211 Err(_) => StrykeValue::UNDEF,
19212 }
19213 });
19214 pmap_progress.finish();
19215 return Ok(result.unwrap_or(StrykeValue::UNDEF));
19216 }
19217 }
19218 }
19219 Ok(StrykeValue::array(v))
19220 }
19221
19222 fn pipeline_collect_streaming(
19225 &mut self,
19226 source: Vec<StrykeValue>,
19227 ops: &[PipelineOp],
19228 workers_per_stage: usize,
19229 buffer: usize,
19230 line: usize,
19231 ) -> StrykeResult<StrykeValue> {
19232 use crossbeam::channel::{bounded, Receiver, Sender};
19233
19234 for op in ops {
19236 match op {
19237 PipelineOp::PSort { .. }
19238 | PipelineOp::PReduce { .. }
19239 | PipelineOp::PReduceInit { .. }
19240 | PipelineOp::PMapReduce { .. }
19241 | PipelineOp::PMapChunked { .. } => {
19242 return Err(StrykeError::runtime(
19243 format!(
19244 "par_pipeline_stream: {:?} requires all items and cannot stream; use par_pipeline instead",
19245 std::mem::discriminant(op)
19246 ),
19247 line,
19248 ));
19249 }
19250 _ => {}
19251 }
19252 }
19253
19254 let streamable_ops: Vec<&PipelineOp> = ops.iter().collect();
19257 if streamable_ops.is_empty() {
19258 return Ok(StrykeValue::array(source));
19259 }
19260
19261 let n_stages = streamable_ops.len();
19262 let wn = if workers_per_stage > 0 {
19263 workers_per_stage
19264 } else {
19265 self.parallel_thread_count()
19266 };
19267 let subs = self.subs.clone();
19268 let (capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
19269
19270 let mut channels: Vec<(Sender<StrykeValue>, Receiver<StrykeValue>)> =
19275 (0..=n_stages).map(|_| bounded(buffer)).collect();
19276
19277 let err: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
19278 let take_done: Arc<std::sync::atomic::AtomicBool> =
19279 Arc::new(std::sync::atomic::AtomicBool::new(false));
19280
19281 let source_tx = channels[0].0.clone();
19284 let result_rx = channels[n_stages].1.clone();
19285 let results: Arc<Mutex<Vec<StrykeValue>>> = Arc::new(Mutex::new(Vec::new()));
19286
19287 std::thread::scope(|scope| {
19288 let result_rx_c = result_rx.clone();
19291 let results_c = Arc::clone(&results);
19292 scope.spawn(move || {
19293 while let Ok(item) = result_rx_c.recv() {
19294 results_c.lock().push(item);
19295 }
19296 });
19297
19298 let err_s = Arc::clone(&err);
19300 let take_done_s = Arc::clone(&take_done);
19301 scope.spawn(move || {
19302 for item in source {
19303 if err_s.lock().is_some()
19304 || take_done_s.load(std::sync::atomic::Ordering::Relaxed)
19305 {
19306 break;
19307 }
19308 if source_tx.send(item).is_err() {
19309 break;
19310 }
19311 }
19312 });
19313
19314 for (stage_idx, op) in streamable_ops.iter().enumerate() {
19316 let rx = channels[stage_idx].1.clone();
19317 let tx = channels[stage_idx + 1].0.clone();
19318
19319 for _ in 0..wn {
19320 let rx = rx.clone();
19321 let tx = tx.clone();
19322 let subs = subs.clone();
19323 let capture = capture.clone();
19324 let atomic_arrays = atomic_arrays.clone();
19325 let atomic_hashes = atomic_hashes.clone();
19326 let err_w = Arc::clone(&err);
19327 let take_done_w = Arc::clone(&take_done);
19328
19329 match *op {
19330 PipelineOp::Filter(ref sub) | PipelineOp::PGrep { ref sub, .. } => {
19331 let sub = Arc::clone(sub);
19332 scope.spawn(move || {
19333 while let Ok(item) = rx.recv() {
19334 if err_w.lock().is_some() {
19335 break;
19336 }
19337 let mut interp = VMHelper::new();
19338 interp.subs = subs.clone();
19339 interp.scope.restore_capture(&capture);
19340 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
19341 interp.enable_parallel_guard();
19342 interp.scope.set_topic(item.clone());
19343 interp.scope_push_hook();
19344 let keep = match interp.exec_block_no_scope(&sub.body) {
19345 Ok(val) => val.is_true(),
19346 Err(_) => false,
19347 };
19348 interp.scope_pop_hook();
19349 if keep && tx.send(item).is_err() {
19350 break;
19351 }
19352 }
19353 });
19354 }
19355 PipelineOp::Map(ref sub) | PipelineOp::PMap { ref sub, .. } => {
19356 let sub = Arc::clone(sub);
19357 scope.spawn(move || {
19358 while let Ok(item) = rx.recv() {
19359 if err_w.lock().is_some() {
19360 break;
19361 }
19362 let mut interp = VMHelper::new();
19363 interp.subs = subs.clone();
19364 interp.scope.restore_capture(&capture);
19365 interp.scope.restore_atomics(&atomic_arrays, &atomic_hashes);
19366 interp.enable_parallel_guard();
19367 interp.scope.set_topic(item);
19368 interp.scope_push_hook();
19369 let mapped = match interp.exec_block_no_scope(&sub.body) {
19370 Ok(val) => val,
19371 Err(_) => StrykeValue::UNDEF,
19372 };
19373 interp.scope_pop_hook();
19374 if tx.send(mapped).is_err() {
19375 break;
19376 }
19377 }
19378 });
19379 }
19380 PipelineOp::Take(n) => {
19381 let limit = (*n).max(0) as usize;
19382 let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
19383 let count_w = Arc::clone(&count);
19384 scope.spawn(move || {
19385 while let Ok(item) = rx.recv() {
19386 let prev =
19387 count_w.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
19388 if prev >= limit {
19389 take_done_w
19390 .store(true, std::sync::atomic::Ordering::Relaxed);
19391 break;
19392 }
19393 if tx.send(item).is_err() {
19394 break;
19395 }
19396 }
19397 });
19398 break;
19400 }
19401 PipelineOp::PFor { ref sub, .. } => {
19402 let sub = Arc::clone(sub);
19403 scope.spawn(move || {
19404 while let Ok(item) = rx.recv() {
19405 if err_w.lock().is_some() {
19406 break;
19407 }
19408 let mut interp = VMHelper::new();
19409 interp.subs = subs.clone();
19410 interp.scope.restore_capture(&capture);
19411 interp
19412 .scope
19413 .restore_atomics(&atomic_arrays, &atomic_hashes);
19414 interp.enable_parallel_guard();
19415 interp.scope.set_topic(item.clone());
19416 interp.scope_push_hook();
19417 match interp.exec_block_no_scope(&sub.body) {
19418 Ok(_) => {}
19419 Err(e) => {
19420 let msg = match e {
19421 FlowOrError::Error(stryke) => stryke.to_string(),
19422 FlowOrError::Flow(_) => {
19423 "unexpected control flow in par_pipeline_stream pfor".into()
19424 }
19425 };
19426 let mut g = err_w.lock();
19427 if g.is_none() {
19428 *g = Some(msg);
19429 }
19430 interp.scope_pop_hook();
19431 break;
19432 }
19433 }
19434 interp.scope_pop_hook();
19435 if tx.send(item).is_err() {
19436 break;
19437 }
19438 }
19439 });
19440 }
19441 PipelineOp::Tap(ref sub) => {
19442 let sub = Arc::clone(sub);
19443 scope.spawn(move || {
19444 while let Ok(item) = rx.recv() {
19445 if err_w.lock().is_some() {
19446 break;
19447 }
19448 let mut interp = VMHelper::new();
19449 interp.subs = subs.clone();
19450 interp.scope.restore_capture(&capture);
19451 interp
19452 .scope
19453 .restore_atomics(&atomic_arrays, &atomic_hashes);
19454 interp.enable_parallel_guard();
19455 match interp.call_sub(
19456 &sub,
19457 vec![item.clone()],
19458 WantarrayCtx::Void,
19459 line,
19460 )
19461 {
19462 Ok(_) => {}
19463 Err(e) => {
19464 let msg = match e {
19465 FlowOrError::Error(stryke) => stryke.to_string(),
19466 FlowOrError::Flow(_) => {
19467 "unexpected control flow in par_pipeline_stream tap"
19468 .into()
19469 }
19470 };
19471 let mut g = err_w.lock();
19472 if g.is_none() {
19473 *g = Some(msg);
19474 }
19475 break;
19476 }
19477 }
19478 if tx.send(item).is_err() {
19479 break;
19480 }
19481 }
19482 });
19483 }
19484 PipelineOp::PCache { ref sub, .. } => {
19485 let sub = Arc::clone(sub);
19486 scope.spawn(move || {
19487 while let Ok(item) = rx.recv() {
19488 if err_w.lock().is_some() {
19489 break;
19490 }
19491 let k = crate::pcache::cache_key(&item);
19492 let val = if let Some(cached) =
19493 crate::pcache::GLOBAL_PCACHE.get(&k)
19494 {
19495 cached.clone()
19496 } else {
19497 let mut interp = VMHelper::new();
19498 interp.subs = subs.clone();
19499 interp.scope.restore_capture(&capture);
19500 interp
19501 .scope
19502 .restore_atomics(&atomic_arrays, &atomic_hashes);
19503 interp.enable_parallel_guard();
19504 interp.scope.set_topic(item);
19505 interp.scope_push_hook();
19506 let v = match interp.exec_block_no_scope(&sub.body) {
19507 Ok(v) => v,
19508 Err(_) => StrykeValue::UNDEF,
19509 };
19510 interp.scope_pop_hook();
19511 crate::pcache::GLOBAL_PCACHE.insert(k, v.clone());
19512 v
19513 };
19514 if tx.send(val).is_err() {
19515 break;
19516 }
19517 }
19518 });
19519 }
19520 _ => unreachable!(),
19522 }
19523 }
19524 }
19525
19526 channels.clear();
19530 drop(result_rx);
19531 });
19532
19533 if let Some(msg) = err.lock().take() {
19534 return Err(StrykeError::runtime(msg, line));
19535 }
19536
19537 let results = std::mem::take(&mut *results.lock());
19538 Ok(StrykeValue::array(results))
19539 }
19540
19541 fn heap_compare(&mut self, cmp: &Arc<StrykeSub>, a: &StrykeValue, b: &StrykeValue) -> Ordering {
19542 self.scope_push_hook();
19543 if let Some(ref env) = cmp.closure_env {
19544 self.scope.restore_capture(env);
19545 }
19546 self.scope.set_sort_pair(a.clone(), b.clone());
19547 let ord = match self.exec_block_no_scope(&cmp.body) {
19548 Ok(v) => {
19549 let n = v.to_int();
19550 if n < 0 {
19551 Ordering::Less
19552 } else if n > 0 {
19553 Ordering::Greater
19554 } else {
19555 Ordering::Equal
19556 }
19557 }
19558 Err(_) => Ordering::Equal,
19559 };
19560 self.scope_pop_hook();
19561 ord
19562 }
19563
19564 fn heap_sift_up(&mut self, items: &mut [StrykeValue], cmp: &Arc<StrykeSub>, mut i: usize) {
19565 while i > 0 {
19566 let p = (i - 1) / 2;
19567 if self.heap_compare(cmp, &items[i], &items[p]) != Ordering::Less {
19568 break;
19569 }
19570 items.swap(i, p);
19571 i = p;
19572 }
19573 }
19574
19575 fn heap_sift_down(&mut self, items: &mut [StrykeValue], cmp: &Arc<StrykeSub>, mut i: usize) {
19576 let n = items.len();
19577 loop {
19578 let mut sm = i;
19579 let l = 2 * i + 1;
19580 let r = 2 * i + 2;
19581 if l < n && self.heap_compare(cmp, &items[l], &items[sm]) == Ordering::Less {
19582 sm = l;
19583 }
19584 if r < n && self.heap_compare(cmp, &items[r], &items[sm]) == Ordering::Less {
19585 sm = r;
19586 }
19587 if sm == i {
19588 break;
19589 }
19590 items.swap(i, sm);
19591 i = sm;
19592 }
19593 }
19594
19595 fn hash_for_signature_destruct(
19596 &mut self,
19597 v: &StrykeValue,
19598 line: usize,
19599 ) -> StrykeResult<IndexMap<String, StrykeValue>> {
19600 let Some(m) = self.match_subject_as_hash(v) else {
19601 return Err(StrykeError::runtime(
19602 format!(
19603 "sub signature hash destruct: expected HASH or HASH reference, got {}",
19604 v.ref_type()
19605 ),
19606 line,
19607 ));
19608 };
19609 Ok(m)
19610 }
19611
19612 pub(crate) fn apply_sub_signature(
19614 &mut self,
19615 sub: &StrykeSub,
19616 argv: &[StrykeValue],
19617 line: usize,
19618 ) -> StrykeResult<()> {
19619 if sub.params.is_empty() {
19620 return Ok(());
19621 }
19622 let mut i = 0usize;
19623 for p in &sub.params {
19624 match p {
19625 SubSigParam::Scalar(name, ty, default) => {
19626 let val = if i < argv.len() {
19627 argv[i].clone()
19628 } else if let Some(default_expr) = default {
19629 match self.eval_expr(default_expr) {
19630 Ok(v) => v,
19631 Err(FlowOrError::Error(e)) => return Err(e),
19632 Err(FlowOrError::Flow(_)) => {
19633 return Err(StrykeError::runtime(
19634 "unexpected control flow in parameter default",
19635 line,
19636 ))
19637 }
19638 }
19639 } else {
19640 StrykeValue::UNDEF
19641 };
19642 i += 1;
19643 if let Some(t) = ty {
19644 if let Err(e) = t.check_value(&val) {
19645 return Err(StrykeError::runtime(
19646 format!("sub parameter ${}: {}", name, e),
19647 line,
19648 ));
19649 }
19650 }
19651 let n = self.english_scalar_name(name);
19652 self.scope.declare_scalar(n, val);
19653 }
19654 SubSigParam::Array(name, default) => {
19655 let rest: Vec<StrykeValue> = if i < argv.len() {
19656 let r = argv[i..].to_vec();
19657 i = argv.len();
19658 r
19659 } else if let Some(default_expr) = default {
19660 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19661 Ok(v) => v,
19662 Err(FlowOrError::Error(e)) => return Err(e),
19663 Err(FlowOrError::Flow(_)) => {
19664 return Err(StrykeError::runtime(
19665 "unexpected control flow in parameter default",
19666 line,
19667 ))
19668 }
19669 };
19670 val.to_list()
19671 } else {
19672 vec![]
19673 };
19674 let aname = self.stash_array_name_for_package(name);
19675 self.scope.declare_array(&aname, rest);
19676 }
19677 SubSigParam::Hash(name, default) => {
19678 let rest: Vec<StrykeValue> = if i < argv.len() {
19679 let r = argv[i..].to_vec();
19680 i = argv.len();
19681 r
19682 } else if let Some(default_expr) = default {
19683 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
19684 Ok(v) => v,
19685 Err(FlowOrError::Error(e)) => return Err(e),
19686 Err(FlowOrError::Flow(_)) => {
19687 return Err(StrykeError::runtime(
19688 "unexpected control flow in parameter default",
19689 line,
19690 ))
19691 }
19692 };
19693 val.to_list()
19694 } else {
19695 vec![]
19696 };
19697 let mut map = IndexMap::new();
19698 let mut j = 0;
19699 while j + 1 < rest.len() {
19700 map.insert(rest[j].to_string(), rest[j + 1].clone());
19701 j += 2;
19702 }
19703 self.scope.declare_hash(name, map);
19704 }
19705 SubSigParam::ArrayDestruct(elems) => {
19706 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19707 i += 1;
19708 let Some(arr) = self.match_subject_as_array(&arg) else {
19709 return Err(StrykeError::runtime(
19710 format!(
19711 "sub signature array destruct: expected ARRAY or ARRAY reference, got {}",
19712 arg.ref_type()
19713 ),
19714 line,
19715 ));
19716 };
19717 let binds = self
19718 .match_array_pattern_elems(&arr, elems, line)
19719 .map_err(|e| match e {
19720 FlowOrError::Error(stryke) => stryke,
19721 FlowOrError::Flow(_) => StrykeError::runtime(
19722 "unexpected flow in sub signature array destruct",
19723 line,
19724 ),
19725 })?;
19726 let Some(binds) = binds else {
19727 return Err(StrykeError::runtime(
19728 "sub signature array destruct: length or element mismatch",
19729 line,
19730 ));
19731 };
19732 for b in binds {
19733 match b {
19734 PatternBinding::Scalar(name, v) => {
19735 let n = self.english_scalar_name(&name);
19736 self.scope.declare_scalar(n, v);
19737 }
19738 PatternBinding::Array(name, elems) => {
19739 self.scope.declare_array(&name, elems);
19740 }
19741 }
19742 }
19743 }
19744 SubSigParam::HashDestruct(pairs) => {
19745 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
19746 i += 1;
19747 let map = self.hash_for_signature_destruct(&arg, line)?;
19748 for (key, varname) in pairs {
19749 let v = map.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
19750 let n = self.english_scalar_name(varname);
19751 self.scope.declare_scalar(n, v);
19752 }
19753 }
19754 }
19755 }
19756 Ok(())
19757 }
19758
19759 pub(crate) fn try_hof_dispatch(
19763 &mut self,
19764 sub: &StrykeSub,
19765 args: &[StrykeValue],
19766 want: WantarrayCtx,
19767 line: usize,
19768 ) -> Option<ExecResult> {
19769 let env = sub.closure_env.as_ref()?;
19770 fn env_get<'a>(env: &'a [(String, StrykeValue)], key: &str) -> Option<&'a StrykeValue> {
19771 env.iter().find(|(k, _)| k == key).map(|(_, v)| v)
19772 }
19773
19774 match sub.name.as_str() {
19775 "__comp__" => {
19777 let fns = env_get(env, "__comp_fns__")?.to_list();
19778 let mut val = args.first().cloned().unwrap_or(StrykeValue::UNDEF);
19779 for f in fns.iter().rev() {
19780 match self.dispatch_indirect_call(f.clone(), vec![val], want, line) {
19781 Ok(v) => val = v,
19782 Err(e) => return Some(Err(e)),
19783 }
19784 }
19785 Some(Ok(val))
19786 }
19787 "__constantly__" => Some(Ok(env_get(env, "__const_val__")?.clone())),
19789 "__juxt__" => {
19791 let fns = env_get(env, "__juxt_fns__")?.to_list();
19792 let mut results = Vec::with_capacity(fns.len());
19793 for f in &fns {
19794 match self.dispatch_indirect_call(f.clone(), args.to_vec(), want, line) {
19795 Ok(v) => results.push(v),
19796 Err(e) => return Some(Err(e)),
19797 }
19798 }
19799 Some(Ok(StrykeValue::array(results)))
19800 }
19801 "__partial__" => {
19803 let fn_val = env_get(env, "__partial_fn__")?.clone();
19804 let bound = env_get(env, "__partial_args__")?.to_list();
19805 let mut all_args = bound;
19806 all_args.extend_from_slice(args);
19807 Some(self.dispatch_indirect_call(fn_val, all_args, want, line))
19808 }
19809 "__complement__" => {
19811 let fn_val = env_get(env, "__complement_fn__")?.clone();
19812 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19813 Ok(v) => Some(Ok(StrykeValue::integer(if v.is_true() { 0 } else { 1 }))),
19814 Err(e) => Some(Err(e)),
19815 }
19816 }
19817 "__fnil__" => {
19819 let fn_val = env_get(env, "__fnil_fn__")?.clone();
19820 let defaults = env_get(env, "__fnil_defaults__")?.to_list();
19821 let mut patched = args.to_vec();
19822 for (i, d) in defaults.iter().enumerate() {
19823 if i < patched.len() {
19824 if patched[i].is_undef() {
19825 patched[i] = d.clone();
19826 }
19827 } else {
19828 patched.push(d.clone());
19829 }
19830 }
19831 Some(self.dispatch_indirect_call(fn_val, patched, want, line))
19832 }
19833 "__memoize__" => {
19835 let fn_val = env_get(env, "__memoize_fn__")?.clone();
19836 let cache_ref = env_get(env, "__memoize_cache__")?.clone();
19837 let key = args
19838 .iter()
19839 .map(|a| a.to_string())
19840 .collect::<Vec<_>>()
19841 .join("\x00");
19842 if let Some(href) = cache_ref.as_hash_ref() {
19843 if let Some(cached) = href.read().get(&key) {
19844 return Some(Ok(cached.clone()));
19845 }
19846 }
19847 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19848 Ok(v) => {
19849 if let Some(href) = cache_ref.as_hash_ref() {
19850 href.write().insert(key, v.clone());
19851 }
19852 Some(Ok(v))
19853 }
19854 Err(e) => Some(Err(e)),
19855 }
19856 }
19857 "__curry__" => {
19859 let fn_val = env_get(env, "__curry_fn__")?.clone();
19860 let arity = env_get(env, "__curry_arity__")?.to_int() as usize;
19861 let bound = env_get(env, "__curry_bound__")?.to_list();
19862 let mut all = bound;
19863 all.extend_from_slice(args);
19864 if all.len() >= arity {
19865 Some(self.dispatch_indirect_call(fn_val, all, want, line))
19866 } else {
19867 let curry_sub = StrykeSub {
19868 name: "__curry__".to_string(),
19869 params: vec![],
19870 body: vec![],
19871 closure_env: Some(vec![
19872 ("__curry_fn__".to_string(), fn_val),
19873 (
19874 "__curry_arity__".to_string(),
19875 StrykeValue::integer(arity as i64),
19876 ),
19877 ("__curry_bound__".to_string(), StrykeValue::array(all)),
19878 ]),
19879 prototype: None,
19880 fib_like: None,
19881 };
19882 Some(Ok(StrykeValue::code_ref(Arc::new(curry_sub))))
19883 }
19884 }
19885 "__once__" => {
19887 let cache_ref = env_get(env, "__once_cache__")?.clone();
19888 if let Some(href) = cache_ref.as_hash_ref() {
19889 let r = href.read();
19890 if r.contains_key("done") {
19891 return Some(Ok(r.get("val").cloned().unwrap_or(StrykeValue::UNDEF)));
19892 }
19893 }
19894 let fn_val = env_get(env, "__once_fn__")?.clone();
19895 match self.dispatch_indirect_call(fn_val, args.to_vec(), want, line) {
19896 Ok(v) => {
19897 if let Some(href) = cache_ref.as_hash_ref() {
19898 let mut w = href.write();
19899 w.insert("done".to_string(), StrykeValue::integer(1));
19900 w.insert("val".to_string(), v.clone());
19901 }
19902 Some(Ok(v))
19903 }
19904 Err(e) => Some(Err(e)),
19905 }
19906 }
19907 _ => None,
19908 }
19909 }
19910
19911 pub(crate) fn call_sub(
19912 &mut self,
19913 sub: &StrykeSub,
19914 args: Vec<StrykeValue>,
19915 want: WantarrayCtx,
19916 line: usize,
19917 ) -> ExecResult {
19918 let pkg = sub.name.rsplit_once("::").map(|(p, _)| p.to_string());
19921 self.call_sub_with_package(sub, args, want, line, pkg)
19922 }
19923
19924 fn call_sub_with_package(
19928 &mut self,
19929 sub: &StrykeSub,
19930 args: Vec<StrykeValue>,
19931 want: WantarrayCtx,
19932 _line: usize,
19933 home_package: Option<String>,
19934 ) -> ExecResult {
19935 self.current_sub_stack.push(Arc::new(sub.clone()));
19937
19938 self.scope_push_hook();
19941 self.scope.declare_array("_", args.clone());
19942 if let Some(ref env) = sub.closure_env {
19943 self.scope.restore_capture(env);
19944 }
19945 if let Some(pkg) = home_package {
19951 self.scope
19952 .declare_scalar("__PACKAGE__", StrykeValue::string(pkg));
19953 }
19954 self.scope.set_closure_args(&args);
19958 let argv = self.scope.take_sub_underscore().unwrap_or_default();
19960 self.apply_sub_signature(sub, &argv, _line)?;
19961 let saved = self.wantarray_kind;
19962 self.wantarray_kind = want;
19963 if let Some(r) = self.try_hof_dispatch(sub, &argv, want, _line) {
19964 self.wantarray_kind = saved;
19965 self.scope_pop_hook();
19966 self.current_sub_stack.pop();
19967 return match r {
19968 Ok(v) => Ok(v),
19969 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
19970 Err(e) => Err(e),
19971 };
19972 }
19973 if let Some(pat) = sub.fib_like.as_ref() {
19974 if argv.len() == 1 {
19975 if let Some(n0) = argv.first().and_then(|v| v.as_integer()) {
19976 let t0 = self.profiler.is_some().then(std::time::Instant::now);
19977 if let Some(p) = &mut self.profiler {
19978 p.enter_sub(&sub.name);
19979 }
19980 self.debugger_enter_sub(&sub.name);
19981 let n = crate::fib_like_tail::eval_fib_like_recursive_add(n0, pat);
19982 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
19983 p.exit_sub(t0.elapsed());
19984 }
19985 self.debugger_leave_sub();
19986 self.wantarray_kind = saved;
19987 self.scope_pop_hook();
19988 self.current_sub_stack.pop();
19989 return Ok(StrykeValue::integer(n));
19990 }
19991 }
19992 }
19993 self.scope.declare_array("_", argv.clone());
19994 let t0 = self.profiler.is_some().then(std::time::Instant::now);
19997 if let Some(p) = &mut self.profiler {
19998 p.enter_sub(&sub.name);
19999 }
20000 self.debugger_enter_sub(&sub.name);
20001 let result = self.exec_block_no_scope_with_tail(&sub.body, WantarrayCtx::List);
20005 if let (Some(p), Some(t0)) = (&mut self.profiler, t0) {
20006 p.exit_sub(t0.elapsed());
20007 }
20008 self.debugger_leave_sub();
20009 let goto_args = if matches!(result, Err(FlowOrError::Flow(Flow::GotoSub(_)))) {
20011 Some(self.scope.get_array("_"))
20012 } else {
20013 None
20014 };
20015 self.wantarray_kind = saved;
20016 self.scope_pop_hook();
20017 self.current_sub_stack.pop();
20018 match result {
20019 Ok(v) => Ok(v),
20020 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
20021 Err(FlowOrError::Flow(Flow::GotoSub(target_name))) => {
20022 let goto_args = goto_args.unwrap_or_default();
20024 let fqn = if target_name.contains("::") {
20025 target_name.clone()
20026 } else {
20027 format!("{}::{}", self.current_package(), target_name)
20028 };
20029 if let Some(target_sub) = self
20030 .subs
20031 .get(&fqn)
20032 .cloned()
20033 .or_else(|| self.subs.get(&target_name).cloned())
20034 {
20035 self.call_sub(&target_sub, goto_args, want, _line)
20036 } else {
20037 Err(StrykeError::runtime(
20038 format!("Undefined subroutine &{}", target_name),
20039 _line,
20040 )
20041 .into())
20042 }
20043 }
20044 Err(FlowOrError::Flow(Flow::Yield(_))) => {
20045 Err(StrykeError::runtime("yield is only valid inside gen { }", 0).into())
20046 }
20047 Err(e) => Err(e),
20048 }
20049 }
20050
20051 fn call_struct_method(
20053 &mut self,
20054 body: &Block,
20055 params: &[SubSigParam],
20056 args: Vec<StrykeValue>,
20057 line: usize,
20058 ) -> ExecResult {
20059 self.scope_push_hook();
20060 self.scope.declare_array("_", args.clone());
20061 if let Some(self_val) = args.first() {
20063 self.scope.declare_scalar("self", self_val.clone());
20064 }
20065 if args.len() > 1 {
20071 self.scope.set_closure_args(&args[1..]);
20072 }
20073 let user_args: Vec<StrykeValue> = args.iter().skip(1).cloned().collect();
20075 self.apply_params_to_argv(params, &user_args, line)?;
20076 let result = self.exec_block_no_scope(body);
20077 self.scope_pop_hook();
20078 match result {
20079 Ok(v) => Ok(v),
20080 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
20081 Err(e) => Err(e),
20082 }
20083 }
20084
20085 pub(crate) fn call_class_method(
20087 &mut self,
20088 body: &Block,
20089 params: &[SubSigParam],
20090 args: Vec<StrykeValue>,
20091 line: usize,
20092 ) -> ExecResult {
20093 self.call_class_method_inner(body, params, args, line, false)
20094 }
20095
20096 pub(crate) fn call_static_class_method(
20098 &mut self,
20099 body: &Block,
20100 params: &[SubSigParam],
20101 args: Vec<StrykeValue>,
20102 line: usize,
20103 ) -> ExecResult {
20104 self.call_class_method_inner(body, params, args, line, true)
20105 }
20106
20107 fn call_class_method_inner(
20108 &mut self,
20109 body: &Block,
20110 params: &[SubSigParam],
20111 args: Vec<StrykeValue>,
20112 line: usize,
20113 is_static: bool,
20114 ) -> ExecResult {
20115 self.scope_push_hook();
20116 self.scope.declare_array("_", args.clone());
20117 if !is_static {
20118 if let Some(self_val) = args.first() {
20120 self.scope.declare_scalar("self", self_val.clone());
20121 }
20122 }
20123 if is_static {
20130 self.scope.set_closure_args(&args);
20131 } else if args.len() > 1 {
20132 self.scope.set_closure_args(&args[1..]);
20133 }
20134 let user_args: Vec<StrykeValue> = if is_static {
20136 args.clone()
20137 } else {
20138 args.iter().skip(1).cloned().collect()
20139 };
20140 self.apply_params_to_argv(params, &user_args, line)?;
20141 let result = self.exec_block_no_scope(body);
20142 self.scope_pop_hook();
20143 match result {
20144 Ok(v) => Ok(v),
20145 Err(FlowOrError::Flow(Flow::Return(v))) => Ok(v),
20146 Err(e) => Err(e),
20147 }
20148 }
20149
20150 fn apply_params_to_argv(
20152 &mut self,
20153 params: &[SubSigParam],
20154 argv: &[StrykeValue],
20155 line: usize,
20156 ) -> StrykeResult<()> {
20157 let mut i = 0;
20158 for param in params {
20159 match param {
20160 SubSigParam::Scalar(name, ty_opt, default) => {
20161 let v = if i < argv.len() {
20162 argv[i].clone()
20163 } else if let Some(default_expr) = default {
20164 match self.eval_expr(default_expr) {
20165 Ok(v) => v,
20166 Err(FlowOrError::Error(e)) => return Err(e),
20167 Err(FlowOrError::Flow(_)) => {
20168 return Err(StrykeError::runtime(
20169 "unexpected control flow in parameter default",
20170 line,
20171 ))
20172 }
20173 }
20174 } else {
20175 StrykeValue::UNDEF
20176 };
20177 i += 1;
20178 if let Some(ty) = ty_opt {
20179 ty.check_value(&v).map_err(|msg| {
20180 StrykeError::type_error(
20181 format!("method parameter ${}: {}", name, msg),
20182 line,
20183 )
20184 })?;
20185 }
20186 let n = self.english_scalar_name(name);
20187 self.scope.declare_scalar(n, v);
20188 }
20189 SubSigParam::Array(name, default) => {
20190 let rest: Vec<StrykeValue> = if i < argv.len() {
20191 let r = argv[i..].to_vec();
20192 i = argv.len();
20193 r
20194 } else if let Some(default_expr) = default {
20195 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
20196 Ok(v) => v,
20197 Err(FlowOrError::Error(e)) => return Err(e),
20198 Err(FlowOrError::Flow(_)) => {
20199 return Err(StrykeError::runtime(
20200 "unexpected control flow in parameter default",
20201 line,
20202 ))
20203 }
20204 };
20205 val.to_list()
20206 } else {
20207 vec![]
20208 };
20209 let aname = self.stash_array_name_for_package(name);
20210 self.scope.declare_array(&aname, rest);
20211 }
20212 SubSigParam::Hash(name, default) => {
20213 let rest: Vec<StrykeValue> = if i < argv.len() {
20214 let r = argv[i..].to_vec();
20215 i = argv.len();
20216 r
20217 } else if let Some(default_expr) = default {
20218 let val = match self.eval_expr_ctx(default_expr, WantarrayCtx::List) {
20219 Ok(v) => v,
20220 Err(FlowOrError::Error(e)) => return Err(e),
20221 Err(FlowOrError::Flow(_)) => {
20222 return Err(StrykeError::runtime(
20223 "unexpected control flow in parameter default",
20224 line,
20225 ))
20226 }
20227 };
20228 val.to_list()
20229 } else {
20230 vec![]
20231 };
20232 let mut map = IndexMap::new();
20233 let mut j = 0;
20234 while j + 1 < rest.len() {
20235 map.insert(rest[j].to_string(), rest[j + 1].clone());
20236 j += 2;
20237 }
20238 self.scope.declare_hash(name, map);
20239 }
20240 SubSigParam::ArrayDestruct(elems) => {
20241 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
20242 i += 1;
20243 let Some(arr) = self.match_subject_as_array(&arg) else {
20244 return Err(StrykeError::runtime(
20245 format!("method parameter: expected ARRAY, got {}", arg.ref_type()),
20246 line,
20247 ));
20248 };
20249 let binds = self
20250 .match_array_pattern_elems(&arr, elems, line)
20251 .map_err(|e| match e {
20252 FlowOrError::Error(stryke) => stryke,
20253 FlowOrError::Flow(_) => StrykeError::runtime(
20254 "unexpected flow in method array destruct",
20255 line,
20256 ),
20257 })?;
20258 let Some(binds) = binds else {
20259 return Err(StrykeError::runtime(
20260 format!(
20261 "method parameter: array destructure failed at position {}",
20262 i
20263 ),
20264 line,
20265 ));
20266 };
20267 for b in binds {
20268 match b {
20269 PatternBinding::Scalar(name, v) => {
20270 let n = self.english_scalar_name(&name);
20271 self.scope.declare_scalar(n, v);
20272 }
20273 PatternBinding::Array(name, elems) => {
20274 self.scope.declare_array(&name, elems);
20275 }
20276 }
20277 }
20278 }
20279 SubSigParam::HashDestruct(pairs) => {
20280 let arg = argv.get(i).cloned().unwrap_or(StrykeValue::UNDEF);
20281 i += 1;
20282 let map = self.hash_for_signature_destruct(&arg, line)?;
20283 for (key, varname) in pairs {
20284 let v = map.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
20285 let n = self.english_scalar_name(varname);
20286 self.scope.declare_scalar(n, v);
20287 }
20288 }
20289 }
20290 }
20291 Ok(())
20292 }
20293
20294 fn builtin_new(&mut self, class: &str, args: Vec<StrykeValue>, line: usize) -> ExecResult {
20295 if class == "Set" {
20296 return Ok(crate::value::set_from_elements(args.into_iter().skip(1)));
20297 }
20298 if let Some(def) = self.struct_defs.get(class).cloned() {
20299 let mut provided = Vec::new();
20300 let mut i = 1;
20301 while i + 1 < args.len() {
20302 let k = args[i].to_string();
20303 let v = args[i + 1].clone();
20304 provided.push((k, v));
20305 i += 2;
20306 }
20307 let mut defaults = Vec::with_capacity(def.fields.len());
20308 for field in &def.fields {
20309 if let Some(ref expr) = field.default {
20310 let val = self.eval_expr(expr)?;
20311 defaults.push(Some(val));
20312 } else {
20313 defaults.push(None);
20314 }
20315 }
20316 return Ok(crate::native_data::struct_new_with_defaults(
20317 &def, &provided, &defaults, line,
20318 )?);
20319 }
20320 if let Some(def) = self.class_defs.get(class).cloned() {
20329 let user_args: Vec<StrykeValue> = args.into_iter().skip(1).collect();
20330 return self.class_construct(&def, user_args, line);
20331 }
20332 let mut map = IndexMap::new();
20334 let mut i = 1; while i + 1 < args.len() {
20336 let k = args[i].to_string();
20337 let v = args[i + 1].clone();
20338 map.insert(k, v);
20339 i += 2;
20340 }
20341 Ok(StrykeValue::blessed(Arc::new(
20342 crate::value::BlessedRef::new_blessed(class.to_string(), StrykeValue::hash(map)),
20343 )))
20344 }
20345
20346 fn exec_print(
20347 &mut self,
20348 handle: Option<&str>,
20349 args: &[Expr],
20350 newline: bool,
20351 line: usize,
20352 ) -> ExecResult {
20353 if newline && (self.feature_bits & FEAT_SAY) == 0 {
20354 return Err(StrykeError::runtime(
20355 "say() is disabled (enable with use feature 'say' or use feature ':5.10')",
20356 line,
20357 )
20358 .into());
20359 }
20360 let mut output = String::new();
20361 if args.is_empty() {
20362 let topic = self.scope.get_scalar("_").clone();
20364 let s = self.stringify_value(topic, line)?;
20365 output.push_str(&s);
20366 } else {
20367 for (i, a) in args.iter().enumerate() {
20370 if i > 0 {
20371 output.push_str(&self.ofs);
20372 }
20373 let val = self.eval_expr_ctx(a, WantarrayCtx::List)?;
20374 for item in val.to_list() {
20375 let s = self.stringify_value(item, line)?;
20376 output.push_str(&s);
20377 }
20378 }
20379 }
20380 if newline {
20381 output.push('\n');
20382 }
20383 output.push_str(&self.ors);
20384
20385 let handle_name =
20386 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
20387 self.write_formatted_print(handle_name.as_str(), &output, line)?;
20388 Ok(StrykeValue::integer(1))
20389 }
20390
20391 fn exec_printf(&mut self, handle: Option<&str>, args: &[Expr], line: usize) -> ExecResult {
20392 let (fmt, rest): (String, &[Expr]) = if args.is_empty() {
20393 let s = self.stringify_value(self.scope.get_scalar("_").clone(), line)?;
20395 (s, &[])
20396 } else {
20397 (self.eval_expr(&args[0])?.to_string(), &args[1..])
20398 };
20399 let mut arg_vals = Vec::new();
20403 for a in rest {
20404 let v = self.eval_expr_ctx(a, WantarrayCtx::List)?;
20405 if let Some(items) = v.as_array_vec() {
20406 arg_vals.extend(items);
20407 } else {
20408 arg_vals.push(v);
20409 }
20410 }
20411 let output = self.perl_sprintf_stringify(&fmt, &arg_vals, line)?;
20412 let handle_name =
20413 self.resolve_io_handle_name(handle.unwrap_or(self.default_print_handle.as_str()));
20414 match handle_name.as_str() {
20415 "STDOUT" => {
20416 if !self.suppress_stdout {
20417 print!("{}", output);
20418 if self.output_autoflush {
20419 let _ = io::stdout().flush();
20420 }
20421 }
20422 }
20423 "STDERR" => {
20424 eprint!("{}", output);
20425 let _ = io::stderr().flush();
20426 }
20427 name => {
20428 if let Some(writer) = self.output_handles.get_mut(name) {
20429 let _ = writer.write_all(output.as_bytes());
20430 if self.output_autoflush {
20431 let _ = writer.flush();
20432 }
20433 }
20434 }
20435 }
20436 Ok(StrykeValue::integer(1))
20437 }
20438
20439 pub(crate) fn eval_substr_expr(
20441 &mut self,
20442 string: &Expr,
20443 offset: &Expr,
20444 length: Option<&Expr>,
20445 replacement: Option<&Expr>,
20446 _line: usize,
20447 ) -> Result<StrykeValue, FlowOrError> {
20448 let s = self.eval_expr(string)?.to_string();
20449 let off = self.eval_expr(offset)?.to_int();
20450 let start = if off < 0 {
20451 (s.len() as i64 + off).max(0) as usize
20452 } else {
20453 off as usize
20454 };
20455 let len = if let Some(l) = length {
20456 let len_val = self.eval_expr(l)?.to_int();
20457 if len_val < 0 {
20458 let remaining = s.len().saturating_sub(start) as i64;
20460 (remaining + len_val).max(0) as usize
20461 } else {
20462 len_val as usize
20463 }
20464 } else {
20465 s.len().saturating_sub(start)
20466 };
20467 let end = start.saturating_add(len).min(s.len());
20468 let result = s.get(start..end).unwrap_or("").to_string();
20469 if let Some(rep) = replacement {
20470 let rep_s = self.eval_expr(rep)?.to_string();
20471 let mut new_s = String::new();
20472 new_s.push_str(&s[..start]);
20473 new_s.push_str(&rep_s);
20474 new_s.push_str(&s[end..]);
20475 self.assign_value(string, StrykeValue::string(new_s))?;
20476 }
20477 Ok(StrykeValue::string(result))
20478 }
20479
20480 pub(crate) fn eval_push_expr(
20481 &mut self,
20482 array: &Expr,
20483 values: &[Expr],
20484 line: usize,
20485 ) -> Result<StrykeValue, FlowOrError> {
20486 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20487 for v in values {
20488 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20489 self.push_array_deref_value(aref.clone(), val, line)?;
20490 }
20491 let len = self.array_deref_len(aref, line)?;
20492 return Ok(StrykeValue::integer(len));
20493 }
20494 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20495 if self.scope.is_array_frozen(&arr_name) {
20496 return Err(StrykeError::runtime(
20497 format!("Modification of a frozen value: @{}", arr_name),
20498 line,
20499 )
20500 .into());
20501 }
20502 for v in values {
20503 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20504 if let Some(items) = val.as_array_vec() {
20505 for item in items {
20506 self.scope
20507 .push_to_array(&arr_name, item)
20508 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20509 }
20510 } else {
20511 self.scope
20512 .push_to_array(&arr_name, val)
20513 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20514 }
20515 }
20516 let len = self.scope.array_len(&arr_name);
20517 Ok(StrykeValue::integer(len as i64))
20518 }
20519
20520 pub(crate) fn eval_pop_expr(
20521 &mut self,
20522 array: &Expr,
20523 line: usize,
20524 ) -> Result<StrykeValue, FlowOrError> {
20525 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20526 return self.pop_array_deref(aref, line);
20527 }
20528 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20529 self.scope
20530 .pop_from_array(&arr_name)
20531 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20532 }
20533
20534 pub(crate) fn eval_shift_expr(
20535 &mut self,
20536 array: &Expr,
20537 line: usize,
20538 ) -> Result<StrykeValue, FlowOrError> {
20539 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20540 return self.shift_array_deref(aref, line);
20541 }
20542 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20543 self.scope
20544 .shift_from_array(&arr_name)
20545 .map_err(|e| FlowOrError::Error(e.at_line(line)))
20546 }
20547
20548 pub(crate) fn eval_unshift_expr(
20549 &mut self,
20550 array: &Expr,
20551 values: &[Expr],
20552 line: usize,
20553 ) -> Result<StrykeValue, FlowOrError> {
20554 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20555 let mut vals = Vec::new();
20556 for v in values {
20557 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20558 if let Some(items) = val.as_array_vec() {
20559 vals.extend(items);
20560 } else {
20561 vals.push(val);
20562 }
20563 }
20564 let len = self.unshift_array_deref_multi(aref, vals, line)?;
20565 return Ok(StrykeValue::integer(len));
20566 }
20567 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20568 let mut vals = Vec::new();
20569 for v in values {
20570 let val = self.eval_expr_ctx(v, WantarrayCtx::List)?;
20571 if let Some(items) = val.as_array_vec() {
20572 vals.extend(items);
20573 } else {
20574 vals.push(val);
20575 }
20576 }
20577 let arr = self
20578 .scope
20579 .get_array_mut(&arr_name)
20580 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20581 for (i, v) in vals.into_iter().enumerate() {
20582 arr.insert(i, v);
20583 }
20584 let len = arr.len();
20585 Ok(StrykeValue::integer(len as i64))
20586 }
20587
20588 pub(crate) fn push_array_deref_value(
20590 &mut self,
20591 arr_ref: StrykeValue,
20592 val: StrykeValue,
20593 line: usize,
20594 ) -> Result<(), FlowOrError> {
20595 let val = self.scope.resolve_container_binding_ref(val);
20598 if let Some(r) = arr_ref.as_array_ref() {
20599 let mut w = r.write();
20600 if let Some(items) = val.as_array_vec() {
20601 w.extend(items.iter().cloned());
20602 } else {
20603 w.push(val);
20604 }
20605 return Ok(());
20606 }
20607 if let Some(name) = arr_ref.as_array_binding_name() {
20608 if let Some(items) = val.as_array_vec() {
20609 for item in items {
20610 self.scope
20611 .push_to_array(&name, item)
20612 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20613 }
20614 } else {
20615 self.scope
20616 .push_to_array(&name, val)
20617 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20618 }
20619 return Ok(());
20620 }
20621 if let Some(s) = arr_ref.as_str() {
20622 if self.strict_refs {
20623 return Err(StrykeError::runtime(
20624 format!(
20625 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20626 s
20627 ),
20628 line,
20629 )
20630 .into());
20631 }
20632 let name = s.to_string();
20633 if let Some(items) = val.as_array_vec() {
20634 for item in items {
20635 self.scope
20636 .push_to_array(&name, item)
20637 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20638 }
20639 } else {
20640 self.scope
20641 .push_to_array(&name, val)
20642 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20643 }
20644 return Ok(());
20645 }
20646 Err(StrykeError::runtime("push argument is not an ARRAY reference", line).into())
20647 }
20648
20649 pub(crate) fn array_deref_len(
20650 &self,
20651 arr_ref: StrykeValue,
20652 line: usize,
20653 ) -> Result<i64, FlowOrError> {
20654 if let Some(r) = arr_ref.as_array_ref() {
20655 return Ok(r.read().len() as i64);
20656 }
20657 if let Some(name) = arr_ref.as_array_binding_name() {
20658 return Ok(self.scope.array_len(&name) as i64);
20659 }
20660 if let Some(s) = arr_ref.as_str() {
20661 if self.strict_refs {
20662 return Err(StrykeError::runtime(
20663 format!(
20664 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20665 s
20666 ),
20667 line,
20668 )
20669 .into());
20670 }
20671 return Ok(self.scope.array_len(&s) as i64);
20672 }
20673 Err(StrykeError::runtime("argument is not an ARRAY reference", line).into())
20674 }
20675
20676 pub(crate) fn pop_array_deref(
20677 &mut self,
20678 arr_ref: StrykeValue,
20679 line: usize,
20680 ) -> Result<StrykeValue, FlowOrError> {
20681 if let Some(r) = arr_ref.as_array_ref() {
20682 let mut w = r.write();
20683 return Ok(w.pop().unwrap_or(StrykeValue::UNDEF));
20684 }
20685 if let Some(name) = arr_ref.as_array_binding_name() {
20686 return self
20687 .scope
20688 .pop_from_array(&name)
20689 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20690 }
20691 if let Some(s) = arr_ref.as_str() {
20692 if self.strict_refs {
20693 return Err(StrykeError::runtime(
20694 format!(
20695 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20696 s
20697 ),
20698 line,
20699 )
20700 .into());
20701 }
20702 return self
20703 .scope
20704 .pop_from_array(&s)
20705 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20706 }
20707 Err(StrykeError::runtime("pop argument is not an ARRAY reference", line).into())
20708 }
20709
20710 pub(crate) fn shift_array_deref(
20711 &mut self,
20712 arr_ref: StrykeValue,
20713 line: usize,
20714 ) -> Result<StrykeValue, FlowOrError> {
20715 if let Some(r) = arr_ref.as_array_ref() {
20716 let mut w = r.write();
20717 return Ok(if w.is_empty() {
20718 StrykeValue::UNDEF
20719 } else {
20720 w.remove(0)
20721 });
20722 }
20723 if let Some(name) = arr_ref.as_array_binding_name() {
20724 return self
20725 .scope
20726 .shift_from_array(&name)
20727 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20728 }
20729 if let Some(s) = arr_ref.as_str() {
20730 if self.strict_refs {
20731 return Err(StrykeError::runtime(
20732 format!(
20733 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20734 s
20735 ),
20736 line,
20737 )
20738 .into());
20739 }
20740 return self
20741 .scope
20742 .shift_from_array(&s)
20743 .map_err(|e| FlowOrError::Error(e.at_line(line)));
20744 }
20745 Err(StrykeError::runtime("shift argument is not an ARRAY reference", line).into())
20746 }
20747
20748 pub(crate) fn unshift_array_deref_multi(
20749 &mut self,
20750 arr_ref: StrykeValue,
20751 vals: Vec<StrykeValue>,
20752 line: usize,
20753 ) -> Result<i64, FlowOrError> {
20754 let mut flat: Vec<StrykeValue> = Vec::new();
20755 for v in vals {
20756 if let Some(items) = v.as_array_vec() {
20757 flat.extend(items);
20758 } else {
20759 flat.push(v);
20760 }
20761 }
20762 if let Some(r) = arr_ref.as_array_ref() {
20763 let mut w = r.write();
20764 for (i, v) in flat.into_iter().enumerate() {
20765 w.insert(i, v);
20766 }
20767 return Ok(w.len() as i64);
20768 }
20769 if let Some(name) = arr_ref.as_array_binding_name() {
20770 let arr = self
20771 .scope
20772 .get_array_mut(&name)
20773 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20774 for (i, v) in flat.into_iter().enumerate() {
20775 arr.insert(i, v);
20776 }
20777 return Ok(arr.len() as i64);
20778 }
20779 if let Some(s) = arr_ref.as_str() {
20780 if self.strict_refs {
20781 return Err(StrykeError::runtime(
20782 format!(
20783 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20784 s
20785 ),
20786 line,
20787 )
20788 .into());
20789 }
20790 let name = s.to_string();
20791 let arr = self
20792 .scope
20793 .get_array_mut(&name)
20794 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20795 for (i, v) in flat.into_iter().enumerate() {
20796 arr.insert(i, v);
20797 }
20798 return Ok(arr.len() as i64);
20799 }
20800 Err(StrykeError::runtime("unshift argument is not an ARRAY reference", line).into())
20801 }
20802
20803 pub(crate) fn splice_array_deref(
20806 &mut self,
20807 aref: StrykeValue,
20808 offset_val: StrykeValue,
20809 length_val: StrykeValue,
20810 rep_vals: Vec<StrykeValue>,
20811 line: usize,
20812 ) -> Result<StrykeValue, FlowOrError> {
20813 let ctx = self.wantarray_kind;
20814 if let Some(r) = aref.as_array_ref() {
20815 let arr_len = r.read().len();
20816 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20817 let mut w = r.write();
20818 let removed: Vec<StrykeValue> = w.drain(off..end).collect();
20819 for (i, v) in rep_vals.into_iter().enumerate() {
20820 w.insert(off + i, v);
20821 }
20822 return Ok(match ctx {
20823 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20824 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20825 });
20826 }
20827 if let Some(name) = aref.as_array_binding_name() {
20828 let arr_len = self.scope.array_len(&name);
20829 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20830 let removed = self
20831 .scope
20832 .splice_in_place(&name, off, end, rep_vals)
20833 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20834 return Ok(match ctx {
20835 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20836 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20837 });
20838 }
20839 if let Some(s) = aref.as_str() {
20840 if self.strict_refs {
20841 return Err(StrykeError::runtime(
20842 format!(
20843 "Can't use string (\"{}\") as an ARRAY ref while \"strict refs\" in use",
20844 s
20845 ),
20846 line,
20847 )
20848 .into());
20849 }
20850 let arr_len = self.scope.array_len(&s);
20851 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20852 let removed = self
20853 .scope
20854 .splice_in_place(&s, off, end, rep_vals)
20855 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20856 return Ok(match ctx {
20857 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20858 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20859 });
20860 }
20861 Err(StrykeError::runtime("splice argument is not an ARRAY reference", line).into())
20862 }
20863
20864 fn eval_splice_replacement(
20869 &mut self,
20870 replacement: &[Expr],
20871 ) -> Result<Vec<StrykeValue>, FlowOrError> {
20872 let saved = self.wantarray_kind;
20873 self.wantarray_kind = WantarrayCtx::List;
20874 let mut out = Vec::new();
20875 for r in replacement {
20876 let v = self.eval_expr_ctx(r, WantarrayCtx::List)?;
20877 if let Some(items) = v.as_array_vec() {
20878 out.extend(items);
20879 } else if v.is_iterator() {
20880 out.extend(v.into_iterator().collect_all());
20881 } else {
20882 out.push(v);
20883 }
20884 }
20885 self.wantarray_kind = saved;
20886 Ok(out)
20887 }
20888
20889 pub(crate) fn eval_splice_expr(
20890 &mut self,
20891 array: &Expr,
20892 offset: Option<&Expr>,
20893 length: Option<&Expr>,
20894 replacement: &[Expr],
20895 ctx: WantarrayCtx,
20896 line: usize,
20897 ) -> Result<StrykeValue, FlowOrError> {
20898 if let Some(aref) = self.try_eval_array_deref_container(array)? {
20899 let offset_val = if let Some(o) = offset {
20900 self.eval_expr(o)?
20901 } else {
20902 StrykeValue::integer(0)
20903 };
20904 let length_val = if let Some(l) = length {
20905 self.eval_expr(l)?
20906 } else {
20907 StrykeValue::UNDEF
20908 };
20909 let rep_vals = self.eval_splice_replacement(replacement)?;
20910 let saved = self.wantarray_kind;
20911 self.wantarray_kind = ctx;
20912 let out = self.splice_array_deref(aref, offset_val, length_val, rep_vals, line);
20913 self.wantarray_kind = saved;
20914 return out;
20915 }
20916 let arr_name = self.extract_array_name(Self::peel_array_builtin_operand(array))?;
20917 let arr_len = self.scope.array_len(&arr_name);
20918 let offset_val = if let Some(o) = offset {
20919 self.eval_expr(o)?
20920 } else {
20921 StrykeValue::integer(0)
20922 };
20923 let length_val = if let Some(l) = length {
20924 self.eval_expr(l)?
20925 } else {
20926 StrykeValue::UNDEF
20927 };
20928 let (off, end) = splice_compute_range(arr_len, &offset_val, &length_val);
20929 let rep_vals = self.eval_splice_replacement(replacement)?;
20930 let removed = self
20931 .scope
20932 .splice_in_place(&arr_name, off, end, rep_vals)
20933 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
20934 Ok(match ctx {
20935 WantarrayCtx::Scalar => removed.last().cloned().unwrap_or(StrykeValue::UNDEF),
20936 WantarrayCtx::List | WantarrayCtx::Void => StrykeValue::array(removed),
20937 })
20938 }
20939
20940 pub(crate) fn keys_from_value(
20942 val: StrykeValue,
20943 line: usize,
20944 ) -> Result<StrykeValue, FlowOrError> {
20945 if let Some(h) = val.as_hash_map() {
20946 Ok(StrykeValue::array(
20947 h.keys().map(|k| StrykeValue::string(k.clone())).collect(),
20948 ))
20949 } else if let Some(r) = val.as_hash_ref() {
20950 Ok(StrykeValue::array(
20951 r.read()
20952 .keys()
20953 .map(|k| StrykeValue::string(k.clone()))
20954 .collect(),
20955 ))
20956 } else {
20957 Err(StrykeError::runtime("keys requires hash", line).into())
20958 }
20959 }
20960
20961 pub(crate) fn eval_keys_expr(
20962 &mut self,
20963 expr: &Expr,
20964 line: usize,
20965 ) -> Result<StrykeValue, FlowOrError> {
20966 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
20969 Self::keys_from_value(val, line)
20970 }
20971
20972 pub(crate) fn values_from_value(
20974 val: StrykeValue,
20975 line: usize,
20976 ) -> Result<StrykeValue, FlowOrError> {
20977 if let Some(h) = val.as_hash_map() {
20978 Ok(StrykeValue::array(h.values().cloned().collect()))
20979 } else if let Some(r) = val.as_hash_ref() {
20980 Ok(StrykeValue::array(r.read().values().cloned().collect()))
20981 } else {
20982 Err(StrykeError::runtime("values requires hash", line).into())
20983 }
20984 }
20985
20986 pub(crate) fn eval_values_expr(
20987 &mut self,
20988 expr: &Expr,
20989 line: usize,
20990 ) -> Result<StrykeValue, FlowOrError> {
20991 let val = self.eval_expr_ctx(expr, WantarrayCtx::List)?;
20992 Self::values_from_value(val, line)
20993 }
20994
20995 pub(crate) fn eval_delete_operand(
20996 &mut self,
20997 expr: &Expr,
20998 line: usize,
20999 ) -> Result<StrykeValue, FlowOrError> {
21000 match &expr.kind {
21001 ExprKind::HashElement { hash, key } => {
21002 let k = self.eval_expr(key)?.to_string();
21003 self.touch_env_hash(hash);
21004 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
21005 let class = obj
21006 .as_blessed_ref()
21007 .map(|b| b.class.clone())
21008 .unwrap_or_default();
21009 let full = format!("{}::DELETE", class);
21010 if let Some(sub) = self.subs.get(&full).cloned() {
21011 return self.call_sub(
21012 &sub,
21013 vec![obj, StrykeValue::string(k)],
21014 WantarrayCtx::Scalar,
21015 line,
21016 );
21017 }
21018 }
21019 self.scope
21020 .delete_hash_element(hash, &k)
21021 .map_err(|e| FlowOrError::Error(e.at_line(line)))
21022 }
21023 ExprKind::ArrayElement { array, index } => {
21024 self.check_strict_array_var(array, line)?;
21025 let idx = self.eval_expr(index)?.to_int();
21026 let aname = self.stash_array_name_for_package(array);
21027 self.scope
21028 .delete_array_element(&aname, idx)
21029 .map_err(|e| FlowOrError::Error(e.at_line(line)))
21030 }
21031 ExprKind::ArrowDeref {
21032 expr: inner,
21033 index,
21034 kind: DerefKind::Hash,
21035 } => {
21036 let k = self.eval_expr(index)?.to_string();
21037 let container = self.eval_expr(inner)?;
21038 self.delete_arrow_hash_element(container, &k, line)
21039 .map_err(Into::into)
21040 }
21041 ExprKind::ArrowDeref {
21042 expr: inner,
21043 index,
21044 kind: DerefKind::Array,
21045 } => {
21046 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
21047 return Err(StrykeError::runtime(
21048 "delete on array element needs scalar subscript",
21049 line,
21050 )
21051 .into());
21052 }
21053 let container = self.eval_expr(inner)?;
21054 let idx = self.eval_expr(index)?.to_int();
21055 self.delete_arrow_array_element(container, idx, line)
21056 .map_err(Into::into)
21057 }
21058 ExprKind::HashSlice { hash, keys } => {
21061 self.touch_env_hash(hash);
21062 let mut all_keys: Vec<String> = Vec::new();
21063 for key_expr in keys {
21064 all_keys.extend(self.eval_hash_slice_key_components(key_expr)?);
21065 }
21066 let mut deleted = Vec::with_capacity(all_keys.len());
21067 for k in &all_keys {
21068 let v = self
21069 .scope
21070 .delete_hash_element(hash, k)
21071 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
21072 deleted.push(v);
21073 }
21074 Ok(StrykeValue::array(deleted))
21075 }
21076 ExprKind::ArraySlice { array, indices } => {
21078 self.check_strict_array_var(array, line)?;
21079 let aname = self.stash_array_name_for_package(array);
21080 let mut all_idx: Vec<i64> = Vec::new();
21081 for idx_expr in indices {
21082 let v = self.eval_expr_ctx(idx_expr, WantarrayCtx::List)?;
21083 if let Some(items) = v.as_array_vec() {
21084 all_idx.extend(items.iter().map(|x| x.to_int()));
21085 } else if let Some(r) = v.as_array_ref() {
21086 all_idx.extend(r.read().iter().map(|x| x.to_int()));
21087 } else if v.is_iterator() {
21088 all_idx.extend(v.into_iterator().collect_all().iter().map(|x| x.to_int()));
21089 } else {
21090 all_idx.push(v.to_int());
21091 }
21092 }
21093 let mut deleted = Vec::with_capacity(all_idx.len());
21094 for idx in &all_idx {
21095 let v = self
21096 .scope
21097 .delete_array_element(&aname, *idx)
21098 .map_err(|e| FlowOrError::Error(e.at_line(line)))?;
21099 deleted.push(v);
21100 }
21101 Ok(StrykeValue::array(deleted))
21102 }
21103 _ => Err(StrykeError::runtime("delete requires hash or array element", line).into()),
21104 }
21105 }
21106
21107 fn eval_expr_exists_mode(&mut self, expr: &Expr) -> Result<StrykeValue, FlowOrError> {
21113 match &expr.kind {
21114 ExprKind::ArrowDeref {
21115 expr: inner,
21116 index,
21117 kind: DerefKind::Hash,
21118 } => {
21119 let inner_val = self.eval_expr_exists_mode(inner)?;
21120 if inner_val.is_undef() {
21121 return Ok(StrykeValue::UNDEF);
21122 }
21123 if let Some(r) = inner_val.as_hash_ref() {
21124 let k = self.eval_expr(index)?.to_string();
21125 return Ok(r.read().get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
21126 }
21127 if let Some(b) = inner_val.as_blessed_ref() {
21128 let data = b.data.read();
21129 if let Some(r) = data.as_hash_ref() {
21130 let k = self.eval_expr(index)?.to_string();
21131 return Ok(r.read().get(&k).cloned().unwrap_or(StrykeValue::UNDEF));
21132 }
21133 }
21134 if let Some(s) = inner_val.as_struct_inst() {
21138 let k = self.eval_expr(index)?.to_string();
21139 if let Some(idx) = s.def.field_index(&k) {
21140 return Ok(s.get_field(idx).unwrap_or(StrykeValue::UNDEF));
21141 }
21142 return Ok(StrykeValue::UNDEF);
21143 }
21144 if let Some(c) = inner_val.as_class_inst() {
21145 let k = self.eval_expr(index)?.to_string();
21146 if let Some(idx) = c.def.field_index(&k) {
21147 return Ok(c.get_field(idx).unwrap_or(StrykeValue::UNDEF));
21148 }
21149 return Ok(StrykeValue::UNDEF);
21150 }
21151 Ok(StrykeValue::UNDEF)
21152 }
21153 ExprKind::ArrowDeref {
21154 expr: inner,
21155 index,
21156 kind: DerefKind::Array,
21157 } => {
21158 let inner_val = self.eval_expr_exists_mode(inner)?;
21159 if inner_val.is_undef() {
21160 return Ok(StrykeValue::UNDEF);
21161 }
21162 if let Some(r) = inner_val.as_array_ref() {
21163 let idx = self.eval_expr(index)?.to_int();
21164 let arr = r.read();
21165 let i = if idx < 0 {
21166 (arr.len() as i64 + idx).max(0) as usize
21167 } else {
21168 idx as usize
21169 };
21170 return Ok(arr.get(i).cloned().unwrap_or(StrykeValue::UNDEF));
21171 }
21172 Ok(StrykeValue::UNDEF)
21173 }
21174 _ => self.eval_expr(expr),
21175 }
21176 }
21177
21178 pub(crate) fn eval_exists_operand(
21179 &mut self,
21180 expr: &Expr,
21181 line: usize,
21182 ) -> Result<StrykeValue, FlowOrError> {
21183 match &expr.kind {
21184 ExprKind::HashElement { hash, key } => {
21185 let k = self.eval_expr(key)?.to_string();
21186 self.touch_env_hash(hash);
21187 if let Some(obj) = self.tied_hashes.get(hash).cloned() {
21188 let class = obj
21189 .as_blessed_ref()
21190 .map(|b| b.class.clone())
21191 .unwrap_or_default();
21192 let full = format!("{}::EXISTS", class);
21193 if let Some(sub) = self.subs.get(&full).cloned() {
21194 return self.call_sub(
21195 &sub,
21196 vec![obj, StrykeValue::string(k)],
21197 WantarrayCtx::Scalar,
21198 line,
21199 );
21200 }
21201 }
21202 Ok(StrykeValue::integer(
21203 if self.scope.exists_hash_element(hash, &k) {
21204 1
21205 } else {
21206 0
21207 },
21208 ))
21209 }
21210 ExprKind::ArrayElement { array, index } => {
21211 self.check_strict_array_var(array, line)?;
21212 let idx = self.eval_expr(index)?.to_int();
21213 let aname = self.stash_array_name_for_package(array);
21214 Ok(StrykeValue::integer(
21215 if self.scope.exists_array_element(&aname, idx) {
21216 1
21217 } else {
21218 0
21219 },
21220 ))
21221 }
21222 ExprKind::ArrowDeref {
21223 expr: inner,
21224 index,
21225 kind: DerefKind::Hash,
21226 } => {
21227 let k = self.eval_expr(index)?.to_string();
21228 let container = match self.eval_expr_exists_mode(inner) {
21233 Ok(v) => v,
21234 Err(_) => return Ok(StrykeValue::integer(0)),
21235 };
21236 if container.is_undef() {
21237 return Ok(StrykeValue::integer(0));
21238 }
21239 let yes = self.exists_arrow_hash_element(container, &k, line)?;
21240 Ok(StrykeValue::integer(if yes { 1 } else { 0 }))
21241 }
21242 ExprKind::ArrowDeref {
21243 expr: inner,
21244 index,
21245 kind: DerefKind::Array,
21246 } => {
21247 if !crate::compiler::arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
21248 return Err(StrykeError::runtime(
21249 "exists on array element needs scalar subscript",
21250 line,
21251 )
21252 .into());
21253 }
21254 let container = match self.eval_expr_exists_mode(inner) {
21255 Ok(v) => v,
21256 Err(_) => return Ok(StrykeValue::integer(0)),
21257 };
21258 if container.is_undef() {
21259 return Ok(StrykeValue::integer(0));
21260 }
21261 let idx = self.eval_expr(index)?.to_int();
21262 let yes = self.exists_arrow_array_element(container, idx, line)?;
21263 Ok(StrykeValue::integer(if yes { 1 } else { 0 }))
21264 }
21265 ExprKind::SubroutineRef(name) => {
21266 let resolved = self.resolve_sub_by_name(name);
21270 Ok(StrykeValue::integer(if resolved.is_some() { 1 } else { 0 }))
21271 }
21272 _ => Err(StrykeError::runtime("exists requires hash or array element", line).into()),
21273 }
21274 }
21275
21276 pub(crate) fn eval_pmap_remote(
21284 &mut self,
21285 cluster_pv: StrykeValue,
21286 list_pv: StrykeValue,
21287 show_progress: bool,
21288 block: &Block,
21289 flat_outputs: bool,
21290 line: usize,
21291 ) -> Result<StrykeValue, FlowOrError> {
21292 let Some(cluster) = cluster_pv.as_remote_cluster() else {
21293 return Err(StrykeError::runtime("pmap_on: expected cluster(...) value", line).into());
21294 };
21295 let items = list_pv.to_list();
21296 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21297 if !atomic_arrays.is_empty() || !atomic_hashes.is_empty() {
21298 return Err(StrykeError::runtime(
21299 "pmap_on: mysync/atomic capture is not supported for remote workers",
21300 line,
21301 )
21302 .into());
21303 }
21304 let cap_json = crate::remote_wire::capture_entries_to_json(&scope_capture)
21305 .map_err(|e| StrykeError::runtime(e, line))?;
21306 let subs_prelude = crate::remote_wire::build_subs_prelude(&self.subs);
21307 let block_src = crate::fmt::format_block(block);
21308 let item_jsons = crate::cluster::perl_items_to_json(&items)
21309 .map_err(|e| StrykeError::runtime(e, line))?;
21310
21311 let pmap_progress = PmapProgress::new(show_progress, items.len());
21314 let result_values =
21315 crate::cluster::run_cluster(&cluster, subs_prelude, block_src, cap_json, item_jsons)
21316 .map_err(|e| StrykeError::runtime(format!("pmap_on remote: {e}"), line))?;
21317 for _ in 0..result_values.len() {
21318 pmap_progress.tick();
21319 }
21320 pmap_progress.finish();
21321
21322 if flat_outputs {
21323 let flattened: Vec<StrykeValue> = result_values
21324 .into_iter()
21325 .flat_map(|v| v.map_flatten_outputs(true))
21326 .collect();
21327 Ok(StrykeValue::array(flattened))
21328 } else {
21329 Ok(StrykeValue::array(result_values))
21330 }
21331 }
21332
21333 pub(crate) fn eval_par_lines_expr(
21335 &mut self,
21336 path: &Expr,
21337 callback: &Expr,
21338 progress: Option<&Expr>,
21339 line: usize,
21340 ) -> Result<StrykeValue, FlowOrError> {
21341 let show_progress = progress
21342 .map(|p| self.eval_expr(p))
21343 .transpose()?
21344 .map(|v| v.is_true())
21345 .unwrap_or(false);
21346 let raw = self.eval_expr(path)?.to_string();
21347 let path_s = self.resolve_stryke_path_string(&raw);
21348 let cb_val = self.eval_expr(callback)?;
21349 let sub = if let Some(s) = cb_val.as_code_ref() {
21350 s
21351 } else {
21352 return Err(StrykeError::runtime(
21353 "par_lines: second argument must be a code reference",
21354 line,
21355 )
21356 .into());
21357 };
21358 let subs = self.subs.clone();
21359 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21360 let file = std::fs::File::open(std::path::Path::new(&path_s)).map_err(|e| {
21361 FlowOrError::Error(StrykeError::runtime(format!("par_lines: {}", e), line))
21362 })?;
21363 let mmap = unsafe {
21364 memmap2::Mmap::map(&file).map_err(|e| {
21365 FlowOrError::Error(StrykeError::runtime(
21366 format!("par_lines: mmap: {}", e),
21367 line,
21368 ))
21369 })?
21370 };
21371 let data: &[u8] = &mmap;
21372 if data.is_empty() {
21373 return Ok(StrykeValue::UNDEF);
21374 }
21375 let line_total = crate::par_lines::line_count_bytes(data);
21376 let pmap_progress = PmapProgress::new(show_progress, line_total);
21377 if self.num_threads == 0 {
21378 self.num_threads = rayon::current_num_threads();
21379 }
21380 let num_chunks = self.num_threads.saturating_mul(8).max(1);
21381 let chunks = crate::par_lines::line_aligned_chunks(data, num_chunks);
21382 chunks.into_par_iter().try_for_each(|(start, end)| {
21383 let slice = &data[start..end];
21384 let mut s = 0usize;
21385 while s < slice.len() {
21386 let e = slice[s..]
21387 .iter()
21388 .position(|&b| b == b'\n')
21389 .map(|p| s + p)
21390 .unwrap_or(slice.len());
21391 let line_bytes = &slice[s..e];
21392 let line_str = crate::par_lines::line_to_perl_string(line_bytes);
21393 let mut local_interp = VMHelper::new();
21394 local_interp.subs = subs.clone();
21395 local_interp.scope.restore_capture(&scope_capture);
21396 local_interp
21397 .scope
21398 .restore_atomics(&atomic_arrays, &atomic_hashes);
21399 local_interp.enable_parallel_guard();
21400 local_interp.scope.set_topic(StrykeValue::string(line_str));
21401 match local_interp.call_sub(&sub, vec![], WantarrayCtx::Void, line) {
21402 Ok(_) => {}
21403 Err(e) => return Err(e),
21404 }
21405 pmap_progress.tick();
21406 if e >= slice.len() {
21407 break;
21408 }
21409 s = e + 1;
21410 }
21411 Ok(())
21412 })?;
21413 pmap_progress.finish();
21414 Ok(StrykeValue::UNDEF)
21415 }
21416
21417 pub(crate) fn eval_par_walk_expr(
21419 &mut self,
21420 path: &Expr,
21421 callback: &Expr,
21422 progress: Option<&Expr>,
21423 line: usize,
21424 ) -> Result<StrykeValue, FlowOrError> {
21425 let show_progress = progress
21426 .map(|p| self.eval_expr(p))
21427 .transpose()?
21428 .map(|v| v.is_true())
21429 .unwrap_or(false);
21430 let path_val = self.eval_expr(path)?;
21431 let roots: Vec<PathBuf> = if let Some(arr) = path_val.as_array_vec() {
21432 arr.into_iter()
21433 .map(|v| PathBuf::from(v.to_string()))
21434 .collect()
21435 } else {
21436 vec![PathBuf::from(path_val.to_string())]
21437 };
21438 let cb_val = self.eval_expr(callback)?;
21439 let sub = if let Some(s) = cb_val.as_code_ref() {
21440 s
21441 } else {
21442 return Err(StrykeError::runtime(
21443 "par_walk: second argument must be a code reference",
21444 line,
21445 )
21446 .into());
21447 };
21448 let subs = self.subs.clone();
21449 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21450
21451 if show_progress {
21452 let paths = crate::par_walk::collect_paths(&roots);
21453 let pmap_progress = PmapProgress::new(true, paths.len());
21454 paths.into_par_iter().try_for_each(|p| {
21455 let s = p.to_string_lossy().into_owned();
21456 let mut local_interp = VMHelper::new();
21457 local_interp.subs = subs.clone();
21458 local_interp.scope.restore_capture(&scope_capture);
21459 local_interp
21460 .scope
21461 .restore_atomics(&atomic_arrays, &atomic_hashes);
21462 local_interp.enable_parallel_guard();
21463 local_interp.scope.set_topic(StrykeValue::string(s));
21464 match local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line) {
21465 Ok(_) => {}
21466 Err(e) => return Err(e),
21467 }
21468 pmap_progress.tick();
21469 Ok(())
21470 })?;
21471 pmap_progress.finish();
21472 } else {
21473 for r in &roots {
21474 par_walk_recursive(
21475 r.as_path(),
21476 &sub,
21477 &subs,
21478 &scope_capture,
21479 &atomic_arrays,
21480 &atomic_hashes,
21481 line,
21482 )?;
21483 }
21484 }
21485 Ok(StrykeValue::UNDEF)
21486 }
21487
21488 pub(crate) fn builtin_par_sed(
21490 &mut self,
21491 args: &[StrykeValue],
21492 line: usize,
21493 has_progress: bool,
21494 ) -> StrykeResult<StrykeValue> {
21495 let show_progress = if has_progress {
21496 args.last().map(|v| v.is_true()).unwrap_or(false)
21497 } else {
21498 false
21499 };
21500 let slice = if has_progress {
21501 &args[..args.len().saturating_sub(1)]
21502 } else {
21503 args
21504 };
21505 if slice.len() < 3 {
21506 return Err(StrykeError::runtime(
21507 "par_sed: need pattern, replacement, and at least one file path",
21508 line,
21509 ));
21510 }
21511 let pat_val = &slice[0];
21512 let repl = slice[1].to_string();
21513 let files: Vec<String> = slice[2..].iter().map(|v| v.to_string()).collect();
21514
21515 let re = if let Some(rx) = pat_val.as_regex() {
21516 rx
21517 } else {
21518 let pattern = pat_val.to_string();
21519 match self.compile_regex(&pattern, "g", line) {
21520 Ok(r) => r,
21521 Err(FlowOrError::Error(e)) => return Err(e),
21522 Err(FlowOrError::Flow(f)) => {
21523 return Err(StrykeError::runtime(format!("par_sed: {:?}", f), line))
21524 }
21525 }
21526 };
21527
21528 let pmap = PmapProgress::new(show_progress, files.len());
21529 let touched = AtomicUsize::new(0);
21530 files.par_iter().try_for_each(|path| {
21531 let content = read_file_text_perl_compat(path)
21532 .map_err(|e| StrykeError::runtime(format!("par_sed {}: {}", path, e), line))?;
21533 let new_s = re.replace_all(&content, &repl);
21534 if new_s != content {
21535 std::fs::write(path, new_s.as_bytes())
21536 .map_err(|e| StrykeError::runtime(format!("par_sed {}: {}", path, e), line))?;
21537 touched.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
21538 }
21539 pmap.tick();
21540 Ok(())
21541 })?;
21542 pmap.finish();
21543 Ok(StrykeValue::integer(
21544 touched.load(std::sync::atomic::Ordering::Relaxed) as i64,
21545 ))
21546 }
21547
21548 pub(crate) fn eval_pwatch_expr(
21550 &mut self,
21551 path: &Expr,
21552 callback: &Expr,
21553 line: usize,
21554 ) -> Result<StrykeValue, FlowOrError> {
21555 let pattern_s = self.eval_expr(path)?.to_string();
21556 let cb_val = self.eval_expr(callback)?;
21557 let sub = if let Some(s) = cb_val.as_code_ref() {
21558 s
21559 } else {
21560 return Err(StrykeError::runtime(
21561 "pwatch: second argument must be a code reference",
21562 line,
21563 )
21564 .into());
21565 };
21566 let subs = self.subs.clone();
21567 let (scope_capture, atomic_arrays, atomic_hashes) = self.scope.capture_with_atomics();
21568 crate::pwatch::run_pwatch(
21569 &pattern_s,
21570 sub,
21571 subs,
21572 scope_capture,
21573 atomic_arrays,
21574 atomic_hashes,
21575 line,
21576 )
21577 .map_err(FlowOrError::Error)
21578 }
21579
21580 fn interpolate_replacement_string(&self, replacement: &str) -> String {
21582 let mut out = String::with_capacity(replacement.len());
21583 let chars: Vec<char> = replacement.chars().collect();
21584 let mut i = 0;
21585 while i < chars.len() {
21586 if chars[i] == '\\' && i + 1 < chars.len() {
21587 out.push(chars[i]);
21588 out.push(chars[i + 1]);
21589 i += 2;
21590 continue;
21591 }
21592 if chars[i] == '$' && i + 1 < chars.len() {
21593 let start = i;
21594 i += 1;
21595 if chars[i].is_ascii_digit() {
21596 out.push('$');
21597 while i < chars.len() && chars[i].is_ascii_digit() {
21598 out.push(chars[i]);
21599 i += 1;
21600 }
21601 continue;
21602 }
21603 if chars[i] == '&' || chars[i] == '`' || chars[i] == '\'' {
21604 out.push('$');
21605 out.push(chars[i]);
21606 i += 1;
21607 continue;
21608 }
21609 if !chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{' {
21610 out.push('$');
21611 continue;
21612 }
21613 let mut name = String::new();
21614 if chars[i] == '{' {
21615 i += 1;
21616 while i < chars.len() && chars[i] != '}' {
21617 name.push(chars[i]);
21618 i += 1;
21619 }
21620 if i < chars.len() {
21621 i += 1;
21622 }
21623 } else {
21624 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
21625 name.push(chars[i]);
21626 i += 1;
21627 }
21628 }
21629 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
21630 let val = self.scope.get_scalar(&name);
21631 out.push_str(&val.to_string());
21632 } else if !name.is_empty() {
21633 out.push_str(&replacement[start..i]);
21634 } else {
21635 out.push('$');
21636 }
21637 continue;
21638 }
21639 out.push(chars[i]);
21640 i += 1;
21641 }
21642 out
21643 }
21644
21645 fn interpolate_regex_pattern(&self, pattern: &str) -> String {
21647 let mut out = String::with_capacity(pattern.len());
21648 let chars: Vec<char> = pattern.chars().collect();
21649 let mut i = 0;
21650 while i < chars.len() {
21651 if chars[i] == '\\' && i + 1 < chars.len() {
21652 out.push(chars[i]);
21654 out.push(chars[i + 1]);
21655 i += 2;
21656 continue;
21657 }
21658 if chars[i] == '$' && i + 1 < chars.len() {
21659 i += 1;
21660 if i >= chars.len()
21662 || (!chars[i].is_alphanumeric() && chars[i] != '_' && chars[i] != '{')
21663 {
21664 out.push('$');
21665 continue;
21666 }
21667 let mut name = String::new();
21668 if chars[i] == '{' {
21669 i += 1;
21670 while i < chars.len() && chars[i] != '}' {
21671 name.push(chars[i]);
21672 i += 1;
21673 }
21674 if i < chars.len() {
21675 i += 1;
21676 } } else {
21678 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
21679 name.push(chars[i]);
21680 i += 1;
21681 }
21682 }
21683 if !name.is_empty() {
21684 let val = self.scope.get_scalar(&name);
21685 out.push_str(&val.to_string());
21686 } else {
21687 out.push('$');
21688 }
21689 continue;
21690 }
21691 out.push(chars[i]);
21692 i += 1;
21693 }
21694 out
21695 }
21696
21697 pub(crate) fn compile_regex(
21698 &mut self,
21699 pattern: &str,
21700 flags: &str,
21701 line: usize,
21702 ) -> Result<Arc<PerlCompiledRegex>, FlowOrError> {
21703 let pattern = if pattern.contains('$') || pattern.contains('@') {
21705 std::borrow::Cow::Owned(self.interpolate_regex_pattern(pattern))
21706 } else {
21707 std::borrow::Cow::Borrowed(pattern)
21708 };
21709 let pattern = pattern.as_ref();
21710 let multiline = self.multiline_match;
21713 if let Some((ref lp, ref lf, ref lm, ref lr)) = self.regex_last {
21714 if lp == pattern && lf == flags && *lm == multiline {
21715 return Ok(lr.clone());
21716 }
21717 }
21718 let key = format!("{}\x00{}\x00{}", multiline as u8, flags, pattern);
21720 if let Some(cached) = self.regex_cache.get(&key) {
21721 self.regex_last = Some((
21722 pattern.to_string(),
21723 flags.to_string(),
21724 multiline,
21725 cached.clone(),
21726 ));
21727 return Ok(cached.clone());
21728 }
21729 let expanded = expand_perl_regex_quotemeta(pattern);
21730 let expanded = expand_perl_regex_octal_escapes(&expanded);
21731 let expanded = rewrite_perl_regex_dollar_end_anchor(&expanded, flags.contains('m'));
21732 let mut re_str = String::new();
21733 if flags.contains('i') {
21734 re_str.push_str("(?i)");
21735 }
21736 if flags.contains('s') {
21737 re_str.push_str("(?s)");
21738 }
21739 if flags.contains('m') {
21740 re_str.push_str("(?m)");
21741 }
21742 if flags.contains('x') {
21743 re_str.push_str("(?x)");
21744 }
21745 if multiline {
21747 re_str.push_str("(?s)");
21748 }
21749 re_str.push_str(&expanded);
21750 let re = PerlCompiledRegex::compile(&re_str).map_err(|e| {
21751 FlowOrError::Error(StrykeError::runtime(
21752 format!("Invalid regex /{}/: {}", pattern, e),
21753 line,
21754 ))
21755 })?;
21756 let arc = re;
21757 self.regex_last = Some((
21758 pattern.to_string(),
21759 flags.to_string(),
21760 multiline,
21761 arc.clone(),
21762 ));
21763 self.regex_cache.insert(key, arc.clone());
21764 Ok(arc)
21765 }
21766
21767 pub(crate) fn die_warn_io_annotation(&self) -> Option<(String, i64)> {
21769 if self.last_readline_handle.is_empty() {
21770 return (self.line_number > 0).then_some(("<>".to_string(), self.line_number));
21771 }
21772 let n = *self
21773 .handle_line_numbers
21774 .get(&self.last_readline_handle)
21775 .unwrap_or(&0);
21776 if n <= 0 {
21777 return None;
21778 }
21779 if !self.argv_current_file.is_empty() && self.last_readline_handle == self.argv_current_file
21780 {
21781 return Some(("<>".to_string(), n));
21782 }
21783 if self.last_readline_handle == "STDIN" {
21784 return Some((self.last_stdin_die_bracket.clone(), n));
21785 }
21786 Some((format!("<{}>", self.last_readline_handle), n))
21787 }
21788
21789 pub(crate) fn die_warn_at_suffix(&self, source_line: usize) -> String {
21791 let mut s = format!(" at {} line {}", self.file, source_line);
21792 if let Some((bracket, n)) = self.die_warn_io_annotation() {
21793 s.push_str(&format!(", {} line {}.", bracket, n));
21794 } else {
21795 s.push('.');
21796 }
21797 s
21798 }
21799
21800 pub fn process_line(
21805 &mut self,
21806 line_str: &str,
21807 _program: &Program,
21808 is_last_input_line: bool,
21809 ) -> StrykeResult<Option<String>> {
21810 let chunk = self
21815 .line_mode_chunk
21816 .as_ref()
21817 .expect("process_line called without compiled chunk — execute() must run first")
21818 .clone();
21819 crate::run_line_body(&chunk, self, line_str, is_last_input_line)
21820 }
21821}
21822
21823fn both_non_numeric_strings_iv(a: &StrykeValue, b: &StrykeValue) -> bool {
21827 if !a.is_string_like() || !b.is_string_like() {
21828 return false;
21829 }
21830 let sa = a.to_string();
21831 let sb = b.to_string();
21832 let looks = |s: &str| {
21833 let t = s.trim();
21834 !t.is_empty() && t.parse::<f64>().is_ok()
21835 };
21836 !looks(&sa) && !looks(&sb)
21837}
21838
21839fn par_walk_invoke_entry(
21840 path: &Path,
21841 sub: &Arc<StrykeSub>,
21842 subs: &HashMap<String, Arc<StrykeSub>>,
21843 scope_capture: &[(String, StrykeValue)],
21844 atomic_arrays: &[(String, crate::scope::AtomicArray)],
21845 atomic_hashes: &[(String, crate::scope::AtomicHash)],
21846 line: usize,
21847) -> Result<(), FlowOrError> {
21848 let s = path.to_string_lossy().into_owned();
21849 let mut local_interp = VMHelper::new();
21850 local_interp.subs = subs.clone();
21851 local_interp.scope.restore_capture(scope_capture);
21852 local_interp
21853 .scope
21854 .restore_atomics(atomic_arrays, atomic_hashes);
21855 local_interp.enable_parallel_guard();
21856 local_interp.scope.set_topic(StrykeValue::string(s));
21857 local_interp.call_sub(sub.as_ref(), vec![], WantarrayCtx::Void, line)?;
21858 Ok(())
21859}
21860
21861fn par_walk_recursive(
21862 path: &Path,
21863 sub: &Arc<StrykeSub>,
21864 subs: &HashMap<String, Arc<StrykeSub>>,
21865 scope_capture: &[(String, StrykeValue)],
21866 atomic_arrays: &[(String, crate::scope::AtomicArray)],
21867 atomic_hashes: &[(String, crate::scope::AtomicHash)],
21868 line: usize,
21869) -> Result<(), FlowOrError> {
21870 if path.is_file() || (path.is_symlink() && !path.is_dir()) {
21871 return par_walk_invoke_entry(
21872 path,
21873 sub,
21874 subs,
21875 scope_capture,
21876 atomic_arrays,
21877 atomic_hashes,
21878 line,
21879 );
21880 }
21881 if !path.is_dir() {
21882 return Ok(());
21883 }
21884 par_walk_invoke_entry(
21885 path,
21886 sub,
21887 subs,
21888 scope_capture,
21889 atomic_arrays,
21890 atomic_hashes,
21891 line,
21892 )?;
21893 let read = match std::fs::read_dir(path) {
21894 Ok(r) => r,
21895 Err(_) => return Ok(()),
21896 };
21897 let entries: Vec<_> = read.filter_map(|e| e.ok()).collect();
21898 entries.par_iter().try_for_each(|e| {
21899 par_walk_recursive(
21900 &e.path(),
21901 sub,
21902 subs,
21903 scope_capture,
21904 atomic_arrays,
21905 atomic_hashes,
21906 line,
21907 )
21908 })?;
21909 Ok(())
21910}
21911
21912fn par_chunk_value(v: &StrykeValue, n_threads: usize) -> Vec<StrykeValue> {
21940 let n = n_threads.max(1);
21941 if let Some(s) = v.as_str() {
21943 let bytes = s.as_bytes();
21944 if bytes.len() < 16_384 || n < 2 {
21945 return vec![StrykeValue::string(s)];
21946 }
21947 let target = bytes.len().div_ceil(n);
21948 let mut splits = vec![0usize];
21949 let mut cursor = target;
21950 while cursor < bytes.len() {
21951 while cursor < bytes.len() && (bytes[cursor] & 0xC0) == 0x80 {
21953 cursor += 1;
21954 }
21955 splits.push(cursor);
21956 cursor += target;
21957 }
21958 splits.push(bytes.len());
21959 return splits
21960 .windows(2)
21961 .map(|w| {
21962 let chunk = std::str::from_utf8(&bytes[w[0]..w[1]]).unwrap_or("");
21963 StrykeValue::string(chunk.to_string())
21964 })
21965 .collect();
21966 }
21967 if let Some(arr) = v.as_array_vec() {
21969 if arr.len() < 32 || n < 2 {
21970 return vec![StrykeValue::array(arr)];
21971 }
21972 let target = arr.len().div_ceil(n);
21973 let mut chunks = Vec::with_capacity(n);
21974 for slice in arr.chunks(target) {
21975 chunks.push(StrykeValue::array(slice.to_vec()));
21976 }
21977 return chunks;
21978 }
21979 if let Some(arr_ref) = v.as_array_ref() {
21980 let arr = arr_ref.read().clone();
21981 if arr.len() < 32 || n < 2 {
21982 return vec![StrykeValue::array(arr)];
21983 }
21984 let target = arr.len().div_ceil(n);
21985 let mut chunks = Vec::with_capacity(n);
21986 for slice in arr.chunks(target) {
21987 chunks.push(StrykeValue::array(slice.to_vec()));
21988 }
21989 return chunks;
21990 }
21991 vec![v.clone()]
21993}
21994
21995fn par_reduce_auto_merge(chunks: Vec<StrykeValue>) -> StrykeValue {
22005 if chunks.is_empty() {
22006 return StrykeValue::UNDEF;
22007 }
22008 let first = &chunks[0];
22009 if let Some(_h) = first.as_hash_ref() {
22011 let mut out: indexmap::IndexMap<String, f64> = indexmap::IndexMap::new();
22012 for chunk in &chunks {
22013 if let Some(hr) = chunk.as_hash_ref() {
22014 for (k, v) in hr.read().iter() {
22015 *out.entry(k.clone()).or_insert(0.0) += v.to_number();
22016 }
22017 }
22018 }
22019 let mut indexmap_out: indexmap::IndexMap<String, StrykeValue> = indexmap::IndexMap::new();
22022 for (k, v) in out {
22023 let pv = if v == v.trunc() && v.abs() < 1e15 {
22024 StrykeValue::integer(v as i64)
22025 } else {
22026 StrykeValue::float(v)
22027 };
22028 indexmap_out.insert(k, pv);
22029 }
22030 return StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(indexmap_out)));
22031 }
22032 if first.is_integer_like() || first.is_float_like() {
22034 let s: f64 = chunks.iter().map(|v| v.to_number()).sum();
22035 if s == s.trunc() && s.abs() < 1e15 {
22036 return StrykeValue::integer(s as i64);
22037 }
22038 return StrykeValue::float(s);
22039 }
22040 if first.as_array_vec().is_some() || first.as_array_ref().is_some() {
22042 let mut out = Vec::new();
22043 for v in &chunks {
22044 out.extend(v.map_flatten_outputs(true));
22045 }
22046 return StrykeValue::array(out);
22047 }
22048 if first.is_string_like() {
22050 let mut out = String::new();
22051 for v in &chunks {
22052 out.push_str(&v.to_string());
22053 }
22054 return StrykeValue::string(out);
22055 }
22056 StrykeValue::array(chunks)
22058}
22059
22060fn perl_magic_str_inc(s: &str) -> Option<String> {
22063 if s.is_empty() {
22064 return Some("1".to_string());
22065 }
22066 let bytes = s.as_bytes();
22067 let mut i = 0;
22068 while i < bytes.len() && bytes[i].is_ascii_alphabetic() {
22069 i += 1;
22070 }
22071 let letters_end = i;
22072 while i < bytes.len() && bytes[i].is_ascii_digit() {
22073 i += 1;
22074 }
22075 if i != bytes.len() {
22076 return None;
22077 }
22078 if letters_end == 0 {
22079 return None;
22081 }
22082
22083 let mut result: Vec<u8> = bytes.to_vec();
22084 let mut carry = true;
22085 let mut idx = result.len();
22086
22087 while carry && idx > letters_end {
22089 idx -= 1;
22090 if result[idx] == b'9' {
22091 result[idx] = b'0';
22092 } else {
22094 result[idx] += 1;
22095 carry = false;
22096 }
22097 }
22098
22099 while carry && idx > 0 {
22101 idx -= 1;
22102 let c = result[idx];
22103 if c == b'z' {
22104 result[idx] = b'a';
22105 } else if c == b'Z' {
22106 result[idx] = b'A';
22107 } else {
22108 result[idx] += 1;
22109 carry = false;
22110 }
22111 }
22112
22113 if carry {
22115 let prepend = if bytes[0].is_ascii_uppercase() {
22116 b'A'
22117 } else {
22118 b'a'
22119 };
22120 let mut grown = Vec::with_capacity(result.len() + 1);
22121 grown.push(prepend);
22122 grown.extend_from_slice(&result);
22123 return String::from_utf8(grown).ok();
22124 }
22125
22126 String::from_utf8(result).ok()
22127}
22128
22129pub(crate) fn perl_inc(v: &StrykeValue) -> StrykeValue {
22133 if let Some(s) = v.as_str() {
22134 if let Some(new_s) = perl_magic_str_inc(&s) {
22135 return StrykeValue::string(new_s);
22136 }
22137 }
22138 StrykeValue::integer(v.to_int() + 1)
22139}
22140
22141fn perl_exponent_form(rust_repr: &str, upper: bool) -> String {
22142 let marker = if upper { 'E' } else { 'e' };
22143 if let Some(pos) = rust_repr.find(marker) {
22144 let (mantissa, after) = rust_repr.split_at(pos);
22145 let exp_part = &after[1..]; let (sign, digits) = match exp_part.chars().next() {
22147 Some('+') => ("+", &exp_part[1..]),
22148 Some('-') => ("-", &exp_part[1..]),
22149 _ => ("+", exp_part),
22150 };
22151 let padded = if digits.len() < 2 {
22152 format!("0{}", digits)
22153 } else {
22154 digits.to_string()
22155 };
22156 return format!("{}{}{}{}", mantissa, marker, sign, padded);
22157 }
22158 rust_repr.to_string()
22159}
22160
22161fn perl_hex_float(n: f64, upper: bool) -> String {
22165 if n.is_nan() {
22166 return if upper { "NAN" } else { "nan" }.to_string();
22167 }
22168 if n.is_infinite() {
22169 let sign = if n.is_sign_negative() { "-" } else { "" };
22170 let body = if upper { "INF" } else { "inf" };
22171 return format!("{}{}", sign, body);
22172 }
22173 let prefix = if upper { "0X" } else { "0x" };
22174 let p_letter = if upper { 'P' } else { 'p' };
22175 let bits = n.to_bits();
22176 let sign_bit = bits >> 63;
22177 let exp_bits = (bits >> 52) & 0x7FF;
22178 let mant_bits = bits & 0x000F_FFFF_FFFF_FFFF;
22179 let sign_str = if sign_bit == 1 { "-" } else { "" };
22180 if exp_bits == 0 && mant_bits == 0 {
22181 return format!("{}{}{}{}{}", sign_str, prefix, "0", p_letter, "+0");
22182 }
22183 let (lead_digit, exp_unbiased): (u64, i32) = if exp_bits == 0 {
22184 (0, -1022)
22186 } else {
22187 (1, (exp_bits as i32) - 1023)
22188 };
22189 let exp_sign = if exp_unbiased >= 0 { "+" } else { "-" };
22190 let exp_abs = exp_unbiased.unsigned_abs();
22191 if mant_bits == 0 {
22192 return format!(
22193 "{}{}{}{}{}{}",
22194 sign_str, prefix, lead_digit, p_letter, exp_sign, exp_abs
22195 );
22196 }
22197 let mant_hex = format!("{:013x}", mant_bits);
22199 let trimmed = mant_hex.trim_end_matches('0');
22200 let mant_str = if upper {
22201 trimmed.to_uppercase()
22202 } else {
22203 trimmed.to_string()
22204 };
22205 format!(
22206 "{}{}{}.{}{}{}{}",
22207 sign_str, prefix, lead_digit, mant_str, p_letter, exp_sign, exp_abs
22208 )
22209}
22210
22211fn perl_g_form(n: f64, prec: usize, upper: bool) -> String {
22214 let prec = prec.max(1);
22215 if !n.is_finite() {
22216 return if upper {
22217 format!("{}", n).to_uppercase()
22218 } else {
22219 format!("{}", n)
22220 };
22221 }
22222 let abs = n.abs();
22224 let x = if abs == 0.0 {
22225 0i32
22226 } else {
22227 abs.log10().floor() as i32
22228 };
22229 let use_e = x < -4 || x >= prec as i32;
22231 let formatted = if use_e {
22234 let raw = format!("{:.*e}", prec - 1, n);
22235 perl_exponent_form(&raw, false)
22236 } else {
22237 let f_prec = (prec as i32 - 1 - x).max(0) as usize;
22238 format!("{:.*}", f_prec, n)
22239 };
22240 let (mant, exp) = if let Some(pos) = formatted.find('e') {
22243 (formatted[..pos].to_string(), formatted[pos..].to_string())
22244 } else {
22245 (formatted.clone(), String::new())
22246 };
22247 let trimmed = if mant.contains('.') {
22248 let t = mant.trim_end_matches('0');
22249 let t = t.trim_end_matches('.');
22250 t.to_string()
22251 } else {
22252 mant
22253 };
22254 let combined = format!("{}{}", trimmed, exp);
22255 if upper {
22256 combined.replace('e', "E")
22257 } else {
22258 combined
22259 }
22260}
22261
22262pub(crate) fn perl_sprintf_format_full<F>(
22267 fmt: &str,
22268 args: &[StrykeValue],
22269 string_for_s: &mut F,
22270) -> Result<(String, Vec<(StrykeValue, i64)>), FlowOrError>
22271where
22272 F: FnMut(&StrykeValue) -> Result<String, FlowOrError>,
22273{
22274 let mut pending_n: Vec<(StrykeValue, i64)> = Vec::new();
22275 let mut result = String::new();
22276 let mut arg_idx = 0;
22277 let chars: Vec<char> = fmt.chars().collect();
22278 let mut i = 0;
22279
22280 let take_arg_int = |args: &[StrykeValue], idx: &mut usize| -> i64 {
22282 let v = args.get(*idx).cloned().unwrap_or(StrykeValue::UNDEF);
22283 *idx += 1;
22284 v.to_int()
22285 };
22286
22287 while i < chars.len() {
22288 if chars[i] == '%' {
22289 i += 1;
22290 if i >= chars.len() {
22291 break;
22292 }
22293 if chars[i] == '%' {
22294 result.push('%');
22295 i += 1;
22296 continue;
22297 }
22298
22299 let mut positional: Option<usize> = None;
22304 {
22305 let saved = i;
22306 let mut digits = String::new();
22307 let mut j = i;
22308 while j < chars.len() && chars[j].is_ascii_digit() {
22309 digits.push(chars[j]);
22310 j += 1;
22311 }
22312 if j < chars.len() && chars[j] == '$' && !digits.is_empty() {
22313 if let Ok(n) = digits.parse::<usize>() {
22314 if n >= 1 {
22315 positional = Some(n - 1);
22316 i = j + 1; }
22318 }
22319 }
22320 if positional.is_none() {
22321 i = saved;
22322 }
22323 }
22324
22325 let mut flags = String::new();
22327 while i < chars.len() && "-+ #0".contains(chars[i]) {
22328 flags.push(chars[i]);
22329 i += 1;
22330 }
22331 let mut vector_sep: Option<String> = None;
22335 if i < chars.len() && chars[i] == 'v' {
22336 vector_sep = Some(".".to_string());
22337 i += 1;
22338 } else if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == 'v' {
22339 let sep_arg = args.get(arg_idx).cloned().unwrap_or(StrykeValue::UNDEF);
22340 arg_idx += 1;
22341 vector_sep = Some(sep_arg.to_string());
22342 i += 2;
22343 }
22344 let mut width = String::new();
22346 let mut left_align = flags.contains('-');
22347 if i < chars.len() && chars[i] == '*' {
22348 let n = take_arg_int(args, &mut arg_idx);
22349 if n < 0 {
22350 left_align = true;
22352 width = (-n).to_string();
22353 } else {
22354 width = n.to_string();
22355 }
22356 i += 1;
22357 } else {
22358 while i < chars.len() && chars[i].is_ascii_digit() {
22359 width.push(chars[i]);
22360 i += 1;
22361 }
22362 }
22363 let mut precision = String::new();
22365 if i < chars.len() && chars[i] == '.' {
22366 i += 1;
22367 if i < chars.len() && chars[i] == '*' {
22368 let n = take_arg_int(args, &mut arg_idx);
22369 precision = n.max(0).to_string();
22370 i += 1;
22371 } else {
22372 while i < chars.len() && chars[i].is_ascii_digit() {
22373 precision.push(chars[i]);
22374 i += 1;
22375 }
22376 if precision.is_empty() {
22378 precision = "0".to_string();
22379 }
22380 }
22381 }
22382 if i >= chars.len() {
22383 break;
22384 }
22385 let spec = chars[i];
22386 i += 1;
22387
22388 let arg = if let Some(idx) = positional {
22393 args.get(idx).cloned().unwrap_or(StrykeValue::UNDEF)
22394 } else {
22395 let v = args.get(arg_idx).cloned().unwrap_or(StrykeValue::UNDEF);
22396 arg_idx += 1;
22397 v
22398 };
22399
22400 let w: usize = width.parse().unwrap_or(0);
22401 let p: usize = precision.parse().unwrap_or(6);
22402
22403 let zero_pad = flags.contains('0') && !left_align;
22404 let plus = flags.contains('+');
22405 let space = flags.contains(' ');
22406 let hash = flags.contains('#');
22407
22408 let pad_align = |body: &str, width: usize, left: bool, zero: bool| -> String {
22412 if width == 0 || body.len() >= width {
22413 return body.to_string();
22414 }
22415 if zero && !left {
22416 if let Some(rest) = body.strip_prefix('-') {
22417 return format!("-{:0>width$}", rest, width = width - 1);
22418 }
22419 if let Some(rest) = body.strip_prefix('+') {
22420 return format!("+{:0>width$}", rest, width = width - 1);
22421 }
22422 return format!("{:0>width$}", body, width = width);
22423 }
22424 if left {
22425 format!("{:<width$}", body, width = width)
22426 } else {
22427 format!("{:>width$}", body, width = width)
22428 }
22429 };
22430
22431 let format_int_for_vector = |n: i64, spec: char| -> String {
22435 match spec {
22436 'd' | 'i' => format!("{}", n),
22437 'u' => format!("{}", n as u64),
22438 'x' => {
22439 if hash && n != 0 {
22440 format!("0x{:x}", n)
22441 } else {
22442 format!("{:x}", n)
22443 }
22444 }
22445 'X' => {
22446 if hash && n != 0 {
22447 format!("0X{:X}", n)
22448 } else {
22449 format!("{:X}", n)
22450 }
22451 }
22452 'o' => {
22453 if hash && n != 0 {
22454 format!("0{:o}", n)
22455 } else {
22456 format!("{:o}", n)
22457 }
22458 }
22459 'b' => {
22460 if hash && n != 0 {
22461 format!("0b{:b}", n)
22462 } else {
22463 format!("{:b}", n)
22464 }
22465 }
22466 'c' => char::from_u32(n as u32)
22467 .map(|c| c.to_string())
22468 .unwrap_or_default(),
22469 _ => format!("{}", n),
22470 }
22471 };
22472
22473 if let Some(ref sep) = vector_sep {
22477 let s = arg.to_string();
22478 let parts: Vec<String> = s
22479 .bytes()
22480 .map(|b| format_int_for_vector(b as i64, spec))
22481 .collect();
22482 let body = parts.join(sep);
22483 let final_body = if width.is_empty() {
22484 body
22485 } else if left_align {
22486 format!("{:<width$}", body, width = w)
22487 } else {
22488 format!("{:>width$}", body, width = w)
22489 };
22490 result.push_str(&final_body);
22491 continue;
22492 }
22493
22494 let formatted = match spec {
22495 'd' | 'i' => {
22496 let v = arg.to_int();
22497 let body = if plus && v >= 0 {
22498 format!("+{}", v)
22499 } else if space && v >= 0 {
22500 format!(" {}", v)
22501 } else {
22502 format!("{}", v)
22503 };
22504 pad_align(&body, w, left_align, zero_pad)
22505 }
22506 'u' => {
22507 let v = arg.to_int() as u64;
22508 pad_align(&format!("{}", v), w, left_align, zero_pad)
22509 }
22510 'f' => {
22511 let n = arg.to_number();
22512 let body = if plus && n.is_sign_positive() {
22513 format!("+{:.*}", p, n)
22514 } else if space && n.is_sign_positive() {
22515 format!(" {:.*}", p, n)
22516 } else {
22517 format!("{:.*}", p, n)
22518 };
22519 pad_align(&body, w, left_align, zero_pad)
22520 }
22521 'e' => {
22522 let n = arg.to_number();
22523 let raw = format!("{:.*e}", p, n);
22524 let body0 = perl_exponent_form(&raw, false);
22525 let body = if plus && n.is_sign_positive() {
22526 format!("+{}", body0)
22527 } else if space && n.is_sign_positive() {
22528 format!(" {}", body0)
22529 } else {
22530 body0
22531 };
22532 pad_align(&body, w, left_align, zero_pad)
22533 }
22534 'E' => {
22535 let n = arg.to_number();
22536 let raw = format!("{:.*E}", p, n);
22537 let body0 = perl_exponent_form(&raw, true);
22538 let body = if plus && n.is_sign_positive() {
22539 format!("+{}", body0)
22540 } else if space && n.is_sign_positive() {
22541 format!(" {}", body0)
22542 } else {
22543 body0
22544 };
22545 pad_align(&body, w, left_align, zero_pad)
22546 }
22547 'g' => {
22548 let n = arg.to_number();
22549 let prec_g = if precision.is_empty() { 6 } else { p };
22551 let body0 = perl_g_form(n, prec_g, false);
22552 let body = if plus && n.is_sign_positive() {
22553 format!("+{}", body0)
22554 } else if space && n.is_sign_positive() {
22555 format!(" {}", body0)
22556 } else {
22557 body0
22558 };
22559 pad_align(&body, w, left_align, zero_pad)
22560 }
22561 'G' => {
22562 let n = arg.to_number();
22563 let prec_g = if precision.is_empty() { 6 } else { p };
22564 let body0 = perl_g_form(n, prec_g, true);
22565 let body = if plus && n.is_sign_positive() {
22566 format!("+{}", body0)
22567 } else if space && n.is_sign_positive() {
22568 format!(" {}", body0)
22569 } else {
22570 body0
22571 };
22572 pad_align(&body, w, left_align, zero_pad)
22573 }
22574 's' => {
22575 let s = string_for_s(&arg)?;
22576 let body = if !precision.is_empty() {
22577 s.chars().take(p).collect::<String>()
22578 } else {
22579 s
22580 };
22581 if left_align {
22582 format!("{:<width$}", body, width = w)
22583 } else {
22584 format!("{:>width$}", body, width = w)
22585 }
22586 }
22587 'x' => {
22588 let v = arg.to_int();
22589 let body = if hash && v != 0 {
22590 format!("0x{:x}", v)
22591 } else {
22592 format!("{:x}", v)
22593 };
22594 pad_align(&body, w, left_align, zero_pad)
22595 }
22596 'X' => {
22597 let v = arg.to_int();
22598 let body = if hash && v != 0 {
22599 format!("0X{:X}", v)
22600 } else {
22601 format!("{:X}", v)
22602 };
22603 pad_align(&body, w, left_align, zero_pad)
22604 }
22605 'o' => {
22606 let v = arg.to_int();
22607 let body = if hash && v != 0 {
22608 format!("0{:o}", v)
22609 } else {
22610 format!("{:o}", v)
22611 };
22612 pad_align(&body, w, left_align, zero_pad)
22613 }
22614 'b' => {
22615 let v = arg.to_int();
22616 let body = if hash && v != 0 {
22617 format!("0b{:b}", v)
22618 } else {
22619 format!("{:b}", v)
22620 };
22621 pad_align(&body, w, left_align, zero_pad)
22622 }
22623 'c' => char::from_u32(arg.to_int() as u32)
22624 .map(|c| c.to_string())
22625 .unwrap_or_default(),
22626 'a' | 'A' => {
22627 let upper = spec == 'A';
22628 let body0 = perl_hex_float(arg.to_number(), upper);
22629 let body = if plus && !body0.starts_with('-') {
22630 format!("+{}", body0)
22631 } else if space && !body0.starts_with('-') {
22632 format!(" {}", body0)
22633 } else {
22634 body0
22635 };
22636 pad_align(&body, w, left_align, zero_pad)
22637 }
22638 'p' => {
22639 pad_align("0x...", w, left_align, false)
22643 }
22644 'n' => {
22645 pending_n.push((arg.clone(), result.len() as i64));
22653 String::new()
22654 }
22655 _ => arg.to_string(),
22656 };
22657
22658 result.push_str(&formatted);
22659 } else {
22660 result.push(chars[i]);
22661 i += 1;
22662 }
22663 }
22664 Ok((result, pending_n))
22665}
22666
22667#[cfg(test)]
22668mod regex_expand_tests {
22669 use super::VMHelper;
22670
22671 #[test]
22672 fn compile_regex_quotemeta_qe_matches_literal() {
22673 let mut i = VMHelper::new();
22674 let re = i.compile_regex(r"\Qa.c\E", "", 1).expect("regex");
22675 assert!(re.is_match("a.c"));
22676 assert!(!re.is_match("abc"));
22677 }
22678
22679 #[test]
22682 fn compile_regex_char_class_leading_close_bracket_is_literal() {
22683 let mut i = VMHelper::new();
22684 let re = i.compile_regex(r"[]\[^$.*/]", "", 1).expect("regex");
22685 assert!(re.is_match("$"));
22686 assert!(re.is_match("]"));
22687 assert!(!re.is_match("x"));
22688 }
22689}
22690
22691#[cfg(test)]
22692mod special_scalar_name_tests {
22693 use super::VMHelper;
22694
22695 #[test]
22696 fn special_scalar_name_for_get_matches_magic_globals() {
22697 assert!(VMHelper::is_special_scalar_name_for_get("0"));
22698 assert!(VMHelper::is_special_scalar_name_for_get("!"));
22699 assert!(VMHelper::is_special_scalar_name_for_get("^W"));
22700 assert!(VMHelper::is_special_scalar_name_for_get("^O"));
22701 assert!(VMHelper::is_special_scalar_name_for_get("^MATCH"));
22702 assert!(VMHelper::is_special_scalar_name_for_get("<"));
22703 assert!(VMHelper::is_special_scalar_name_for_get("?"));
22704 assert!(VMHelper::is_special_scalar_name_for_get("|"));
22705 assert!(VMHelper::is_special_scalar_name_for_get("^UNICODE"));
22706 assert!(VMHelper::is_special_scalar_name_for_get("\""));
22707 assert!(!VMHelper::is_special_scalar_name_for_get("foo"));
22708 assert!(!VMHelper::is_special_scalar_name_for_get("plainvar"));
22709 }
22710
22711 #[test]
22712 fn special_scalar_name_for_set_matches_set_special_var_arms() {
22713 assert!(VMHelper::is_special_scalar_name_for_set("0"));
22714 assert!(VMHelper::is_special_scalar_name_for_set("^D"));
22715 assert!(VMHelper::is_special_scalar_name_for_set("^H"));
22716 assert!(VMHelper::is_special_scalar_name_for_set("^WARNING_BITS"));
22717 assert!(VMHelper::is_special_scalar_name_for_set("ARGV"));
22718 assert!(VMHelper::is_special_scalar_name_for_set("|"));
22719 assert!(VMHelper::is_special_scalar_name_for_set("?"));
22720 assert!(VMHelper::is_special_scalar_name_for_set("^UNICODE"));
22721 assert!(VMHelper::is_special_scalar_name_for_set("."));
22722 assert!(!VMHelper::is_special_scalar_name_for_set("foo"));
22723 assert!(!VMHelper::is_special_scalar_name_for_set("__PACKAGE__"));
22724 }
22725
22726 #[test]
22727 fn caret_and_id_specials_roundtrip_get() {
22728 let i = VMHelper::new();
22729 assert_eq!(i.get_special_var("^O").to_string(), super::perl_osname());
22730 assert_eq!(
22731 i.get_special_var("^V").to_string(),
22732 format!("v{}", env!("CARGO_PKG_VERSION"))
22733 );
22734 assert_eq!(i.get_special_var("^GLOBAL_PHASE").to_string(), "RUN");
22735 assert!(i.get_special_var("^T").to_int() >= 0);
22736 #[cfg(unix)]
22737 {
22738 assert!(i.get_special_var("<").to_int() >= 0);
22739 }
22740 }
22741
22742 #[test]
22743 fn scalar_flip_flop_three_dot_same_dollar_dot_second_eval_stays_active() {
22744 let mut i = VMHelper::new();
22745 i.last_readline_handle.clear();
22746 i.line_number = 3;
22747 i.prepare_flip_flop_vm_slots(1);
22748 assert_eq!(
22749 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
22750 1
22751 );
22752 assert!(i.flip_flop_active[0]);
22753 assert_eq!(i.flip_flop_exclusive_left_line[0], Some(3));
22754 assert_eq!(
22756 i.scalar_flip_flop_eval(3, 3, 0, true).expect("ok").to_int(),
22757 1
22758 );
22759 assert!(i.flip_flop_active[0]);
22760 }
22761
22762 #[test]
22763 fn scalar_flip_flop_three_dot_deactivates_when_past_left_line_and_dot_matches_right() {
22764 let mut i = VMHelper::new();
22765 i.last_readline_handle.clear();
22766 i.line_number = 2;
22767 i.prepare_flip_flop_vm_slots(1);
22768 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
22769 assert!(i.flip_flop_active[0]);
22770 i.line_number = 3;
22771 i.scalar_flip_flop_eval(2, 3, 0, true).expect("ok");
22772 assert!(!i.flip_flop_active[0]);
22773 assert_eq!(i.flip_flop_exclusive_left_line[0], None);
22774 }
22775}