1use std::collections::{HashMap, HashSet};
2
3use crate::ast::*;
4use crate::bytecode::{
5 BuiltinId, Chunk, Op, RuntimeAdviceDecl, RuntimeSubDecl, GP_CHECK, GP_END, GP_INIT, GP_RUN,
6 GP_START,
7};
8use crate::sort_fast::detect_sort_block_fast;
9use crate::value::StrykeValue;
10use crate::vm_helper::{assign_rhs_wantarray, VMHelper, WantarrayCtx};
11
12pub fn expr_tail_is_list_sensitive(expr: &Expr) -> bool {
19 match &expr.kind {
20 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => true,
22 ExprKind::List(items) => items.len() != 1 || expr_tail_is_list_sensitive(&items[0]),
24 ExprKind::QW(ws) => ws.len() != 1,
25 ExprKind::Deref {
27 kind: Sigil::Array | Sigil::Hash,
28 ..
29 } => true,
30 ExprKind::ArraySlice { .. }
32 | ExprKind::HashSlice { .. }
33 | ExprKind::HashSliceDeref { .. }
34 | ExprKind::AnonymousListSlice { .. } => true,
35 ExprKind::MapExpr { .. }
37 | ExprKind::MapExprComma { .. }
38 | ExprKind::GrepExpr { .. }
39 | ExprKind::SortExpr { .. } => true,
40 ExprKind::FuncCall { name, .. } => matches!(
42 name.as_str(),
43 "wantarray"
44 | "reverse"
45 | "sort"
46 | "map"
47 | "grep"
48 | "keys"
49 | "values"
50 | "each"
51 | "split"
52 | "caller"
53 | "localtime"
54 | "gmtime"
55 | "stat"
56 | "lstat"
57 ),
58 ExprKind::Ternary {
59 then_expr,
60 else_expr,
61 ..
62 } => expr_tail_is_list_sensitive(then_expr) || expr_tail_is_list_sensitive(else_expr),
63 _ => false,
66 }
67}
68
69pub(crate) fn hash_slice_key_expr_is_multi_key(k: &Expr) -> bool {
72 match &k.kind {
73 ExprKind::QW(ws) => ws.len() > 1,
74 ExprKind::List(el) => el.len() > 1,
75 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => true,
76 _ => false,
77 }
78}
79
80pub(crate) fn hash_slice_needs_slice_ops(keys: &[Expr]) -> bool {
83 keys.len() != 1 || keys.first().is_some_and(hash_slice_key_expr_is_multi_key)
84}
85
86pub(crate) fn arrow_deref_arrow_subscript_is_plain_scalar_index(index: &Expr) -> bool {
91 match &index.kind {
92 ExprKind::Range { .. } | ExprKind::SliceRange { .. } => false,
93 ExprKind::QW(_) => false,
94 ExprKind::List(el) => {
95 if el.len() == 1 {
96 arrow_deref_arrow_subscript_is_plain_scalar_index(&el[0])
97 } else {
98 false
99 }
100 }
101 _ => !hash_slice_key_expr_is_multi_key(index),
102 }
103}
104
105#[derive(Debug)]
107pub enum CompileError {
108 Unsupported(String),
109 Frozen {
111 line: usize,
112 detail: String,
113 },
114}
115
116#[derive(Default, Clone)]
117struct ScopeLayer {
118 declared_scalars: HashSet<String>,
119 declared_our_scalars: HashSet<String>,
121 declared_arrays: HashSet<String>,
122 declared_hashes: HashSet<String>,
123 frozen_scalars: HashSet<String>,
124 frozen_arrays: HashSet<String>,
125 frozen_hashes: HashSet<String>,
126 scalar_slots: HashMap<String, u8>,
130 next_scalar_slot: u8,
131 use_slots: bool,
133 mysync_arrays: HashSet<String>,
135 mysync_hashes: HashSet<String>,
137 mysync_scalars: HashSet<String>,
141 is_sub_body: bool,
147}
148
149struct LoopCtx {
162 label: Option<String>,
163 entry_frame_depth: usize,
164 entry_try_depth: usize,
165 body_start_ip: usize,
168 break_jumps: Vec<usize>,
170 continue_jumps: Vec<usize>,
172}
173
174pub struct Compiler {
175 pub chunk: Chunk,
176 ast_expr_intern: HashMap<usize, u32>,
178 pub begin_blocks: Vec<Block>,
179 pub unit_check_blocks: Vec<Block>,
180 pub check_blocks: Vec<Block>,
181 pub init_blocks: Vec<Block>,
182 pub end_blocks: Vec<Block>,
183 scope_stack: Vec<ScopeLayer>,
185 current_package: String,
187 program_last_stmt_takes_value: bool,
190 pub source_file: String,
192 frame_depth: usize,
195 try_depth: usize,
197 loop_stack: Vec<LoopCtx>,
199 goto_ctx_stack: Vec<GotoCtx>,
204 strict_vars: bool,
210 force_name_for_sort_pair: bool,
217 sort_pair_block_indices: std::collections::HashSet<u16>,
221 sub_body_block_indices: std::collections::HashSet<u16>,
227 code_ref_block_params: Vec<Vec<crate::ast::SubSigParam>>,
231 block_scope_snapshots: Vec<Option<Vec<ScopeLayer>>>,
242}
243
244#[derive(Default)]
247struct GotoCtx {
248 labels: HashMap<String, (usize, usize)>,
250 pending: Vec<(usize, String, usize, usize)>,
252}
253
254impl Default for Compiler {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl Compiler {
261 fn compile_array_slice_index_expr(&mut self, index_expr: &Expr) -> Result<(), CompileError> {
265 self.compile_expr_ctx(index_expr, WantarrayCtx::List)
266 }
267
268 fn compile_hash_slice_key_expr(&mut self, key_expr: &Expr) -> Result<(), CompileError> {
271 if matches!(
272 &key_expr.kind,
273 ExprKind::Range { .. } | ExprKind::SliceRange { .. }
274 ) {
275 self.compile_expr_ctx(key_expr, WantarrayCtx::List)
276 } else {
277 self.compile_expr(key_expr)
278 }
279 }
280
281 fn compile_optional_or_undef(&mut self, expr: Option<&Expr>) -> Result<(), CompileError> {
284 match expr {
285 Some(e) => self.compile_expr(e),
286 None => {
287 self.emit_op(Op::LoadUndef, 0, None);
288 Ok(())
289 }
290 }
291 }
292
293 pub fn new() -> Self {
294 Self {
295 chunk: Chunk::new(),
296 ast_expr_intern: HashMap::new(),
297 begin_blocks: Vec::new(),
298 unit_check_blocks: Vec::new(),
299 check_blocks: Vec::new(),
300 init_blocks: Vec::new(),
301 end_blocks: Vec::new(),
302 scope_stack: vec![ScopeLayer {
305 use_slots: true,
306 ..Default::default()
307 }],
308 current_package: String::new(),
309 program_last_stmt_takes_value: false,
310 source_file: String::new(),
311 frame_depth: 0,
312 try_depth: 0,
313 loop_stack: Vec::new(),
314 goto_ctx_stack: Vec::new(),
315 strict_vars: false,
316 force_name_for_sort_pair: false,
317 sort_pair_block_indices: std::collections::HashSet::new(),
318 sub_body_block_indices: std::collections::HashSet::new(),
319 code_ref_block_params: Vec::new(),
320 block_scope_snapshots: Vec::new(),
321 }
322 }
323
324 fn add_deferred_block(&mut self, block: Block) -> u16 {
330 let idx = self.chunk.add_block(block);
331 let snap = self.scope_stack.clone();
332 while self.block_scope_snapshots.len() < idx as usize {
333 self.block_scope_snapshots.push(None);
334 }
335 self.block_scope_snapshots.push(Some(snap));
336 idx
337 }
338
339 fn register_sort_pair_block(&mut self, idx: u16) {
344 self.sort_pair_block_indices.insert(idx);
345 }
346
347 pub fn with_strict_vars(mut self, v: bool) -> Self {
354 self.strict_vars = v;
355 self
356 }
357
358 fn enter_goto_scope(&mut self) {
362 self.goto_ctx_stack.push(GotoCtx::default());
363 }
364
365 fn exit_goto_scope(&mut self) -> Result<(), CompileError> {
372 let ctx = self
373 .goto_ctx_stack
374 .pop()
375 .expect("exit_goto_scope called without matching enter");
376 for (jump_ip, label, line, goto_frame_depth) in ctx.pending {
377 if let Some(&(target_ip, label_frame_depth)) = ctx.labels.get(&label) {
378 if label_frame_depth != goto_frame_depth {
379 return Err(CompileError::Unsupported(format!(
380 "goto LABEL crosses a scope frame (label `{}` at depth {} vs goto at depth {})",
381 label, label_frame_depth, goto_frame_depth
382 )));
383 }
384 self.chunk.patch_jump_to(jump_ip, target_ip);
385 } else {
386 return Err(CompileError::Frozen {
387 line,
388 detail: format!("goto: unknown label {}", label),
389 });
390 }
391 }
392 Ok(())
393 }
394
395 fn record_stmt_label(&mut self, label: &str) {
398 if let Some(top) = self.goto_ctx_stack.last_mut() {
399 top.labels
400 .insert(label.to_string(), (self.chunk.len(), self.frame_depth));
401 }
402 }
403
404 fn try_emit_goto_label(&mut self, target: &Expr, line: usize) -> bool {
410 let name = match &target.kind {
411 ExprKind::Bareword(n) => n.clone(),
412 ExprKind::String(s) => s.clone(),
413 ExprKind::FuncCall { name, args } if args.is_empty() => name.clone(),
415 _ => return false,
416 };
417 if self.goto_ctx_stack.is_empty() {
418 return false;
419 }
420 let jump_ip = self.chunk.emit(Op::Jump(0), line);
421 let frame_depth = self.frame_depth;
422 self.goto_ctx_stack
423 .last_mut()
424 .expect("goto scope must be active")
425 .pending
426 .push((jump_ip, name, line, frame_depth));
427 true
428 }
429
430 fn emit_push_frame(&mut self, line: usize) {
432 self.chunk.emit(Op::PushFrame, line);
433 self.frame_depth += 1;
434 }
435
436 fn emit_pop_frame(&mut self, line: usize) {
438 self.chunk.emit(Op::PopFrame, line);
439 self.frame_depth = self.frame_depth.saturating_sub(1);
440 }
441
442 pub fn with_source_file(mut self, path: String) -> Self {
443 self.source_file = path;
444 self
445 }
446
447 fn qualify_stash_array_name(&self, name: &str) -> String {
449 if matches!(name, "ISA" | "EXPORT" | "EXPORT_OK") {
450 let pkg = &self.current_package;
451 if !pkg.is_empty() && pkg != "main" {
452 return format!("{}::{}", pkg, name);
453 }
454 }
455 name.to_string()
456 }
457
458 fn qualify_stash_scalar_name(&self, name: &str) -> String {
460 if name.contains("::") {
461 return name.to_string();
462 }
463 let pkg = &self.current_package;
464 if pkg.is_empty() || pkg == "main" {
465 format!("main::{}", name)
466 } else {
467 format!("{}::{}", pkg, name)
468 }
469 }
470
471 fn scalar_storage_name_for_ops(&self, bare: &str) -> String {
473 if bare.contains("::") {
474 return bare.to_string();
475 }
476 for layer in self.scope_stack.iter().rev() {
477 if layer.declared_scalars.contains(bare) {
478 if layer.declared_our_scalars.contains(bare) {
479 return self.qualify_stash_scalar_name(bare);
480 }
481 return bare.to_string();
482 }
483 }
484 bare.to_string()
485 }
486
487 #[inline]
488 fn intern_scalar_var_for_ops(&mut self, bare: &str) -> u16 {
489 let s = self.scalar_storage_name_for_ops(bare);
490 self.chunk.intern_name(&s)
491 }
492
493 fn intern_scalar_for_local(&mut self, bare: &str) -> u16 {
496 if VMHelper::is_special_scalar_name_for_set(bare) || bare.starts_with('^') {
497 self.chunk.intern_name(bare)
498 } else {
499 let s = self.qualify_stash_scalar_name(bare);
500 self.chunk.intern_name(&s)
501 }
502 }
503
504 fn register_declare_our_scalar(&mut self, bare_name: &str) {
505 let layer = self.scope_stack.last_mut().expect("scope stack");
506 layer.declared_scalars.insert(bare_name.to_string());
507 layer.declared_our_scalars.insert(bare_name.to_string());
508 }
509
510 fn emit_declare_our_scalar(&mut self, bare_name: &str, line: usize, frozen: bool) {
512 let stash = self.qualify_stash_scalar_name(bare_name);
513 let stash_idx = self.chunk.intern_name(&stash);
514 self.register_declare_our_scalar(bare_name);
515 if frozen {
516 self.chunk.emit(Op::DeclareScalarFrozen(stash_idx), line);
517 } else {
518 self.chunk.emit(Op::DeclareScalar(stash_idx), line);
519 }
520 }
521
522 fn qualify_sub_key(&self, name: &str) -> String {
524 if name.contains("::") {
525 return name.to_string();
526 }
527 let pkg = &self.current_package;
528 if pkg.is_empty() || pkg == "main" {
529 name.to_string()
530 } else {
531 format!("{}::{}", pkg, name)
532 }
533 }
534
535 fn qualify_sub_decl_pass1(name: &str, pending_pkg: &str) -> String {
538 if name.contains("::") {
539 return name.to_string();
540 }
541 if pending_pkg.is_empty() || pending_pkg == "main" {
542 name.to_string()
543 } else {
544 format!("{}::{}", pending_pkg, name)
545 }
546 }
547
548 fn patch_static_sub_calls(chunk: &mut Chunk) {
551 for i in 0..chunk.ops.len() {
552 if let Op::Call(name_idx, argc, wa) = chunk.ops[i] {
553 if let Some((entry_ip, stack_args)) = chunk.find_sub_entry(name_idx) {
554 if chunk.static_sub_calls.len() < u16::MAX as usize {
555 let sid = chunk.static_sub_calls.len() as u16;
556 chunk
557 .static_sub_calls
558 .push((entry_ip, stack_args, name_idx));
559 chunk.ops[i] = Op::CallStaticSubId(sid, name_idx, argc, wa);
560 }
561 }
562 }
563 }
564 }
565
566 fn compile_arrow_array_base_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
569 if let ExprKind::Deref {
570 expr: inner,
571 kind: Sigil::Array | Sigil::Scalar,
572 } = &expr.kind
573 {
574 self.compile_expr(inner)
575 } else {
576 self.compile_expr(expr)
577 }
578 }
579
580 fn compile_arrow_hash_base_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
582 if let ExprKind::Deref {
583 expr: inner,
584 kind: Sigil::Scalar,
585 } = &expr.kind
586 {
587 self.compile_expr(inner)
588 } else {
589 self.compile_expr(expr)
590 }
591 }
592
593 fn push_scope_layer(&mut self) {
594 self.scope_stack.push(ScopeLayer::default());
595 }
596
597 fn push_scope_layer_with_slots(&mut self) {
599 self.scope_stack.push(ScopeLayer {
600 use_slots: true,
601 is_sub_body: true,
602 ..Default::default()
603 });
604 }
605
606 fn register_sig_param(&mut self, p: &crate::ast::SubSigParam) {
610 use crate::ast::SubSigParam;
611 let layer = self.scope_stack.last_mut().expect("scope stack");
612 match p {
613 SubSigParam::Scalar(name, _, _) => {
614 layer.declared_scalars.insert(name.clone());
615 }
616 SubSigParam::Array(name, _) => {
617 layer.declared_arrays.insert(name.clone());
618 }
619 SubSigParam::Hash(name, _) => {
620 layer.declared_hashes.insert(name.clone());
621 }
622 SubSigParam::ArrayDestruct(elems) => {
623 for el in elems {
624 match el {
625 crate::ast::MatchArrayElem::CaptureScalar(n) => {
626 layer.declared_scalars.insert(n.clone());
627 }
628 crate::ast::MatchArrayElem::RestBind(n) => {
629 layer.declared_arrays.insert(n.clone());
630 }
631 _ => {}
632 }
633 }
634 }
635 SubSigParam::HashDestruct(pairs) => {
636 for (_, var) in pairs {
637 layer.declared_scalars.insert(var.clone());
638 }
639 }
640 }
641 }
642
643 fn pop_scope_layer(&mut self) {
644 if self.scope_stack.len() > 1 {
645 self.scope_stack.pop();
646 }
647 }
648
649 fn scalar_slot(&self, name: &str) -> Option<u8> {
651 if self.force_name_for_sort_pair && (name == "a" || name == "b") {
657 return None;
658 }
659 if let Some(layer) = self.scope_stack.last() {
660 if layer.use_slots {
661 return layer.scalar_slots.get(name).copied();
662 }
663 }
664 None
665 }
666
667 fn intern_ast_expr(&mut self, expr: &Expr) -> u32 {
669 let p = expr as *const Expr as usize;
670 if let Some(&id) = self.ast_expr_intern.get(&p) {
671 return id;
672 }
673 let id = self.chunk.ast_expr_pool.len() as u32;
674 self.chunk.ast_expr_pool.push(expr.clone());
675 self.ast_expr_intern.insert(p, id);
676 id
677 }
678
679 #[inline]
681 fn emit_op(&mut self, op: Op, line: usize, ast: Option<&Expr>) -> usize {
682 let idx = ast.map(|e| self.intern_ast_expr(e));
683 self.chunk.emit_with_ast_idx(op, line, idx)
684 }
685
686 fn emit_get_scalar(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
688 let name = &self.chunk.names[name_idx as usize];
689 if let Some(slot) = self.scalar_slot(name) {
690 self.emit_op(Op::GetScalarSlot(slot), line, ast);
691 } else if VMHelper::is_special_scalar_name_for_get(name) {
692 self.emit_op(Op::GetScalar(name_idx), line, ast);
693 } else {
694 self.emit_op(Op::GetScalarPlain(name_idx), line, ast);
695 }
696 }
697
698 fn compile_boolean_rvalue_condition(&mut self, cond: &Expr) -> Result<(), CompileError> {
701 let line = cond.line;
702 if let ExprKind::Regex(pattern, flags) = &cond.kind {
703 let name_idx = self.chunk.intern_name("_");
704 self.emit_get_scalar(name_idx, line, Some(cond));
705 let pat_idx = self
706 .chunk
707 .add_constant(StrykeValue::string(pattern.clone()));
708 let flags_idx = self.chunk.add_constant(StrykeValue::string(flags.clone()));
709 self.emit_op(Op::LoadRegex(pat_idx, flags_idx), line, Some(cond));
710 self.emit_op(Op::RegexMatchDyn(false), line, Some(cond));
711 Ok(())
712 } else if matches!(&cond.kind, ExprKind::ReadLine(_)) {
713 self.compile_expr(cond)?;
715 let name_idx = self.chunk.intern_name("_");
716 self.emit_set_scalar_keep(name_idx, line, Some(cond));
717 self.emit_op(
718 Op::CallBuiltin(BuiltinId::Defined as u16, 1),
719 line,
720 Some(cond),
721 );
722 Ok(())
723 } else {
724 self.compile_expr(cond)
725 }
726 }
727
728 fn emit_set_scalar(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
730 let name = &self.chunk.names[name_idx as usize];
731 if let Some(slot) = self.scalar_slot(name) {
732 self.emit_op(Op::SetScalarSlot(slot), line, ast);
733 } else if VMHelper::is_special_scalar_name_for_set(name) {
734 self.emit_op(Op::SetScalar(name_idx), line, ast);
735 } else {
736 self.emit_op(Op::SetScalarPlain(name_idx), line, ast);
737 }
738 }
739
740 fn emit_set_scalar_keep(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
742 let name = &self.chunk.names[name_idx as usize];
743 if let Some(slot) = self.scalar_slot(name) {
744 self.emit_op(Op::SetScalarSlotKeep(slot), line, ast);
745 } else if VMHelper::is_special_scalar_name_for_set(name) {
746 self.emit_op(Op::SetScalarKeep(name_idx), line, ast);
747 } else {
748 self.emit_op(Op::SetScalarKeepPlain(name_idx), line, ast);
749 }
750 }
751
752 fn emit_pre_inc(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
753 let name = &self.chunk.names[name_idx as usize];
754 if let Some(slot) = self.scalar_slot(name) {
755 self.emit_op(Op::PreIncSlot(slot), line, ast);
756 } else {
757 self.emit_op(Op::PreInc(name_idx), line, ast);
758 }
759 }
760
761 fn emit_pre_dec(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
762 let name = &self.chunk.names[name_idx as usize];
763 if let Some(slot) = self.scalar_slot(name) {
764 self.emit_op(Op::PreDecSlot(slot), line, ast);
765 } else {
766 self.emit_op(Op::PreDec(name_idx), line, ast);
767 }
768 }
769
770 fn emit_post_inc(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
771 let name = &self.chunk.names[name_idx as usize];
772 if let Some(slot) = self.scalar_slot(name) {
773 self.emit_op(Op::PostIncSlot(slot), line, ast);
774 } else {
775 self.emit_op(Op::PostInc(name_idx), line, ast);
776 }
777 }
778
779 fn emit_post_dec(&mut self, name_idx: u16, line: usize, ast: Option<&Expr>) {
780 let name = &self.chunk.names[name_idx as usize];
781 if let Some(slot) = self.scalar_slot(name) {
782 self.emit_op(Op::PostDecSlot(slot), line, ast);
783 } else {
784 self.emit_op(Op::PostDec(name_idx), line, ast);
785 }
786 }
787
788 fn assign_scalar_slot(&mut self, name: &str) -> Option<u8> {
791 if let Some(layer) = self.scope_stack.last_mut() {
792 if layer.use_slots && layer.next_scalar_slot < 255 {
793 let slot = layer.next_scalar_slot;
794 layer.scalar_slots.insert(name.to_string(), slot);
795 layer.next_scalar_slot += 1;
796 return Some(slot);
797 }
798 }
799 None
800 }
801
802 fn register_declare(&mut self, sigil: Sigil, name: &str, frozen: bool) {
803 let layer = self.scope_stack.last_mut().expect("scope stack");
804 match sigil {
805 Sigil::Scalar => {
806 layer.declared_scalars.insert(name.to_string());
807 if frozen {
808 layer.frozen_scalars.insert(name.to_string());
809 }
810 }
811 Sigil::Array => {
812 layer.declared_arrays.insert(name.to_string());
813 if frozen {
814 layer.frozen_arrays.insert(name.to_string());
815 }
816 }
817 Sigil::Hash => {
818 layer.declared_hashes.insert(name.to_string());
819 if frozen {
820 layer.frozen_hashes.insert(name.to_string());
821 }
822 }
823 Sigil::Typeglob => {
824 layer.declared_scalars.insert(name.to_string());
825 }
826 }
827 }
828
829 fn check_strict_scalar_access(&self, name: &str, line: usize) -> Result<(), CompileError> {
835 if !self.strict_vars
836 || name.contains("::")
837 || VMHelper::strict_scalar_exempt(name)
838 || VMHelper::is_special_scalar_name_for_get(name)
839 || self
840 .scope_stack
841 .iter()
842 .any(|l| l.declared_scalars.contains(name))
843 {
844 return Ok(());
845 }
846 Err(CompileError::Frozen {
847 line,
848 detail: format!(
849 "Global symbol \"${}\" requires explicit package name (did you forget to declare \"my ${}\"?)",
850 name, name
851 ),
852 })
853 }
854
855 fn strict_array_exempt(name: &str) -> bool {
861 if matches!(
862 name,
863 "_" | "ARGV" | "INC" | "ENV" | "ISA" | "EXPORT" | "EXPORT_OK" | "EXPORT_FAIL"
864 ) {
865 return true;
866 }
867 name.starts_with('_') && name.len() > 1 && name[1..].chars().all(|c| c.is_ascii_digit())
868 }
869
870 fn strict_hash_exempt(name: &str) -> bool {
874 if matches!(
875 name,
876 "ENV" | "INC" | "SIG" | "EXPORT_TAGS" | "ISA" | "OVERLOAD" | "+" | "-" | "!" | "^H"
877 ) {
878 return true;
879 }
880 name.starts_with('_') && name.len() > 1 && name[1..].chars().all(|c| c.is_ascii_digit())
881 }
882
883 fn check_strict_array_access(&self, name: &str, line: usize) -> Result<(), CompileError> {
884 if !self.strict_vars
885 || name.contains("::")
886 || Self::strict_array_exempt(name)
887 || self
888 .scope_stack
889 .iter()
890 .any(|l| l.declared_arrays.contains(name))
891 {
892 return Ok(());
893 }
894 Err(CompileError::Frozen {
895 line,
896 detail: format!(
897 "Global symbol \"@{}\" requires explicit package name (did you forget to declare \"my @{}\"?)",
898 name, name
899 ),
900 })
901 }
902
903 fn check_strict_hash_access(&self, name: &str, line: usize) -> Result<(), CompileError> {
904 if !self.strict_vars
905 || name.contains("::")
906 || Self::strict_hash_exempt(name)
907 || self
908 .scope_stack
909 .iter()
910 .any(|l| l.declared_hashes.contains(name))
911 {
912 return Ok(());
913 }
914 Err(CompileError::Frozen {
915 line,
916 detail: format!(
917 "Global symbol \"%{}\" requires explicit package name (did you forget to declare \"my %{}\"?)",
918 name, name
919 ),
920 })
921 }
922
923 fn check_scalar_mutable(&self, name: &str, line: usize) -> Result<(), CompileError> {
924 for layer in self.scope_stack.iter().rev() {
925 if layer.declared_scalars.contains(name) {
926 if layer.frozen_scalars.contains(name) {
927 return Err(CompileError::Frozen {
928 line,
929 detail: format!("cannot assign to frozen variable `${}`", name),
930 });
931 }
932 return Ok(());
933 }
934 }
935 Ok(())
936 }
937
938 fn check_closure_write_to_outer_my(&self, name: &str, line: usize) -> Result<(), CompileError> {
951 if crate::compat_mode() {
952 return Ok(());
953 }
954 let mut crossed_sub_body = false;
958 for layer in self.scope_stack.iter().rev() {
959 if layer.declared_scalars.contains(name) {
960 if !crossed_sub_body {
961 return Ok(());
962 }
963 if layer.mysync_scalars.contains(name) {
964 return Ok(());
965 }
966 if layer.declared_our_scalars.contains(name) {
967 return Ok(());
968 }
969 return Err(CompileError::Frozen {
970 line,
971 detail: format!(
972 "cannot modify outer-scope `my ${name}` from inside a closure — \
973 stryke closures capture by value to keep parallel dispatch race-free. \
974 Use `mysync ${name}` for shared mutable state, or `--compat` for Perl 5 \
975 shared-storage semantics"
976 ),
977 });
978 }
979 if layer.is_sub_body {
980 crossed_sub_body = true;
981 }
982 }
983 Ok(())
984 }
985
986 fn check_array_mutable(&self, name: &str, line: usize) -> Result<(), CompileError> {
987 for layer in self.scope_stack.iter().rev() {
988 if layer.declared_arrays.contains(name) {
989 if layer.frozen_arrays.contains(name) {
990 return Err(CompileError::Frozen {
991 line,
992 detail: format!("cannot modify frozen array `@{}`", name),
993 });
994 }
995 return Ok(());
996 }
997 }
998 Ok(())
999 }
1000
1001 fn check_hash_mutable(&self, name: &str, line: usize) -> Result<(), CompileError> {
1002 for layer in self.scope_stack.iter().rev() {
1003 if layer.declared_hashes.contains(name) {
1004 if layer.frozen_hashes.contains(name) {
1005 return Err(CompileError::Frozen {
1006 line,
1007 detail: format!("cannot modify frozen hash `%{}`", name),
1008 });
1009 }
1010 return Ok(());
1011 }
1012 }
1013 Ok(())
1014 }
1015
1016 fn register_env_imports(layer: &mut ScopeLayer, imports: &[Expr]) {
1019 for e in imports {
1020 let mut names_owned: Vec<String> = Vec::new();
1021 match &e.kind {
1022 ExprKind::String(s) => names_owned.push(s.clone()),
1023 ExprKind::QW(ws) => names_owned.extend(ws.iter().cloned()),
1024 ExprKind::InterpolatedString(parts) => {
1025 let mut s = String::new();
1026 for p in parts {
1027 match p {
1028 StringPart::Literal(l) => s.push_str(l),
1029 StringPart::ScalarVar(v) => {
1030 s.push('$');
1031 s.push_str(v);
1032 }
1033 StringPart::ArrayVar(v) => {
1034 s.push('@');
1035 s.push_str(v);
1036 }
1037 _ => continue,
1038 }
1039 }
1040 names_owned.push(s);
1041 }
1042 _ => continue,
1043 };
1044 for raw in &names_owned {
1045 if let Some(arr) = raw.strip_prefix('@') {
1046 layer.declared_arrays.insert(arr.to_string());
1047 } else if let Some(hash) = raw.strip_prefix('%') {
1048 layer.declared_hashes.insert(hash.to_string());
1049 } else {
1050 let scalar = raw.strip_prefix('$').unwrap_or(raw);
1051 layer.declared_scalars.insert(scalar.to_string());
1052 }
1053 }
1054 }
1055 }
1056
1057 fn emit_aggregate_symbolic_inc_dec_error(
1064 &mut self,
1065 kind: Sigil,
1066 is_pre: bool,
1067 is_inc: bool,
1068 line: usize,
1069 root: &Expr,
1070 ) -> Result<(), CompileError> {
1071 let agg = match kind {
1072 Sigil::Array => "array",
1073 Sigil::Hash => "hash",
1074 _ => {
1075 return Err(CompileError::Unsupported(
1076 "internal: non-aggregate sigil passed to symbolic ++/-- error emitter".into(),
1077 ));
1078 }
1079 };
1080 let op_str = match (is_pre, is_inc) {
1081 (true, true) => "preincrement (++)",
1082 (true, false) => "predecrement (--)",
1083 (false, true) => "postincrement (++)",
1084 (false, false) => "postdecrement (--)",
1085 };
1086 let msg = format!("Can't modify {} dereference in {}", agg, op_str);
1087 let idx = self.chunk.add_constant(StrykeValue::string(msg));
1088 self.emit_op(Op::RuntimeErrorConst(idx), line, Some(root));
1089 self.emit_op(Op::LoadUndef, line, Some(root));
1092 Ok(())
1093 }
1094
1095 fn is_mysync_array(&self, array_name: &str) -> bool {
1097 let q = self.qualify_stash_array_name(array_name);
1098 self.scope_stack
1099 .iter()
1100 .rev()
1101 .any(|l| l.mysync_arrays.contains(&q))
1102 }
1103
1104 fn is_mysync_hash(&self, hash_name: &str) -> bool {
1105 self.scope_stack
1106 .iter()
1107 .rev()
1108 .any(|l| l.mysync_hashes.contains(hash_name))
1109 }
1110
1111 pub fn compile_program(mut self, program: &Program) -> Result<Chunk, CompileError> {
1112 for stmt in &program.statements {
1114 match &stmt.kind {
1115 StmtKind::Begin(block) => self.begin_blocks.push(block.clone()),
1116 StmtKind::UnitCheck(block) => self.unit_check_blocks.push(block.clone()),
1117 StmtKind::Check(block) => self.check_blocks.push(block.clone()),
1118 StmtKind::Init(block) => self.init_blocks.push(block.clone()),
1119 StmtKind::End(block) => self.end_blocks.push(block.clone()),
1120 _ => {}
1121 }
1122 }
1123
1124 let mut pending_pkg = String::new();
1126 for stmt in &program.statements {
1127 match &stmt.kind {
1128 StmtKind::Package { name } => pending_pkg = name.clone(),
1129 StmtKind::SubDecl { name, .. } => {
1130 let q = Self::qualify_sub_decl_pass1(name, &pending_pkg);
1131 let name_idx = self.chunk.intern_name(&q);
1132 self.chunk.sub_entries.push((name_idx, 0, false));
1133 }
1134 _ => {}
1135 }
1136 }
1137
1138 let main_stmts: Vec<&Statement> = program
1142 .statements
1143 .iter()
1144 .filter(|s| {
1145 !matches!(
1146 s.kind,
1147 StmtKind::SubDecl { .. }
1148 | StmtKind::Begin(_)
1149 | StmtKind::UnitCheck(_)
1150 | StmtKind::Check(_)
1151 | StmtKind::Init(_)
1152 | StmtKind::End(_)
1153 )
1154 })
1155 .collect();
1156 let last_idx = main_stmts.len().saturating_sub(1);
1157 self.program_last_stmt_takes_value = main_stmts
1158 .last()
1159 .map(|s| matches!(s.kind, StmtKind::TryCatch { .. }))
1160 .unwrap_or(false);
1161 if !self.begin_blocks.is_empty() {
1163 self.chunk.emit(Op::SetGlobalPhase(GP_START), 0);
1164 }
1165 for block in &self.begin_blocks.clone() {
1166 self.compile_block(block)?;
1167 }
1168 let unit_check_rev: Vec<Block> = self.unit_check_blocks.iter().rev().cloned().collect();
1170 for block in unit_check_rev {
1171 self.compile_block(&block)?;
1172 }
1173 if !self.check_blocks.is_empty() {
1174 self.chunk.emit(Op::SetGlobalPhase(GP_CHECK), 0);
1175 }
1176 let check_rev: Vec<Block> = self.check_blocks.iter().rev().cloned().collect();
1177 for block in check_rev {
1178 self.compile_block(&block)?;
1179 }
1180 if !self.init_blocks.is_empty() {
1181 self.chunk.emit(Op::SetGlobalPhase(GP_INIT), 0);
1182 }
1183 let inits = self.init_blocks.clone();
1184 for block in inits {
1185 self.compile_block(&block)?;
1186 }
1187 self.chunk.emit(Op::SetGlobalPhase(GP_RUN), 0);
1188 self.chunk.body_start_ip = self.chunk.ops.len();
1190
1191 self.enter_goto_scope();
1195
1196 let mut i = 0;
1197 while i < main_stmts.len() {
1198 let stmt = main_stmts[i];
1199 if i == last_idx {
1200 if let Some(lbl) = &stmt.label {
1204 self.record_stmt_label(lbl);
1205 }
1206 match &stmt.kind {
1207 StmtKind::Expression(expr) => {
1208 if matches!(&expr.kind, ExprKind::Regex(..)) {
1210 self.compile_boolean_rvalue_condition(expr)?;
1211 } else {
1212 self.compile_expr(expr)?;
1213 }
1214 }
1215 StmtKind::If {
1216 condition,
1217 body,
1218 elsifs,
1219 else_block,
1220 } => {
1221 self.compile_boolean_rvalue_condition(condition)?;
1222 let j0 = self.chunk.emit(Op::JumpIfFalse(0), stmt.line);
1223 self.emit_block_value(body, stmt.line)?;
1224 let mut ends = vec![self.chunk.emit(Op::Jump(0), stmt.line)];
1225 self.chunk.patch_jump_here(j0);
1226 for (c, blk) in elsifs {
1227 self.compile_boolean_rvalue_condition(c)?;
1228 let j = self.chunk.emit(Op::JumpIfFalse(0), c.line);
1229 self.emit_block_value(blk, c.line)?;
1230 ends.push(self.chunk.emit(Op::Jump(0), c.line));
1231 self.chunk.patch_jump_here(j);
1232 }
1233 if let Some(eb) = else_block {
1234 self.emit_block_value(eb, stmt.line)?;
1235 } else {
1236 self.chunk.emit(Op::LoadUndef, stmt.line);
1237 }
1238 for j in ends {
1239 self.chunk.patch_jump_here(j);
1240 }
1241 }
1242 StmtKind::Unless {
1243 condition,
1244 body,
1245 else_block,
1246 } => {
1247 self.compile_boolean_rvalue_condition(condition)?;
1248 let j0 = self.chunk.emit(Op::JumpIfFalse(0), stmt.line);
1249 if let Some(eb) = else_block {
1250 self.emit_block_value(eb, stmt.line)?;
1251 } else {
1252 self.chunk.emit(Op::LoadUndef, stmt.line);
1253 }
1254 let end = self.chunk.emit(Op::Jump(0), stmt.line);
1255 self.chunk.patch_jump_here(j0);
1256 self.emit_block_value(body, stmt.line)?;
1257 self.chunk.patch_jump_here(end);
1258 }
1259 StmtKind::Block(block) => {
1260 self.chunk.emit(Op::PushFrame, stmt.line);
1261 self.emit_block_value(block, stmt.line)?;
1262 self.chunk.emit(Op::PopFrame, stmt.line);
1263 }
1264 StmtKind::StmtGroup(block) => {
1265 self.emit_block_value(block, stmt.line)?;
1266 }
1267 _ => self.compile_statement(stmt)?,
1268 }
1269 } else {
1270 self.compile_statement(stmt)?;
1271 }
1272 i += 1;
1273 }
1274 self.program_last_stmt_takes_value = false;
1275
1276 self.exit_goto_scope()?;
1278
1279 if !self.end_blocks.is_empty() {
1281 self.chunk.emit(Op::SetGlobalPhase(GP_END), 0);
1282 }
1283 for block in &self.end_blocks.clone() {
1284 self.compile_block(block)?;
1285 }
1286
1287 self.chunk.emit(Op::Halt, 0);
1288
1289 let mut entries: Vec<(String, Vec<Statement>, String, Vec<crate::ast::SubSigParam>)> =
1291 Vec::new();
1292 let mut pending_pkg = String::new();
1293 for stmt in &program.statements {
1294 match &stmt.kind {
1295 StmtKind::Package { name } => pending_pkg = name.clone(),
1296 StmtKind::SubDecl {
1297 name, body, params, ..
1298 } => {
1299 entries.push((
1300 name.clone(),
1301 body.clone(),
1302 pending_pkg.clone(),
1303 params.clone(),
1304 ));
1305 }
1306 _ => {}
1307 }
1308 }
1309
1310 for (name, body, sub_pkg, params) in &entries {
1311 let saved_pkg = self.current_package.clone();
1312 self.current_package = sub_pkg.clone();
1313 self.push_scope_layer_with_slots();
1314 for p in params {
1321 self.register_sig_param(p);
1322 }
1323 let entry_ip = self.chunk.len();
1324 let q = self.qualify_sub_key(name);
1325 let name_idx = self.chunk.intern_name(&q);
1326 for e in &mut self.chunk.sub_entries {
1328 if e.0 == name_idx {
1329 e.1 = entry_ip;
1330 }
1331 }
1332 self.enter_goto_scope();
1335 self.emit_subroutine_body_return(body)?;
1337 self.exit_goto_scope()?;
1338 self.pop_scope_layer();
1339
1340 let underscore_idx = self.chunk.intern_name("_");
1344 self.peephole_stack_args(name_idx, entry_ip, underscore_idx);
1345 self.current_package = saved_pkg;
1346 }
1347
1348 self.chunk.block_bytecode_ranges = vec![None; self.chunk.blocks.len()];
1357 for i in 0..self.chunk.blocks.len() {
1358 let b = self.chunk.blocks[i].clone();
1359 if Self::block_has_return(&b) {
1360 continue;
1361 }
1362 let saved_scope_stack = self
1363 .block_scope_snapshots
1364 .get(i)
1365 .cloned()
1366 .flatten()
1367 .map(|snap| std::mem::replace(&mut self.scope_stack, snap));
1368 let is_sort_pair = self.sort_pair_block_indices.contains(&(i as u16));
1372 self.force_name_for_sort_pair = is_sort_pair;
1373 let pushed_sub_body = self.sub_body_block_indices.contains(&(i as u16));
1379 if pushed_sub_body {
1380 self.scope_stack.push(ScopeLayer {
1381 use_slots: true,
1382 is_sub_body: true,
1383 ..Default::default()
1384 });
1385 if let Some(params) = self.code_ref_block_params.get(i).cloned() {
1388 for p in ¶ms {
1389 self.register_sig_param(p);
1390 }
1391 }
1392 }
1393 let result = self.try_compile_block_region(&b);
1394 self.force_name_for_sort_pair = false;
1395 if pushed_sub_body {
1396 self.scope_stack.pop();
1397 }
1398 match result {
1399 Ok(range) => {
1400 self.chunk.block_bytecode_ranges[i] = Some(range);
1401 }
1402 Err(CompileError::Frozen { .. }) => {
1403 if let Some(orig) = saved_scope_stack {
1407 self.scope_stack = orig;
1408 }
1409 return Err(result.unwrap_err());
1410 }
1411 Err(CompileError::Unsupported(_)) => {
1412 }
1416 }
1417 if let Some(orig) = saved_scope_stack {
1418 self.scope_stack = orig;
1419 }
1420 }
1421
1422 self.chunk.map_expr_bytecode_ranges = vec![None; self.chunk.map_expr_entries.len()];
1424 for i in 0..self.chunk.map_expr_entries.len() {
1425 let e = self.chunk.map_expr_entries[i].clone();
1426 if let Ok(range) = self.try_compile_grep_expr_region(&e, WantarrayCtx::List) {
1427 self.chunk.map_expr_bytecode_ranges[i] = Some(range);
1428 }
1429 }
1430
1431 self.chunk.grep_expr_bytecode_ranges = vec![None; self.chunk.grep_expr_entries.len()];
1433 for i in 0..self.chunk.grep_expr_entries.len() {
1434 let e = self.chunk.grep_expr_entries[i].clone();
1435 if let Ok(range) = self.try_compile_grep_expr_region(&e, WantarrayCtx::Scalar) {
1436 self.chunk.grep_expr_bytecode_ranges[i] = Some(range);
1437 }
1438 }
1439
1440 self.chunk.regex_flip_flop_rhs_expr_bytecode_ranges =
1442 vec![None; self.chunk.regex_flip_flop_rhs_expr_entries.len()];
1443 for i in 0..self.chunk.regex_flip_flop_rhs_expr_entries.len() {
1444 let e = self.chunk.regex_flip_flop_rhs_expr_entries[i].clone();
1445 if let Ok(range) = self.try_compile_flip_flop_rhs_expr_region(&e) {
1446 self.chunk.regex_flip_flop_rhs_expr_bytecode_ranges[i] = Some(range);
1447 }
1448 }
1449
1450 self.chunk.eval_timeout_expr_bytecode_ranges =
1452 vec![None; self.chunk.eval_timeout_entries.len()];
1453 for i in 0..self.chunk.eval_timeout_entries.len() {
1454 let timeout_expr = self.chunk.eval_timeout_entries[i].0.clone();
1455 if let Ok(range) =
1456 self.try_compile_grep_expr_region(&timeout_expr, WantarrayCtx::Scalar)
1457 {
1458 self.chunk.eval_timeout_expr_bytecode_ranges[i] = Some(range);
1459 }
1460 }
1461
1462 self.chunk.keys_expr_bytecode_ranges = vec![None; self.chunk.keys_expr_entries.len()];
1464 for i in 0..self.chunk.keys_expr_entries.len() {
1465 let e = self.chunk.keys_expr_entries[i].clone();
1466 if let Ok(range) = self.try_compile_grep_expr_region(&e, WantarrayCtx::List) {
1467 self.chunk.keys_expr_bytecode_ranges[i] = Some(range);
1468 }
1469 }
1470 self.chunk.values_expr_bytecode_ranges = vec![None; self.chunk.values_expr_entries.len()];
1471 for i in 0..self.chunk.values_expr_entries.len() {
1472 let e = self.chunk.values_expr_entries[i].clone();
1473 if let Ok(range) = self.try_compile_grep_expr_region(&e, WantarrayCtx::List) {
1474 self.chunk.values_expr_bytecode_ranges[i] = Some(range);
1475 }
1476 }
1477
1478 self.chunk.given_topic_bytecode_ranges = vec![None; self.chunk.given_entries.len()];
1480 for i in 0..self.chunk.given_entries.len() {
1481 let topic = self.chunk.given_entries[i].0.clone();
1482 if let Ok(range) = self.try_compile_grep_expr_region(&topic, WantarrayCtx::Scalar) {
1483 self.chunk.given_topic_bytecode_ranges[i] = Some(range);
1484 }
1485 }
1486
1487 self.chunk.algebraic_match_subject_bytecode_ranges =
1489 vec![None; self.chunk.algebraic_match_entries.len()];
1490 for i in 0..self.chunk.algebraic_match_entries.len() {
1491 let subject = self.chunk.algebraic_match_entries[i].0.clone();
1492 let range: Option<(usize, usize)> = match &subject.kind {
1493 ExprKind::ArrayVar(name) => {
1494 self.check_strict_array_access(name, subject.line)?;
1495 let line = subject.line;
1496 let start = self.chunk.len();
1497 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
1498 self.chunk.emit(Op::MakeArrayBindingRef(idx), line);
1499 self.chunk.emit(Op::BlockReturnValue, line);
1500 Some((start, self.chunk.len()))
1501 }
1502 ExprKind::HashVar(name) => {
1503 self.check_strict_hash_access(name, subject.line)?;
1504 let line = subject.line;
1505 let start = self.chunk.len();
1506 let idx = self.chunk.intern_name(name);
1507 self.chunk.emit(Op::MakeHashBindingRef(idx), line);
1508 self.chunk.emit(Op::BlockReturnValue, line);
1509 Some((start, self.chunk.len()))
1510 }
1511 _ => self
1512 .try_compile_grep_expr_region(&subject, WantarrayCtx::Scalar)
1513 .ok(),
1514 };
1515 self.chunk.algebraic_match_subject_bytecode_ranges[i] = range;
1516 }
1517
1518 Self::patch_static_sub_calls(&mut self.chunk);
1519 self.chunk.peephole_fuse();
1520
1521 Ok(self.chunk)
1522 }
1523
1524 fn try_compile_block_region(&mut self, block: &Block) -> Result<(usize, usize), CompileError> {
1530 let line0 = block.first().map(|s| s.line).unwrap_or(0);
1531 let start = self.chunk.len();
1532 if block.is_empty() {
1533 self.chunk.emit(Op::LoadUndef, line0);
1534 self.chunk.emit(Op::BlockReturnValue, line0);
1535 return Ok((start, self.chunk.len()));
1536 }
1537 let last = block.last().expect("non-empty block");
1538 let StmtKind::Expression(expr) = &last.kind else {
1539 return Err(CompileError::Unsupported(
1540 "block last statement must be an expression for bytecode lowering".into(),
1541 ));
1542 };
1543 for stmt in &block[..block.len() - 1] {
1544 self.compile_statement(stmt)?;
1545 }
1546 let line = last.line;
1547 self.compile_expr(expr)?;
1548 self.chunk.emit(Op::BlockReturnValue, line);
1549 Ok((start, self.chunk.len()))
1550 }
1551
1552 fn try_compile_grep_expr_region(
1558 &mut self,
1559 expr: &Expr,
1560 ctx: WantarrayCtx,
1561 ) -> Result<(usize, usize), CompileError> {
1562 let line = expr.line;
1563 let start = self.chunk.len();
1564 self.compile_expr_ctx(expr, ctx)?;
1565 self.chunk.emit(Op::BlockReturnValue, line);
1566 Ok((start, self.chunk.len()))
1567 }
1568
1569 fn try_compile_flip_flop_rhs_expr_region(
1571 &mut self,
1572 expr: &Expr,
1573 ) -> Result<(usize, usize), CompileError> {
1574 let line = expr.line;
1575 let start = self.chunk.len();
1576 self.compile_boolean_rvalue_condition(expr)?;
1577 self.chunk.emit(Op::BlockReturnValue, line);
1578 Ok((start, self.chunk.len()))
1579 }
1580
1581 fn peephole_stack_args(&mut self, sub_name_idx: u16, entry_ip: usize, underscore_idx: u16) {
1586 let ops = &self.chunk.ops;
1587 let end = ops.len();
1588
1589 let mut shift_count: u8 = 0;
1591 let mut ip = entry_ip;
1592 while ip < end {
1593 if ops[ip] == Op::ShiftArray(underscore_idx) {
1594 shift_count += 1;
1595 ip += 1;
1596 } else {
1597 break;
1598 }
1599 }
1600 if shift_count == 0 {
1601 return;
1602 }
1603
1604 let refs_underscore = |op: &Op| -> bool {
1606 match op {
1607 Op::GetArray(idx)
1608 | Op::SetArray(idx)
1609 | Op::DeclareArray(idx)
1610 | Op::DeclareArrayFrozen(idx)
1611 | Op::GetArrayElem(idx)
1612 | Op::SetArrayElem(idx)
1613 | Op::SetArrayElemKeep(idx)
1614 | Op::PushArray(idx)
1615 | Op::PopArray(idx)
1616 | Op::ShiftArray(idx)
1617 | Op::ArrayLen(idx) => *idx == underscore_idx,
1618 _ => false,
1619 }
1620 };
1621
1622 for op in ops.iter().take(end).skip(entry_ip + shift_count as usize) {
1623 if refs_underscore(op) {
1624 return; }
1626 if matches!(op, Op::Halt | Op::ReturnValue) {
1627 break; }
1629 }
1630
1631 for i in 0..shift_count {
1633 self.chunk.ops[entry_ip + i as usize] = Op::GetArg(i);
1634 }
1635
1636 for e in &mut self.chunk.sub_entries {
1638 if e.0 == sub_name_idx {
1639 e.2 = true;
1640 }
1641 }
1642 }
1643
1644 fn emit_declare_scalar(&mut self, name_idx: u16, line: usize, frozen: bool) {
1645 let name = self.chunk.names[name_idx as usize].clone();
1646 self.register_declare(Sigil::Scalar, &name, frozen);
1647 if frozen {
1648 self.chunk.emit(Op::DeclareScalarFrozen(name_idx), line);
1649 } else if let Some(slot) = self.assign_scalar_slot(&name) {
1650 self.chunk.emit(Op::DeclareScalarSlot(slot, name_idx), line);
1651 } else {
1652 self.chunk.emit(Op::DeclareScalar(name_idx), line);
1653 }
1654 }
1655
1656 fn emit_declare_scalar_typed(
1661 &mut self,
1662 name_idx: u16,
1663 ty: &crate::ast::PerlTypeName,
1664 line: usize,
1665 frozen: bool,
1666 ) {
1667 let name = self.chunk.names[name_idx as usize].clone();
1668 self.register_declare(Sigil::Scalar, &name, frozen);
1669 if let Some(ty_byte) = ty.as_byte() {
1670 if frozen {
1671 self.chunk
1672 .emit(Op::DeclareScalarTypedFrozen(name_idx, ty_byte), line);
1673 } else {
1674 self.chunk
1675 .emit(Op::DeclareScalarTyped(name_idx, ty_byte), line);
1676 }
1677 return;
1678 }
1679 let (type_name, is_enum) = match ty {
1681 crate::ast::PerlTypeName::Struct(n) => (n.clone(), false),
1682 crate::ast::PerlTypeName::Enum(n) => (n.clone(), true),
1683 _ => unreachable!("non-byte non-user type slipped past as_byte()"),
1684 };
1685 let type_name_idx = self.chunk.intern_name(&type_name);
1686 let flag = (u8::from(frozen) << 1) | u8::from(is_enum);
1687 self.chunk.emit(
1688 Op::DeclareScalarTypedUser(name_idx, type_name_idx, flag),
1689 line,
1690 );
1691 }
1692
1693 fn emit_declare_array(&mut self, name_idx: u16, line: usize, frozen: bool) {
1694 let name = self.chunk.names[name_idx as usize].clone();
1695 self.register_declare(Sigil::Array, &name, frozen);
1696 if frozen {
1697 self.chunk.emit(Op::DeclareArrayFrozen(name_idx), line);
1698 } else {
1699 self.chunk.emit(Op::DeclareArray(name_idx), line);
1700 }
1701 }
1702
1703 fn emit_declare_hash(&mut self, name_idx: u16, line: usize, frozen: bool) {
1704 let name = self.chunk.names[name_idx as usize].clone();
1705 self.register_declare(Sigil::Hash, &name, frozen);
1706 if frozen {
1707 self.chunk.emit(Op::DeclareHashFrozen(name_idx), line);
1708 } else {
1709 self.chunk.emit(Op::DeclareHash(name_idx), line);
1710 }
1711 }
1712
1713 fn compile_var_declarations(
1714 &mut self,
1715 decls: &[VarDecl],
1716 line: usize,
1717 is_my: bool,
1718 ) -> Result<(), CompileError> {
1719 let allow_frozen = is_my;
1720 if decls.len() > 1 && decls[0].initializer.is_some() {
1722 self.compile_expr_ctx(decls[0].initializer.as_ref().unwrap(), WantarrayCtx::List)?;
1723 let tmp_name = self.chunk.intern_name("__list_assign_tmp__");
1724 self.emit_declare_array(tmp_name, line, false);
1725 for (i, decl) in decls.iter().enumerate() {
1726 let frozen = allow_frozen && decl.frozen;
1727 match decl.sigil {
1728 Sigil::Scalar => {
1729 self.chunk.emit(Op::LoadInt(i as i64), line);
1730 self.chunk.emit(Op::GetArrayElem(tmp_name), line);
1731 if is_my {
1732 let name_idx = self.chunk.intern_name(&decl.name);
1733 if let Some(ref ty) = decl.type_annotation {
1734 self.emit_declare_scalar_typed(name_idx, ty, line, frozen);
1735 } else {
1736 self.emit_declare_scalar(name_idx, line, frozen);
1737 }
1738 } else {
1739 if decl.type_annotation.is_some() {
1740 return Err(CompileError::Unsupported("typed our".into()));
1741 }
1742 self.emit_declare_our_scalar(&decl.name, line, frozen);
1743 }
1744 }
1745 Sigil::Array => {
1746 let name_idx = self
1747 .chunk
1748 .intern_name(&self.qualify_stash_array_name(&decl.name));
1749 self.chunk
1752 .emit(Op::GetArrayFromIndex(tmp_name, i as u16), line);
1753 self.emit_declare_array(name_idx, line, frozen);
1754 }
1755 Sigil::Hash => {
1756 let name_idx = self.chunk.intern_name(&decl.name);
1757 self.chunk
1760 .emit(Op::GetArrayFromIndex(tmp_name, i as u16), line);
1761 self.emit_declare_hash(name_idx, line, frozen);
1762 }
1763 Sigil::Typeglob => {
1764 return Err(CompileError::Unsupported(
1765 "list assignment to typeglob (my (*a, *b) = ...)".into(),
1766 ));
1767 }
1768 }
1769 }
1770 } else {
1771 for decl in decls {
1772 let frozen = allow_frozen && decl.frozen;
1773 match decl.sigil {
1774 Sigil::Scalar => {
1775 if let Some(init) = &decl.initializer {
1776 if decl.list_context {
1777 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1779 self.chunk.emit(Op::ListFirst, line);
1780 } else {
1781 self.compile_expr(init)?;
1782 }
1783 } else {
1784 self.chunk.emit(Op::LoadUndef, line);
1785 }
1786 if is_my {
1787 let name_idx = self.chunk.intern_name(&decl.name);
1788 if let Some(ref ty) = decl.type_annotation {
1789 self.emit_declare_scalar_typed(name_idx, ty, line, frozen);
1790 } else {
1791 self.emit_declare_scalar(name_idx, line, frozen);
1792 }
1793 } else {
1794 if decl.type_annotation.is_some() {
1795 return Err(CompileError::Unsupported("typed our".into()));
1796 }
1797 self.emit_declare_our_scalar(&decl.name, line, false);
1798 }
1799 }
1800 Sigil::Array => {
1801 let name_idx = self
1802 .chunk
1803 .intern_name(&self.qualify_stash_array_name(&decl.name));
1804 if let Some(init) = &decl.initializer {
1805 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1806 } else {
1807 self.chunk.emit(Op::LoadUndef, line);
1808 }
1809 self.emit_declare_array(name_idx, line, frozen);
1810 }
1811 Sigil::Hash => {
1812 let name_idx = self.chunk.intern_name(&decl.name);
1813 if let Some(init) = &decl.initializer {
1814 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1815 } else {
1816 self.chunk.emit(Op::LoadUndef, line);
1817 }
1818 self.emit_declare_hash(name_idx, line, frozen);
1819 }
1820 Sigil::Typeglob => {
1821 return Err(CompileError::Unsupported("my/our *GLOB".into()));
1822 }
1823 }
1824 }
1825 }
1826 Ok(())
1827 }
1828
1829 fn compile_state_declarations(
1830 &mut self,
1831 decls: &[VarDecl],
1832 line: usize,
1833 ) -> Result<(), CompileError> {
1834 for decl in decls {
1835 match decl.sigil {
1836 Sigil::Scalar => {
1837 if let Some(init) = &decl.initializer {
1838 self.compile_expr(init)?;
1839 } else {
1840 self.chunk.emit(Op::LoadUndef, line);
1841 }
1842 let name_idx = self.chunk.intern_name(&decl.name);
1843 let name = self.chunk.names[name_idx as usize].clone();
1844 self.register_declare(Sigil::Scalar, &name, false);
1845 self.chunk.emit(Op::DeclareStateScalar(name_idx), line);
1846 }
1847 Sigil::Array => {
1848 let name_idx = self
1849 .chunk
1850 .intern_name(&self.qualify_stash_array_name(&decl.name));
1851 if let Some(init) = &decl.initializer {
1852 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1853 } else {
1854 self.chunk.emit(Op::LoadUndef, line);
1855 }
1856 self.chunk.emit(Op::DeclareStateArray(name_idx), line);
1857 }
1858 Sigil::Hash => {
1859 let name_idx = self.chunk.intern_name(&decl.name);
1860 if let Some(init) = &decl.initializer {
1861 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1862 } else {
1863 self.chunk.emit(Op::LoadUndef, line);
1864 }
1865 self.chunk.emit(Op::DeclareStateHash(name_idx), line);
1866 }
1867 Sigil::Typeglob => {
1868 return Err(CompileError::Unsupported("state *GLOB".into()));
1869 }
1870 }
1871 }
1872 Ok(())
1873 }
1874
1875 fn compile_local_declarations(
1876 &mut self,
1877 decls: &[VarDecl],
1878 line: usize,
1879 ) -> Result<(), CompileError> {
1880 if decls.iter().any(|d| d.type_annotation.is_some()) {
1881 return Err(CompileError::Unsupported("typed local".into()));
1882 }
1883 if decls.len() > 1 && decls[0].initializer.is_some() {
1884 self.compile_expr_ctx(decls[0].initializer.as_ref().unwrap(), WantarrayCtx::List)?;
1885 let tmp_name = self.chunk.intern_name("__list_assign_tmp__");
1886 self.emit_declare_array(tmp_name, line, false);
1887 for (i, decl) in decls.iter().enumerate() {
1888 match decl.sigil {
1889 Sigil::Scalar => {
1890 let name_idx = self.intern_scalar_for_local(&decl.name);
1891 self.chunk.emit(Op::LoadInt(i as i64), line);
1892 self.chunk.emit(Op::GetArrayElem(tmp_name), line);
1893 self.chunk.emit(Op::LocalDeclareScalar(name_idx), line);
1894 }
1895 Sigil::Array => {
1896 let q = self.qualify_stash_array_name(&decl.name);
1897 let name_idx = self.chunk.intern_name(&q);
1898 self.chunk.emit(Op::GetArray(tmp_name), line);
1899 self.chunk.emit(Op::LocalDeclareArray(name_idx), line);
1900 }
1901 Sigil::Hash => {
1902 let name_idx = self.chunk.intern_name(&decl.name);
1903 self.chunk.emit(Op::GetArray(tmp_name), line);
1904 self.chunk.emit(Op::LocalDeclareHash(name_idx), line);
1905 }
1906 Sigil::Typeglob => {
1907 return Err(CompileError::Unsupported(
1908 "local (*a,*b,...) with list initializer and typeglob".into(),
1909 ));
1910 }
1911 }
1912 }
1913 } else {
1914 for decl in decls {
1915 match decl.sigil {
1916 Sigil::Scalar => {
1917 let name_idx = self.intern_scalar_for_local(&decl.name);
1918 if let Some(init) = &decl.initializer {
1919 self.compile_expr(init)?;
1920 } else {
1921 self.chunk.emit(Op::LoadUndef, line);
1922 }
1923 self.chunk.emit(Op::LocalDeclareScalar(name_idx), line);
1924 }
1925 Sigil::Array => {
1926 let q = self.qualify_stash_array_name(&decl.name);
1927 let name_idx = self.chunk.intern_name(&q);
1928 if let Some(init) = &decl.initializer {
1929 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1930 } else {
1931 self.chunk.emit(Op::LoadUndef, line);
1932 }
1933 self.chunk.emit(Op::LocalDeclareArray(name_idx), line);
1934 }
1935 Sigil::Hash => {
1936 let name_idx = self.chunk.intern_name(&decl.name);
1937 if let Some(init) = &decl.initializer {
1938 self.compile_expr_ctx(init, WantarrayCtx::List)?;
1939 } else {
1940 self.chunk.emit(Op::LoadUndef, line);
1941 }
1942 self.chunk.emit(Op::LocalDeclareHash(name_idx), line);
1943 }
1944 Sigil::Typeglob => {
1945 let name_idx = self.chunk.intern_name(&decl.name);
1946 if let Some(init) = &decl.initializer {
1947 let ExprKind::Typeglob(rhs) = &init.kind else {
1948 return Err(CompileError::Unsupported(
1949 "local *GLOB = non-typeglob".into(),
1950 ));
1951 };
1952 let rhs_idx = self.chunk.intern_name(rhs);
1953 self.chunk
1954 .emit(Op::LocalDeclareTypeglob(name_idx, Some(rhs_idx)), line);
1955 } else {
1956 self.chunk
1957 .emit(Op::LocalDeclareTypeglob(name_idx, None), line);
1958 }
1959 }
1960 }
1961 }
1962 }
1963 Ok(())
1964 }
1965
1966 fn compile_oursync_declarations(
1973 &mut self,
1974 decls: &[VarDecl],
1975 line: usize,
1976 ) -> Result<(), CompileError> {
1977 for decl in decls {
1978 if decl.type_annotation.is_some() {
1979 return Err(CompileError::Unsupported("typed oursync".into()));
1980 }
1981 match decl.sigil {
1982 Sigil::Typeglob => {
1983 return Err(CompileError::Unsupported(
1984 "`oursync` does not support typeglob variables".into(),
1985 ));
1986 }
1987 Sigil::Scalar => {
1988 if let Some(init) = &decl.initializer {
1989 self.compile_expr(init)?;
1990 } else {
1991 self.chunk.emit(Op::LoadUndef, line);
1992 }
1993 let stash = self.qualify_stash_scalar_name(&decl.name);
1994 let name_idx = self.chunk.intern_name(&stash);
1995 self.register_declare_our_scalar(&decl.name);
1996 if let Some(layer) = self.scope_stack.last_mut() {
1997 layer.mysync_scalars.insert(stash);
1998 }
1999 self.chunk.emit(Op::DeclareOurSyncScalar(name_idx), line);
2000 }
2001 Sigil::Array => {
2002 let stash = self.qualify_stash_array_name(&decl.name);
2003 if let Some(init) = &decl.initializer {
2004 self.compile_expr_ctx(init, WantarrayCtx::List)?;
2005 } else {
2006 self.chunk.emit(Op::LoadUndef, line);
2007 }
2008 let name_idx = self.chunk.intern_name(&stash);
2009 self.register_declare(Sigil::Array, &stash, false);
2010 if let Some(layer) = self.scope_stack.last_mut() {
2011 layer.mysync_arrays.insert(stash);
2012 }
2013 self.chunk.emit(Op::DeclareOurSyncArray(name_idx), line);
2014 }
2015 Sigil::Hash => {
2016 if let Some(init) = &decl.initializer {
2017 self.compile_expr_ctx(init, WantarrayCtx::List)?;
2018 } else {
2019 self.chunk.emit(Op::LoadUndef, line);
2020 }
2021 let name_idx = self.chunk.intern_name(&decl.name);
2025 self.register_declare(Sigil::Hash, &decl.name, false);
2026 if let Some(layer) = self.scope_stack.last_mut() {
2027 layer.mysync_hashes.insert(decl.name.clone());
2028 }
2029 self.chunk.emit(Op::DeclareOurSyncHash(name_idx), line);
2030 }
2031 }
2032 }
2033 Ok(())
2034 }
2035
2036 fn compile_mysync_declarations(
2037 &mut self,
2038 decls: &[VarDecl],
2039 line: usize,
2040 ) -> Result<(), CompileError> {
2041 for decl in decls {
2042 if decl.type_annotation.is_some() {
2043 return Err(CompileError::Unsupported("typed mysync".into()));
2044 }
2045 match decl.sigil {
2046 Sigil::Typeglob => {
2047 return Err(CompileError::Unsupported(
2048 "`mysync` does not support typeglob variables".into(),
2049 ));
2050 }
2051 Sigil::Scalar => {
2052 if let Some(init) = &decl.initializer {
2053 self.compile_expr(init)?;
2054 } else {
2055 self.chunk.emit(Op::LoadUndef, line);
2056 }
2057 let name_idx = self.chunk.intern_name(&decl.name);
2058 self.register_declare(Sigil::Scalar, &decl.name, false);
2059 self.chunk.emit(Op::DeclareMySyncScalar(name_idx), line);
2060 if let Some(layer) = self.scope_stack.last_mut() {
2061 layer.mysync_scalars.insert(decl.name.clone());
2062 }
2063 }
2064 Sigil::Array => {
2065 let stash = self.qualify_stash_array_name(&decl.name);
2066 if let Some(init) = &decl.initializer {
2067 self.compile_expr_ctx(init, WantarrayCtx::List)?;
2068 } else {
2069 self.chunk.emit(Op::LoadUndef, line);
2070 }
2071 let name_idx = self.chunk.intern_name(&stash);
2072 self.register_declare(Sigil::Array, &stash, false);
2073 self.chunk.emit(Op::DeclareMySyncArray(name_idx), line);
2074 if let Some(layer) = self.scope_stack.last_mut() {
2075 layer.mysync_arrays.insert(stash);
2076 }
2077 }
2078 Sigil::Hash => {
2079 if let Some(init) = &decl.initializer {
2080 self.compile_expr_ctx(init, WantarrayCtx::List)?;
2081 } else {
2082 self.chunk.emit(Op::LoadUndef, line);
2083 }
2084 let name_idx = self.chunk.intern_name(&decl.name);
2085 self.register_declare(Sigil::Hash, &decl.name, false);
2086 self.chunk.emit(Op::DeclareMySyncHash(name_idx), line);
2087 if let Some(layer) = self.scope_stack.last_mut() {
2088 layer.mysync_hashes.insert(decl.name.clone());
2089 }
2090 }
2091 }
2092 }
2093 Ok(())
2094 }
2095
2096 fn compile_local_expr(
2098 &mut self,
2099 target: &Expr,
2100 initializer: Option<&Expr>,
2101 line: usize,
2102 ) -> Result<(), CompileError> {
2103 match &target.kind {
2104 ExprKind::HashElement { hash, key } => {
2105 self.check_strict_hash_access(hash, line)?;
2106 self.check_hash_mutable(hash, line)?;
2107 let hash_idx = self.chunk.intern_name(hash);
2108 if let Some(init) = initializer {
2109 self.compile_expr(init)?;
2110 } else {
2111 self.chunk.emit(Op::LoadUndef, line);
2112 }
2113 self.compile_expr(key)?;
2114 self.chunk.emit(Op::LocalDeclareHashElement(hash_idx), line);
2115 Ok(())
2116 }
2117 ExprKind::ArrayElement { array, index } => {
2118 self.check_strict_array_access(array, line)?;
2119 let q = self.qualify_stash_array_name(array);
2120 self.check_array_mutable(&q, line)?;
2121 let arr_idx = self.chunk.intern_name(&q);
2122 if let Some(init) = initializer {
2123 self.compile_expr(init)?;
2124 } else {
2125 self.chunk.emit(Op::LoadUndef, line);
2126 }
2127 self.compile_expr(index)?;
2128 self.chunk.emit(Op::LocalDeclareArrayElement(arr_idx), line);
2129 Ok(())
2130 }
2131 ExprKind::Typeglob(name) => {
2132 let lhs_idx = self.chunk.intern_name(name);
2133 if let Some(init) = initializer {
2134 let ExprKind::Typeglob(rhs) = &init.kind else {
2135 return Err(CompileError::Unsupported(
2136 "local *GLOB = non-typeglob".into(),
2137 ));
2138 };
2139 let rhs_idx = self.chunk.intern_name(rhs);
2140 self.chunk
2141 .emit(Op::LocalDeclareTypeglob(lhs_idx, Some(rhs_idx)), line);
2142 } else {
2143 self.chunk
2144 .emit(Op::LocalDeclareTypeglob(lhs_idx, None), line);
2145 }
2146 Ok(())
2147 }
2148 ExprKind::Deref {
2149 expr,
2150 kind: Sigil::Typeglob,
2151 } => {
2152 if let Some(init) = initializer {
2153 let ExprKind::Typeglob(rhs) = &init.kind else {
2154 return Err(CompileError::Unsupported(
2155 "local *GLOB = non-typeglob".into(),
2156 ));
2157 };
2158 let rhs_idx = self.chunk.intern_name(rhs);
2159 self.compile_expr(expr)?;
2160 self.chunk
2161 .emit(Op::LocalDeclareTypeglobDynamic(Some(rhs_idx)), line);
2162 } else {
2163 self.compile_expr(expr)?;
2164 self.chunk.emit(Op::LocalDeclareTypeglobDynamic(None), line);
2165 }
2166 Ok(())
2167 }
2168 ExprKind::TypeglobExpr(expr) => {
2169 if let Some(init) = initializer {
2170 let ExprKind::Typeglob(rhs) = &init.kind else {
2171 return Err(CompileError::Unsupported(
2172 "local *GLOB = non-typeglob".into(),
2173 ));
2174 };
2175 let rhs_idx = self.chunk.intern_name(rhs);
2176 self.compile_expr(expr)?;
2177 self.chunk
2178 .emit(Op::LocalDeclareTypeglobDynamic(Some(rhs_idx)), line);
2179 } else {
2180 self.compile_expr(expr)?;
2181 self.chunk.emit(Op::LocalDeclareTypeglobDynamic(None), line);
2182 }
2183 Ok(())
2184 }
2185 ExprKind::ScalarVar(name) => {
2186 let name_idx = self.intern_scalar_for_local(name);
2187 if let Some(init) = initializer {
2188 self.compile_expr(init)?;
2189 } else {
2190 self.chunk.emit(Op::LoadUndef, line);
2191 }
2192 self.chunk.emit(Op::LocalDeclareScalar(name_idx), line);
2193 Ok(())
2194 }
2195 ExprKind::ArrayVar(name) => {
2196 self.check_strict_array_access(name, line)?;
2197 let q = self.qualify_stash_array_name(name);
2198 let name_idx = self.chunk.intern_name(&q);
2199 if let Some(init) = initializer {
2200 self.compile_expr_ctx(init, WantarrayCtx::List)?;
2201 } else {
2202 self.chunk.emit(Op::LoadUndef, line);
2203 }
2204 self.chunk.emit(Op::LocalDeclareArray(name_idx), line);
2205 Ok(())
2206 }
2207 ExprKind::HashVar(name) => {
2208 let name_idx = self.chunk.intern_name(name);
2209 if let Some(init) = initializer {
2210 self.compile_expr_ctx(init, WantarrayCtx::List)?;
2211 } else {
2212 self.chunk.emit(Op::LoadUndef, line);
2213 }
2214 self.chunk.emit(Op::LocalDeclareHash(name_idx), line);
2215 Ok(())
2216 }
2217 _ => Err(CompileError::Unsupported("local on this lvalue".into())),
2218 }
2219 }
2220
2221 fn compile_statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
2222 if let Some(lbl) = &stmt.label {
2225 self.record_stmt_label(lbl);
2226 }
2227 let line = stmt.line;
2228 match &stmt.kind {
2229 StmtKind::FormatDecl { name, lines } => {
2230 let idx = self.chunk.add_format_decl(name.clone(), lines.clone());
2231 self.chunk.emit(Op::FormatDecl(idx), line);
2232 }
2233 StmtKind::Expression(expr) => {
2234 self.compile_expr_ctx(expr, WantarrayCtx::Void)?;
2235 self.chunk.emit(Op::Pop, line);
2236 }
2237 StmtKind::Local(decls) => self.compile_local_declarations(decls, line)?,
2238 StmtKind::LocalExpr {
2239 target,
2240 initializer,
2241 } => {
2242 self.compile_local_expr(target, initializer.as_ref(), line)?;
2243 }
2244 StmtKind::MySync(decls) => self.compile_mysync_declarations(decls, line)?,
2245 StmtKind::OurSync(decls) => self.compile_oursync_declarations(decls, line)?,
2246 StmtKind::My(decls) => self.compile_var_declarations(decls, line, true)?,
2247 StmtKind::Our(decls) => self.compile_var_declarations(decls, line, false)?,
2248 StmtKind::State(decls) => self.compile_state_declarations(decls, line)?,
2249 StmtKind::If {
2250 condition,
2251 body,
2252 elsifs,
2253 else_block,
2254 } => {
2255 self.compile_boolean_rvalue_condition(condition)?;
2256 let jump_else = self.chunk.emit(Op::JumpIfFalse(0), line);
2257 self.compile_block(body)?;
2258 let mut end_jumps = vec![self.chunk.emit(Op::Jump(0), line)];
2259 self.chunk.patch_jump_here(jump_else);
2260
2261 for (cond, blk) in elsifs {
2262 self.compile_boolean_rvalue_condition(cond)?;
2263 let j = self.chunk.emit(Op::JumpIfFalse(0), cond.line);
2264 self.compile_block(blk)?;
2265 end_jumps.push(self.chunk.emit(Op::Jump(0), cond.line));
2266 self.chunk.patch_jump_here(j);
2267 }
2268
2269 if let Some(eb) = else_block {
2270 self.compile_block(eb)?;
2271 }
2272 for j in end_jumps {
2273 self.chunk.patch_jump_here(j);
2274 }
2275 }
2276 StmtKind::Unless {
2277 condition,
2278 body,
2279 else_block,
2280 } => {
2281 self.compile_boolean_rvalue_condition(condition)?;
2282 let jump_else = self.chunk.emit(Op::JumpIfTrue(0), line);
2283 self.compile_block(body)?;
2284 if let Some(eb) = else_block {
2285 let end_j = self.chunk.emit(Op::Jump(0), line);
2286 self.chunk.patch_jump_here(jump_else);
2287 self.compile_block(eb)?;
2288 self.chunk.patch_jump_here(end_j);
2289 } else {
2290 self.chunk.patch_jump_here(jump_else);
2291 }
2292 }
2293 StmtKind::While {
2294 condition,
2295 body,
2296 label,
2297 continue_block,
2298 } => {
2299 let loop_start = self.chunk.len();
2300 self.compile_boolean_rvalue_condition(condition)?;
2301 let exit_jump = self.chunk.emit(Op::JumpIfFalse(0), line);
2302 let body_start_ip = self.chunk.len();
2303
2304 self.loop_stack.push(LoopCtx {
2305 label: label.clone(),
2306 entry_frame_depth: self.frame_depth,
2307 entry_try_depth: self.try_depth,
2308 body_start_ip,
2309 break_jumps: vec![],
2310 continue_jumps: vec![],
2311 });
2312 self.compile_block_no_frame(body)?;
2313 let continue_entry = self.chunk.len();
2316 let cont_jumps =
2317 std::mem::take(&mut self.loop_stack.last_mut().expect("loop").continue_jumps);
2318 for j in cont_jumps {
2319 self.chunk.patch_jump_to(j, continue_entry);
2320 }
2321 if let Some(cb) = continue_block {
2322 self.compile_block_no_frame(cb)?;
2323 }
2324 self.chunk.emit(Op::Jump(loop_start), line);
2325 self.chunk.patch_jump_here(exit_jump);
2326 let ctx = self.loop_stack.pop().expect("loop");
2327 for j in ctx.break_jumps {
2328 self.chunk.patch_jump_here(j);
2329 }
2330 }
2331 StmtKind::Until {
2332 condition,
2333 body,
2334 label,
2335 continue_block,
2336 } => {
2337 let loop_start = self.chunk.len();
2338 self.compile_boolean_rvalue_condition(condition)?;
2339 let exit_jump = self.chunk.emit(Op::JumpIfTrue(0), line);
2340 let body_start_ip = self.chunk.len();
2341
2342 self.loop_stack.push(LoopCtx {
2343 label: label.clone(),
2344 entry_frame_depth: self.frame_depth,
2345 entry_try_depth: self.try_depth,
2346 body_start_ip,
2347 break_jumps: vec![],
2348 continue_jumps: vec![],
2349 });
2350 self.compile_block_no_frame(body)?;
2351 let continue_entry = self.chunk.len();
2352 let cont_jumps =
2353 std::mem::take(&mut self.loop_stack.last_mut().expect("loop").continue_jumps);
2354 for j in cont_jumps {
2355 self.chunk.patch_jump_to(j, continue_entry);
2356 }
2357 if let Some(cb) = continue_block {
2358 self.compile_block_no_frame(cb)?;
2359 }
2360 self.chunk.emit(Op::Jump(loop_start), line);
2361 self.chunk.patch_jump_here(exit_jump);
2362 let ctx = self.loop_stack.pop().expect("loop");
2363 for j in ctx.break_jumps {
2364 self.chunk.patch_jump_here(j);
2365 }
2366 }
2367 StmtKind::For {
2368 init,
2369 condition,
2370 step,
2371 body,
2372 label,
2373 continue_block,
2374 } => {
2375 let outer_has_slots = self.scope_stack.last().is_some_and(|l| l.use_slots);
2381 if !outer_has_slots {
2382 self.emit_push_frame(line);
2383 }
2384 if let Some(init) = init {
2385 self.compile_statement(init)?;
2386 }
2387 let loop_start = self.chunk.len();
2388 let cond_exit = if let Some(cond) = condition {
2389 self.compile_boolean_rvalue_condition(cond)?;
2390 Some(self.chunk.emit(Op::JumpIfFalse(0), line))
2391 } else {
2392 None
2393 };
2394 let body_start_ip = self.chunk.len();
2395
2396 self.loop_stack.push(LoopCtx {
2397 label: label.clone(),
2398 entry_frame_depth: self.frame_depth,
2399 entry_try_depth: self.try_depth,
2400 body_start_ip,
2401 break_jumps: cond_exit.into_iter().collect(),
2402 continue_jumps: vec![],
2403 });
2404 self.compile_block_no_frame(body)?;
2405
2406 let continue_entry = self.chunk.len();
2407 let cont_jumps =
2408 std::mem::take(&mut self.loop_stack.last_mut().expect("loop").continue_jumps);
2409 for j in cont_jumps {
2410 self.chunk.patch_jump_to(j, continue_entry);
2411 }
2412 if let Some(cb) = continue_block {
2413 self.compile_block_no_frame(cb)?;
2414 }
2415 if let Some(step) = step {
2416 self.compile_expr(step)?;
2417 self.chunk.emit(Op::Pop, line);
2418 }
2419 self.chunk.emit(Op::Jump(loop_start), line);
2420
2421 let ctx = self.loop_stack.pop().expect("loop");
2422 for j in ctx.break_jumps {
2423 self.chunk.patch_jump_here(j);
2424 }
2425 if !outer_has_slots {
2426 self.emit_pop_frame(line);
2427 }
2428 }
2429 StmtKind::Foreach {
2430 var,
2431 list,
2432 body,
2433 label,
2434 continue_block,
2435 } => {
2436 let alias_array_name_idx: Option<u16> = match &list.kind {
2444 ExprKind::ArrayVar(name) => Some(self.chunk.intern_name(name)),
2445 _ => None,
2446 };
2447 self.emit_push_frame(line);
2449 self.compile_expr_ctx(list, WantarrayCtx::List)?;
2450 let list_name = self.chunk.intern_name("__foreach_list__");
2451 self.chunk.emit(Op::DeclareArray(list_name), line);
2452
2453 let counter_name = self.chunk.intern_name("__foreach_i__");
2459 self.chunk.emit(Op::LoadInt(0), line);
2460 let counter_slot_opt = self.assign_scalar_slot("__foreach_i__");
2461 if let Some(slot) = counter_slot_opt {
2462 self.chunk
2463 .emit(Op::DeclareScalarSlot(slot, counter_name), line);
2464 } else {
2465 self.chunk.emit(Op::DeclareScalar(counter_name), line);
2466 }
2467
2468 let var_name = self.chunk.intern_name(var);
2469 self.register_declare(Sigil::Scalar, var, false);
2470 self.chunk.emit(Op::LoadUndef, line);
2471 let var_slot_opt = if var == "_" {
2475 None
2476 } else {
2477 self.assign_scalar_slot(var)
2478 };
2479 if let Some(slot) = var_slot_opt {
2480 self.chunk.emit(Op::DeclareScalarSlot(slot, var_name), line);
2481 } else {
2482 self.chunk.emit(Op::DeclareScalar(var_name), line);
2483 }
2484
2485 let loop_start = self.chunk.len();
2486 if let Some(s) = counter_slot_opt {
2488 self.chunk.emit(Op::GetScalarSlot(s), line);
2489 } else {
2490 self.emit_get_scalar(counter_name, line, None);
2491 }
2492 self.chunk.emit(Op::ArrayLen(list_name), line);
2493 self.chunk.emit(Op::NumLt, line);
2494 let exit_jump = self.chunk.emit(Op::JumpIfFalse(0), line);
2495
2496 if let Some(s) = counter_slot_opt {
2498 self.chunk.emit(Op::GetScalarSlot(s), line);
2499 } else {
2500 self.emit_get_scalar(counter_name, line, None);
2501 }
2502 self.chunk.emit(Op::GetArrayElem(list_name), line);
2503 if let Some(s) = var_slot_opt {
2504 self.chunk.emit(Op::SetScalarSlot(s), line);
2505 } else {
2506 self.emit_set_scalar(var_name, line, None);
2507 }
2508 let body_start_ip = self.chunk.len();
2509
2510 self.loop_stack.push(LoopCtx {
2511 label: label.clone(),
2512 entry_frame_depth: self.frame_depth,
2513 entry_try_depth: self.try_depth,
2514 body_start_ip,
2515 break_jumps: vec![],
2516 continue_jumps: vec![],
2517 });
2518 self.compile_block_no_frame(body)?;
2519 let step_ip = self.chunk.len();
2522 let cont_jumps =
2523 std::mem::take(&mut self.loop_stack.last_mut().expect("loop").continue_jumps);
2524 for j in cont_jumps {
2525 self.chunk.patch_jump_to(j, step_ip);
2526 }
2527 if let Some(arr_idx) = alias_array_name_idx {
2532 if let Some(s) = var_slot_opt {
2533 self.chunk.emit(Op::GetScalarSlot(s), line);
2534 } else {
2535 self.emit_get_scalar(var_name, line, None);
2536 }
2537 if let Some(s) = counter_slot_opt {
2538 self.chunk.emit(Op::GetScalarSlot(s), line);
2539 } else {
2540 self.emit_get_scalar(counter_name, line, None);
2541 }
2542 self.chunk.emit(Op::SetArrayElem(arr_idx), line);
2543 }
2544 if let Some(cb) = continue_block {
2545 self.compile_block_no_frame(cb)?;
2546 }
2547
2548 if let Some(s) = counter_slot_opt {
2552 self.chunk.emit(Op::PreIncSlot(s), line);
2553 } else {
2554 self.emit_pre_inc(counter_name, line, None);
2555 }
2556 self.chunk.emit(Op::Pop, line);
2557 self.chunk.emit(Op::Jump(loop_start), line);
2558
2559 self.chunk.patch_jump_here(exit_jump);
2560 let ctx = self.loop_stack.pop().expect("loop");
2561 for j in ctx.break_jumps {
2562 self.chunk.patch_jump_here(j);
2563 }
2564 self.emit_pop_frame(line);
2565 }
2566 StmtKind::DoWhile { body, condition } => {
2567 let loop_start = self.chunk.len();
2568 self.loop_stack.push(LoopCtx {
2569 label: None,
2570 entry_frame_depth: self.frame_depth,
2571 entry_try_depth: self.try_depth,
2572 body_start_ip: loop_start,
2573 break_jumps: vec![],
2574 continue_jumps: vec![],
2575 });
2576 self.compile_block_no_frame(body)?;
2577 let cont_jumps =
2578 std::mem::take(&mut self.loop_stack.last_mut().expect("loop").continue_jumps);
2579 for j in cont_jumps {
2580 self.chunk.patch_jump_to(j, loop_start);
2581 }
2582 self.compile_boolean_rvalue_condition(condition)?;
2583 let exit_jump = self.chunk.emit(Op::JumpIfFalse(0), line);
2584 self.chunk.emit(Op::Jump(loop_start), line);
2585 self.chunk.patch_jump_here(exit_jump);
2586 let ctx = self.loop_stack.pop().expect("loop");
2587 for j in ctx.break_jumps {
2588 self.chunk.patch_jump_here(j);
2589 }
2590 }
2591 StmtKind::Goto { target } => {
2592 if !self.try_emit_goto_label(target, line) {
2596 return Err(CompileError::Unsupported(
2597 "goto with dynamic or sub-ref target".into(),
2598 ));
2599 }
2600 }
2601 StmtKind::Continue(block) => {
2602 for stmt in block {
2606 self.compile_statement(stmt)?;
2607 }
2608 }
2609 StmtKind::Return(val) => {
2610 if let Some(expr) = val {
2611 match &expr.kind {
2617 ExprKind::Range { .. }
2618 | ExprKind::SliceRange { .. }
2619 | ExprKind::ArrayVar(_)
2620 | ExprKind::List(_)
2621 | ExprKind::HashVar(_)
2622 | ExprKind::HashSlice { .. }
2623 | ExprKind::HashKvSlice { .. }
2624 | ExprKind::ArraySlice { .. } => {
2625 self.compile_expr_ctx(expr, WantarrayCtx::List)?;
2626 }
2627 _ => {
2628 self.compile_expr(expr)?;
2629 }
2630 }
2631 self.chunk.emit(Op::ReturnValue, line);
2632 } else {
2633 self.chunk.emit(Op::Return, line);
2634 }
2635 }
2636 StmtKind::Last(label) | StmtKind::Next(label) => {
2637 let is_last = matches!(&stmt.kind, StmtKind::Last(_));
2643 let (target_idx, entry_frame_depth, entry_try_depth) = {
2645 let mut found: Option<(usize, usize, usize)> = None;
2646 for (i, lc) in self.loop_stack.iter().enumerate().rev() {
2647 let matches = match (label.as_deref(), lc.label.as_deref()) {
2648 (None, _) => true, (Some(l), Some(lcl)) => l == lcl,
2650 (Some(_), None) => false,
2651 };
2652 if matches {
2653 found = Some((i, lc.entry_frame_depth, lc.entry_try_depth));
2654 break;
2655 }
2656 }
2657 found.ok_or_else(|| {
2658 CompileError::Unsupported(if label.is_some() {
2659 format!(
2660 "last/next with label `{}` — no matching loop in compile scope",
2661 label.as_deref().unwrap_or("")
2662 )
2663 } else {
2664 "last/next outside any loop".into()
2665 })
2666 })?
2667 };
2668 if self.try_depth != entry_try_depth {
2670 return Err(CompileError::Unsupported(
2671 "last/next across try { } frame".into(),
2672 ));
2673 }
2674 let frames_to_pop = self.frame_depth.saturating_sub(entry_frame_depth);
2676 for _ in 0..frames_to_pop {
2677 self.chunk.emit(Op::PopFrame, line);
2682 }
2683 let j = self.chunk.emit(Op::Jump(0), line);
2684 let slot = &mut self.loop_stack[target_idx];
2685 if is_last {
2686 slot.break_jumps.push(j);
2687 } else {
2688 slot.continue_jumps.push(j);
2689 }
2690 }
2691 StmtKind::Redo(label) => {
2692 let (target_idx, entry_frame_depth, entry_try_depth) = {
2693 let mut found: Option<(usize, usize, usize)> = None;
2694 for (i, lc) in self.loop_stack.iter().enumerate().rev() {
2695 let matches = match (label.as_deref(), lc.label.as_deref()) {
2696 (None, _) => true,
2697 (Some(l), Some(lcl)) => l == lcl,
2698 (Some(_), None) => false,
2699 };
2700 if matches {
2701 found = Some((i, lc.entry_frame_depth, lc.entry_try_depth));
2702 break;
2703 }
2704 }
2705 found.ok_or_else(|| {
2706 CompileError::Unsupported(if label.is_some() {
2707 format!(
2708 "redo with label `{}` — no matching loop in compile scope",
2709 label.as_deref().unwrap_or("")
2710 )
2711 } else {
2712 "redo outside any loop".into()
2713 })
2714 })?
2715 };
2716 if self.try_depth != entry_try_depth {
2717 return Err(CompileError::Unsupported(
2718 "redo across try { } frame".into(),
2719 ));
2720 }
2721 let frames_to_pop = self.frame_depth.saturating_sub(entry_frame_depth);
2722 for _ in 0..frames_to_pop {
2723 self.chunk.emit(Op::PopFrame, line);
2724 }
2725 let body_start = self.loop_stack[target_idx].body_start_ip;
2726 let j = self.chunk.emit(Op::Jump(0), line);
2727 self.chunk.patch_jump_to(j, body_start);
2728 }
2729 StmtKind::Block(block) => {
2730 self.chunk.emit(Op::PushFrame, line);
2731 self.compile_block_inner(block)?;
2732 self.chunk.emit(Op::PopFrame, line);
2733 }
2734 StmtKind::StmtGroup(block) => {
2735 self.compile_block_no_frame(block)?;
2736 }
2737 StmtKind::Package { name } => {
2738 self.current_package = name.clone();
2739 let val_idx = self.chunk.add_constant(StrykeValue::string(name.clone()));
2740 let name_idx = self.chunk.intern_name("__PACKAGE__");
2741 self.chunk.emit(Op::LoadConst(val_idx), line);
2742 self.emit_set_scalar(name_idx, line, None);
2743 }
2744 StmtKind::SubDecl {
2745 name,
2746 params,
2747 body,
2748 prototype,
2749 } => {
2750 let idx = self.chunk.runtime_sub_decls.len();
2751 if idx > u16::MAX as usize {
2752 return Err(CompileError::Unsupported(
2753 "too many runtime sub declarations in one chunk".into(),
2754 ));
2755 }
2756 self.chunk.runtime_sub_decls.push(RuntimeSubDecl {
2757 name: name.clone(),
2758 params: params.clone(),
2759 body: body.clone(),
2760 prototype: prototype.clone(),
2761 });
2762 self.chunk.emit(Op::RuntimeSubDecl(idx as u16), line);
2763 }
2764 StmtKind::AdviceDecl {
2765 kind,
2766 pattern,
2767 body,
2768 } => {
2769 let idx = self.chunk.runtime_advice_decls.len();
2770 if idx > u16::MAX as usize {
2771 return Err(CompileError::Unsupported(
2772 "too many AOP advice declarations in one chunk".into(),
2773 ));
2774 }
2775 let body_block_idx = self.add_deferred_block(body.clone());
2781 self.chunk.runtime_advice_decls.push(RuntimeAdviceDecl {
2782 kind: *kind,
2783 pattern: pattern.clone(),
2784 body: body.clone(),
2785 body_block_idx,
2786 });
2787 self.chunk.emit(Op::RegisterAdvice(idx as u16), line);
2788 }
2789 StmtKind::StructDecl { def } => {
2790 if self.chunk.struct_defs.iter().any(|d| d.name == def.name) {
2791 return Err(CompileError::Unsupported(format!(
2792 "duplicate struct `{}`",
2793 def.name
2794 )));
2795 }
2796 self.chunk.struct_defs.push(def.clone());
2797 }
2798 StmtKind::EnumDecl { def } => {
2799 if self.chunk.enum_defs.iter().any(|d| d.name == def.name) {
2800 return Err(CompileError::Unsupported(format!(
2801 "duplicate enum `{}`",
2802 def.name
2803 )));
2804 }
2805 self.chunk.enum_defs.push(def.clone());
2806 }
2807 StmtKind::ClassDecl { def } => {
2808 if self.chunk.class_defs.iter().any(|d| d.name == def.name) {
2809 return Err(CompileError::Unsupported(format!(
2810 "duplicate class `{}`",
2811 def.name
2812 )));
2813 }
2814 self.chunk.class_defs.push(def.clone());
2815 }
2816 StmtKind::TraitDecl { def } => {
2817 if self.chunk.trait_defs.iter().any(|d| d.name == def.name) {
2818 return Err(CompileError::Unsupported(format!(
2819 "duplicate trait `{}`",
2820 def.name
2821 )));
2822 }
2823 self.chunk.trait_defs.push(def.clone());
2824 }
2825 StmtKind::TryCatch {
2826 try_block,
2827 catch_var,
2828 catch_block,
2829 finally_block,
2830 } => {
2831 let catch_var_idx = self.chunk.intern_name(catch_var);
2832 let try_push_idx = self.chunk.emit(
2833 Op::TryPush {
2834 catch_ip: 0,
2835 finally_ip: None,
2836 after_ip: 0,
2837 catch_var_idx,
2838 },
2839 line,
2840 );
2841 self.chunk.emit(Op::PushFrame, line);
2842 if self.program_last_stmt_takes_value {
2843 self.emit_block_value(try_block, line)?;
2844 } else {
2845 self.compile_block_inner(try_block)?;
2846 }
2847 self.chunk.emit(Op::PopFrame, line);
2848 self.chunk.emit(Op::TryContinueNormal, line);
2849
2850 let catch_start = self.chunk.len();
2851 self.chunk.patch_try_push_catch(try_push_idx, catch_start);
2852
2853 self.chunk.emit(Op::CatchReceive(catch_var_idx), line);
2854 if self.program_last_stmt_takes_value {
2855 self.emit_block_value(catch_block, line)?;
2856 } else {
2857 self.compile_block_inner(catch_block)?;
2858 }
2859 self.chunk.emit(Op::PopFrame, line);
2860 self.chunk.emit(Op::TryContinueNormal, line);
2861
2862 if let Some(fin) = finally_block {
2863 let finally_start = self.chunk.len();
2864 self.chunk
2865 .patch_try_push_finally(try_push_idx, Some(finally_start));
2866 self.chunk.emit(Op::PushFrame, line);
2867 self.compile_block_inner(fin)?;
2868 self.chunk.emit(Op::PopFrame, line);
2869 self.chunk.emit(Op::TryFinallyEnd, line);
2870 }
2871 let merge = self.chunk.len();
2872 self.chunk.patch_try_push_after(try_push_idx, merge);
2873 }
2874 StmtKind::EvalTimeout { timeout, body } => {
2875 let idx = self
2876 .chunk
2877 .add_eval_timeout_entry(timeout.clone(), body.clone());
2878 self.chunk.emit(Op::EvalTimeout(idx), line);
2879 }
2880 StmtKind::Given { topic, body } => {
2881 let idx = self.chunk.add_given_entry(topic.clone(), body.clone());
2882 self.chunk.emit(Op::Given(idx), line);
2883 }
2884 StmtKind::When { .. } | StmtKind::DefaultCase { .. } => {
2885 return Err(CompileError::Unsupported(
2886 "`when` / `default` only valid inside `given`".into(),
2887 ));
2888 }
2889 StmtKind::Tie {
2890 target,
2891 class,
2892 args,
2893 } => {
2894 self.compile_expr(class)?;
2895 for a in args {
2896 self.compile_expr(a)?;
2897 }
2898 let (kind, name_idx) = match target {
2899 TieTarget::Scalar(s) => (0u8, self.chunk.intern_name(s)),
2900 TieTarget::Array(a) => (1u8, self.chunk.intern_name(a)),
2901 TieTarget::Hash(h) => (2u8, self.chunk.intern_name(h)),
2902 };
2903 let argc = (1 + args.len()) as u8;
2904 self.chunk.emit(
2905 Op::Tie {
2906 target_kind: kind,
2907 name_idx,
2908 argc,
2909 },
2910 line,
2911 );
2912 }
2913 StmtKind::UseOverload { pairs } => {
2914 let idx = self.chunk.add_use_overload(pairs.clone());
2915 self.chunk.emit(Op::UseOverload(idx), line);
2916 }
2917 StmtKind::Use { module, imports } => {
2918 if module == "Env" {
2920 Self::register_env_imports(
2921 self.scope_stack.last_mut().expect("scope"),
2922 imports,
2923 );
2924 }
2925 }
2926 StmtKind::UsePerlVersion { .. }
2927 | StmtKind::No { .. }
2928 | StmtKind::Begin(_)
2929 | StmtKind::UnitCheck(_)
2930 | StmtKind::Check(_)
2931 | StmtKind::Init(_)
2932 | StmtKind::End(_)
2933 | StmtKind::Empty => {
2934 }
2936 }
2937 Ok(())
2938 }
2939
2940 fn block_has_return(block: &Block) -> bool {
2942 for stmt in block {
2943 match &stmt.kind {
2944 StmtKind::Return(_) => return true,
2945 StmtKind::If {
2946 body,
2947 elsifs,
2948 else_block,
2949 ..
2950 } => {
2951 if Self::block_has_return(body) {
2952 return true;
2953 }
2954 for (_, blk) in elsifs {
2955 if Self::block_has_return(blk) {
2956 return true;
2957 }
2958 }
2959 if let Some(eb) = else_block {
2960 if Self::block_has_return(eb) {
2961 return true;
2962 }
2963 }
2964 }
2965 StmtKind::Unless {
2966 body, else_block, ..
2967 } => {
2968 if Self::block_has_return(body) {
2969 return true;
2970 }
2971 if let Some(eb) = else_block {
2972 if Self::block_has_return(eb) {
2973 return true;
2974 }
2975 }
2976 }
2977 StmtKind::While { body, .. }
2978 | StmtKind::Until { body, .. }
2979 | StmtKind::Foreach { body, .. }
2980 if Self::block_has_return(body) =>
2981 {
2982 return true;
2983 }
2984 StmtKind::For { body, .. } if Self::block_has_return(body) => {
2985 return true;
2986 }
2987 StmtKind::Block(blk) if Self::block_has_return(blk) => {
2988 return true;
2989 }
2990 StmtKind::DoWhile { body, .. } if Self::block_has_return(body) => {
2991 return true;
2992 }
2993 _ => {}
2994 }
2995 }
2996 false
2997 }
2998
2999 fn block_has_local(block: &Block) -> bool {
3001 block.iter().any(|s| match &s.kind {
3002 StmtKind::Local(_) | StmtKind::LocalExpr { .. } => true,
3003 StmtKind::StmtGroup(inner) => Self::block_has_local(inner),
3004 _ => false,
3005 })
3006 }
3007
3008 fn compile_block(&mut self, block: &Block) -> Result<(), CompileError> {
3009 if Self::block_has_return(block) {
3010 self.compile_block_inner(block)?;
3011 } else if self.scope_stack.last().is_some_and(|l| l.use_slots)
3012 && !Self::block_has_local(block)
3013 {
3014 self.compile_block_inner(block)?;
3017 } else {
3018 self.push_scope_layer();
3019 self.chunk.emit(Op::PushFrame, 0);
3020 self.compile_block_inner(block)?;
3021 self.chunk.emit(Op::PopFrame, 0);
3022 self.pop_scope_layer();
3023 }
3024 Ok(())
3025 }
3026
3027 fn compile_block_inner(&mut self, block: &Block) -> Result<(), CompileError> {
3028 for stmt in block {
3029 self.compile_statement(stmt)?;
3030 }
3031 Ok(())
3032 }
3033
3034 fn emit_block_value(&mut self, block: &Block, line: usize) -> Result<(), CompileError> {
3037 if block.is_empty() {
3038 self.chunk.emit(Op::LoadUndef, line);
3039 return Ok(());
3040 }
3041 let last_idx = block.len() - 1;
3042 for (i, stmt) in block.iter().enumerate() {
3043 if i == last_idx {
3044 match &stmt.kind {
3045 StmtKind::Expression(expr) => {
3046 self.compile_expr(expr)?;
3047 }
3048 StmtKind::Block(inner) => {
3049 self.chunk.emit(Op::PushFrame, stmt.line);
3050 self.emit_block_value(inner, stmt.line)?;
3051 self.chunk.emit(Op::PopFrame, stmt.line);
3052 }
3053 StmtKind::StmtGroup(inner) => {
3054 self.emit_block_value(inner, stmt.line)?;
3055 }
3056 StmtKind::If {
3057 condition,
3058 body,
3059 elsifs,
3060 else_block,
3061 } => {
3062 self.compile_boolean_rvalue_condition(condition)?;
3063 let j0 = self.chunk.emit(Op::JumpIfFalse(0), stmt.line);
3064 self.emit_block_value(body, stmt.line)?;
3065 let mut ends = vec![self.chunk.emit(Op::Jump(0), stmt.line)];
3066 self.chunk.patch_jump_here(j0);
3067 for (c, blk) in elsifs {
3068 self.compile_boolean_rvalue_condition(c)?;
3069 let j = self.chunk.emit(Op::JumpIfFalse(0), c.line);
3070 self.emit_block_value(blk, c.line)?;
3071 ends.push(self.chunk.emit(Op::Jump(0), c.line));
3072 self.chunk.patch_jump_here(j);
3073 }
3074 if let Some(eb) = else_block {
3075 self.emit_block_value(eb, stmt.line)?;
3076 } else {
3077 self.chunk.emit(Op::LoadUndef, stmt.line);
3078 }
3079 for j in ends {
3080 self.chunk.patch_jump_here(j);
3081 }
3082 }
3083 StmtKind::Unless {
3084 condition,
3085 body,
3086 else_block,
3087 } => {
3088 self.compile_boolean_rvalue_condition(condition)?;
3089 let j0 = self.chunk.emit(Op::JumpIfFalse(0), stmt.line);
3090 if let Some(eb) = else_block {
3091 self.emit_block_value(eb, stmt.line)?;
3092 } else {
3093 self.chunk.emit(Op::LoadUndef, stmt.line);
3094 }
3095 let end = self.chunk.emit(Op::Jump(0), stmt.line);
3096 self.chunk.patch_jump_here(j0);
3097 self.emit_block_value(body, stmt.line)?;
3098 self.chunk.patch_jump_here(end);
3099 }
3100 _ => self.compile_statement(stmt)?,
3101 }
3102 } else {
3103 self.compile_statement(stmt)?;
3104 }
3105 }
3106 Ok(())
3107 }
3108
3109 fn emit_subroutine_body_return(&mut self, body: &Block) -> Result<(), CompileError> {
3114 if body.is_empty() {
3115 self.chunk.emit(Op::LoadUndef, 0);
3116 self.chunk.emit(Op::ReturnValue, 0);
3117 return Ok(());
3118 }
3119 let last_idx = body.len() - 1;
3120 let last = &body[last_idx];
3121 match &last.kind {
3122 StmtKind::Return(_) => {
3123 for stmt in body {
3124 self.compile_statement(stmt)?;
3125 }
3126 }
3127 StmtKind::Expression(expr) => {
3128 for stmt in &body[..last_idx] {
3129 self.compile_statement(stmt)?;
3130 }
3131 self.compile_expr_ctx(expr, WantarrayCtx::List)?;
3135 self.chunk.emit(Op::ReturnValue, last.line);
3136 }
3137 StmtKind::If {
3138 condition,
3139 body: if_body,
3140 elsifs,
3141 else_block,
3142 } => {
3143 for stmt in &body[..last_idx] {
3144 self.compile_statement(stmt)?;
3145 }
3146 self.compile_boolean_rvalue_condition(condition)?;
3147 let j0 = self.chunk.emit(Op::JumpIfFalse(0), last.line);
3148 self.emit_block_value(if_body, last.line)?;
3149 let mut ends = vec![self.chunk.emit(Op::Jump(0), last.line)];
3150 self.chunk.patch_jump_here(j0);
3151 for (c, blk) in elsifs {
3152 self.compile_boolean_rvalue_condition(c)?;
3153 let j = self.chunk.emit(Op::JumpIfFalse(0), c.line);
3154 self.emit_block_value(blk, c.line)?;
3155 ends.push(self.chunk.emit(Op::Jump(0), c.line));
3156 self.chunk.patch_jump_here(j);
3157 }
3158 if let Some(eb) = else_block {
3159 self.emit_block_value(eb, last.line)?;
3160 } else {
3161 self.chunk.emit(Op::LoadUndef, last.line);
3162 }
3163 for j in ends {
3164 self.chunk.patch_jump_here(j);
3165 }
3166 self.chunk.emit(Op::ReturnValue, last.line);
3167 }
3168 StmtKind::Unless {
3169 condition,
3170 body: unless_body,
3171 else_block,
3172 } => {
3173 for stmt in &body[..last_idx] {
3174 self.compile_statement(stmt)?;
3175 }
3176 self.compile_boolean_rvalue_condition(condition)?;
3177 let j0 = self.chunk.emit(Op::JumpIfFalse(0), last.line);
3178 if let Some(eb) = else_block {
3179 self.emit_block_value(eb, last.line)?;
3180 } else {
3181 self.chunk.emit(Op::LoadUndef, last.line);
3182 }
3183 let end = self.chunk.emit(Op::Jump(0), last.line);
3184 self.chunk.patch_jump_here(j0);
3185 self.emit_block_value(unless_body, last.line)?;
3186 self.chunk.patch_jump_here(end);
3187 self.chunk.emit(Op::ReturnValue, last.line);
3188 }
3189 _ => {
3190 for stmt in body {
3191 self.compile_statement(stmt)?;
3192 }
3193 self.chunk.emit(Op::LoadUndef, 0);
3194 self.chunk.emit(Op::ReturnValue, 0);
3195 }
3196 }
3197 Ok(())
3198 }
3199
3200 fn compile_block_no_frame(&mut self, block: &Block) -> Result<(), CompileError> {
3204 for stmt in block {
3205 self.compile_statement(stmt)?;
3206 }
3207 Ok(())
3208 }
3209
3210 fn compile_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
3211 self.compile_expr_ctx(expr, WantarrayCtx::Scalar)
3212 }
3213
3214 fn compile_expr_ctx(&mut self, root: &Expr, ctx: WantarrayCtx) -> Result<(), CompileError> {
3215 let line = root.line;
3216 match &root.kind {
3217 ExprKind::Integer(n) => {
3218 self.emit_op(Op::LoadInt(*n), line, Some(root));
3219 }
3220 ExprKind::Float(f) => {
3221 self.emit_op(Op::LoadFloat(*f), line, Some(root));
3222 }
3223 ExprKind::String(s) => {
3224 let processed = VMHelper::process_case_escapes(s);
3225 let idx = self.chunk.add_constant(StrykeValue::string(processed));
3226 self.emit_op(Op::LoadConst(idx), line, Some(root));
3227 }
3228 ExprKind::Bareword(name) => {
3229 let idx = self.chunk.intern_name(name);
3233 self.emit_op(Op::BarewordRvalue(idx), line, Some(root));
3234 }
3235 ExprKind::Undef => {
3236 self.emit_op(Op::LoadUndef, line, Some(root));
3237 }
3238 ExprKind::MagicConst(crate::ast::MagicConstKind::File) => {
3239 let idx = self
3240 .chunk
3241 .add_constant(StrykeValue::string(self.source_file.clone()));
3242 self.emit_op(Op::LoadConst(idx), line, Some(root));
3243 }
3244 ExprKind::MagicConst(crate::ast::MagicConstKind::Line) => {
3245 let idx = self
3246 .chunk
3247 .add_constant(StrykeValue::integer(root.line as i64));
3248 self.emit_op(Op::LoadConst(idx), line, Some(root));
3249 }
3250 ExprKind::MagicConst(crate::ast::MagicConstKind::Sub) => {
3251 self.emit_op(Op::LoadCurrentSub, line, Some(root));
3252 }
3253 ExprKind::ScalarVar(name) => {
3254 self.check_strict_scalar_access(name, line)?;
3255 let idx = self.intern_scalar_var_for_ops(name);
3256 self.emit_get_scalar(idx, line, Some(root));
3257 }
3258 ExprKind::ArrayVar(name) => {
3259 self.check_strict_array_access(name, line)?;
3260 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
3261 if ctx == WantarrayCtx::List {
3262 self.emit_op(Op::GetArray(idx), line, Some(root));
3263 } else {
3264 self.emit_op(Op::ArrayLen(idx), line, Some(root));
3265 }
3266 }
3267 ExprKind::HashVar(name) => {
3268 self.check_strict_hash_access(name, line)?;
3269 let idx = self.chunk.intern_name(name);
3270 self.emit_op(Op::GetHash(idx), line, Some(root));
3271 if ctx != WantarrayCtx::List {
3272 self.emit_op(Op::ValueScalarContext, line, Some(root));
3273 }
3274 }
3275 ExprKind::Typeglob(name) => {
3276 let idx = self.chunk.add_constant(StrykeValue::string(name.clone()));
3277 self.emit_op(Op::LoadConst(idx), line, Some(root));
3278 }
3279 ExprKind::TypeglobExpr(expr) => {
3280 self.compile_expr(expr)?;
3281 self.emit_op(Op::LoadDynamicTypeglob, line, Some(root));
3282 }
3283 ExprKind::ArrayElement { array, index } => {
3284 self.check_strict_array_access(array, line)?;
3285 let idx = self
3286 .chunk
3287 .intern_name(&self.qualify_stash_array_name(array));
3288 if let ExprKind::Range {
3293 from,
3294 to,
3295 exclusive,
3296 step,
3297 } = &index.kind
3298 {
3299 self.compile_expr(from)?;
3300 self.compile_expr(to)?;
3301 self.compile_optional_or_undef(step.as_deref())?;
3302 let _ = exclusive; self.emit_op(Op::ArraySliceRange(idx), line, Some(root));
3304 return Ok(());
3305 }
3306 if let ExprKind::SliceRange { from, to, step } = &index.kind {
3308 self.compile_optional_or_undef(from.as_deref())?;
3309 self.compile_optional_or_undef(to.as_deref())?;
3310 self.compile_optional_or_undef(step.as_deref())?;
3311 self.emit_op(Op::ArraySliceRange(idx), line, Some(root));
3312 return Ok(());
3313 }
3314 self.compile_expr(index)?;
3315 self.emit_op(Op::GetArrayElem(idx), line, Some(root));
3316 }
3317 ExprKind::HashElement { hash, key } => {
3318 self.check_strict_hash_access(hash, line)?;
3319 let idx = self.chunk.intern_name(hash);
3320 self.compile_expr(key)?;
3321 self.emit_op(Op::GetHashElem(idx), line, Some(root));
3322 }
3323 ExprKind::ArraySlice { array, indices } => {
3324 let arr_idx = self
3325 .chunk
3326 .intern_name(&self.qualify_stash_array_name(array));
3327 if indices.is_empty() {
3328 self.emit_op(Op::MakeArray(0), line, Some(root));
3329 } else if indices.len() == 1 {
3330 match &indices[0].kind {
3331 ExprKind::SliceRange { from, to, step } => {
3332 self.compile_optional_or_undef(from.as_deref())?;
3336 self.compile_optional_or_undef(to.as_deref())?;
3337 self.compile_optional_or_undef(step.as_deref())?;
3338 self.emit_op(Op::ArraySliceRange(arr_idx), line, Some(root));
3339 }
3340 ExprKind::Range { from, to, step, .. } => {
3341 self.compile_expr(from)?;
3344 self.compile_expr(to)?;
3345 self.compile_optional_or_undef(step.as_deref())?;
3346 self.emit_op(Op::ArraySliceRange(arr_idx), line, Some(root));
3347 }
3348 _ => {
3349 self.compile_array_slice_index_expr(&indices[0])?;
3350 self.emit_op(Op::ArraySlicePart(arr_idx), line, Some(root));
3351 }
3352 }
3353 } else {
3354 for (ix, index_expr) in indices.iter().enumerate() {
3355 self.compile_array_slice_index_expr(index_expr)?;
3356 self.emit_op(Op::ArraySlicePart(arr_idx), line, Some(root));
3357 if ix > 0 {
3358 self.emit_op(Op::ArrayConcatTwo, line, Some(root));
3359 }
3360 }
3361 }
3362 }
3363 ExprKind::HashSlice { hash, keys } => {
3364 let hash_idx = self.chunk.intern_name(hash);
3365 if keys.len() == 1 {
3366 match &keys[0].kind {
3367 ExprKind::SliceRange { from, to, step } => {
3368 self.compile_optional_or_undef(from.as_deref())?;
3371 self.compile_optional_or_undef(to.as_deref())?;
3372 self.compile_optional_or_undef(step.as_deref())?;
3373 self.emit_op(Op::HashSliceRange(hash_idx), line, Some(root));
3374 return Ok(());
3375 }
3376 ExprKind::Range { from, to, step, .. } => {
3377 self.compile_expr(from)?;
3381 self.compile_expr(to)?;
3382 self.compile_optional_or_undef(step.as_deref())?;
3383 self.emit_op(Op::HashSliceRange(hash_idx), line, Some(root));
3384 return Ok(());
3385 }
3386 _ => {}
3387 }
3388 }
3389 let has_dynamic_keys = keys
3391 .iter()
3392 .any(|k| matches!(&k.kind, ExprKind::Range { .. }));
3393 if has_dynamic_keys {
3394 for key_expr in keys {
3395 self.compile_hash_slice_key_expr(key_expr)?;
3396 }
3397 self.emit_op(
3398 Op::GetHashSlice(hash_idx, keys.len() as u16),
3399 line,
3400 Some(root),
3401 );
3402 } else {
3403 let mut total_keys = 0u16;
3405 for key_expr in keys {
3406 match &key_expr.kind {
3407 ExprKind::QW(words) => {
3408 for w in words {
3409 let cidx =
3410 self.chunk.add_constant(StrykeValue::string(w.clone()));
3411 self.emit_op(Op::LoadConst(cidx), line, Some(root));
3412 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3413 total_keys += 1;
3414 }
3415 }
3416 ExprKind::List(elems) => {
3417 for e in elems {
3418 self.compile_expr(e)?;
3419 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3420 total_keys += 1;
3421 }
3422 }
3423 _ => {
3424 self.compile_expr(key_expr)?;
3425 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3426 total_keys += 1;
3427 }
3428 }
3429 }
3430 self.emit_op(Op::MakeArray(total_keys), line, Some(root));
3431 }
3432 }
3433 ExprKind::HashKvSlice { hash, keys } => {
3434 let hash_idx = self.chunk.intern_name(hash);
3437 let mut total_pairs = 0u16;
3438 for key_expr in keys {
3439 match &key_expr.kind {
3440 ExprKind::QW(words) => {
3441 for w in words {
3442 let kidx = self.chunk.add_constant(StrykeValue::string(w.clone()));
3443 self.emit_op(Op::LoadConst(kidx), line, Some(root));
3444 let kidx2 = self.chunk.add_constant(StrykeValue::string(w.clone()));
3445 self.emit_op(Op::LoadConst(kidx2), line, Some(root));
3446 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3447 total_pairs += 1;
3448 }
3449 }
3450 ExprKind::List(elems) => {
3451 for e in elems {
3452 self.compile_expr(e)?;
3453 self.emit_op(Op::Dup, line, Some(root));
3454 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3455 total_pairs += 1;
3456 }
3457 }
3458 _ => {
3459 self.compile_expr(key_expr)?;
3460 self.emit_op(Op::Dup, line, Some(root));
3461 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3462 total_pairs += 1;
3463 }
3464 }
3465 }
3466 self.emit_op(Op::MakeArray(total_pairs * 2), line, Some(root));
3467 }
3468 ExprKind::HashSliceDeref { container, keys } => {
3469 self.compile_expr(container)?;
3470 for key_expr in keys {
3471 self.compile_hash_slice_key_expr(key_expr)?;
3472 }
3473 self.emit_op(Op::HashSliceDeref(keys.len() as u16), line, Some(root));
3474 }
3475 ExprKind::AnonymousListSlice { source, indices } => {
3476 if indices.is_empty() {
3477 self.compile_expr_ctx(source, WantarrayCtx::List)?;
3478 self.emit_op(Op::MakeArray(0), line, Some(root));
3479 } else {
3480 self.compile_expr_ctx(source, WantarrayCtx::List)?;
3481 for index_expr in indices {
3482 self.compile_array_slice_index_expr(index_expr)?;
3483 }
3484 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, Some(root));
3485 }
3486 if ctx != WantarrayCtx::List {
3487 self.emit_op(Op::ListSliceToScalar, line, Some(root));
3488 }
3489 }
3490
3491 ExprKind::BinOp { left, op, right } => {
3493 match op {
3495 BinOp::LogAnd | BinOp::LogAndWord => {
3496 if matches!(left.kind, ExprKind::Regex(..)) {
3497 self.compile_boolean_rvalue_condition(left)?;
3498 self.emit_op(Op::RegexBoolToScalar, line, Some(root));
3499 } else {
3500 self.compile_expr(left)?;
3501 }
3502 let j = self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root));
3503 if matches!(right.kind, ExprKind::Regex(..)) {
3505 self.compile_boolean_rvalue_condition(right)?;
3506 self.emit_op(Op::RegexBoolToScalar, line, Some(root));
3507 } else {
3508 self.compile_expr(right)?;
3509 }
3510 self.chunk.patch_jump_here(j);
3511 return Ok(());
3512 }
3513 BinOp::LogOr | BinOp::LogOrWord => {
3514 if matches!(left.kind, ExprKind::Regex(..)) {
3515 self.compile_boolean_rvalue_condition(left)?;
3516 self.emit_op(Op::RegexBoolToScalar, line, Some(root));
3517 } else {
3518 self.compile_expr(left)?;
3519 }
3520 let j = self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root));
3521 if matches!(right.kind, ExprKind::Regex(..)) {
3523 self.compile_boolean_rvalue_condition(right)?;
3524 self.emit_op(Op::RegexBoolToScalar, line, Some(root));
3525 } else {
3526 self.compile_expr(right)?;
3527 }
3528 self.chunk.patch_jump_here(j);
3529 return Ok(());
3530 }
3531 BinOp::DefinedOr => {
3532 self.compile_expr(left)?;
3533 let j = self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root));
3534 self.compile_expr(right)?;
3536 self.chunk.patch_jump_here(j);
3537 return Ok(());
3538 }
3539 BinOp::BindMatch => {
3540 self.compile_expr(left)?;
3541 self.compile_expr(right)?;
3542 self.emit_op(Op::RegexMatchDyn(false), line, Some(root));
3543 return Ok(());
3544 }
3545 BinOp::BindNotMatch => {
3546 self.compile_expr(left)?;
3547 self.compile_expr(right)?;
3548 self.emit_op(Op::RegexMatchDyn(true), line, Some(root));
3549 return Ok(());
3550 }
3551 _ => {}
3552 }
3553
3554 self.compile_expr(left)?;
3555 self.compile_expr(right)?;
3556 let op_code = match op {
3557 BinOp::Add => Op::Add,
3558 BinOp::Sub => Op::Sub,
3559 BinOp::Mul => Op::Mul,
3560 BinOp::Div => Op::Div,
3561 BinOp::Mod => Op::Mod,
3562 BinOp::Pow => Op::Pow,
3563 BinOp::Concat => Op::Concat,
3564 BinOp::NumEq => Op::NumEq,
3565 BinOp::NumNe => Op::NumNe,
3566 BinOp::NumLt => Op::NumLt,
3567 BinOp::NumGt => Op::NumGt,
3568 BinOp::NumLe => Op::NumLe,
3569 BinOp::NumGe => Op::NumGe,
3570 BinOp::Spaceship => Op::Spaceship,
3571 BinOp::StrEq => Op::StrEq,
3572 BinOp::StrNe => Op::StrNe,
3573 BinOp::StrLt => Op::StrLt,
3574 BinOp::StrGt => Op::StrGt,
3575 BinOp::StrLe => Op::StrLe,
3576 BinOp::StrGe => Op::StrGe,
3577 BinOp::StrCmp => Op::StrCmp,
3578 BinOp::BitAnd => Op::BitAnd,
3579 BinOp::BitOr => Op::BitOr,
3580 BinOp::BitXor => Op::BitXor,
3581 BinOp::ShiftLeft => Op::Shl,
3582 BinOp::ShiftRight => Op::Shr,
3583 BinOp::LogAnd
3585 | BinOp::LogOr
3586 | BinOp::DefinedOr
3587 | BinOp::LogAndWord
3588 | BinOp::LogOrWord
3589 | BinOp::BindMatch
3590 | BinOp::BindNotMatch => unreachable!(),
3591 };
3592 self.emit_op(op_code, line, Some(root));
3593 }
3594
3595 ExprKind::UnaryOp { op, expr } => match op {
3596 UnaryOp::PreIncrement => {
3597 if let ExprKind::ScalarVar(name) = &expr.kind {
3598 self.check_scalar_mutable(name, line)?;
3599 self.check_closure_write_to_outer_my(name, line)?;
3600 let idx = self.intern_scalar_var_for_ops(name);
3601 self.emit_pre_inc(idx, line, Some(root));
3602 } else if let ExprKind::ArrayElement { array, index } = &expr.kind {
3603 if self.is_mysync_array(array) {
3604 return Err(CompileError::Unsupported(
3605 "mysync array element update".into(),
3606 ));
3607 }
3608 let q = self.qualify_stash_array_name(array);
3609 self.check_array_mutable(&q, line)?;
3610 let arr_idx = self.chunk.intern_name(&q);
3611 self.compile_expr(index)?;
3612 self.emit_op(Op::Dup, line, Some(root));
3613 self.emit_op(Op::GetArrayElem(arr_idx), line, Some(root));
3614 self.emit_op(Op::LoadInt(1), line, Some(root));
3615 self.emit_op(Op::Add, line, Some(root));
3616 self.emit_op(Op::Dup, line, Some(root));
3617 self.emit_op(Op::Rot, line, Some(root));
3618 self.emit_op(Op::SetArrayElem(arr_idx), line, Some(root));
3619 } else if let ExprKind::ArraySlice { array, indices } = &expr.kind {
3620 if self.is_mysync_array(array) {
3621 return Err(CompileError::Unsupported(
3622 "mysync array element update".into(),
3623 ));
3624 }
3625 self.check_strict_array_access(array, line)?;
3626 let q = self.qualify_stash_array_name(array);
3627 self.check_array_mutable(&q, line)?;
3628 let arr_idx = self.chunk.intern_name(&q);
3629 for ix in indices {
3630 self.compile_array_slice_index_expr(ix)?;
3631 }
3632 self.emit_op(
3633 Op::NamedArraySliceIncDec(0, arr_idx, indices.len() as u16),
3634 line,
3635 Some(root),
3636 );
3637 } else if let ExprKind::HashElement { hash, key } = &expr.kind {
3638 if self.is_mysync_hash(hash) {
3639 return Err(CompileError::Unsupported(
3640 "mysync hash element update".into(),
3641 ));
3642 }
3643 self.check_hash_mutable(hash, line)?;
3644 let hash_idx = self.chunk.intern_name(hash);
3645 self.compile_expr(key)?;
3646 self.emit_op(Op::Dup, line, Some(root));
3647 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3648 self.emit_op(Op::LoadInt(1), line, Some(root));
3649 self.emit_op(Op::Add, line, Some(root));
3650 self.emit_op(Op::Dup, line, Some(root));
3651 self.emit_op(Op::Rot, line, Some(root));
3652 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
3653 } else if let ExprKind::HashSlice { hash, keys } = &expr.kind {
3654 if self.is_mysync_hash(hash) {
3655 return Err(CompileError::Unsupported(
3656 "mysync hash element update".into(),
3657 ));
3658 }
3659 self.check_hash_mutable(hash, line)?;
3660 let hash_idx = self.chunk.intern_name(hash);
3661 if hash_slice_needs_slice_ops(keys) {
3662 for hk in keys {
3663 self.compile_expr(hk)?;
3664 }
3665 self.emit_op(
3666 Op::NamedHashSliceIncDec(0, hash_idx, keys.len() as u16),
3667 line,
3668 Some(root),
3669 );
3670 return Ok(());
3671 }
3672 let hk = &keys[0];
3673 self.compile_expr(hk)?;
3674 self.emit_op(Op::Dup, line, Some(root));
3675 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3676 self.emit_op(Op::LoadInt(1), line, Some(root));
3677 self.emit_op(Op::Add, line, Some(root));
3678 self.emit_op(Op::Dup, line, Some(root));
3679 self.emit_op(Op::Rot, line, Some(root));
3680 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
3681 } else if let ExprKind::ArrowDeref {
3682 expr,
3683 index,
3684 kind: DerefKind::Array,
3685 } = &expr.kind
3686 {
3687 if let ExprKind::List(indices) = &index.kind {
3688 self.compile_arrow_array_base_expr(expr)?;
3690 for ix in indices {
3691 self.compile_array_slice_index_expr(ix)?;
3692 }
3693 self.emit_op(
3694 Op::ArrowArraySliceIncDec(0, indices.len() as u16),
3695 line,
3696 Some(root),
3697 );
3698 return Ok(());
3699 }
3700 self.compile_arrow_array_base_expr(expr)?;
3701 self.compile_array_slice_index_expr(index)?;
3702 self.emit_op(Op::ArrowArraySliceIncDec(0, 1), line, Some(root));
3703 } else if let ExprKind::AnonymousListSlice { source, indices } = &expr.kind {
3704 if let ExprKind::Deref {
3705 expr: inner,
3706 kind: Sigil::Array,
3707 } = &source.kind
3708 {
3709 self.compile_arrow_array_base_expr(inner)?;
3710 for ix in indices {
3711 self.compile_array_slice_index_expr(ix)?;
3712 }
3713 self.emit_op(
3714 Op::ArrowArraySliceIncDec(0, indices.len() as u16),
3715 line,
3716 Some(root),
3717 );
3718 return Ok(());
3719 }
3720 } else if let ExprKind::ArrowDeref {
3721 expr,
3722 index,
3723 kind: DerefKind::Hash,
3724 } = &expr.kind
3725 {
3726 self.compile_arrow_hash_base_expr(expr)?;
3727 self.compile_expr(index)?;
3728 self.emit_op(Op::Dup2, line, Some(root));
3729 self.emit_op(Op::ArrowHash, line, Some(root));
3730 self.emit_op(Op::LoadInt(1), line, Some(root));
3731 self.emit_op(Op::Add, line, Some(root));
3732 self.emit_op(Op::Dup, line, Some(root));
3733 self.emit_op(Op::Pop, line, Some(root));
3734 self.emit_op(Op::Swap, line, Some(root));
3735 self.emit_op(Op::Rot, line, Some(root));
3736 self.emit_op(Op::Swap, line, Some(root));
3737 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
3738 } else if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
3739 if hash_slice_needs_slice_ops(keys) {
3740 self.compile_expr(container)?;
3744 for hk in keys {
3745 self.compile_expr(hk)?;
3746 }
3747 self.emit_op(
3748 Op::HashSliceDerefIncDec(0, keys.len() as u16),
3749 line,
3750 Some(root),
3751 );
3752 return Ok(());
3753 }
3754 let hk = &keys[0];
3755 self.compile_expr(container)?;
3756 self.compile_expr(hk)?;
3757 self.emit_op(Op::Dup2, line, Some(root));
3758 self.emit_op(Op::ArrowHash, line, Some(root));
3759 self.emit_op(Op::LoadInt(1), line, Some(root));
3760 self.emit_op(Op::Add, line, Some(root));
3761 self.emit_op(Op::Dup, line, Some(root));
3762 self.emit_op(Op::Pop, line, Some(root));
3763 self.emit_op(Op::Swap, line, Some(root));
3764 self.emit_op(Op::Rot, line, Some(root));
3765 self.emit_op(Op::Swap, line, Some(root));
3766 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
3767 } else if let ExprKind::Deref {
3768 expr,
3769 kind: Sigil::Scalar,
3770 } = &expr.kind
3771 {
3772 self.compile_expr(expr)?;
3773 self.emit_op(Op::Dup, line, Some(root));
3774 self.emit_op(Op::SymbolicDeref(0), line, Some(root));
3775 self.emit_op(Op::LoadInt(1), line, Some(root));
3776 self.emit_op(Op::Add, line, Some(root));
3777 self.emit_op(Op::Swap, line, Some(root));
3778 self.emit_op(Op::SetSymbolicScalarRefKeep, line, Some(root));
3779 } else if let ExprKind::Deref { kind, .. } = &expr.kind {
3780 self.emit_aggregate_symbolic_inc_dec_error(*kind, true, true, line, root)?;
3784 } else {
3785 return Err(CompileError::Unsupported("PreInc on non-scalar".into()));
3786 }
3787 }
3788 UnaryOp::PreDecrement => {
3789 if let ExprKind::ScalarVar(name) = &expr.kind {
3790 self.check_scalar_mutable(name, line)?;
3791 self.check_closure_write_to_outer_my(name, line)?;
3792 let idx = self.intern_scalar_var_for_ops(name);
3793 self.emit_pre_dec(idx, line, Some(root));
3794 } else if let ExprKind::ArrayElement { array, index } = &expr.kind {
3795 if self.is_mysync_array(array) {
3796 return Err(CompileError::Unsupported(
3797 "mysync array element update".into(),
3798 ));
3799 }
3800 let q = self.qualify_stash_array_name(array);
3801 self.check_array_mutable(&q, line)?;
3802 let arr_idx = self.chunk.intern_name(&q);
3803 self.compile_expr(index)?;
3804 self.emit_op(Op::Dup, line, Some(root));
3805 self.emit_op(Op::GetArrayElem(arr_idx), line, Some(root));
3806 self.emit_op(Op::LoadInt(1), line, Some(root));
3807 self.emit_op(Op::Sub, line, Some(root));
3808 self.emit_op(Op::Dup, line, Some(root));
3809 self.emit_op(Op::Rot, line, Some(root));
3810 self.emit_op(Op::SetArrayElem(arr_idx), line, Some(root));
3811 } else if let ExprKind::ArraySlice { array, indices } = &expr.kind {
3812 if self.is_mysync_array(array) {
3813 return Err(CompileError::Unsupported(
3814 "mysync array element update".into(),
3815 ));
3816 }
3817 self.check_strict_array_access(array, line)?;
3818 let q = self.qualify_stash_array_name(array);
3819 self.check_array_mutable(&q, line)?;
3820 let arr_idx = self.chunk.intern_name(&q);
3821 for ix in indices {
3822 self.compile_array_slice_index_expr(ix)?;
3823 }
3824 self.emit_op(
3825 Op::NamedArraySliceIncDec(1, arr_idx, indices.len() as u16),
3826 line,
3827 Some(root),
3828 );
3829 } else if let ExprKind::HashElement { hash, key } = &expr.kind {
3830 if self.is_mysync_hash(hash) {
3831 return Err(CompileError::Unsupported(
3832 "mysync hash element update".into(),
3833 ));
3834 }
3835 self.check_hash_mutable(hash, line)?;
3836 let hash_idx = self.chunk.intern_name(hash);
3837 self.compile_expr(key)?;
3838 self.emit_op(Op::Dup, line, Some(root));
3839 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3840 self.emit_op(Op::LoadInt(1), line, Some(root));
3841 self.emit_op(Op::Sub, line, Some(root));
3842 self.emit_op(Op::Dup, line, Some(root));
3843 self.emit_op(Op::Rot, line, Some(root));
3844 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
3845 } else if let ExprKind::HashSlice { hash, keys } = &expr.kind {
3846 if self.is_mysync_hash(hash) {
3847 return Err(CompileError::Unsupported(
3848 "mysync hash element update".into(),
3849 ));
3850 }
3851 self.check_hash_mutable(hash, line)?;
3852 let hash_idx = self.chunk.intern_name(hash);
3853 if hash_slice_needs_slice_ops(keys) {
3854 for hk in keys {
3855 self.compile_expr(hk)?;
3856 }
3857 self.emit_op(
3858 Op::NamedHashSliceIncDec(1, hash_idx, keys.len() as u16),
3859 line,
3860 Some(root),
3861 );
3862 return Ok(());
3863 }
3864 let hk = &keys[0];
3865 self.compile_expr(hk)?;
3866 self.emit_op(Op::Dup, line, Some(root));
3867 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
3868 self.emit_op(Op::LoadInt(1), line, Some(root));
3869 self.emit_op(Op::Sub, line, Some(root));
3870 self.emit_op(Op::Dup, line, Some(root));
3871 self.emit_op(Op::Rot, line, Some(root));
3872 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
3873 } else if let ExprKind::ArrowDeref {
3874 expr,
3875 index,
3876 kind: DerefKind::Array,
3877 } = &expr.kind
3878 {
3879 if let ExprKind::List(indices) = &index.kind {
3880 self.compile_arrow_array_base_expr(expr)?;
3881 for ix in indices {
3882 self.compile_array_slice_index_expr(ix)?;
3883 }
3884 self.emit_op(
3885 Op::ArrowArraySliceIncDec(1, indices.len() as u16),
3886 line,
3887 Some(root),
3888 );
3889 return Ok(());
3890 }
3891 self.compile_arrow_array_base_expr(expr)?;
3892 self.compile_array_slice_index_expr(index)?;
3893 self.emit_op(Op::ArrowArraySliceIncDec(1, 1), line, Some(root));
3894 } else if let ExprKind::AnonymousListSlice { source, indices } = &expr.kind {
3895 if let ExprKind::Deref {
3896 expr: inner,
3897 kind: Sigil::Array,
3898 } = &source.kind
3899 {
3900 self.compile_arrow_array_base_expr(inner)?;
3901 for ix in indices {
3902 self.compile_array_slice_index_expr(ix)?;
3903 }
3904 self.emit_op(
3905 Op::ArrowArraySliceIncDec(1, indices.len() as u16),
3906 line,
3907 Some(root),
3908 );
3909 return Ok(());
3910 }
3911 } else if let ExprKind::ArrowDeref {
3912 expr,
3913 index,
3914 kind: DerefKind::Hash,
3915 } = &expr.kind
3916 {
3917 self.compile_arrow_hash_base_expr(expr)?;
3918 self.compile_expr(index)?;
3919 self.emit_op(Op::Dup2, line, Some(root));
3920 self.emit_op(Op::ArrowHash, line, Some(root));
3921 self.emit_op(Op::LoadInt(1), line, Some(root));
3922 self.emit_op(Op::Sub, line, Some(root));
3923 self.emit_op(Op::Dup, line, Some(root));
3924 self.emit_op(Op::Pop, line, Some(root));
3925 self.emit_op(Op::Swap, line, Some(root));
3926 self.emit_op(Op::Rot, line, Some(root));
3927 self.emit_op(Op::Swap, line, Some(root));
3928 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
3929 } else if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
3930 if hash_slice_needs_slice_ops(keys) {
3931 self.compile_expr(container)?;
3932 for hk in keys {
3933 self.compile_expr(hk)?;
3934 }
3935 self.emit_op(
3936 Op::HashSliceDerefIncDec(1, keys.len() as u16),
3937 line,
3938 Some(root),
3939 );
3940 return Ok(());
3941 }
3942 let hk = &keys[0];
3943 self.compile_expr(container)?;
3944 self.compile_expr(hk)?;
3945 self.emit_op(Op::Dup2, line, Some(root));
3946 self.emit_op(Op::ArrowHash, line, Some(root));
3947 self.emit_op(Op::LoadInt(1), line, Some(root));
3948 self.emit_op(Op::Sub, line, Some(root));
3949 self.emit_op(Op::Dup, line, Some(root));
3950 self.emit_op(Op::Pop, line, Some(root));
3951 self.emit_op(Op::Swap, line, Some(root));
3952 self.emit_op(Op::Rot, line, Some(root));
3953 self.emit_op(Op::Swap, line, Some(root));
3954 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
3955 } else if let ExprKind::Deref {
3956 expr,
3957 kind: Sigil::Scalar,
3958 } = &expr.kind
3959 {
3960 self.compile_expr(expr)?;
3961 self.emit_op(Op::Dup, line, Some(root));
3962 self.emit_op(Op::SymbolicDeref(0), line, Some(root));
3963 self.emit_op(Op::LoadInt(1), line, Some(root));
3964 self.emit_op(Op::Sub, line, Some(root));
3965 self.emit_op(Op::Swap, line, Some(root));
3966 self.emit_op(Op::SetSymbolicScalarRefKeep, line, Some(root));
3967 } else if let ExprKind::Deref { kind, .. } = &expr.kind {
3968 self.emit_aggregate_symbolic_inc_dec_error(*kind, true, false, line, root)?;
3969 } else {
3970 return Err(CompileError::Unsupported("PreDec on non-scalar".into()));
3971 }
3972 }
3973 UnaryOp::Ref => {
3974 self.compile_expr(expr)?;
3975 self.emit_op(Op::MakeScalarRef, line, Some(root));
3976 }
3977 _ => match op {
3978 UnaryOp::LogNot | UnaryOp::LogNotWord => {
3979 if matches!(expr.kind, ExprKind::Regex(..)) {
3980 self.compile_boolean_rvalue_condition(expr)?;
3981 } else {
3982 self.compile_expr(expr)?;
3983 }
3984 self.emit_op(Op::LogNot, line, Some(root));
3985 }
3986 UnaryOp::Negate => {
3987 self.compile_expr(expr)?;
3988 self.emit_op(Op::Negate, line, Some(root));
3989 }
3990 UnaryOp::BitNot => {
3991 self.compile_expr(expr)?;
3992 self.emit_op(Op::BitNot, line, Some(root));
3993 }
3994 _ => unreachable!(),
3995 },
3996 },
3997 ExprKind::PostfixOp { expr, op } => {
3998 if let ExprKind::ScalarVar(name) = &expr.kind {
3999 self.check_scalar_mutable(name, line)?;
4000 self.check_closure_write_to_outer_my(name, line)?;
4001 let idx = self.intern_scalar_var_for_ops(name);
4002 match op {
4003 PostfixOp::Increment => {
4004 self.emit_post_inc(idx, line, Some(root));
4005 }
4006 PostfixOp::Decrement => {
4007 self.emit_post_dec(idx, line, Some(root));
4008 }
4009 }
4010 } else if let ExprKind::ArrayElement { array, index } = &expr.kind {
4011 if self.is_mysync_array(array) {
4012 return Err(CompileError::Unsupported(
4013 "mysync array element update".into(),
4014 ));
4015 }
4016 let q = self.qualify_stash_array_name(array);
4017 self.check_array_mutable(&q, line)?;
4018 let arr_idx = self.chunk.intern_name(&q);
4019 self.compile_expr(index)?;
4020 self.emit_op(Op::Dup, line, Some(root));
4021 self.emit_op(Op::GetArrayElem(arr_idx), line, Some(root));
4022 self.emit_op(Op::Dup, line, Some(root));
4023 self.emit_op(Op::LoadInt(1), line, Some(root));
4024 match op {
4025 PostfixOp::Increment => {
4026 self.emit_op(Op::Add, line, Some(root));
4027 }
4028 PostfixOp::Decrement => {
4029 self.emit_op(Op::Sub, line, Some(root));
4030 }
4031 }
4032 self.emit_op(Op::Rot, line, Some(root));
4033 self.emit_op(Op::SetArrayElem(arr_idx), line, Some(root));
4034 } else if let ExprKind::ArraySlice { array, indices } = &expr.kind {
4035 if self.is_mysync_array(array) {
4036 return Err(CompileError::Unsupported(
4037 "mysync array element update".into(),
4038 ));
4039 }
4040 self.check_strict_array_access(array, line)?;
4041 let q = self.qualify_stash_array_name(array);
4042 self.check_array_mutable(&q, line)?;
4043 let arr_idx = self.chunk.intern_name(&q);
4044 let kind_byte: u8 = match op {
4045 PostfixOp::Increment => 2,
4046 PostfixOp::Decrement => 3,
4047 };
4048 for ix in indices {
4049 self.compile_array_slice_index_expr(ix)?;
4050 }
4051 self.emit_op(
4052 Op::NamedArraySliceIncDec(kind_byte, arr_idx, indices.len() as u16),
4053 line,
4054 Some(root),
4055 );
4056 } else if let ExprKind::HashElement { hash, key } = &expr.kind {
4057 if self.is_mysync_hash(hash) {
4058 return Err(CompileError::Unsupported(
4059 "mysync hash element update".into(),
4060 ));
4061 }
4062 self.check_hash_mutable(hash, line)?;
4063 let hash_idx = self.chunk.intern_name(hash);
4064 self.compile_expr(key)?;
4065 self.emit_op(Op::Dup, line, Some(root));
4066 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
4067 self.emit_op(Op::Dup, line, Some(root));
4068 self.emit_op(Op::LoadInt(1), line, Some(root));
4069 match op {
4070 PostfixOp::Increment => {
4071 self.emit_op(Op::Add, line, Some(root));
4072 }
4073 PostfixOp::Decrement => {
4074 self.emit_op(Op::Sub, line, Some(root));
4075 }
4076 }
4077 self.emit_op(Op::Rot, line, Some(root));
4078 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
4079 } else if let ExprKind::HashSlice { hash, keys } = &expr.kind {
4080 if self.is_mysync_hash(hash) {
4081 return Err(CompileError::Unsupported(
4082 "mysync hash element update".into(),
4083 ));
4084 }
4085 self.check_hash_mutable(hash, line)?;
4086 let hash_idx = self.chunk.intern_name(hash);
4087 if hash_slice_needs_slice_ops(keys) {
4088 let kind_byte: u8 = match op {
4089 PostfixOp::Increment => 2,
4090 PostfixOp::Decrement => 3,
4091 };
4092 for hk in keys {
4093 self.compile_expr(hk)?;
4094 }
4095 self.emit_op(
4096 Op::NamedHashSliceIncDec(kind_byte, hash_idx, keys.len() as u16),
4097 line,
4098 Some(root),
4099 );
4100 return Ok(());
4101 }
4102 let hk = &keys[0];
4103 self.compile_expr(hk)?;
4104 self.emit_op(Op::Dup, line, Some(root));
4105 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
4106 self.emit_op(Op::Dup, line, Some(root));
4107 self.emit_op(Op::LoadInt(1), line, Some(root));
4108 match op {
4109 PostfixOp::Increment => {
4110 self.emit_op(Op::Add, line, Some(root));
4111 }
4112 PostfixOp::Decrement => {
4113 self.emit_op(Op::Sub, line, Some(root));
4114 }
4115 }
4116 self.emit_op(Op::Rot, line, Some(root));
4117 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
4118 } else if let ExprKind::ArrowDeref {
4119 expr: inner,
4120 index,
4121 kind: DerefKind::Array,
4122 } = &expr.kind
4123 {
4124 if let ExprKind::List(indices) = &index.kind {
4125 let kind_byte: u8 = match op {
4126 PostfixOp::Increment => 2,
4127 PostfixOp::Decrement => 3,
4128 };
4129 self.compile_arrow_array_base_expr(inner)?;
4130 for ix in indices {
4131 self.compile_array_slice_index_expr(ix)?;
4132 }
4133 self.emit_op(
4134 Op::ArrowArraySliceIncDec(kind_byte, indices.len() as u16),
4135 line,
4136 Some(root),
4137 );
4138 return Ok(());
4139 }
4140 self.compile_arrow_array_base_expr(inner)?;
4141 self.compile_array_slice_index_expr(index)?;
4142 let kind_byte: u8 = match op {
4143 PostfixOp::Increment => 2,
4144 PostfixOp::Decrement => 3,
4145 };
4146 self.emit_op(Op::ArrowArraySliceIncDec(kind_byte, 1), line, Some(root));
4147 } else if let ExprKind::AnonymousListSlice { source, indices } = &expr.kind {
4148 let ExprKind::Deref {
4149 expr: inner,
4150 kind: Sigil::Array,
4151 } = &source.kind
4152 else {
4153 return Err(CompileError::Unsupported(
4154 "PostfixOp on list slice (non-array deref)".into(),
4155 ));
4156 };
4157 if indices.is_empty() {
4158 return Err(CompileError::Unsupported(
4159 "postfix ++/-- on empty list slice (internal)".into(),
4160 ));
4161 }
4162 let kind_byte: u8 = match op {
4163 PostfixOp::Increment => 2,
4164 PostfixOp::Decrement => 3,
4165 };
4166 self.compile_arrow_array_base_expr(inner)?;
4167 if indices.len() > 1 {
4168 for ix in indices {
4169 self.compile_array_slice_index_expr(ix)?;
4170 }
4171 self.emit_op(
4172 Op::ArrowArraySliceIncDec(kind_byte, indices.len() as u16),
4173 line,
4174 Some(root),
4175 );
4176 } else {
4177 self.compile_array_slice_index_expr(&indices[0])?;
4178 self.emit_op(Op::ArrowArraySliceIncDec(kind_byte, 1), line, Some(root));
4179 }
4180 } else if let ExprKind::ArrowDeref {
4181 expr: inner,
4182 index,
4183 kind: DerefKind::Hash,
4184 } = &expr.kind
4185 {
4186 self.compile_arrow_hash_base_expr(inner)?;
4187 self.compile_expr(index)?;
4188 let b = match op {
4189 PostfixOp::Increment => 0u8,
4190 PostfixOp::Decrement => 1u8,
4191 };
4192 self.emit_op(Op::ArrowHashPostfix(b), line, Some(root));
4193 } else if let ExprKind::HashSliceDeref { container, keys } = &expr.kind {
4194 if hash_slice_needs_slice_ops(keys) {
4195 let kind_byte: u8 = match op {
4198 PostfixOp::Increment => 2,
4199 PostfixOp::Decrement => 3,
4200 };
4201 self.compile_expr(container)?;
4202 for hk in keys {
4203 self.compile_expr(hk)?;
4204 }
4205 self.emit_op(
4206 Op::HashSliceDerefIncDec(kind_byte, keys.len() as u16),
4207 line,
4208 Some(root),
4209 );
4210 return Ok(());
4211 }
4212 let hk = &keys[0];
4213 self.compile_expr(container)?;
4214 self.compile_expr(hk)?;
4215 let b = match op {
4216 PostfixOp::Increment => 0u8,
4217 PostfixOp::Decrement => 1u8,
4218 };
4219 self.emit_op(Op::ArrowHashPostfix(b), line, Some(root));
4220 } else if let ExprKind::Deref {
4221 expr,
4222 kind: Sigil::Scalar,
4223 } = &expr.kind
4224 {
4225 self.compile_expr(expr)?;
4226 let b = match op {
4227 PostfixOp::Increment => 0u8,
4228 PostfixOp::Decrement => 1u8,
4229 };
4230 self.emit_op(Op::SymbolicScalarRefPostfix(b), line, Some(root));
4231 } else if let ExprKind::Deref { kind, .. } = &expr.kind {
4232 let is_inc = matches!(op, PostfixOp::Increment);
4233 self.emit_aggregate_symbolic_inc_dec_error(*kind, false, is_inc, line, root)?;
4234 } else {
4235 return Err(CompileError::Unsupported("PostfixOp on non-scalar".into()));
4236 }
4237 }
4238
4239 ExprKind::Assign { target, value } => {
4240 if let ExprKind::Substr {
4245 string,
4246 offset,
4247 length,
4248 replacement: None,
4249 } = &target.kind
4250 {
4251 let rewritten = Expr {
4252 kind: ExprKind::Substr {
4253 string: string.clone(),
4254 offset: offset.clone(),
4255 length: length.clone(),
4256 replacement: Some(value.clone()),
4257 },
4258 line: target.line,
4259 };
4260 self.compile_expr(&rewritten)?;
4261 return Ok(());
4262 }
4263 if let ExprKind::FuncCall { name, args } = &target.kind {
4271 if name == "vec" && args.len() == 3 {
4272 let new_call = Expr {
4273 kind: ExprKind::FuncCall {
4274 name: "vec_set_value".to_string(),
4275 args: vec![
4276 args[0].clone(),
4277 args[1].clone(),
4278 args[2].clone(),
4279 (**value).clone(),
4280 ],
4281 },
4282 line: target.line,
4283 };
4284 let rewritten = Expr {
4285 kind: ExprKind::Assign {
4286 target: Box::new(args[0].clone()),
4287 value: Box::new(new_call),
4288 },
4289 line,
4290 };
4291 self.compile_expr(&rewritten)?;
4292 return Ok(());
4293 }
4294 }
4295 if let (ExprKind::Typeglob(lhs), ExprKind::Typeglob(rhs)) =
4296 (&target.kind, &value.kind)
4297 {
4298 let lhs_idx = self.chunk.intern_name(lhs);
4299 let rhs_idx = self.chunk.intern_name(rhs);
4300 self.emit_op(Op::CopyTypeglobSlots(lhs_idx, rhs_idx), line, Some(root));
4301 self.compile_expr(value)?;
4302 return Ok(());
4303 }
4304 if let ExprKind::TypeglobExpr(expr) = &target.kind {
4305 if let ExprKind::Typeglob(rhs) = &value.kind {
4306 self.compile_expr(expr)?;
4307 let rhs_idx = self.chunk.intern_name(rhs);
4308 self.emit_op(Op::CopyTypeglobSlotsDynamicLhs(rhs_idx), line, Some(root));
4309 self.compile_expr(value)?;
4310 return Ok(());
4311 }
4312 self.compile_expr(expr)?;
4313 self.compile_expr(value)?;
4314 self.emit_op(Op::TypeglobAssignFromValueDynamic, line, Some(root));
4315 return Ok(());
4316 }
4317 if let ExprKind::Deref {
4319 expr,
4320 kind: Sigil::Typeglob,
4321 } = &target.kind
4322 {
4323 if let ExprKind::Typeglob(rhs) = &value.kind {
4324 self.compile_expr(expr)?;
4325 let rhs_idx = self.chunk.intern_name(rhs);
4326 self.emit_op(Op::CopyTypeglobSlotsDynamicLhs(rhs_idx), line, Some(root));
4327 self.compile_expr(value)?;
4328 return Ok(());
4329 }
4330 self.compile_expr(expr)?;
4331 self.compile_expr(value)?;
4332 self.emit_op(Op::TypeglobAssignFromValueDynamic, line, Some(root));
4333 return Ok(());
4334 }
4335 if let ExprKind::ArrowDeref {
4336 expr,
4337 index,
4338 kind: DerefKind::Array,
4339 } = &target.kind
4340 {
4341 if let ExprKind::List(indices) = &index.kind {
4342 if let ExprKind::Deref {
4343 expr: inner,
4344 kind: Sigil::Array,
4345 } = &expr.kind
4346 {
4347 if let ExprKind::List(vals) = &value.kind {
4348 if !indices.is_empty() && indices.len() == vals.len() {
4349 for (idx_e, val_e) in indices.iter().zip(vals.iter()) {
4350 self.compile_expr(val_e)?;
4351 self.compile_expr(inner)?;
4352 self.compile_expr(idx_e)?;
4353 self.emit_op(Op::SetArrowArray, line, Some(root));
4354 }
4355 return Ok(());
4356 }
4357 }
4358 }
4359 }
4360 }
4361 if let ExprKind::ScalarVar(tgt_name) = &target.kind {
4363 if let Some(dst_slot) = self.scalar_slot(tgt_name) {
4364 if let ExprKind::BinOp { left, op, right } = &value.kind {
4365 if let ExprKind::ScalarVar(lv) = &left.kind {
4366 if lv == tgt_name {
4367 if let ExprKind::ScalarVar(rv) = &right.kind {
4369 if let Some(src_slot) = self.scalar_slot(rv) {
4370 let fused = match op {
4371 BinOp::Add => {
4372 Some(Op::AddAssignSlotSlot(dst_slot, src_slot))
4373 }
4374 BinOp::Sub => {
4375 Some(Op::SubAssignSlotSlot(dst_slot, src_slot))
4376 }
4377 BinOp::Mul => {
4378 Some(Op::MulAssignSlotSlot(dst_slot, src_slot))
4379 }
4380 _ => None,
4381 };
4382 if let Some(fop) = fused {
4383 self.emit_op(fop, line, Some(root));
4384 return Ok(());
4385 }
4386 }
4387 }
4388 if let ExprKind::Integer(1) = &right.kind {
4390 match op {
4391 BinOp::Add => {
4392 self.emit_op(
4393 Op::PreIncSlot(dst_slot),
4394 line,
4395 Some(root),
4396 );
4397 return Ok(());
4398 }
4399 BinOp::Sub => {
4400 self.emit_op(
4401 Op::PreDecSlot(dst_slot),
4402 line,
4403 Some(root),
4404 );
4405 return Ok(());
4406 }
4407 _ => {}
4408 }
4409 }
4410 }
4411 }
4412 }
4413 }
4414 }
4415 self.compile_expr_ctx(value, assign_rhs_wantarray(target))?;
4416 self.compile_assign(target, line, true, Some(root))?;
4417 }
4418 ExprKind::CompoundAssign { target, op, value } => {
4419 if let ExprKind::ScalarVar(name) = &target.kind {
4420 self.check_scalar_mutable(name, line)?;
4421 let idx = self.intern_scalar_var_for_ops(name);
4422 if *op == BinOp::Concat {
4424 self.compile_expr(value)?;
4425 if let Some(slot) = self.scalar_slot(name) {
4426 self.emit_op(Op::ConcatAppendSlot(slot), line, Some(root));
4427 } else {
4428 self.emit_op(Op::ConcatAppend(idx), line, Some(root));
4429 }
4430 return Ok(());
4431 }
4432 if let Some(dst_slot) = self.scalar_slot(name) {
4434 if let ExprKind::ScalarVar(rhs_name) = &value.kind {
4435 if let Some(src_slot) = self.scalar_slot(rhs_name) {
4436 let fused = match op {
4437 BinOp::Add => Some(Op::AddAssignSlotSlot(dst_slot, src_slot)),
4438 BinOp::Sub => Some(Op::SubAssignSlotSlot(dst_slot, src_slot)),
4439 BinOp::Mul => Some(Op::MulAssignSlotSlot(dst_slot, src_slot)),
4440 _ => None,
4441 };
4442 if let Some(fop) = fused {
4443 self.emit_op(fop, line, Some(root));
4444 return Ok(());
4445 }
4446 }
4447 }
4448 }
4449 if *op == BinOp::DefinedOr {
4450 if let Some(slot) = self.scalar_slot(name) {
4453 self.emit_op(Op::GetScalarSlot(slot), line, Some(root));
4454 let j_def = self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root));
4455 self.compile_expr(value)?;
4456 self.emit_op(Op::Dup, line, Some(root));
4457 self.emit_op(Op::SetScalarSlot(slot), line, Some(root));
4458 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4459 self.chunk.patch_jump_here(j_def);
4460 self.chunk.patch_jump_here(j_end);
4461 } else {
4462 self.emit_get_scalar(idx, line, Some(root));
4463 let j_def = self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root));
4464 self.compile_expr(value)?;
4465 self.emit_set_scalar_keep(idx, line, Some(root));
4466 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4467 self.chunk.patch_jump_here(j_def);
4468 self.chunk.patch_jump_here(j_end);
4469 }
4470 return Ok(());
4471 }
4472 if *op == BinOp::LogOr {
4473 if let Some(slot) = self.scalar_slot(name) {
4475 self.emit_op(Op::GetScalarSlot(slot), line, Some(root));
4476 let j_true = self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root));
4477 self.compile_expr(value)?;
4478 self.emit_op(Op::Dup, line, Some(root));
4479 self.emit_op(Op::SetScalarSlot(slot), line, Some(root));
4480 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4481 self.chunk.patch_jump_here(j_true);
4482 self.chunk.patch_jump_here(j_end);
4483 } else {
4484 self.emit_get_scalar(idx, line, Some(root));
4485 let j_true = self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root));
4486 self.compile_expr(value)?;
4487 self.emit_set_scalar_keep(idx, line, Some(root));
4488 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4489 self.chunk.patch_jump_here(j_true);
4490 self.chunk.patch_jump_here(j_end);
4491 }
4492 return Ok(());
4493 }
4494 if *op == BinOp::LogAnd {
4495 if let Some(slot) = self.scalar_slot(name) {
4497 self.emit_op(Op::GetScalarSlot(slot), line, Some(root));
4498 let j = self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root));
4499 self.compile_expr(value)?;
4500 self.emit_op(Op::Dup, line, Some(root));
4501 self.emit_op(Op::SetScalarSlot(slot), line, Some(root));
4502 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4503 self.chunk.patch_jump_here(j);
4504 self.chunk.patch_jump_here(j_end);
4505 } else {
4506 self.emit_get_scalar(idx, line, Some(root));
4507 let j = self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root));
4508 self.compile_expr(value)?;
4509 self.emit_set_scalar_keep(idx, line, Some(root));
4510 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4511 self.chunk.patch_jump_here(j);
4512 self.chunk.patch_jump_here(j_end);
4513 }
4514 return Ok(());
4515 }
4516 if let Some(op_b) = scalar_compound_op_to_byte(*op) {
4517 if let Some(slot) = self.scalar_slot(name) {
4519 let vm_op = binop_to_vm_op(*op).ok_or_else(|| {
4520 CompileError::Unsupported("CompoundAssign op (slot)".into())
4521 })?;
4522 self.emit_op(Op::GetScalarSlot(slot), line, Some(root));
4523 self.compile_expr(value)?;
4524 self.emit_op(vm_op, line, Some(root));
4525 self.emit_op(Op::Dup, line, Some(root));
4526 self.emit_op(Op::SetScalarSlot(slot), line, Some(root));
4527 return Ok(());
4528 }
4529 self.compile_expr(value)?;
4530 self.emit_op(
4531 Op::ScalarCompoundAssign {
4532 name_idx: idx,
4533 op: op_b,
4534 },
4535 line,
4536 Some(root),
4537 );
4538 } else {
4539 return Err(CompileError::Unsupported("CompoundAssign op".into()));
4540 }
4541 } else if let ExprKind::ArrayElement { array, index } = &target.kind {
4542 if self.is_mysync_array(array) {
4543 return Err(CompileError::Unsupported(
4544 "mysync array element update".into(),
4545 ));
4546 }
4547 let q = self.qualify_stash_array_name(array);
4548 self.check_array_mutable(&q, line)?;
4549 let arr_idx = self.chunk.intern_name(&q);
4550 match op {
4551 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
4552 self.compile_expr(index)?;
4553 self.emit_op(Op::Dup, line, Some(root));
4554 self.emit_op(Op::GetArrayElem(arr_idx), line, Some(root));
4555 let j = match *op {
4556 BinOp::DefinedOr => {
4557 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4558 }
4559 BinOp::LogOr => {
4560 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4561 }
4562 BinOp::LogAnd => {
4563 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4564 }
4565 _ => unreachable!(),
4566 };
4567 self.compile_expr(value)?;
4568 self.emit_op(Op::Swap, line, Some(root));
4569 self.emit_op(Op::SetArrayElemKeep(arr_idx), line, Some(root));
4570 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4571 self.chunk.patch_jump_here(j);
4572 self.emit_op(Op::Swap, line, Some(root));
4573 self.emit_op(Op::Pop, line, Some(root));
4574 self.chunk.patch_jump_here(j_end);
4575 }
4576 _ => {
4577 let vm_op = binop_to_vm_op(*op).ok_or_else(|| {
4578 CompileError::Unsupported("CompoundAssign op".into())
4579 })?;
4580 self.compile_expr(index)?;
4581 self.emit_op(Op::Dup, line, Some(root));
4582 self.emit_op(Op::GetArrayElem(arr_idx), line, Some(root));
4583 self.compile_expr(value)?;
4584 self.emit_op(vm_op, line, Some(root));
4585 self.emit_op(Op::Dup, line, Some(root));
4586 self.emit_op(Op::Rot, line, Some(root));
4587 self.emit_op(Op::SetArrayElem(arr_idx), line, Some(root));
4588 }
4589 }
4590 } else if let ExprKind::HashElement { hash, key } = &target.kind {
4591 if self.is_mysync_hash(hash) {
4592 return Err(CompileError::Unsupported(
4593 "mysync hash element update".into(),
4594 ));
4595 }
4596 self.check_hash_mutable(hash, line)?;
4597 let hash_idx = self.chunk.intern_name(hash);
4598 match op {
4599 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
4600 self.compile_expr(key)?;
4601 self.emit_op(Op::Dup, line, Some(root));
4602 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
4603 let j = match *op {
4604 BinOp::DefinedOr => {
4605 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4606 }
4607 BinOp::LogOr => {
4608 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4609 }
4610 BinOp::LogAnd => {
4611 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4612 }
4613 _ => unreachable!(),
4614 };
4615 self.compile_expr(value)?;
4616 self.emit_op(Op::Swap, line, Some(root));
4617 self.emit_op(Op::SetHashElemKeep(hash_idx), line, Some(root));
4618 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4619 self.chunk.patch_jump_here(j);
4620 self.emit_op(Op::Swap, line, Some(root));
4621 self.emit_op(Op::Pop, line, Some(root));
4622 self.chunk.patch_jump_here(j_end);
4623 }
4624 _ => {
4625 let vm_op = binop_to_vm_op(*op).ok_or_else(|| {
4626 CompileError::Unsupported("CompoundAssign op".into())
4627 })?;
4628 self.compile_expr(key)?;
4629 self.emit_op(Op::Dup, line, Some(root));
4630 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
4631 self.compile_expr(value)?;
4632 self.emit_op(vm_op, line, Some(root));
4633 self.emit_op(Op::Dup, line, Some(root));
4634 self.emit_op(Op::Rot, line, Some(root));
4635 self.emit_op(Op::SetHashElem(hash_idx), line, Some(root));
4636 }
4637 }
4638 } else if let ExprKind::Deref {
4639 expr,
4640 kind: Sigil::Scalar,
4641 } = &target.kind
4642 {
4643 match op {
4644 BinOp::DefinedOr => {
4645 self.compile_expr(expr)?;
4648 self.emit_op(Op::Dup, line, Some(root));
4649 self.emit_op(Op::SymbolicDeref(0), line, Some(root));
4650 let j_def = self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root));
4651 self.compile_expr(value)?;
4652 self.emit_op(Op::Swap, line, Some(root));
4653 self.emit_op(Op::SetSymbolicScalarRefKeep, line, Some(root));
4654 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4655 self.chunk.patch_jump_here(j_def);
4656 self.emit_op(Op::Swap, line, Some(root));
4657 self.emit_op(Op::Pop, line, Some(root));
4658 self.chunk.patch_jump_here(j_end);
4659 }
4660 BinOp::LogOr => {
4661 self.compile_expr(expr)?;
4663 self.emit_op(Op::Dup, line, Some(root));
4664 self.emit_op(Op::SymbolicDeref(0), line, Some(root));
4665 let j_true = self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root));
4666 self.compile_expr(value)?;
4667 self.emit_op(Op::Swap, line, Some(root));
4668 self.emit_op(Op::SetSymbolicScalarRefKeep, line, Some(root));
4669 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4670 self.chunk.patch_jump_here(j_true);
4671 self.emit_op(Op::Swap, line, Some(root));
4672 self.emit_op(Op::Pop, line, Some(root));
4673 self.chunk.patch_jump_here(j_end);
4674 }
4675 BinOp::LogAnd => {
4676 self.compile_expr(expr)?;
4678 self.emit_op(Op::Dup, line, Some(root));
4679 self.emit_op(Op::SymbolicDeref(0), line, Some(root));
4680 let j = self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root));
4681 self.compile_expr(value)?;
4682 self.emit_op(Op::Swap, line, Some(root));
4683 self.emit_op(Op::SetSymbolicScalarRefKeep, line, Some(root));
4684 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4685 self.chunk.patch_jump_here(j);
4686 self.emit_op(Op::Swap, line, Some(root));
4687 self.emit_op(Op::Pop, line, Some(root));
4688 self.chunk.patch_jump_here(j_end);
4689 }
4690 _ => {
4691 let vm_op = binop_to_vm_op(*op).ok_or_else(|| {
4692 CompileError::Unsupported("CompoundAssign op".into())
4693 })?;
4694 self.compile_expr(expr)?;
4695 self.emit_op(Op::Dup, line, Some(root));
4696 self.emit_op(Op::SymbolicDeref(0), line, Some(root));
4697 self.compile_expr(value)?;
4698 self.emit_op(vm_op, line, Some(root));
4699 self.emit_op(Op::Swap, line, Some(root));
4700 self.emit_op(Op::SetSymbolicScalarRef, line, Some(root));
4701 }
4702 }
4703 } else if let ExprKind::ArrowDeref {
4704 expr,
4705 index,
4706 kind: DerefKind::Hash,
4707 } = &target.kind
4708 {
4709 match op {
4710 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
4711 self.compile_arrow_hash_base_expr(expr)?;
4712 self.compile_expr(index)?;
4713 self.emit_op(Op::Dup2, line, Some(root));
4714 self.emit_op(Op::ArrowHash, line, Some(root));
4715 let j = match *op {
4716 BinOp::DefinedOr => {
4717 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4718 }
4719 BinOp::LogOr => {
4720 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4721 }
4722 BinOp::LogAnd => {
4723 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4724 }
4725 _ => unreachable!(),
4726 };
4727 self.compile_expr(value)?;
4728 self.emit_op(Op::Swap, line, Some(root));
4729 self.emit_op(Op::Rot, line, Some(root));
4730 self.emit_op(Op::Swap, line, Some(root));
4731 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
4732 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4733 self.chunk.patch_jump_here(j);
4734 self.emit_op(Op::Swap, line, Some(root));
4736 self.emit_op(Op::Pop, line, Some(root));
4737 self.emit_op(Op::Swap, line, Some(root));
4738 self.emit_op(Op::Pop, line, Some(root));
4739 self.chunk.patch_jump_here(j_end);
4740 }
4741 _ => {
4742 let vm_op = binop_to_vm_op(*op).ok_or_else(|| {
4743 CompileError::Unsupported("CompoundAssign op".into())
4744 })?;
4745 self.compile_arrow_hash_base_expr(expr)?;
4746 self.compile_expr(index)?;
4747 self.emit_op(Op::Dup2, line, Some(root));
4748 self.emit_op(Op::ArrowHash, line, Some(root));
4749 self.compile_expr(value)?;
4750 self.emit_op(vm_op, line, Some(root));
4751 self.emit_op(Op::Swap, line, Some(root));
4752 self.emit_op(Op::Rot, line, Some(root));
4753 self.emit_op(Op::Swap, line, Some(root));
4754 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
4762 }
4763 }
4764 } else if let ExprKind::ArrowDeref {
4765 expr,
4766 index,
4767 kind: DerefKind::Array,
4768 } = &target.kind
4769 {
4770 if let ExprKind::List(indices) = &index.kind {
4771 if matches!(op, BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd) {
4772 let k = indices.len() as u16;
4773 self.compile_arrow_array_base_expr(expr)?;
4774 for ix in indices {
4775 self.compile_array_slice_index_expr(ix)?;
4776 }
4777 self.emit_op(Op::ArrowArraySlicePeekLast(k), line, Some(root));
4778 let j = match *op {
4779 BinOp::DefinedOr => {
4780 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4781 }
4782 BinOp::LogOr => {
4783 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4784 }
4785 BinOp::LogAnd => {
4786 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4787 }
4788 _ => unreachable!(),
4789 };
4790 self.compile_expr(value)?;
4791 self.emit_op(Op::ArrowArraySliceRollValUnderSpecs(k), line, Some(root));
4792 self.emit_op(Op::SetArrowArraySliceLastKeep(k), line, Some(root));
4793 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4794 self.chunk.patch_jump_here(j);
4795 self.emit_op(Op::ArrowArraySliceDropKeysKeepCur(k), line, Some(root));
4796 self.chunk.patch_jump_here(j_end);
4797 return Ok(());
4798 }
4799 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
4802 CompileError::Unsupported(
4803 "CompoundAssign op on multi-index array slice".into(),
4804 )
4805 })?;
4806 self.compile_expr(value)?;
4807 self.compile_arrow_array_base_expr(expr)?;
4808 for ix in indices {
4809 self.compile_array_slice_index_expr(ix)?;
4810 }
4811 self.emit_op(
4812 Op::ArrowArraySliceCompound(op_byte, indices.len() as u16),
4813 line,
4814 Some(root),
4815 );
4816 return Ok(());
4817 }
4818 match op {
4819 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
4820 self.compile_arrow_array_base_expr(expr)?;
4823 self.compile_array_slice_index_expr(index)?;
4824 self.emit_op(Op::ArrowArraySlicePeekLast(1), line, Some(root));
4825 let j = match *op {
4826 BinOp::DefinedOr => {
4827 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4828 }
4829 BinOp::LogOr => {
4830 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4831 }
4832 BinOp::LogAnd => {
4833 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4834 }
4835 _ => unreachable!(),
4836 };
4837 self.compile_expr(value)?;
4838 self.emit_op(Op::ArrowArraySliceRollValUnderSpecs(1), line, Some(root));
4839 self.emit_op(Op::SetArrowArraySliceLastKeep(1), line, Some(root));
4840 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4841 self.chunk.patch_jump_here(j);
4842 self.emit_op(Op::ArrowArraySliceDropKeysKeepCur(1), line, Some(root));
4843 self.chunk.patch_jump_here(j_end);
4844 }
4845 _ => {
4846 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
4847 CompileError::Unsupported("CompoundAssign op".into())
4848 })?;
4849 self.compile_expr(value)?;
4850 self.compile_arrow_array_base_expr(expr)?;
4851 self.compile_array_slice_index_expr(index)?;
4852 self.emit_op(Op::ArrowArraySliceCompound(op_byte, 1), line, Some(root));
4853 }
4854 }
4855 } else if let ExprKind::HashSliceDeref { container, keys } = &target.kind {
4856 if keys.is_empty() {
4859 self.compile_expr(container)?;
4862 self.emit_op(Op::Pop, line, Some(root));
4863 self.compile_expr(value)?;
4864 self.emit_op(Op::Pop, line, Some(root));
4865 let idx = self
4866 .chunk
4867 .add_constant(StrykeValue::string("assign to empty hash slice".into()));
4868 self.emit_op(Op::RuntimeErrorConst(idx), line, Some(root));
4869 self.emit_op(Op::LoadUndef, line, Some(root));
4870 return Ok(());
4871 }
4872 if hash_slice_needs_slice_ops(keys) {
4873 if matches!(op, BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd) {
4874 let k = keys.len() as u16;
4875 self.compile_expr(container)?;
4876 for hk in keys {
4877 self.compile_expr(hk)?;
4878 }
4879 self.emit_op(Op::HashSliceDerefPeekLast(k), line, Some(root));
4880 let j = match *op {
4881 BinOp::DefinedOr => {
4882 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4883 }
4884 BinOp::LogOr => {
4885 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4886 }
4887 BinOp::LogAnd => {
4888 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4889 }
4890 _ => unreachable!(),
4891 };
4892 self.compile_expr(value)?;
4893 self.emit_op(Op::HashSliceDerefRollValUnderKeys(k), line, Some(root));
4894 self.emit_op(Op::HashSliceDerefSetLastKeep(k), line, Some(root));
4895 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4896 self.chunk.patch_jump_here(j);
4897 self.emit_op(Op::HashSliceDerefDropKeysKeepCur(k), line, Some(root));
4898 self.chunk.patch_jump_here(j_end);
4899 return Ok(());
4900 }
4901 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
4902 CompileError::Unsupported(
4903 "CompoundAssign op on multi-key hash slice".into(),
4904 )
4905 })?;
4906 self.compile_expr(value)?;
4907 self.compile_expr(container)?;
4908 for hk in keys {
4909 self.compile_expr(hk)?;
4910 }
4911 self.emit_op(
4912 Op::HashSliceDerefCompound(op_byte, keys.len() as u16),
4913 line,
4914 Some(root),
4915 );
4916 return Ok(());
4917 }
4918 let hk = &keys[0];
4919 match op {
4920 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
4921 self.compile_expr(container)?;
4922 self.compile_expr(hk)?;
4923 self.emit_op(Op::Dup2, line, Some(root));
4924 self.emit_op(Op::ArrowHash, line, Some(root));
4925 let j = match *op {
4926 BinOp::DefinedOr => {
4927 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
4928 }
4929 BinOp::LogOr => {
4930 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
4931 }
4932 BinOp::LogAnd => {
4933 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
4934 }
4935 _ => unreachable!(),
4936 };
4937 self.compile_expr(value)?;
4938 self.emit_op(Op::Swap, line, Some(root));
4939 self.emit_op(Op::Rot, line, Some(root));
4940 self.emit_op(Op::Swap, line, Some(root));
4941 self.emit_op(Op::SetArrowHashKeep, line, Some(root));
4942 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
4943 self.chunk.patch_jump_here(j);
4944 self.emit_op(Op::Swap, line, Some(root));
4945 self.emit_op(Op::Pop, line, Some(root));
4946 self.emit_op(Op::Swap, line, Some(root));
4947 self.emit_op(Op::Pop, line, Some(root));
4948 self.chunk.patch_jump_here(j_end);
4949 }
4950 _ => {
4951 let vm_op = binop_to_vm_op(*op).ok_or_else(|| {
4952 CompileError::Unsupported("CompoundAssign op".into())
4953 })?;
4954 self.compile_expr(container)?;
4955 self.compile_expr(hk)?;
4956 self.emit_op(Op::Dup2, line, Some(root));
4957 self.emit_op(Op::ArrowHash, line, Some(root));
4958 self.compile_expr(value)?;
4959 self.emit_op(vm_op, line, Some(root));
4960 self.emit_op(Op::Swap, line, Some(root));
4961 self.emit_op(Op::Rot, line, Some(root));
4962 self.emit_op(Op::Swap, line, Some(root));
4963 self.emit_op(Op::SetArrowHash, line, Some(root));
4964 }
4965 }
4966 } else if let ExprKind::HashSlice { hash, keys } = &target.kind {
4967 if keys.is_empty() {
4968 if self.is_mysync_hash(hash) {
4969 return Err(CompileError::Unsupported(
4970 "mysync hash slice update".into(),
4971 ));
4972 }
4973 self.check_strict_hash_access(hash, line)?;
4974 self.check_hash_mutable(hash, line)?;
4975 self.compile_expr(value)?;
4976 self.emit_op(Op::Pop, line, Some(root));
4977 let idx = self
4978 .chunk
4979 .add_constant(StrykeValue::string("assign to empty hash slice".into()));
4980 self.emit_op(Op::RuntimeErrorConst(idx), line, Some(root));
4981 self.emit_op(Op::LoadUndef, line, Some(root));
4982 return Ok(());
4983 }
4984 if self.is_mysync_hash(hash) {
4985 return Err(CompileError::Unsupported("mysync hash slice update".into()));
4986 }
4987 self.check_strict_hash_access(hash, line)?;
4988 self.check_hash_mutable(hash, line)?;
4989 let hash_idx = self.chunk.intern_name(hash);
4990 if hash_slice_needs_slice_ops(keys) {
4991 if matches!(op, BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd) {
4992 let k = keys.len() as u16;
4993 for hk in keys {
4994 self.compile_expr(hk)?;
4995 }
4996 self.emit_op(Op::NamedHashSlicePeekLast(hash_idx, k), line, Some(root));
4997 let j = match *op {
4998 BinOp::DefinedOr => {
4999 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
5000 }
5001 BinOp::LogOr => {
5002 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
5003 }
5004 BinOp::LogAnd => {
5005 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
5006 }
5007 _ => unreachable!(),
5008 };
5009 self.compile_expr(value)?;
5010 self.emit_op(Op::NamedArraySliceRollValUnderSpecs(k), line, Some(root));
5011 self.emit_op(
5012 Op::SetNamedHashSliceLastKeep(hash_idx, k),
5013 line,
5014 Some(root),
5015 );
5016 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
5017 self.chunk.patch_jump_here(j);
5018 self.emit_op(Op::NamedHashSliceDropKeysKeepCur(k), line, Some(root));
5019 self.chunk.patch_jump_here(j_end);
5020 return Ok(());
5021 }
5022 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
5023 CompileError::Unsupported(
5024 "CompoundAssign op on multi-key hash slice".into(),
5025 )
5026 })?;
5027 self.compile_expr(value)?;
5028 for hk in keys {
5029 self.compile_expr(hk)?;
5030 }
5031 self.emit_op(
5032 Op::NamedHashSliceCompound(op_byte, hash_idx, keys.len() as u16),
5033 line,
5034 Some(root),
5035 );
5036 return Ok(());
5037 }
5038 let hk = &keys[0];
5039 match op {
5040 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
5041 self.compile_expr(hk)?;
5042 self.emit_op(Op::Dup, line, Some(root));
5043 self.emit_op(Op::GetHashElem(hash_idx), line, Some(root));
5044 let j = match *op {
5045 BinOp::DefinedOr => {
5046 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
5047 }
5048 BinOp::LogOr => {
5049 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
5050 }
5051 BinOp::LogAnd => {
5052 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
5053 }
5054 _ => unreachable!(),
5055 };
5056 self.compile_expr(value)?;
5057 self.emit_op(Op::Swap, line, Some(root));
5058 self.emit_op(Op::SetHashElemKeep(hash_idx), line, Some(root));
5059 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
5060 self.chunk.patch_jump_here(j);
5061 self.emit_op(Op::Swap, line, Some(root));
5062 self.emit_op(Op::Pop, line, Some(root));
5063 self.chunk.patch_jump_here(j_end);
5064 }
5065 _ => {
5066 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
5067 CompileError::Unsupported("CompoundAssign op".into())
5068 })?;
5069 self.compile_expr(value)?;
5070 self.compile_expr(hk)?;
5071 self.emit_op(
5072 Op::NamedHashSliceCompound(op_byte, hash_idx, 1),
5073 line,
5074 Some(root),
5075 );
5076 }
5077 }
5078 } else if let ExprKind::ArraySlice { array, indices } = &target.kind {
5079 if indices.is_empty() {
5080 if self.is_mysync_array(array) {
5081 return Err(CompileError::Unsupported(
5082 "mysync array slice update".into(),
5083 ));
5084 }
5085 let q = self.qualify_stash_array_name(array);
5086 self.check_array_mutable(&q, line)?;
5087 let arr_idx = self.chunk.intern_name(&q);
5088 if matches!(op, BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd) {
5089 self.compile_expr(value)?;
5090 self.emit_op(Op::Pop, line, Some(root));
5091 let idx = self.chunk.add_constant(StrykeValue::string(
5092 "assign to empty array slice".into(),
5093 ));
5094 self.emit_op(Op::RuntimeErrorConst(idx), line, Some(root));
5095 self.emit_op(Op::LoadUndef, line, Some(root));
5096 return Ok(());
5097 }
5098 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
5099 CompileError::Unsupported(
5100 "CompoundAssign op on named array slice".into(),
5101 )
5102 })?;
5103 self.compile_expr(value)?;
5104 self.emit_op(
5105 Op::NamedArraySliceCompound(op_byte, arr_idx, 0),
5106 line,
5107 Some(root),
5108 );
5109 return Ok(());
5110 }
5111 if self.is_mysync_array(array) {
5112 return Err(CompileError::Unsupported(
5113 "mysync array slice update".into(),
5114 ));
5115 }
5116 let q = self.qualify_stash_array_name(array);
5117 self.check_array_mutable(&q, line)?;
5118 let arr_idx = self.chunk.intern_name(&q);
5119 if matches!(op, BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd) {
5120 let k = indices.len() as u16;
5121 for ix in indices {
5122 self.compile_array_slice_index_expr(ix)?;
5123 }
5124 self.emit_op(Op::NamedArraySlicePeekLast(arr_idx, k), line, Some(root));
5125 let j = match *op {
5126 BinOp::DefinedOr => {
5127 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
5128 }
5129 BinOp::LogOr => self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root)),
5130 BinOp::LogAnd => self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root)),
5131 _ => unreachable!(),
5132 };
5133 self.compile_expr(value)?;
5134 self.emit_op(Op::NamedArraySliceRollValUnderSpecs(k), line, Some(root));
5135 self.emit_op(Op::SetNamedArraySliceLastKeep(arr_idx, k), line, Some(root));
5136 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
5137 self.chunk.patch_jump_here(j);
5138 self.emit_op(Op::NamedArraySliceDropKeysKeepCur(k), line, Some(root));
5139 self.chunk.patch_jump_here(j_end);
5140 return Ok(());
5141 }
5142 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
5143 CompileError::Unsupported("CompoundAssign op on named array slice".into())
5144 })?;
5145 self.compile_expr(value)?;
5146 for ix in indices {
5147 self.compile_array_slice_index_expr(ix)?;
5148 }
5149 self.emit_op(
5150 Op::NamedArraySliceCompound(op_byte, arr_idx, indices.len() as u16),
5151 line,
5152 Some(root),
5153 );
5154 return Ok(());
5155 } else if let ExprKind::AnonymousListSlice { source, indices } = &target.kind {
5156 let ExprKind::Deref {
5157 expr: inner,
5158 kind: Sigil::Array,
5159 } = &source.kind
5160 else {
5161 return Err(CompileError::Unsupported(
5162 "CompoundAssign on AnonymousListSlice (non-array deref)".into(),
5163 ));
5164 };
5165 if indices.is_empty() {
5166 self.compile_arrow_array_base_expr(inner)?;
5167 self.emit_op(Op::Pop, line, Some(root));
5168 self.compile_expr(value)?;
5169 self.emit_op(Op::Pop, line, Some(root));
5170 let idx = self.chunk.add_constant(StrykeValue::string(
5171 "assign to empty array slice".into(),
5172 ));
5173 self.emit_op(Op::RuntimeErrorConst(idx), line, Some(root));
5174 self.emit_op(Op::LoadUndef, line, Some(root));
5175 return Ok(());
5176 }
5177 if indices.len() > 1 {
5178 if matches!(op, BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd) {
5179 let k = indices.len() as u16;
5180 self.compile_arrow_array_base_expr(inner)?;
5181 for ix in indices {
5182 self.compile_array_slice_index_expr(ix)?;
5183 }
5184 self.emit_op(Op::ArrowArraySlicePeekLast(k), line, Some(root));
5185 let j = match *op {
5186 BinOp::DefinedOr => {
5187 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
5188 }
5189 BinOp::LogOr => {
5190 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
5191 }
5192 BinOp::LogAnd => {
5193 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
5194 }
5195 _ => unreachable!(),
5196 };
5197 self.compile_expr(value)?;
5198 self.emit_op(Op::ArrowArraySliceRollValUnderSpecs(k), line, Some(root));
5199 self.emit_op(Op::SetArrowArraySliceLastKeep(k), line, Some(root));
5200 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
5201 self.chunk.patch_jump_here(j);
5202 self.emit_op(Op::ArrowArraySliceDropKeysKeepCur(k), line, Some(root));
5203 self.chunk.patch_jump_here(j_end);
5204 return Ok(());
5205 }
5206 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
5207 CompileError::Unsupported(
5208 "CompoundAssign op on multi-index array slice".into(),
5209 )
5210 })?;
5211 self.compile_expr(value)?;
5212 self.compile_arrow_array_base_expr(inner)?;
5213 for ix in indices {
5214 self.compile_array_slice_index_expr(ix)?;
5215 }
5216 self.emit_op(
5217 Op::ArrowArraySliceCompound(op_byte, indices.len() as u16),
5218 line,
5219 Some(root),
5220 );
5221 return Ok(());
5222 }
5223 let ix0 = &indices[0];
5224 match op {
5225 BinOp::DefinedOr | BinOp::LogOr | BinOp::LogAnd => {
5226 self.compile_arrow_array_base_expr(inner)?;
5227 self.compile_array_slice_index_expr(ix0)?;
5228 self.emit_op(Op::ArrowArraySlicePeekLast(1), line, Some(root));
5229 let j = match *op {
5230 BinOp::DefinedOr => {
5231 self.emit_op(Op::JumpIfDefinedKeep(0), line, Some(root))
5232 }
5233 BinOp::LogOr => {
5234 self.emit_op(Op::JumpIfTrueKeep(0), line, Some(root))
5235 }
5236 BinOp::LogAnd => {
5237 self.emit_op(Op::JumpIfFalseKeep(0), line, Some(root))
5238 }
5239 _ => unreachable!(),
5240 };
5241 self.compile_expr(value)?;
5242 self.emit_op(Op::ArrowArraySliceRollValUnderSpecs(1), line, Some(root));
5243 self.emit_op(Op::SetArrowArraySliceLastKeep(1), line, Some(root));
5244 let j_end = self.emit_op(Op::Jump(0), line, Some(root));
5245 self.chunk.patch_jump_here(j);
5246 self.emit_op(Op::ArrowArraySliceDropKeysKeepCur(1), line, Some(root));
5247 self.chunk.patch_jump_here(j_end);
5248 }
5249 _ => {
5250 let op_byte = scalar_compound_op_to_byte(*op).ok_or_else(|| {
5251 CompileError::Unsupported("CompoundAssign op".into())
5252 })?;
5253 self.compile_expr(value)?;
5254 self.compile_arrow_array_base_expr(inner)?;
5255 self.compile_array_slice_index_expr(ix0)?;
5256 self.emit_op(Op::ArrowArraySliceCompound(op_byte, 1), line, Some(root));
5257 }
5258 }
5259 } else {
5260 return Err(CompileError::Unsupported(
5261 "CompoundAssign on non-scalar".into(),
5262 ));
5263 }
5264 }
5265
5266 ExprKind::Ternary {
5267 condition,
5268 then_expr,
5269 else_expr,
5270 } => {
5271 self.compile_boolean_rvalue_condition(condition)?;
5272 let jump_else = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
5273 self.compile_expr_ctx(then_expr, ctx)?;
5274 let jump_end = self.emit_op(Op::Jump(0), line, Some(root));
5275 self.chunk.patch_jump_here(jump_else);
5276 self.compile_expr_ctx(else_expr, ctx)?;
5277 self.chunk.patch_jump_here(jump_end);
5278 }
5279
5280 ExprKind::Range {
5281 from,
5282 to,
5283 exclusive,
5284 step,
5285 } => {
5286 if ctx == WantarrayCtx::List {
5287 self.compile_expr_ctx(from, WantarrayCtx::Scalar)?;
5288 self.compile_expr_ctx(to, WantarrayCtx::Scalar)?;
5289 if let Some(s) = step {
5290 self.compile_expr_ctx(s, WantarrayCtx::Scalar)?;
5291 self.emit_op(Op::RangeStep, line, Some(root));
5292 } else {
5293 self.emit_op(Op::Range, line, Some(root));
5294 }
5295 } else if let (ExprKind::Regex(lp, lf), ExprKind::Regex(rp, rf)) =
5296 (&from.kind, &to.kind)
5297 {
5298 let slot = self.chunk.alloc_flip_flop_slot();
5299 let lp_idx = self.chunk.add_constant(StrykeValue::string(lp.clone()));
5300 let lf_idx = self.chunk.add_constant(StrykeValue::string(lf.clone()));
5301 let rp_idx = self.chunk.add_constant(StrykeValue::string(rp.clone()));
5302 let rf_idx = self.chunk.add_constant(StrykeValue::string(rf.clone()));
5303 self.emit_op(
5304 Op::RegexFlipFlop(
5305 slot,
5306 u8::from(*exclusive),
5307 lp_idx,
5308 lf_idx,
5309 rp_idx,
5310 rf_idx,
5311 ),
5312 line,
5313 Some(root),
5314 );
5315 } else if let (ExprKind::Regex(lp, lf), ExprKind::Eof(None)) =
5316 (&from.kind, &to.kind)
5317 {
5318 let slot = self.chunk.alloc_flip_flop_slot();
5319 let lp_idx = self.chunk.add_constant(StrykeValue::string(lp.clone()));
5320 let lf_idx = self.chunk.add_constant(StrykeValue::string(lf.clone()));
5321 self.emit_op(
5322 Op::RegexEofFlipFlop(slot, u8::from(*exclusive), lp_idx, lf_idx),
5323 line,
5324 Some(root),
5325 );
5326 } else if matches!(
5327 (&from.kind, &to.kind),
5328 (ExprKind::Regex(_, _), ExprKind::Eof(Some(_)))
5329 ) {
5330 return Err(CompileError::Unsupported(
5331 "regex flip-flop with eof(HANDLE) is not supported".into(),
5332 ));
5333 } else if let ExprKind::Regex(lp, lf) = &from.kind {
5334 let slot = self.chunk.alloc_flip_flop_slot();
5335 let lp_idx = self.chunk.add_constant(StrykeValue::string(lp.clone()));
5336 let lf_idx = self.chunk.add_constant(StrykeValue::string(lf.clone()));
5337 if matches!(to.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
5338 let line_target = match &to.kind {
5339 ExprKind::Integer(n) => *n,
5340 ExprKind::Float(f) => *f as i64,
5341 _ => unreachable!(),
5342 };
5343 let line_cidx = self.chunk.add_constant(StrykeValue::integer(line_target));
5344 self.emit_op(
5345 Op::RegexFlipFlopDotLineRhs(
5346 slot,
5347 u8::from(*exclusive),
5348 lp_idx,
5349 lf_idx,
5350 line_cidx,
5351 ),
5352 line,
5353 Some(root),
5354 );
5355 } else {
5356 let rhs_idx = self
5357 .chunk
5358 .add_regex_flip_flop_rhs_expr_entry((**to).clone());
5359 self.emit_op(
5360 Op::RegexFlipFlopExprRhs(
5361 slot,
5362 u8::from(*exclusive),
5363 lp_idx,
5364 lf_idx,
5365 rhs_idx,
5366 ),
5367 line,
5368 Some(root),
5369 );
5370 }
5371 } else {
5372 self.compile_expr(from)?;
5373 self.compile_expr(to)?;
5374 let slot = self.chunk.alloc_flip_flop_slot();
5375 self.emit_op(
5376 Op::ScalarFlipFlop(slot, u8::from(*exclusive)),
5377 line,
5378 Some(root),
5379 );
5380 }
5381 }
5382
5383 ExprKind::SliceRange { .. } => {
5384 return Err(CompileError::Unsupported(
5389 "open-ended slice range (`:N`/`N:`/`::-1`) is only valid inside `@arr[...]` or `@h{...}` subscripts"
5390 .into(),
5391 ));
5392 }
5393
5394 ExprKind::Repeat {
5395 expr,
5396 count,
5397 list_repeat,
5398 } => {
5399 if *list_repeat {
5400 self.compile_expr_ctx(expr, WantarrayCtx::List)?;
5403 self.compile_expr(count)?;
5404 self.emit_op(Op::ListRepeat, line, Some(root));
5405 } else {
5406 self.compile_expr(expr)?;
5407 self.compile_expr(count)?;
5408 self.emit_op(Op::StringRepeat, line, Some(root));
5409 }
5410 }
5411
5412 ExprKind::FuncCall { name, args } => {
5414 let dispatch_name: &str = name.strip_prefix("CORE::").unwrap_or(name.as_str());
5417 match dispatch_name {
5418 "read" => {
5420 if args.len() < 3 {
5421 return Err(CompileError::Unsupported(
5422 "read() needs at least 3 args".into(),
5423 ));
5424 }
5425 let buf_name =
5427 match &args[1].kind {
5428 ExprKind::ScalarVar(n) => n.clone(),
5429 _ => return Err(CompileError::Unsupported(
5430 "read() buffer must be a simple scalar variable for bytecode"
5431 .into(),
5432 )),
5433 };
5434 let buf_idx = self.chunk.intern_name(&buf_name);
5435 self.compile_expr(&args[0])?; self.compile_expr(&args[2])?; self.emit_op(Op::ReadIntoVar(buf_idx), line, Some(root));
5439 }
5440 "defer__internal" => {
5442 if args.len() != 1 {
5443 return Err(CompileError::Unsupported(
5444 "defer__internal expects exactly one argument".into(),
5445 ));
5446 }
5447 self.compile_expr(&args[0])?;
5454 if let ExprKind::CodeRef { .. } = &args[0].kind {
5455 if let Some(max_idx) = self.sub_body_block_indices.iter().copied().max()
5458 {
5459 self.sub_body_block_indices.remove(&max_idx);
5460 }
5461 }
5462 self.emit_op(Op::DeferBlock, line, Some(root));
5463 }
5464 "deque" => {
5465 if !args.is_empty() {
5466 return Err(CompileError::Unsupported(
5467 "deque() takes no arguments".into(),
5468 ));
5469 }
5470 self.emit_op(
5471 Op::CallBuiltin(BuiltinId::DequeNew as u16, 0),
5472 line,
5473 Some(root),
5474 );
5475 }
5476 "inc" => {
5477 let arg = args.first().cloned().unwrap_or_else(|| Expr {
5478 kind: ExprKind::ScalarVar("_".into()),
5479 line,
5480 });
5481 self.compile_expr(&arg)?;
5482 self.emit_op(Op::Inc, line, Some(root));
5483 }
5484 "dec" => {
5485 let arg = args.first().cloned().unwrap_or_else(|| Expr {
5486 kind: ExprKind::ScalarVar("_".into()),
5487 line,
5488 });
5489 self.compile_expr(&arg)?;
5490 self.emit_op(Op::Dec, line, Some(root));
5491 }
5492 "heap" => {
5493 if args.len() != 1 {
5494 return Err(CompileError::Unsupported(
5495 "heap() expects one comparator sub".into(),
5496 ));
5497 }
5498 self.compile_expr(&args[0])?;
5499 self.emit_op(
5500 Op::CallBuiltin(BuiltinId::HeapNew as u16, 1),
5501 line,
5502 Some(root),
5503 );
5504 }
5505 "pipeline" => {
5506 for arg in args {
5507 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5508 }
5509 self.emit_op(
5510 Op::CallBuiltin(BuiltinId::Pipeline as u16, args.len() as u8),
5511 line,
5512 Some(root),
5513 );
5514 }
5515 "par_pipeline" => {
5516 for arg in args {
5517 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5518 }
5519 self.emit_op(
5520 Op::CallBuiltin(BuiltinId::ParPipeline as u16, args.len() as u8),
5521 line,
5522 Some(root),
5523 );
5524 }
5525 "par_pipeline_stream" => {
5526 for arg in args {
5527 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5528 }
5529 self.emit_op(
5530 Op::CallBuiltin(BuiltinId::ParPipelineStream as u16, args.len() as u8),
5531 line,
5532 Some(root),
5533 );
5534 }
5535 "collect" => {
5541 for arg in args {
5542 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5543 }
5544 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5545 self.emit_op(
5546 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5547 line,
5548 Some(root),
5549 );
5550 }
5551 "ppool" => {
5552 if args.len() != 1 {
5553 return Err(CompileError::Unsupported(
5554 "ppool() expects one argument (worker count)".into(),
5555 ));
5556 }
5557 self.compile_expr(&args[0])?;
5558 self.emit_op(
5559 Op::CallBuiltin(BuiltinId::Ppool as u16, 1),
5560 line,
5561 Some(root),
5562 );
5563 }
5564 "barrier" => {
5565 if args.len() != 1 {
5566 return Err(CompileError::Unsupported(
5567 "barrier() expects one argument (party count)".into(),
5568 ));
5569 }
5570 self.compile_expr(&args[0])?;
5571 self.emit_op(
5572 Op::CallBuiltin(BuiltinId::BarrierNew as u16, 1),
5573 line,
5574 Some(root),
5575 );
5576 }
5577 "cluster" => {
5578 if args.is_empty() {
5584 return Err(CompileError::Unsupported(
5585 "cluster() expects at least one host/slot specifier".into(),
5586 ));
5587 }
5588 for arg in args {
5589 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5590 }
5591 self.emit_op(
5592 Op::CallBuiltin(BuiltinId::ClusterNew as u16, args.len() as u8),
5593 line,
5594 Some(root),
5595 );
5596 }
5597 "pselect" => {
5598 if args.is_empty() {
5599 return Err(CompileError::Unsupported(
5600 "pselect() expects at least one pchannel receiver".into(),
5601 ));
5602 }
5603 for arg in args {
5604 self.compile_expr(arg)?;
5605 }
5606 self.emit_op(
5607 Op::CallBuiltin(BuiltinId::Pselect as u16, args.len() as u8),
5608 line,
5609 Some(root),
5610 );
5611 }
5612 "ssh" => {
5613 for arg in args {
5614 self.compile_expr(arg)?;
5615 }
5616 self.emit_op(
5617 Op::CallBuiltin(BuiltinId::Ssh as u16, args.len() as u8),
5618 line,
5619 Some(root),
5620 );
5621 }
5622 "rmdir" => {
5623 for arg in args {
5624 self.compile_expr(arg)?;
5625 }
5626 self.emit_op(
5627 Op::CallBuiltin(BuiltinId::Rmdir as u16, args.len() as u8),
5628 line,
5629 Some(root),
5630 );
5631 }
5632 "utime" => {
5633 for arg in args {
5634 self.compile_expr(arg)?;
5635 }
5636 self.emit_op(
5637 Op::CallBuiltin(BuiltinId::Utime as u16, args.len() as u8),
5638 line,
5639 Some(root),
5640 );
5641 }
5642 "umask" => {
5643 for arg in args {
5644 self.compile_expr(arg)?;
5645 }
5646 self.emit_op(
5647 Op::CallBuiltin(BuiltinId::Umask as u16, args.len() as u8),
5648 line,
5649 Some(root),
5650 );
5651 }
5652 "getcwd" => {
5653 for arg in args {
5654 self.compile_expr(arg)?;
5655 }
5656 self.emit_op(
5657 Op::CallBuiltin(BuiltinId::Getcwd as u16, args.len() as u8),
5658 line,
5659 Some(root),
5660 );
5661 }
5662 "pipe" => {
5663 if args.len() != 2 {
5664 return Err(CompileError::Unsupported(
5665 "pipe requires exactly two arguments".into(),
5666 ));
5667 }
5668 for arg in args {
5669 self.compile_expr(arg)?;
5670 }
5671 self.emit_op(Op::CallBuiltin(BuiltinId::Pipe as u16, 2), line, Some(root));
5672 }
5673 "uniq" | "distinct" | "flatten" | "set" | "with_index" | "list_count"
5674 | "list_size" | "count" | "size" | "cnt" | "len" | "sum" | "sum0"
5675 | "product" | "min" | "max" | "mean" | "median" | "mode" | "stddev"
5676 | "variance" => {
5677 if matches!(
5682 name.as_str(),
5683 "count" | "cnt" | "size" | "len" | "list_count" | "list_size"
5684 ) && args.len() == 1
5685 {
5686 match &args[0].kind {
5687 ExprKind::ArrayVar(arr_name) => {
5688 self.check_strict_array_access(arr_name, line)?;
5689 let idx = self
5690 .chunk
5691 .intern_name(&self.qualify_stash_array_name(arr_name));
5692 self.emit_op(Op::ArrayLen(idx), line, Some(root));
5693 return Ok(());
5694 }
5695 ExprKind::Deref {
5696 expr,
5697 kind: Sigil::Array,
5698 } => {
5699 self.compile_expr(expr)?;
5700 self.emit_op(Op::ArrayDerefLen, line, Some(root));
5701 return Ok(());
5702 }
5703 _ => {}
5704 }
5705 }
5706 for arg in args {
5707 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5708 }
5709 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5710 self.emit_op(
5711 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5712 line,
5713 Some(root),
5714 );
5715 }
5716 "shuffle" => {
5717 for arg in args {
5718 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5719 }
5720 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5721 self.emit_op(
5722 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5723 line,
5724 Some(root),
5725 );
5726 }
5727 "chunked" | "windowed" => {
5728 match args.len() {
5729 0 => {
5730 return Err(CompileError::Unsupported(
5731 "chunked/windowed need (LIST, N) or unary N (e.g. `|> chunked(2)`)"
5732 .into(),
5733 ));
5734 }
5735 1 => {
5736 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
5738 }
5739 2 => {
5740 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
5741 self.compile_expr(&args[1])?;
5742 }
5743 _ => {
5744 return Err(CompileError::Unsupported(
5745 "chunked/windowed expect exactly two arguments (LIST, N); use a single list expression for the first operand".into(),
5746 ));
5747 }
5748 }
5749 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5750 self.emit_op(
5751 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5752 line,
5753 Some(root),
5754 );
5755 }
5756 "take" | "head" | "tail" | "drop" => {
5757 if args.is_empty() {
5758 return Err(CompileError::Unsupported(
5759 "take/head/tail/drop expect LIST..., N or unary N".into(),
5760 ));
5761 }
5762 if args.len() == 1 {
5763 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
5765 } else {
5766 for a in &args[..args.len() - 1] {
5767 self.compile_expr_ctx(a, WantarrayCtx::List)?;
5768 }
5769 self.compile_expr(&args[args.len() - 1])?;
5770 }
5771 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5772 self.emit_op(
5773 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5774 line,
5775 Some(root),
5776 );
5777 }
5778 "any" | "all" | "none" | "first" | "take_while" | "drop_while" | "tap"
5779 | "peek" => {
5780 if args.is_empty() {
5789 return Err(CompileError::Unsupported(
5790 "any/all/none/first/take_while/drop_while/tap/peek expect BLOCK, LIST"
5791 .into(),
5792 ));
5793 }
5794 self.compile_expr(&args[0])?;
5795 for arg in &args[1..] {
5796 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5797 }
5798 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5799 self.emit_op(
5800 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5801 line,
5802 Some(root),
5803 );
5804 }
5805 "group_by" | "chunk_by" => {
5806 if args.len() != 2 {
5807 return Err(CompileError::Unsupported(
5808 "group_by/chunk_by expect { BLOCK } or EXPR, LIST".into(),
5809 ));
5810 }
5811 self.compile_expr_ctx(&args[1], WantarrayCtx::List)?;
5812 match &args[0].kind {
5813 ExprKind::CodeRef { body, .. } => {
5814 let block_idx = self.add_deferred_block(body.clone());
5815 self.emit_op(Op::ChunkByWithBlock(block_idx), line, Some(root));
5816 }
5817 _ => {
5818 let idx = self.chunk.add_map_expr_entry(args[0].clone());
5819 self.emit_op(Op::ChunkByWithExpr(idx), line, Some(root));
5820 }
5821 }
5822 if ctx != WantarrayCtx::List {
5823 self.emit_op(Op::StackArrayLen, line, Some(root));
5824 }
5825 }
5826 "zip" | "zip_longest" => {
5827 for arg in args {
5828 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5829 }
5830 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(dispatch_name));
5833 self.emit_op(
5834 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5835 line,
5836 Some(root),
5837 );
5838 }
5839 "puniq" => {
5840 if args.is_empty() || args.len() > 2 {
5841 return Err(CompileError::Unsupported(
5842 "puniq expects LIST [, progress => EXPR]".into(),
5843 ));
5844 }
5845 if args.len() == 2 {
5846 self.compile_expr(&args[1])?;
5847 } else {
5848 self.emit_op(Op::LoadInt(0), line, Some(root));
5849 }
5850 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
5851 self.emit_op(Op::Puniq, line, Some(root));
5852 if ctx != WantarrayCtx::List {
5853 self.emit_op(Op::StackArrayLen, line, Some(root));
5854 }
5855 }
5856 "pfirst" | "pany" => {
5857 if args.len() < 2 || args.len() > 3 {
5858 return Err(CompileError::Unsupported(
5859 "pfirst/pany expect BLOCK, LIST [, progress => EXPR]".into(),
5860 ));
5861 }
5862 let body = match &args[0].kind {
5863 ExprKind::CodeRef { body, .. } => body,
5864 _ => {
5865 return Err(CompileError::Unsupported(
5866 "pfirst/pany: first argument must be a { BLOCK }".into(),
5867 ));
5868 }
5869 };
5870 if args.len() == 3 {
5871 self.compile_expr(&args[2])?;
5872 } else {
5873 self.emit_op(Op::LoadInt(0), line, Some(root));
5874 }
5875 self.compile_expr_ctx(&args[1], WantarrayCtx::List)?;
5876 let block_idx = self.add_deferred_block(body.clone());
5877 let op = if name == "pfirst" {
5878 Op::PFirstWithBlock(block_idx)
5879 } else {
5880 Op::PAnyWithBlock(block_idx)
5881 };
5882 self.emit_op(op, line, Some(root));
5883 }
5884 _ => {
5885 for arg in args {
5889 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5890 }
5891 let q = self.qualify_sub_key(name);
5892 let name_idx = self.chunk.intern_name(&q);
5893 self.emit_op(
5894 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5895 line,
5896 Some(root),
5897 );
5898 }
5899 }
5900 }
5901
5902 ExprKind::MethodCall {
5904 object,
5905 method,
5906 args,
5907 super_call,
5908 } => {
5909 self.compile_expr(object)?;
5910 for arg in args {
5911 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5912 }
5913 let name_idx = self.chunk.intern_name(method);
5914 if *super_call {
5915 self.emit_op(
5916 Op::MethodCallSuper(name_idx, args.len() as u8, ctx.as_byte()),
5917 line,
5918 Some(root),
5919 );
5920 } else {
5921 self.emit_op(
5922 Op::MethodCall(name_idx, args.len() as u8, ctx.as_byte()),
5923 line,
5924 Some(root),
5925 );
5926 }
5927 }
5928 ExprKind::IndirectCall {
5929 target,
5930 args,
5931 ampersand: _,
5932 pass_caller_arglist,
5933 } => {
5934 self.compile_expr(target)?;
5935 if !pass_caller_arglist {
5936 for a in args {
5937 self.compile_expr_ctx(a, WantarrayCtx::List)?;
5938 }
5939 }
5940 let argc = if *pass_caller_arglist {
5941 0
5942 } else {
5943 args.len() as u8
5944 };
5945 self.emit_op(
5946 Op::IndirectCall(
5947 argc,
5948 ctx.as_byte(),
5949 if *pass_caller_arglist { 1 } else { 0 },
5950 ),
5951 line,
5952 Some(root),
5953 );
5954 }
5955
5956 ExprKind::Print { handle, args } => {
5958 for arg in args {
5959 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5960 }
5961 let h = handle.as_ref().map(|s| self.chunk.intern_name(s));
5962 self.emit_op(Op::Print(h, args.len() as u8), line, Some(root));
5963 }
5964 ExprKind::Say { handle, args } => {
5965 for arg in args {
5966 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5967 }
5968 let h = handle.as_ref().map(|s| self.chunk.intern_name(s));
5969 self.emit_op(Op::Say(h, args.len() as u8), line, Some(root));
5970 }
5971 ExprKind::Printf { args, .. } => {
5972 for arg in args {
5975 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5976 }
5977 self.emit_op(
5978 Op::CallBuiltin(BuiltinId::Printf as u16, args.len() as u8),
5979 line,
5980 Some(root),
5981 );
5982 }
5983
5984 ExprKind::Die(args) => {
5986 for arg in args {
5989 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5990 }
5991 self.emit_op(
5992 Op::CallBuiltin(BuiltinId::Die as u16, args.len() as u8),
5993 line,
5994 Some(root),
5995 );
5996 }
5997 ExprKind::Warn(args) => {
5998 for arg in args {
5999 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
6000 }
6001 self.emit_op(
6002 Op::CallBuiltin(BuiltinId::Warn as u16, args.len() as u8),
6003 line,
6004 Some(root),
6005 );
6006 }
6007 ExprKind::Exit(code) => {
6008 if let Some(c) = code {
6009 self.compile_expr(c)?;
6010 self.emit_op(Op::CallBuiltin(BuiltinId::Exit as u16, 1), line, Some(root));
6011 } else {
6012 self.emit_op(Op::LoadInt(0), line, Some(root));
6013 self.emit_op(Op::CallBuiltin(BuiltinId::Exit as u16, 1), line, Some(root));
6014 }
6015 }
6016
6017 ExprKind::Push { array, values } => {
6019 if let ExprKind::ArrayVar(name) = &array.kind {
6020 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
6021 for v in values {
6022 self.compile_expr_ctx(v, WantarrayCtx::List)?;
6023 self.emit_op(Op::PushArray(idx), line, Some(root));
6024 }
6025 self.emit_op(Op::ArrayLen(idx), line, Some(root));
6026 } else if let ExprKind::Deref {
6027 expr: aref_expr,
6028 kind: Sigil::Array,
6029 } = &array.kind
6030 {
6031 let needs_autoviv = matches!(
6036 &aref_expr.kind,
6037 ExprKind::ScalarVar(_)
6038 | ExprKind::HashElement { .. }
6039 | ExprKind::ArrayElement { .. }
6040 );
6041 if needs_autoviv {
6042 let pool = self
6043 .chunk
6044 .add_push_expr_entry(array.as_ref().clone(), values.clone());
6045 self.emit_op(Op::PushExpr(pool), line, Some(root));
6046 } else {
6047 self.compile_expr(aref_expr)?;
6048 for v in values {
6049 self.emit_op(Op::Dup, line, Some(root));
6050 self.compile_expr_ctx(v, WantarrayCtx::List)?;
6051 self.emit_op(Op::PushArrayDeref, line, Some(root));
6052 }
6053 self.emit_op(Op::ArrayDerefLen, line, Some(root));
6054 }
6055 } else {
6056 let pool = self
6057 .chunk
6058 .add_push_expr_entry(array.as_ref().clone(), values.clone());
6059 self.emit_op(Op::PushExpr(pool), line, Some(root));
6060 }
6061 }
6062 ExprKind::Pop(array) => {
6063 if let ExprKind::ArrayVar(name) = &array.kind {
6064 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
6065 self.emit_op(Op::PopArray(idx), line, Some(root));
6066 } else if let ExprKind::Deref {
6067 expr: aref_expr,
6068 kind: Sigil::Array,
6069 } = &array.kind
6070 {
6071 let needs_autoviv = matches!(
6072 &aref_expr.kind,
6073 ExprKind::ScalarVar(_)
6074 | ExprKind::HashElement { .. }
6075 | ExprKind::ArrayElement { .. }
6076 );
6077 if needs_autoviv {
6078 let pool = self.chunk.add_pop_expr_entry(array.as_ref().clone());
6079 self.emit_op(Op::PopExpr(pool), line, Some(root));
6080 } else {
6081 self.compile_expr(aref_expr)?;
6082 self.emit_op(Op::PopArrayDeref, line, Some(root));
6083 }
6084 } else {
6085 let pool = self.chunk.add_pop_expr_entry(array.as_ref().clone());
6086 self.emit_op(Op::PopExpr(pool), line, Some(root));
6087 }
6088 }
6089 ExprKind::Shift(array) => {
6090 if let ExprKind::ArrayVar(name) = &array.kind {
6091 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
6092 self.emit_op(Op::ShiftArray(idx), line, Some(root));
6093 } else if let ExprKind::Deref {
6094 expr: aref_expr,
6095 kind: Sigil::Array,
6096 } = &array.kind
6097 {
6098 let needs_autoviv = matches!(
6099 &aref_expr.kind,
6100 ExprKind::ScalarVar(_)
6101 | ExprKind::HashElement { .. }
6102 | ExprKind::ArrayElement { .. }
6103 );
6104 if needs_autoviv {
6105 let pool = self.chunk.add_shift_expr_entry(array.as_ref().clone());
6106 self.emit_op(Op::ShiftExpr(pool), line, Some(root));
6107 } else {
6108 self.compile_expr(aref_expr)?;
6109 self.emit_op(Op::ShiftArrayDeref, line, Some(root));
6110 }
6111 } else {
6112 let pool = self.chunk.add_shift_expr_entry(array.as_ref().clone());
6113 self.emit_op(Op::ShiftExpr(pool), line, Some(root));
6114 }
6115 }
6116 ExprKind::Unshift { array, values } => {
6117 if let ExprKind::ArrayVar(name) = &array.kind {
6118 let q = self.qualify_stash_array_name(name);
6119 let name_const = self.chunk.add_constant(StrykeValue::string(q));
6120 self.emit_op(Op::LoadConst(name_const), line, Some(root));
6121 for v in values {
6122 self.compile_expr_ctx(v, WantarrayCtx::List)?;
6123 }
6124 let nargs = (1 + values.len()) as u8;
6125 self.emit_op(
6126 Op::CallBuiltin(BuiltinId::Unshift as u16, nargs),
6127 line,
6128 Some(root),
6129 );
6130 } else if let ExprKind::Deref {
6131 expr: aref_expr,
6132 kind: Sigil::Array,
6133 } = &array.kind
6134 {
6135 let needs_autoviv = matches!(
6136 &aref_expr.kind,
6137 ExprKind::ScalarVar(_)
6138 | ExprKind::HashElement { .. }
6139 | ExprKind::ArrayElement { .. }
6140 );
6141 if needs_autoviv || values.len() > u8::MAX as usize {
6142 let pool = self
6143 .chunk
6144 .add_unshift_expr_entry(array.as_ref().clone(), values.clone());
6145 self.emit_op(Op::UnshiftExpr(pool), line, Some(root));
6146 } else {
6147 self.compile_expr(aref_expr)?;
6148 for v in values {
6149 self.compile_expr_ctx(v, WantarrayCtx::List)?;
6150 }
6151 self.emit_op(Op::UnshiftArrayDeref(values.len() as u8), line, Some(root));
6152 }
6153 } else {
6154 let pool = self
6155 .chunk
6156 .add_unshift_expr_entry(array.as_ref().clone(), values.clone());
6157 self.emit_op(Op::UnshiftExpr(pool), line, Some(root));
6158 }
6159 }
6160 ExprKind::Splice {
6161 array,
6162 offset,
6163 length,
6164 replacement,
6165 } => {
6166 self.emit_op(Op::WantarrayPush(ctx.as_byte()), line, Some(root));
6167 if let ExprKind::ArrayVar(name) = &array.kind {
6168 let q = self.qualify_stash_array_name(name);
6169 let name_const = self.chunk.add_constant(StrykeValue::string(q));
6170 self.emit_op(Op::LoadConst(name_const), line, Some(root));
6171 if let Some(o) = offset {
6172 self.compile_expr(o)?;
6173 } else {
6174 self.emit_op(Op::LoadInt(0), line, Some(root));
6175 }
6176 if let Some(l) = length {
6177 self.compile_expr(l)?;
6178 } else {
6179 self.emit_op(Op::LoadUndef, line, Some(root));
6180 }
6181 for r in replacement {
6182 self.compile_expr(r)?;
6183 }
6184 let nargs = (3 + replacement.len()) as u8;
6185 self.emit_op(
6186 Op::CallBuiltin(BuiltinId::Splice as u16, nargs),
6187 line,
6188 Some(root),
6189 );
6190 } else if let ExprKind::Deref {
6191 expr: aref_expr,
6192 kind: Sigil::Array,
6193 } = &array.kind
6194 {
6195 if replacement.len() > u8::MAX as usize {
6196 let pool = self.chunk.add_splice_expr_entry(
6197 array.as_ref().clone(),
6198 offset.as_deref().cloned(),
6199 length.as_deref().cloned(),
6200 replacement.clone(),
6201 );
6202 self.emit_op(Op::SpliceExpr(pool), line, Some(root));
6203 } else {
6204 self.compile_expr(aref_expr)?;
6205 if let Some(o) = offset {
6206 self.compile_expr(o)?;
6207 } else {
6208 self.emit_op(Op::LoadInt(0), line, Some(root));
6209 }
6210 if let Some(l) = length {
6211 self.compile_expr(l)?;
6212 } else {
6213 self.emit_op(Op::LoadUndef, line, Some(root));
6214 }
6215 for r in replacement {
6216 self.compile_expr(r)?;
6217 }
6218 self.emit_op(
6219 Op::SpliceArrayDeref(replacement.len() as u8),
6220 line,
6221 Some(root),
6222 );
6223 }
6224 } else {
6225 let pool = self.chunk.add_splice_expr_entry(
6226 array.as_ref().clone(),
6227 offset.as_deref().cloned(),
6228 length.as_deref().cloned(),
6229 replacement.clone(),
6230 );
6231 self.emit_op(Op::SpliceExpr(pool), line, Some(root));
6232 }
6233 self.emit_op(Op::WantarrayPop, line, Some(root));
6234 }
6235 ExprKind::ScalarContext(inner) => {
6236 self.compile_expr_ctx(inner, WantarrayCtx::Scalar)?;
6239 self.emit_op(Op::ValueScalarContext, line, Some(root));
6242 }
6243
6244 ExprKind::Delete(inner) => {
6246 if let ExprKind::HashElement { hash, key } = &inner.kind {
6247 self.check_hash_mutable(hash, line)?;
6248 let idx = self.chunk.intern_name(hash);
6249 self.compile_expr(key)?;
6250 self.emit_op(Op::DeleteHashElem(idx), line, Some(root));
6251 } else if let ExprKind::ArrayElement { array, index } = &inner.kind {
6252 self.check_strict_array_access(array, line)?;
6253 let q = self.qualify_stash_array_name(array);
6254 self.check_array_mutable(&q, line)?;
6255 let arr_idx = self.chunk.intern_name(&q);
6256 self.compile_expr(index)?;
6257 self.emit_op(Op::DeleteArrayElem(arr_idx), line, Some(root));
6258 } else if let ExprKind::ArrowDeref {
6259 expr: container,
6260 index,
6261 kind: DerefKind::Hash,
6262 } = &inner.kind
6263 {
6264 self.compile_arrow_hash_base_expr(container)?;
6265 self.compile_expr(index)?;
6266 self.emit_op(Op::DeleteArrowHashElem, line, Some(root));
6267 } else if let ExprKind::ArrowDeref {
6268 expr: container,
6269 index,
6270 kind: DerefKind::Array,
6271 } = &inner.kind
6272 {
6273 if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
6274 self.compile_expr(container)?;
6275 self.compile_expr(index)?;
6276 self.emit_op(Op::DeleteArrowArrayElem, line, Some(root));
6277 } else {
6278 let pool = self.chunk.add_delete_expr_entry(inner.as_ref().clone());
6279 self.emit_op(Op::DeleteExpr(pool), line, Some(root));
6280 }
6281 } else {
6282 let pool = self.chunk.add_delete_expr_entry(inner.as_ref().clone());
6283 self.emit_op(Op::DeleteExpr(pool), line, Some(root));
6284 }
6285 }
6286 ExprKind::Exists(inner) => {
6287 if let ExprKind::HashElement { hash, key } = &inner.kind {
6288 let idx = self.chunk.intern_name(hash);
6289 self.compile_expr(key)?;
6290 self.emit_op(Op::ExistsHashElem(idx), line, Some(root));
6291 } else if let ExprKind::ArrayElement { array, index } = &inner.kind {
6292 self.check_strict_array_access(array, line)?;
6293 let arr_idx = self
6294 .chunk
6295 .intern_name(&self.qualify_stash_array_name(array));
6296 self.compile_expr(index)?;
6297 self.emit_op(Op::ExistsArrayElem(arr_idx), line, Some(root));
6298 } else if let ExprKind::ArrowDeref {
6299 expr: container,
6300 index,
6301 kind: DerefKind::Hash,
6302 } = &inner.kind
6303 {
6304 if matches!(container.kind, ExprKind::ArrowDeref { .. }) {
6309 let pool = self.chunk.add_exists_expr_entry(inner.as_ref().clone());
6310 self.emit_op(Op::ExistsExpr(pool), line, Some(root));
6311 } else {
6312 self.compile_arrow_hash_base_expr(container)?;
6313 self.compile_expr(index)?;
6314 self.emit_op(Op::ExistsArrowHashElem, line, Some(root));
6315 }
6316 } else if let ExprKind::ArrowDeref {
6317 expr: container,
6318 index,
6319 kind: DerefKind::Array,
6320 } = &inner.kind
6321 {
6322 if !arrow_deref_arrow_subscript_is_plain_scalar_index(index)
6323 || matches!(container.kind, ExprKind::ArrowDeref { .. })
6324 {
6325 let pool = self.chunk.add_exists_expr_entry(inner.as_ref().clone());
6326 self.emit_op(Op::ExistsExpr(pool), line, Some(root));
6327 } else {
6328 self.compile_expr(container)?;
6329 self.compile_expr(index)?;
6330 self.emit_op(Op::ExistsArrowArrayElem, line, Some(root));
6331 }
6332 } else {
6333 let pool = self.chunk.add_exists_expr_entry(inner.as_ref().clone());
6334 self.emit_op(Op::ExistsExpr(pool), line, Some(root));
6335 }
6336 }
6337 ExprKind::Keys(inner) => {
6338 if let ExprKind::HashVar(name) = &inner.kind {
6339 let idx = self.chunk.intern_name(name);
6340 if ctx == WantarrayCtx::List {
6341 self.emit_op(Op::HashKeys(idx), line, Some(root));
6342 } else {
6343 self.emit_op(Op::HashKeysScalar(idx), line, Some(root));
6344 }
6345 } else {
6346 self.compile_expr_ctx(inner, WantarrayCtx::List)?;
6347 if ctx == WantarrayCtx::List {
6348 self.emit_op(Op::KeysFromValue, line, Some(root));
6349 } else {
6350 self.emit_op(Op::KeysFromValueScalar, line, Some(root));
6351 }
6352 }
6353 }
6354 ExprKind::Values(inner) => {
6355 if let ExprKind::HashVar(name) = &inner.kind {
6356 let idx = self.chunk.intern_name(name);
6357 if ctx == WantarrayCtx::List {
6358 self.emit_op(Op::HashValues(idx), line, Some(root));
6359 } else {
6360 self.emit_op(Op::HashValuesScalar(idx), line, Some(root));
6361 }
6362 } else {
6363 self.compile_expr_ctx(inner, WantarrayCtx::List)?;
6364 if ctx == WantarrayCtx::List {
6365 self.emit_op(Op::ValuesFromValue, line, Some(root));
6366 } else {
6367 self.emit_op(Op::ValuesFromValueScalar, line, Some(root));
6368 }
6369 }
6370 }
6371 ExprKind::Each(e) => {
6372 self.compile_expr(e)?;
6373 self.emit_op(Op::CallBuiltin(BuiltinId::Each as u16, 1), line, Some(root));
6374 }
6375
6376 ExprKind::Length(e) => {
6378 self.compile_expr(e)?;
6379 self.emit_op(
6380 Op::CallBuiltin(BuiltinId::Length as u16, 1),
6381 line,
6382 Some(root),
6383 );
6384 }
6385 ExprKind::Chomp(e) => {
6386 self.compile_expr(e)?;
6387 let lv = self.chunk.add_lvalue_expr(e.as_ref().clone());
6388 self.emit_op(Op::ChompInPlace(lv), line, Some(root));
6389 }
6390 ExprKind::Chop(e) => {
6391 self.compile_expr(e)?;
6392 let lv = self.chunk.add_lvalue_expr(e.as_ref().clone());
6393 self.emit_op(Op::ChopInPlace(lv), line, Some(root));
6394 }
6395 ExprKind::Defined(e) => {
6396 self.compile_expr(e)?;
6397 self.emit_op(
6398 Op::CallBuiltin(BuiltinId::Defined as u16, 1),
6399 line,
6400 Some(root),
6401 );
6402 }
6403 ExprKind::Abs(e) => {
6404 self.compile_expr(e)?;
6405 self.emit_op(Op::CallBuiltin(BuiltinId::Abs as u16, 1), line, Some(root));
6406 }
6407 ExprKind::Int(e) => {
6408 self.compile_expr(e)?;
6409 self.emit_op(Op::CallBuiltin(BuiltinId::Int as u16, 1), line, Some(root));
6410 }
6411 ExprKind::Sqrt(e) => {
6412 self.compile_expr(e)?;
6413 self.emit_op(Op::CallBuiltin(BuiltinId::Sqrt as u16, 1), line, Some(root));
6414 }
6415 ExprKind::Sin(e) => {
6416 self.compile_expr(e)?;
6417 self.emit_op(Op::CallBuiltin(BuiltinId::Sin as u16, 1), line, Some(root));
6418 }
6419 ExprKind::Cos(e) => {
6420 self.compile_expr(e)?;
6421 self.emit_op(Op::CallBuiltin(BuiltinId::Cos as u16, 1), line, Some(root));
6422 }
6423 ExprKind::Atan2 { y, x } => {
6424 self.compile_expr(y)?;
6425 self.compile_expr(x)?;
6426 self.emit_op(
6427 Op::CallBuiltin(BuiltinId::Atan2 as u16, 2),
6428 line,
6429 Some(root),
6430 );
6431 }
6432 ExprKind::Exp(e) => {
6433 self.compile_expr(e)?;
6434 self.emit_op(Op::CallBuiltin(BuiltinId::Exp as u16, 1), line, Some(root));
6435 }
6436 ExprKind::Log(e) => {
6437 self.compile_expr(e)?;
6438 self.emit_op(Op::CallBuiltin(BuiltinId::Log as u16, 1), line, Some(root));
6439 }
6440 ExprKind::Rand(upper) => {
6441 if let Some(e) = upper {
6442 self.compile_expr(e)?;
6443 self.emit_op(Op::CallBuiltin(BuiltinId::Rand as u16, 1), line, Some(root));
6444 } else {
6445 self.emit_op(Op::CallBuiltin(BuiltinId::Rand as u16, 0), line, Some(root));
6446 }
6447 }
6448 ExprKind::Srand(seed) => {
6449 if let Some(e) = seed {
6450 self.compile_expr(e)?;
6451 self.emit_op(
6452 Op::CallBuiltin(BuiltinId::Srand as u16, 1),
6453 line,
6454 Some(root),
6455 );
6456 } else {
6457 self.emit_op(
6458 Op::CallBuiltin(BuiltinId::Srand as u16, 0),
6459 line,
6460 Some(root),
6461 );
6462 }
6463 }
6464 ExprKind::Chr(e) => {
6465 self.compile_expr(e)?;
6466 self.emit_op(Op::CallBuiltin(BuiltinId::Chr as u16, 1), line, Some(root));
6467 }
6468 ExprKind::Ord(e) => {
6469 self.compile_expr(e)?;
6470 self.emit_op(Op::CallBuiltin(BuiltinId::Ord as u16, 1), line, Some(root));
6471 }
6472 ExprKind::Hex(e) => {
6473 self.compile_expr(e)?;
6474 self.emit_op(Op::CallBuiltin(BuiltinId::Hex as u16, 1), line, Some(root));
6475 }
6476 ExprKind::Oct(e) => {
6477 self.compile_expr(e)?;
6478 self.emit_op(Op::CallBuiltin(BuiltinId::Oct as u16, 1), line, Some(root));
6479 }
6480 ExprKind::Uc(e) => {
6481 self.compile_expr(e)?;
6482 self.emit_op(Op::CallBuiltin(BuiltinId::Uc as u16, 1), line, Some(root));
6483 }
6484 ExprKind::Lc(e) => {
6485 self.compile_expr(e)?;
6486 self.emit_op(Op::CallBuiltin(BuiltinId::Lc as u16, 1), line, Some(root));
6487 }
6488 ExprKind::Ucfirst(e) => {
6489 self.compile_expr(e)?;
6490 self.emit_op(
6491 Op::CallBuiltin(BuiltinId::Ucfirst as u16, 1),
6492 line,
6493 Some(root),
6494 );
6495 }
6496 ExprKind::Lcfirst(e) => {
6497 self.compile_expr(e)?;
6498 self.emit_op(
6499 Op::CallBuiltin(BuiltinId::Lcfirst as u16, 1),
6500 line,
6501 Some(root),
6502 );
6503 }
6504 ExprKind::Fc(e) => {
6505 self.compile_expr(e)?;
6506 self.emit_op(Op::CallBuiltin(BuiltinId::Fc as u16, 1), line, Some(root));
6507 }
6508 ExprKind::Crypt { plaintext, salt } => {
6509 self.compile_expr(plaintext)?;
6510 self.compile_expr(salt)?;
6511 self.emit_op(
6512 Op::CallBuiltin(BuiltinId::Crypt as u16, 2),
6513 line,
6514 Some(root),
6515 );
6516 }
6517 ExprKind::Pos(e) => match e {
6518 None => {
6519 self.emit_op(Op::CallBuiltin(BuiltinId::Pos as u16, 0), line, Some(root));
6520 }
6521 Some(pos_arg) => {
6522 if let ExprKind::ScalarVar(name) = &pos_arg.kind {
6523 let stor = self.scalar_storage_name_for_ops(name);
6524 let idx = self.chunk.add_constant(StrykeValue::string(stor));
6525 self.emit_op(Op::LoadConst(idx), line, Some(root));
6526 } else {
6527 self.compile_expr(pos_arg)?;
6528 }
6529 self.emit_op(Op::CallBuiltin(BuiltinId::Pos as u16, 1), line, Some(root));
6530 }
6531 },
6532 ExprKind::Study(e) => {
6533 self.compile_expr(e)?;
6534 self.emit_op(
6535 Op::CallBuiltin(BuiltinId::Study as u16, 1),
6536 line,
6537 Some(root),
6538 );
6539 }
6540 ExprKind::Ref(e) => {
6541 self.compile_expr(e)?;
6542 self.emit_op(Op::CallBuiltin(BuiltinId::Ref as u16, 1), line, Some(root));
6543 }
6544 ExprKind::Rev(e) => {
6545 self.compile_expr_ctx(e, WantarrayCtx::List)?;
6547 if ctx == WantarrayCtx::List {
6548 self.emit_op(Op::RevListOp, line, Some(root));
6549 } else {
6550 self.emit_op(Op::RevScalarOp, line, Some(root));
6551 }
6552 }
6553 ExprKind::ReverseExpr(e) => {
6554 self.compile_expr_ctx(e, WantarrayCtx::List)?;
6555 if ctx == WantarrayCtx::List {
6556 self.emit_op(Op::ReverseListOp, line, Some(root));
6557 } else {
6558 self.emit_op(Op::ReverseScalarOp, line, Some(root));
6559 }
6560 }
6561 ExprKind::System(args) => {
6562 for a in args {
6563 self.compile_expr(a)?;
6564 }
6565 self.emit_op(
6566 Op::CallBuiltin(BuiltinId::System as u16, args.len() as u8),
6567 line,
6568 Some(root),
6569 );
6570 }
6571 ExprKind::Exec(args) => {
6572 for a in args {
6573 self.compile_expr(a)?;
6574 }
6575 self.emit_op(
6576 Op::CallBuiltin(BuiltinId::Exec as u16, args.len() as u8),
6577 line,
6578 Some(root),
6579 );
6580 }
6581
6582 ExprKind::Substr {
6584 string,
6585 offset,
6586 length,
6587 replacement,
6588 } => {
6589 if let Some(rep) = replacement {
6590 let idx = self.chunk.add_substr_four_arg_entry(
6591 string.as_ref().clone(),
6592 offset.as_ref().clone(),
6593 length.as_ref().map(|b| b.as_ref().clone()),
6594 rep.as_ref().clone(),
6595 );
6596 self.emit_op(Op::SubstrFourArg(idx), line, Some(root));
6597 } else {
6598 self.compile_expr(string)?;
6599 self.compile_expr(offset)?;
6600 let mut argc: u8 = 2;
6601 if let Some(len) = length {
6602 self.compile_expr(len)?;
6603 argc = 3;
6604 }
6605 self.emit_op(
6606 Op::CallBuiltin(BuiltinId::Substr as u16, argc),
6607 line,
6608 Some(root),
6609 );
6610 }
6611 }
6612 ExprKind::Index {
6613 string,
6614 substr,
6615 position,
6616 } => {
6617 self.compile_expr(string)?;
6618 self.compile_expr(substr)?;
6619 if let Some(pos) = position {
6620 self.compile_expr(pos)?;
6621 self.emit_op(
6622 Op::CallBuiltin(BuiltinId::Index as u16, 3),
6623 line,
6624 Some(root),
6625 );
6626 } else {
6627 self.emit_op(
6628 Op::CallBuiltin(BuiltinId::Index as u16, 2),
6629 line,
6630 Some(root),
6631 );
6632 }
6633 }
6634 ExprKind::Rindex {
6635 string,
6636 substr,
6637 position,
6638 } => {
6639 self.compile_expr(string)?;
6640 self.compile_expr(substr)?;
6641 if let Some(pos) = position {
6642 self.compile_expr(pos)?;
6643 self.emit_op(
6644 Op::CallBuiltin(BuiltinId::Rindex as u16, 3),
6645 line,
6646 Some(root),
6647 );
6648 } else {
6649 self.emit_op(
6650 Op::CallBuiltin(BuiltinId::Rindex as u16, 2),
6651 line,
6652 Some(root),
6653 );
6654 }
6655 }
6656
6657 ExprKind::JoinExpr { separator, list } => {
6658 self.compile_expr(separator)?;
6659 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6661 self.emit_op(Op::CallBuiltin(BuiltinId::Join as u16, 2), line, Some(root));
6662 }
6663 ExprKind::SplitExpr {
6664 pattern,
6665 string,
6666 limit,
6667 } => {
6668 self.compile_expr(pattern)?;
6669 self.compile_expr(string)?;
6670 if let Some(l) = limit {
6671 self.compile_expr(l)?;
6672 self.emit_op(
6673 Op::CallBuiltin(BuiltinId::Split as u16, 3),
6674 line,
6675 Some(root),
6676 );
6677 } else {
6678 self.emit_op(
6679 Op::CallBuiltin(BuiltinId::Split as u16, 2),
6680 line,
6681 Some(root),
6682 );
6683 }
6684 }
6685 ExprKind::Sprintf { format, args } => {
6686 self.compile_expr(format)?;
6689 for a in args {
6690 self.compile_expr_ctx(a, WantarrayCtx::List)?;
6691 }
6692 self.emit_op(
6693 Op::CallBuiltin(BuiltinId::Sprintf as u16, (1 + args.len()) as u8),
6694 line,
6695 Some(root),
6696 );
6697 }
6698
6699 ExprKind::Open { handle, mode, file } => {
6701 if let ExprKind::OpenMyHandle { name } = &handle.kind {
6702 let name_idx = self.chunk.intern_name(name);
6703 self.emit_op(Op::LoadUndef, line, Some(root));
6704 self.emit_declare_scalar(name_idx, line, false);
6705 let h_idx = self.chunk.add_constant(StrykeValue::string(name.clone()));
6706 self.emit_op(Op::LoadConst(h_idx), line, Some(root));
6707 self.compile_expr(mode)?;
6708 if let Some(f) = file {
6709 self.compile_expr(f)?;
6710 self.emit_op(Op::CallBuiltin(BuiltinId::Open as u16, 3), line, Some(root));
6711 } else {
6712 self.emit_op(Op::CallBuiltin(BuiltinId::Open as u16, 2), line, Some(root));
6713 }
6714 self.emit_op(Op::SetScalarKeepPlain(name_idx), line, Some(root));
6715 return Ok(());
6716 }
6717 self.compile_expr(handle)?;
6718 self.compile_expr(mode)?;
6719 if let Some(f) = file {
6720 self.compile_expr(f)?;
6721 self.emit_op(Op::CallBuiltin(BuiltinId::Open as u16, 3), line, Some(root));
6722 } else {
6723 self.emit_op(Op::CallBuiltin(BuiltinId::Open as u16, 2), line, Some(root));
6724 }
6725 }
6726 ExprKind::OpenMyHandle { .. } => {
6727 return Err(CompileError::Unsupported(
6728 "open my $fh handle expression".into(),
6729 ));
6730 }
6731 ExprKind::Close(e) => {
6732 self.compile_expr(e)?;
6733 self.emit_op(
6734 Op::CallBuiltin(BuiltinId::Close as u16, 1),
6735 line,
6736 Some(root),
6737 );
6738 }
6739 ExprKind::ReadLine(handle) => {
6740 let bid = if ctx == WantarrayCtx::List {
6741 BuiltinId::ReadLineList
6742 } else {
6743 BuiltinId::ReadLine
6744 };
6745 if let Some(h) = handle {
6746 let idx = self.chunk.add_constant(StrykeValue::string(h.clone()));
6747 self.emit_op(Op::LoadConst(idx), line, Some(root));
6748 self.emit_op(Op::CallBuiltin(bid as u16, 1), line, Some(root));
6749 } else {
6750 self.emit_op(Op::CallBuiltin(bid as u16, 0), line, Some(root));
6751 }
6752 }
6753 ExprKind::Eof(e) => {
6754 if let Some(inner) = e {
6755 self.compile_expr(inner)?;
6756 self.emit_op(Op::CallBuiltin(BuiltinId::Eof as u16, 1), line, Some(root));
6757 } else {
6758 self.emit_op(Op::CallBuiltin(BuiltinId::Eof as u16, 0), line, Some(root));
6759 }
6760 }
6761 ExprKind::Opendir { handle, path } => {
6762 self.compile_expr(handle)?;
6763 self.compile_expr(path)?;
6764 self.emit_op(
6765 Op::CallBuiltin(BuiltinId::Opendir as u16, 2),
6766 line,
6767 Some(root),
6768 );
6769 }
6770 ExprKind::Readdir(e) => {
6771 let bid = if ctx == WantarrayCtx::List {
6772 BuiltinId::ReaddirList
6773 } else {
6774 BuiltinId::Readdir
6775 };
6776 self.compile_expr(e)?;
6777 self.emit_op(Op::CallBuiltin(bid as u16, 1), line, Some(root));
6778 }
6779 ExprKind::Closedir(e) => {
6780 self.compile_expr(e)?;
6781 self.emit_op(
6782 Op::CallBuiltin(BuiltinId::Closedir as u16, 1),
6783 line,
6784 Some(root),
6785 );
6786 }
6787 ExprKind::Rewinddir(e) => {
6788 self.compile_expr(e)?;
6789 self.emit_op(
6790 Op::CallBuiltin(BuiltinId::Rewinddir as u16, 1),
6791 line,
6792 Some(root),
6793 );
6794 }
6795 ExprKind::Telldir(e) => {
6796 self.compile_expr(e)?;
6797 self.emit_op(
6798 Op::CallBuiltin(BuiltinId::Telldir as u16, 1),
6799 line,
6800 Some(root),
6801 );
6802 }
6803 ExprKind::Seekdir { handle, position } => {
6804 self.compile_expr(handle)?;
6805 self.compile_expr(position)?;
6806 self.emit_op(
6807 Op::CallBuiltin(BuiltinId::Seekdir as u16, 2),
6808 line,
6809 Some(root),
6810 );
6811 }
6812
6813 ExprKind::FileTest { op, expr } => {
6815 self.compile_expr(expr)?;
6816 self.emit_op(Op::FileTestOp(*op as u8), line, Some(root));
6817 }
6818
6819 ExprKind::Eval(e) => {
6821 self.compile_expr(e)?;
6822 if let ExprKind::CodeRef { .. } = &e.kind {
6829 if let Some(max_idx) = self.sub_body_block_indices.iter().copied().max() {
6830 self.sub_body_block_indices.remove(&max_idx);
6831 }
6832 }
6833 self.emit_op(Op::CallBuiltin(BuiltinId::Eval as u16, 1), line, Some(root));
6834 }
6835 ExprKind::Do(e) => {
6836 if let ExprKind::CodeRef { body, .. } = &e.kind {
6838 let block_idx = self.add_deferred_block(body.clone());
6839 self.emit_op(Op::EvalBlock(block_idx, ctx.as_byte()), line, Some(root));
6840 } else {
6841 self.compile_expr(e)?;
6842 self.emit_op(Op::CallBuiltin(BuiltinId::Do as u16, 1), line, Some(root));
6843 }
6844 }
6845 ExprKind::Require(e) => {
6846 self.compile_expr(e)?;
6847 self.emit_op(
6848 Op::CallBuiltin(BuiltinId::Require as u16, 1),
6849 line,
6850 Some(root),
6851 );
6852 }
6853
6854 ExprKind::Chdir(e) => {
6856 self.compile_expr(e)?;
6857 self.emit_op(
6858 Op::CallBuiltin(BuiltinId::Chdir as u16, 1),
6859 line,
6860 Some(root),
6861 );
6862 }
6863 ExprKind::Mkdir { path, mode } => {
6864 self.compile_expr(path)?;
6865 if let Some(m) = mode {
6866 self.compile_expr(m)?;
6867 self.emit_op(
6868 Op::CallBuiltin(BuiltinId::Mkdir as u16, 2),
6869 line,
6870 Some(root),
6871 );
6872 } else {
6873 self.emit_op(
6874 Op::CallBuiltin(BuiltinId::Mkdir as u16, 1),
6875 line,
6876 Some(root),
6877 );
6878 }
6879 }
6880 ExprKind::Unlink(args) => {
6881 for a in args {
6882 self.compile_expr(a)?;
6883 }
6884 self.emit_op(
6885 Op::CallBuiltin(BuiltinId::Unlink as u16, args.len() as u8),
6886 line,
6887 Some(root),
6888 );
6889 }
6890 ExprKind::Rename { old, new } => {
6891 self.compile_expr(old)?;
6892 self.compile_expr(new)?;
6893 self.emit_op(
6894 Op::CallBuiltin(BuiltinId::Rename as u16, 2),
6895 line,
6896 Some(root),
6897 );
6898 }
6899 ExprKind::Chmod(args) => {
6900 for a in args {
6901 self.compile_expr(a)?;
6902 }
6903 self.emit_op(
6904 Op::CallBuiltin(BuiltinId::Chmod as u16, args.len() as u8),
6905 line,
6906 Some(root),
6907 );
6908 }
6909 ExprKind::Chown(args) => {
6910 for a in args {
6911 self.compile_expr(a)?;
6912 }
6913 self.emit_op(
6914 Op::CallBuiltin(BuiltinId::Chown as u16, args.len() as u8),
6915 line,
6916 Some(root),
6917 );
6918 }
6919 ExprKind::Stat(e) => {
6920 self.compile_expr(e)?;
6921 self.emit_op(Op::CallBuiltin(BuiltinId::Stat as u16, 1), line, Some(root));
6922 }
6923 ExprKind::Lstat(e) => {
6924 self.compile_expr(e)?;
6925 self.emit_op(
6926 Op::CallBuiltin(BuiltinId::Lstat as u16, 1),
6927 line,
6928 Some(root),
6929 );
6930 }
6931 ExprKind::Link { old, new } => {
6932 self.compile_expr(old)?;
6933 self.compile_expr(new)?;
6934 self.emit_op(Op::CallBuiltin(BuiltinId::Link as u16, 2), line, Some(root));
6935 }
6936 ExprKind::Symlink { old, new } => {
6937 self.compile_expr(old)?;
6938 self.compile_expr(new)?;
6939 self.emit_op(
6940 Op::CallBuiltin(BuiltinId::Symlink as u16, 2),
6941 line,
6942 Some(root),
6943 );
6944 }
6945 ExprKind::Readlink(e) => {
6946 self.compile_expr(e)?;
6947 self.emit_op(
6948 Op::CallBuiltin(BuiltinId::Readlink as u16, 1),
6949 line,
6950 Some(root),
6951 );
6952 }
6953 ExprKind::Files(args) => {
6954 for a in args {
6955 self.compile_expr(a)?;
6956 }
6957 self.emit_op(
6958 Op::CallBuiltin(BuiltinId::Files as u16, args.len() as u8),
6959 line,
6960 Some(root),
6961 );
6962 }
6963 ExprKind::Filesf(args) => {
6964 for a in args {
6965 self.compile_expr(a)?;
6966 }
6967 self.emit_op(
6968 Op::CallBuiltin(BuiltinId::Filesf as u16, args.len() as u8),
6969 line,
6970 Some(root),
6971 );
6972 }
6973 ExprKind::FilesfRecursive(args) => {
6974 for a in args {
6975 self.compile_expr(a)?;
6976 }
6977 self.emit_op(
6978 Op::CallBuiltin(BuiltinId::FilesfRecursive as u16, args.len() as u8),
6979 line,
6980 Some(root),
6981 );
6982 }
6983 ExprKind::Dirs(args) => {
6984 for a in args {
6985 self.compile_expr(a)?;
6986 }
6987 self.emit_op(
6988 Op::CallBuiltin(BuiltinId::Dirs as u16, args.len() as u8),
6989 line,
6990 Some(root),
6991 );
6992 }
6993 ExprKind::DirsRecursive(args) => {
6994 for a in args {
6995 self.compile_expr(a)?;
6996 }
6997 self.emit_op(
6998 Op::CallBuiltin(BuiltinId::DirsRecursive as u16, args.len() as u8),
6999 line,
7000 Some(root),
7001 );
7002 }
7003 ExprKind::SymLinks(args) => {
7004 for a in args {
7005 self.compile_expr(a)?;
7006 }
7007 self.emit_op(
7008 Op::CallBuiltin(BuiltinId::SymLinks as u16, args.len() as u8),
7009 line,
7010 Some(root),
7011 );
7012 }
7013 ExprKind::Sockets(args) => {
7014 for a in args {
7015 self.compile_expr(a)?;
7016 }
7017 self.emit_op(
7018 Op::CallBuiltin(BuiltinId::Sockets as u16, args.len() as u8),
7019 line,
7020 Some(root),
7021 );
7022 }
7023 ExprKind::Pipes(args) => {
7024 for a in args {
7025 self.compile_expr(a)?;
7026 }
7027 self.emit_op(
7028 Op::CallBuiltin(BuiltinId::Pipes as u16, args.len() as u8),
7029 line,
7030 Some(root),
7031 );
7032 }
7033 ExprKind::BlockDevices(args) => {
7034 for a in args {
7035 self.compile_expr(a)?;
7036 }
7037 self.emit_op(
7038 Op::CallBuiltin(BuiltinId::BlockDevices as u16, args.len() as u8),
7039 line,
7040 Some(root),
7041 );
7042 }
7043 ExprKind::CharDevices(args) => {
7044 for a in args {
7045 self.compile_expr(a)?;
7046 }
7047 self.emit_op(
7048 Op::CallBuiltin(BuiltinId::CharDevices as u16, args.len() as u8),
7049 line,
7050 Some(root),
7051 );
7052 }
7053 ExprKind::Executables(args) => {
7054 for a in args {
7055 self.compile_expr(a)?;
7056 }
7057 self.emit_op(
7058 Op::CallBuiltin(BuiltinId::Executables as u16, args.len() as u8),
7059 line,
7060 Some(root),
7061 );
7062 }
7063 ExprKind::Glob(args) => {
7064 for a in args {
7065 self.compile_expr(a)?;
7066 }
7067 self.emit_op(
7068 Op::CallBuiltin(BuiltinId::Glob as u16, args.len() as u8),
7069 line,
7070 Some(root),
7071 );
7072 }
7073 ExprKind::GlobPar { args, progress } => {
7074 for a in args {
7075 self.compile_expr(a)?;
7076 }
7077 match progress {
7078 None => {
7079 self.emit_op(
7080 Op::CallBuiltin(BuiltinId::GlobPar as u16, args.len() as u8),
7081 line,
7082 Some(root),
7083 );
7084 }
7085 Some(p) => {
7086 self.compile_expr(p)?;
7087 self.emit_op(
7088 Op::CallBuiltin(
7089 BuiltinId::GlobParProgress as u16,
7090 (args.len() + 1) as u8,
7091 ),
7092 line,
7093 Some(root),
7094 );
7095 }
7096 }
7097 }
7098 ExprKind::ParSed { args, progress } => {
7099 for a in args {
7100 self.compile_expr(a)?;
7101 }
7102 match progress {
7103 None => {
7104 self.emit_op(
7105 Op::CallBuiltin(BuiltinId::ParSed as u16, args.len() as u8),
7106 line,
7107 Some(root),
7108 );
7109 }
7110 Some(p) => {
7111 self.compile_expr(p)?;
7112 self.emit_op(
7113 Op::CallBuiltin(
7114 BuiltinId::ParSedProgress as u16,
7115 (args.len() + 1) as u8,
7116 ),
7117 line,
7118 Some(root),
7119 );
7120 }
7121 }
7122 }
7123
7124 ExprKind::Bless { ref_expr, class } => {
7126 self.compile_expr(ref_expr)?;
7127 if let Some(c) = class {
7128 self.compile_expr(c)?;
7129 self.emit_op(
7130 Op::CallBuiltin(BuiltinId::Bless as u16, 2),
7131 line,
7132 Some(root),
7133 );
7134 } else {
7135 self.emit_op(
7136 Op::CallBuiltin(BuiltinId::Bless as u16, 1),
7137 line,
7138 Some(root),
7139 );
7140 }
7141 }
7142 ExprKind::Caller(e) => {
7143 if let Some(inner) = e {
7144 self.compile_expr(inner)?;
7145 self.emit_op(
7146 Op::CallBuiltin(BuiltinId::Caller as u16, 1),
7147 line,
7148 Some(root),
7149 );
7150 } else {
7151 self.emit_op(
7152 Op::CallBuiltin(BuiltinId::Caller as u16, 0),
7153 line,
7154 Some(root),
7155 );
7156 }
7157 }
7158 ExprKind::Wantarray => {
7159 self.emit_op(
7160 Op::CallBuiltin(BuiltinId::Wantarray as u16, 0),
7161 line,
7162 Some(root),
7163 );
7164 }
7165
7166 ExprKind::ScalarRef(e) => match &e.kind {
7168 ExprKind::ScalarVar(name) => {
7169 let idx = self.intern_scalar_var_for_ops(name);
7170 self.emit_op(Op::MakeScalarBindingRef(idx), line, Some(root));
7171 }
7172 ExprKind::ArrayVar(name) => {
7173 self.check_strict_array_access(name, line)?;
7174 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
7175 self.emit_op(Op::MakeArrayBindingRef(idx), line, Some(root));
7176 }
7177 ExprKind::HashVar(name) => {
7178 self.check_strict_hash_access(name, line)?;
7179 let idx = self.chunk.intern_name(name);
7180 self.emit_op(Op::MakeHashBindingRef(idx), line, Some(root));
7181 }
7182 ExprKind::Deref {
7183 expr: inner,
7184 kind: Sigil::Array,
7185 } => {
7186 self.compile_expr(inner)?;
7187 self.emit_op(Op::MakeArrayRefAlias, line, Some(root));
7188 }
7189 ExprKind::Deref {
7190 expr: inner,
7191 kind: Sigil::Hash,
7192 } => {
7193 self.compile_expr(inner)?;
7194 self.emit_op(Op::MakeHashRefAlias, line, Some(root));
7195 }
7196 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
7197 self.compile_expr_ctx(e, WantarrayCtx::List)?;
7198 self.emit_op(Op::MakeArrayRef, line, Some(root));
7199 }
7200 ExprKind::AnonymousListSlice { .. } | ExprKind::HashSliceDeref { .. } => {
7201 self.compile_expr_ctx(e, WantarrayCtx::List)?;
7202 self.emit_op(Op::MakeArrayRef, line, Some(root));
7203 }
7204 _ => {
7205 self.compile_expr(e)?;
7206 self.emit_op(Op::MakeScalarRef, line, Some(root));
7207 }
7208 },
7209 ExprKind::ArrayRef(elems) => {
7210 for e in elems {
7214 self.compile_expr_ctx(e, WantarrayCtx::List)?;
7215 }
7216 self.emit_op(Op::MakeArray(elems.len() as u16), line, Some(root));
7217 self.emit_op(Op::MakeArrayRef, line, Some(root));
7218 }
7219 ExprKind::HashRef(pairs) => {
7220 if pairs.len() == 1 {
7229 if let ExprKind::String(ref k) = pairs[0].0.kind {
7230 if k == "__HASH_SPREAD__" {
7231 self.compile_expr_ctx(&pairs[0].1, WantarrayCtx::List)?;
7232 self.emit_op(Op::MakeHashRef, line, Some(root));
7233 return Ok(());
7234 }
7235 }
7236 }
7237 for (k, v) in pairs {
7238 self.compile_expr(k)?;
7239 self.compile_expr_ctx(v, WantarrayCtx::List)?;
7240 }
7241 self.emit_op(Op::MakeHash((pairs.len() * 2) as u16), line, Some(root));
7242 self.emit_op(Op::MakeHashRef, line, Some(root));
7243 }
7244 ExprKind::CodeRef { body, params } => {
7245 let block_idx = self.add_deferred_block(body.clone());
7246 self.sub_body_block_indices.insert(block_idx);
7247 while self.code_ref_block_params.len() <= block_idx as usize {
7251 self.code_ref_block_params.push(Vec::new());
7252 }
7253 self.code_ref_block_params[block_idx as usize] = params.clone();
7254 let sig_idx = self.chunk.add_code_ref_sig(params.clone());
7255 self.emit_op(Op::MakeCodeRef(block_idx, sig_idx), line, Some(root));
7256 }
7257 ExprKind::SubroutineRef(name) => {
7258 let q = self.qualify_sub_key(name);
7260 let name_idx = self.chunk.intern_name(&q);
7261 self.emit_op(Op::Call(name_idx, 0, ctx.as_byte()), line, Some(root));
7262 }
7263 ExprKind::SubroutineCodeRef(name) => {
7264 let name_idx = self.chunk.intern_name(name);
7266 self.emit_op(Op::LoadNamedSubRef(name_idx), line, Some(root));
7267 }
7268 ExprKind::DynamicSubCodeRef(expr) => {
7269 self.compile_expr(expr)?;
7270 self.emit_op(Op::LoadDynamicSubRef, line, Some(root));
7271 }
7272
7273 ExprKind::ArrowDeref { expr, index, kind } => match kind {
7275 DerefKind::Array => {
7276 self.compile_arrow_array_base_expr(expr)?;
7277 let mut used_arrow_slice = false;
7278 if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
7283 let inner = match &index.kind {
7284 ExprKind::List(el) if el.len() == 1 => &el[0],
7285 _ => index.as_ref(),
7286 };
7287 self.compile_expr(inner)?;
7288 self.emit_op(Op::ArrowArray, line, Some(root));
7289 } else if let ExprKind::List(indices) = &index.kind {
7290 for ix in indices {
7291 self.compile_array_slice_index_expr(ix)?;
7292 }
7293 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, Some(root));
7294 used_arrow_slice = true;
7295 } else {
7296 self.compile_array_slice_index_expr(index)?;
7298 self.emit_op(Op::ArrowArraySlice(1), line, Some(root));
7299 used_arrow_slice = true;
7300 }
7301 if used_arrow_slice && ctx != WantarrayCtx::List {
7302 self.emit_op(Op::ListSliceToScalar, line, Some(root));
7303 }
7304 }
7305 DerefKind::Hash => {
7306 self.compile_arrow_hash_base_expr(expr)?;
7307 self.compile_expr(index)?;
7308 self.emit_op(Op::ArrowHash, line, Some(root));
7309 }
7310 DerefKind::Call => {
7311 self.compile_expr(expr)?;
7312 self.compile_expr_ctx(index, WantarrayCtx::List)?;
7314 self.emit_op(Op::ArrowCall(ctx.as_byte()), line, Some(root));
7315 }
7316 },
7317 ExprKind::Deref { expr, kind } => {
7318 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
7321 self.compile_expr(expr)?;
7322 self.emit_op(Op::ArrayDerefLen, line, Some(root));
7323 } else if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
7324 self.compile_expr(expr)?;
7325 self.emit_op(Op::SymbolicDeref(2), line, Some(root));
7326 self.emit_op(Op::ValueScalarContext, line, Some(root));
7327 } else {
7328 self.compile_expr(expr)?;
7329 let b = match kind {
7330 Sigil::Scalar => 0u8,
7331 Sigil::Array => 1,
7332 Sigil::Hash => 2,
7333 Sigil::Typeglob => 3,
7334 };
7335 self.emit_op(Op::SymbolicDeref(b), line, Some(root));
7336 }
7337 }
7338
7339 ExprKind::InterpolatedString(parts) => {
7341 let has_case_escapes = parts.iter().any(|p| {
7343 if let StringPart::Literal(s) = p {
7344 s.contains('\\')
7345 && (s.contains("\\U")
7346 || s.contains("\\L")
7347 || s.contains("\\u")
7348 || s.contains("\\l")
7349 || s.contains("\\Q")
7350 || s.contains("\\E"))
7351 } else {
7352 false
7353 }
7354 });
7355 if parts.is_empty() {
7356 let idx = self.chunk.add_constant(StrykeValue::string(String::new()));
7357 self.emit_op(Op::LoadConst(idx), line, Some(root));
7358 } else {
7359 if !matches!(&parts[0], StringPart::Literal(_)) {
7362 let idx = self.chunk.add_constant(StrykeValue::string(String::new()));
7363 self.emit_op(Op::LoadConst(idx), line, Some(root));
7364 }
7365 self.compile_string_part(&parts[0], line, Some(root))?;
7366 for part in &parts[1..] {
7367 self.compile_string_part(part, line, Some(root))?;
7368 self.emit_op(Op::Concat, line, Some(root));
7369 }
7370 if !matches!(&parts[0], StringPart::Literal(_)) {
7371 self.emit_op(Op::Concat, line, Some(root));
7372 }
7373 }
7374 if has_case_escapes {
7375 self.emit_op(Op::ProcessCaseEscapes, line, Some(root));
7376 }
7377 }
7378
7379 ExprKind::List(exprs) => {
7381 if ctx == WantarrayCtx::Scalar {
7382 if let Some(last) = exprs.last() {
7384 self.compile_expr_ctx(last, WantarrayCtx::Scalar)?;
7385 } else {
7386 self.emit_op(Op::LoadUndef, line, Some(root));
7387 }
7388 } else {
7389 for e in exprs {
7390 self.compile_expr_ctx(e, ctx)?;
7391 }
7392 if exprs.len() != 1 {
7393 self.emit_op(Op::MakeArray(exprs.len() as u16), line, Some(root));
7394 }
7395 }
7396 }
7397
7398 ExprKind::QW(words) => {
7400 for w in words {
7401 let idx = self.chunk.add_constant(StrykeValue::string(w.clone()));
7402 self.emit_op(Op::LoadConst(idx), line, Some(root));
7403 }
7404 self.emit_op(Op::MakeArray(words.len() as u16), line, Some(root));
7405 }
7406
7407 ExprKind::PostfixIf { expr, condition } => {
7409 self.compile_boolean_rvalue_condition(condition)?;
7410 let j = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
7411 self.compile_expr(expr)?;
7412 let end = self.emit_op(Op::Jump(0), line, Some(root));
7413 self.chunk.patch_jump_here(j);
7414 self.emit_op(Op::LoadUndef, line, Some(root));
7415 self.chunk.patch_jump_here(end);
7416 }
7417 ExprKind::PostfixUnless { expr, condition } => {
7418 self.compile_boolean_rvalue_condition(condition)?;
7419 let j = self.emit_op(Op::JumpIfTrue(0), line, Some(root));
7420 self.compile_expr(expr)?;
7421 let end = self.emit_op(Op::Jump(0), line, Some(root));
7422 self.chunk.patch_jump_here(j);
7423 self.emit_op(Op::LoadUndef, line, Some(root));
7424 self.chunk.patch_jump_here(end);
7425 }
7426
7427 ExprKind::PostfixWhile { expr, condition } => {
7429 let is_do_block = matches!(
7431 &expr.kind,
7432 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
7433 );
7434 if is_do_block {
7435 let loop_start = self.chunk.len();
7437 self.compile_expr(expr)?;
7438 self.emit_op(Op::Pop, line, Some(root));
7439 self.compile_boolean_rvalue_condition(condition)?;
7440 self.emit_op(Op::JumpIfTrue(loop_start), line, Some(root));
7441 self.emit_op(Op::LoadUndef, line, Some(root));
7442 } else {
7443 let loop_start = self.chunk.len();
7445 self.compile_boolean_rvalue_condition(condition)?;
7446 let exit_jump = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
7447 self.compile_expr(expr)?;
7448 self.emit_op(Op::Pop, line, Some(root));
7449 self.emit_op(Op::Jump(loop_start), line, Some(root));
7450 self.chunk.patch_jump_here(exit_jump);
7451 self.emit_op(Op::LoadUndef, line, Some(root));
7452 }
7453 }
7454 ExprKind::PostfixUntil { expr, condition } => {
7455 let is_do_block = matches!(
7456 &expr.kind,
7457 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
7458 );
7459 if is_do_block {
7460 let loop_start = self.chunk.len();
7461 self.compile_expr(expr)?;
7462 self.emit_op(Op::Pop, line, Some(root));
7463 self.compile_boolean_rvalue_condition(condition)?;
7464 self.emit_op(Op::JumpIfFalse(loop_start), line, Some(root));
7465 self.emit_op(Op::LoadUndef, line, Some(root));
7466 } else {
7467 let loop_start = self.chunk.len();
7468 self.compile_boolean_rvalue_condition(condition)?;
7469 let exit_jump = self.emit_op(Op::JumpIfTrue(0), line, Some(root));
7470 self.compile_expr(expr)?;
7471 self.emit_op(Op::Pop, line, Some(root));
7472 self.emit_op(Op::Jump(loop_start), line, Some(root));
7473 self.chunk.patch_jump_here(exit_jump);
7474 self.emit_op(Op::LoadUndef, line, Some(root));
7475 }
7476 }
7477 ExprKind::PostfixForeach { expr, list } => {
7478 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7479 let list_name = self.chunk.intern_name("__pf_foreach_list__");
7480 self.emit_op(Op::DeclareArray(list_name), line, Some(root));
7481 let counter = self.chunk.intern_name("__pf_foreach_i__");
7482 self.emit_op(Op::LoadInt(0), line, Some(root));
7483 self.emit_op(Op::DeclareScalar(counter), line, Some(root));
7484 let underscore = self.chunk.intern_name("_");
7485
7486 let loop_start = self.chunk.len();
7487 self.emit_get_scalar(counter, line, Some(root));
7488 self.emit_op(Op::ArrayLen(list_name), line, Some(root));
7489 self.emit_op(Op::NumLt, line, Some(root));
7490 let exit_jump = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
7491
7492 self.emit_get_scalar(counter, line, Some(root));
7493 self.emit_op(Op::GetArrayElem(list_name), line, Some(root));
7494 self.emit_set_scalar(underscore, line, Some(root));
7495
7496 self.compile_expr(expr)?;
7497 self.emit_op(Op::Pop, line, Some(root));
7498
7499 self.emit_pre_inc(counter, line, Some(root));
7500 self.emit_op(Op::Pop, line, Some(root));
7501 self.emit_op(Op::Jump(loop_start), line, Some(root));
7502 self.chunk.patch_jump_here(exit_jump);
7503 self.emit_op(Op::LoadUndef, line, Some(root));
7504 }
7505
7506 ExprKind::AlgebraicMatch { subject, arms } => {
7507 let idx = self
7508 .chunk
7509 .add_algebraic_match_entry(subject.as_ref().clone(), arms.clone());
7510 self.emit_op(Op::AlgebraicMatch(idx), line, Some(root));
7511 }
7512
7513 ExprKind::Match {
7515 expr,
7516 pattern,
7517 flags,
7518 scalar_g,
7519 delim: _,
7520 } => {
7521 self.compile_expr(expr)?;
7522 let pat_idx = self
7523 .chunk
7524 .add_constant(StrykeValue::string(pattern.clone()));
7525 let flags_idx = self.chunk.add_constant(StrykeValue::string(flags.clone()));
7526 let pos_key_idx = if *scalar_g && flags.contains('g') {
7527 if let ExprKind::ScalarVar(n) = &expr.kind {
7528 let stor = self.scalar_storage_name_for_ops(n);
7529 self.chunk.add_constant(StrykeValue::string(stor))
7530 } else {
7531 u16::MAX
7532 }
7533 } else {
7534 u16::MAX
7535 };
7536 self.emit_op(
7537 Op::RegexMatch(pat_idx, flags_idx, *scalar_g, pos_key_idx),
7538 line,
7539 Some(root),
7540 );
7541 }
7542
7543 ExprKind::Substitution {
7544 expr,
7545 pattern,
7546 replacement,
7547 flags,
7548 delim: _,
7549 } => {
7550 self.compile_expr(expr)?;
7551 let pat_idx = self
7552 .chunk
7553 .add_constant(StrykeValue::string(pattern.clone()));
7554 let repl_idx = self
7555 .chunk
7556 .add_constant(StrykeValue::string(replacement.clone()));
7557 let flags_idx = self.chunk.add_constant(StrykeValue::string(flags.clone()));
7558 let lv_idx = self.chunk.add_lvalue_expr(expr.as_ref().clone());
7559 self.emit_op(
7560 Op::RegexSubst(pat_idx, repl_idx, flags_idx, lv_idx),
7561 line,
7562 Some(root),
7563 );
7564 }
7565 ExprKind::Transliterate {
7566 expr,
7567 from,
7568 to,
7569 flags,
7570 delim: _,
7571 } => {
7572 self.compile_expr(expr)?;
7573 let from_idx = self.chunk.add_constant(StrykeValue::string(from.clone()));
7574 let to_idx = self.chunk.add_constant(StrykeValue::string(to.clone()));
7575 let flags_idx = self.chunk.add_constant(StrykeValue::string(flags.clone()));
7576 let lv_idx = self.chunk.add_lvalue_expr(expr.as_ref().clone());
7577 self.emit_op(
7578 Op::RegexTransliterate(from_idx, to_idx, flags_idx, lv_idx),
7579 line,
7580 Some(root),
7581 );
7582 }
7583
7584 ExprKind::Regex(pattern, flags) => {
7586 if ctx == WantarrayCtx::Void {
7587 self.compile_boolean_rvalue_condition(root)?;
7589 } else {
7590 let pat_idx = self
7591 .chunk
7592 .add_constant(StrykeValue::string(pattern.clone()));
7593 let flags_idx = self.chunk.add_constant(StrykeValue::string(flags.clone()));
7594 self.emit_op(Op::LoadRegex(pat_idx, flags_idx), line, Some(root));
7595 }
7596 }
7597
7598 ExprKind::MapExpr {
7600 block,
7601 list,
7602 flatten_array_refs,
7603 stream,
7604 } => {
7605 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7606 if *stream {
7607 let block_idx = self.add_deferred_block(block.clone());
7608 if *flatten_array_refs {
7609 self.emit_op(Op::MapsFlatMapWithBlock(block_idx), line, Some(root));
7610 } else {
7611 self.emit_op(Op::MapsWithBlock(block_idx), line, Some(root));
7612 }
7613 } else if let Some(k) = crate::map_grep_fast::detect_map_int_mul(block) {
7614 self.emit_op(Op::MapIntMul(k), line, Some(root));
7615 } else {
7616 let block_idx = self.add_deferred_block(block.clone());
7617 if *flatten_array_refs {
7618 self.emit_op(Op::FlatMapWithBlock(block_idx), line, Some(root));
7619 } else {
7620 self.emit_op(Op::MapWithBlock(block_idx), line, Some(root));
7621 }
7622 }
7623 if ctx != WantarrayCtx::List {
7624 self.emit_op(Op::StackArrayLen, line, Some(root));
7625 }
7626 }
7627 ExprKind::MapExprComma {
7628 expr,
7629 list,
7630 flatten_array_refs,
7631 stream,
7632 } => {
7633 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7634 let idx = self.chunk.add_map_expr_entry(*expr.clone());
7635 if *stream {
7636 if *flatten_array_refs {
7637 self.emit_op(Op::MapsFlatMapWithExpr(idx), line, Some(root));
7638 } else {
7639 self.emit_op(Op::MapsWithExpr(idx), line, Some(root));
7640 }
7641 } else if *flatten_array_refs {
7642 self.emit_op(Op::FlatMapWithExpr(idx), line, Some(root));
7643 } else {
7644 self.emit_op(Op::MapWithExpr(idx), line, Some(root));
7645 }
7646 if ctx != WantarrayCtx::List {
7647 self.emit_op(Op::StackArrayLen, line, Some(root));
7648 }
7649 }
7650 ExprKind::ForEachExpr { block, list } => {
7651 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7652 let block_idx = self.add_deferred_block(block.clone());
7653 self.emit_op(Op::ForEachWithBlock(block_idx), line, Some(root));
7654 }
7655 ExprKind::GrepExpr {
7656 block,
7657 list,
7658 keyword,
7659 } => {
7660 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7661 if keyword.is_stream() {
7662 let block_idx = self.add_deferred_block(block.clone());
7663 self.emit_op(Op::FilterWithBlock(block_idx), line, Some(root));
7664 } else if let Some((m, r)) = crate::map_grep_fast::detect_grep_int_mod_eq(block) {
7665 self.emit_op(Op::GrepIntModEq(m, r), line, Some(root));
7666 } else {
7667 let block_idx = self.add_deferred_block(block.clone());
7668 self.emit_op(Op::GrepWithBlock(block_idx), line, Some(root));
7669 }
7670 if ctx != WantarrayCtx::List {
7671 self.emit_op(Op::StackArrayLen, line, Some(root));
7672 }
7673 }
7674 ExprKind::GrepExprComma {
7675 expr,
7676 list,
7677 keyword,
7678 } => {
7679 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7680 let idx = self.chunk.add_grep_expr_entry(*expr.clone());
7681 if keyword.is_stream() {
7682 self.emit_op(Op::FilterWithExpr(idx), line, Some(root));
7683 } else {
7684 self.emit_op(Op::GrepWithExpr(idx), line, Some(root));
7685 }
7686 if ctx != WantarrayCtx::List {
7687 self.emit_op(Op::StackArrayLen, line, Some(root));
7688 }
7689 }
7690 ExprKind::SortExpr { cmp, list } => {
7691 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7692 match cmp {
7693 Some(crate::ast::SortComparator::Block(block)) => {
7694 if let Some(mode) = detect_sort_block_fast(block) {
7695 let tag = match mode {
7696 crate::sort_fast::SortBlockFast::Numeric => 0u8,
7697 crate::sort_fast::SortBlockFast::String => 1u8,
7698 crate::sort_fast::SortBlockFast::NumericRev => 2u8,
7699 crate::sort_fast::SortBlockFast::StringRev => 3u8,
7700 };
7701 self.emit_op(Op::SortWithBlockFast(tag), line, Some(root));
7702 } else {
7703 let block_idx = self.add_deferred_block(block.clone());
7704 self.register_sort_pair_block(block_idx);
7705 self.emit_op(Op::SortWithBlock(block_idx), line, Some(root));
7706 }
7707 }
7708 Some(crate::ast::SortComparator::Code(code_expr)) => {
7709 self.compile_expr(code_expr)?;
7710 self.emit_op(Op::SortWithCodeComparator(ctx.as_byte()), line, Some(root));
7711 }
7712 None => {
7713 self.emit_op(Op::SortNoBlock, line, Some(root));
7714 }
7715 }
7716 }
7717
7718 ExprKind::ParExpr { .. }
7720 | ExprKind::ParReduceExpr { .. }
7721 | ExprKind::DistReduceExpr { .. } => {
7722 let idx = self.chunk.ast_eval_exprs.len() as u16;
7727 self.chunk.ast_eval_exprs.push(root.clone());
7728 self.emit_op(Op::EvalAstExpr(idx), line, Some(root));
7729 }
7730 ExprKind::PMapExpr {
7731 block,
7732 list,
7733 progress,
7734 flat_outputs,
7735 on_cluster,
7736 stream,
7737 } => {
7738 if *stream {
7739 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7741 let block_idx = self.add_deferred_block(block.clone());
7742 if *flat_outputs {
7743 self.emit_op(Op::PFlatMapsWithBlock(block_idx), line, Some(root));
7744 } else {
7745 self.emit_op(Op::PMapsWithBlock(block_idx), line, Some(root));
7746 }
7747 } else {
7748 if let Some(p) = progress {
7749 self.compile_expr(p)?;
7750 } else {
7751 self.emit_op(Op::LoadInt(0), line, Some(root));
7752 }
7753 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7754 if let Some(cluster_e) = on_cluster {
7755 self.compile_expr(cluster_e)?;
7756 let block_idx = self.add_deferred_block(block.clone());
7757 self.emit_op(
7758 Op::PMapRemote {
7759 block_idx,
7760 flat: u8::from(*flat_outputs),
7761 },
7762 line,
7763 Some(root),
7764 );
7765 } else {
7766 let block_idx = self.add_deferred_block(block.clone());
7767 if *flat_outputs {
7768 self.emit_op(Op::PFlatMapWithBlock(block_idx), line, Some(root));
7769 } else {
7770 self.emit_op(Op::PMapWithBlock(block_idx), line, Some(root));
7771 }
7772 }
7773 }
7774 }
7775 ExprKind::PMapChunkedExpr {
7776 chunk_size,
7777 block,
7778 list,
7779 progress,
7780 } => {
7781 if let Some(p) = progress {
7782 self.compile_expr(p)?;
7783 } else {
7784 self.emit_op(Op::LoadInt(0), line, Some(root));
7785 }
7786 self.compile_expr(chunk_size)?;
7787 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7788 let block_idx = self.add_deferred_block(block.clone());
7789 self.emit_op(Op::PMapChunkedWithBlock(block_idx), line, Some(root));
7790 }
7791 ExprKind::PGrepExpr {
7792 block,
7793 list,
7794 progress,
7795 stream,
7796 } => {
7797 if *stream {
7798 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7799 let block_idx = self.add_deferred_block(block.clone());
7800 self.emit_op(Op::PGrepsWithBlock(block_idx), line, Some(root));
7801 } else {
7802 if let Some(p) = progress {
7803 self.compile_expr(p)?;
7804 } else {
7805 self.emit_op(Op::LoadInt(0), line, Some(root));
7806 }
7807 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7808 let block_idx = self.add_deferred_block(block.clone());
7809 self.emit_op(Op::PGrepWithBlock(block_idx), line, Some(root));
7810 }
7811 }
7812 ExprKind::PForExpr {
7813 block,
7814 list,
7815 progress,
7816 } => {
7817 if let Some(p) = progress {
7818 self.compile_expr(p)?;
7819 } else {
7820 self.emit_op(Op::LoadInt(0), line, Some(root));
7821 }
7822 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7823 let block_idx = self.add_deferred_block(block.clone());
7824 self.emit_op(Op::PForWithBlock(block_idx), line, Some(root));
7825 }
7826 ExprKind::ParLinesExpr {
7827 path,
7828 callback,
7829 progress,
7830 } => {
7831 let idx = self.chunk.add_par_lines_entry(
7832 path.as_ref().clone(),
7833 callback.as_ref().clone(),
7834 progress.as_ref().map(|p| p.as_ref().clone()),
7835 );
7836 self.emit_op(Op::ParLines(idx), line, Some(root));
7837 }
7838 ExprKind::ParWalkExpr {
7839 path,
7840 callback,
7841 progress,
7842 } => {
7843 let idx = self.chunk.add_par_walk_entry(
7844 path.as_ref().clone(),
7845 callback.as_ref().clone(),
7846 progress.as_ref().map(|p| p.as_ref().clone()),
7847 );
7848 self.emit_op(Op::ParWalk(idx), line, Some(root));
7849 }
7850 ExprKind::PwatchExpr { path, callback } => {
7851 let idx = self
7852 .chunk
7853 .add_pwatch_entry(path.as_ref().clone(), callback.as_ref().clone());
7854 self.emit_op(Op::Pwatch(idx), line, Some(root));
7855 }
7856 ExprKind::PSortExpr {
7857 cmp,
7858 list,
7859 progress,
7860 } => {
7861 if let Some(p) = progress {
7862 self.compile_expr(p)?;
7863 } else {
7864 self.emit_op(Op::LoadInt(0), line, Some(root));
7865 }
7866 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7867 if let Some(block) = cmp {
7868 if let Some(mode) = detect_sort_block_fast(block) {
7869 let tag = match mode {
7870 crate::sort_fast::SortBlockFast::Numeric => 0u8,
7871 crate::sort_fast::SortBlockFast::String => 1u8,
7872 crate::sort_fast::SortBlockFast::NumericRev => 2u8,
7873 crate::sort_fast::SortBlockFast::StringRev => 3u8,
7874 };
7875 self.emit_op(Op::PSortWithBlockFast(tag), line, Some(root));
7876 } else {
7877 let block_idx = self.add_deferred_block(block.clone());
7878 self.register_sort_pair_block(block_idx);
7879 self.emit_op(Op::PSortWithBlock(block_idx), line, Some(root));
7880 }
7881 } else {
7882 self.emit_op(Op::PSortNoBlockParallel, line, Some(root));
7883 }
7884 }
7885 ExprKind::ReduceExpr { block, list } => {
7886 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7887 let block_idx = self.add_deferred_block(block.clone());
7888 self.register_sort_pair_block(block_idx);
7889 self.emit_op(Op::ReduceWithBlock(block_idx), line, Some(root));
7890 }
7891 ExprKind::PReduceExpr {
7892 block,
7893 list,
7894 progress,
7895 } => {
7896 if let Some(p) = progress {
7897 self.compile_expr(p)?;
7898 } else {
7899 self.emit_op(Op::LoadInt(0), line, Some(root));
7900 }
7901 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7902 let block_idx = self.add_deferred_block(block.clone());
7903 self.register_sort_pair_block(block_idx);
7904 self.emit_op(Op::PReduceWithBlock(block_idx), line, Some(root));
7905 }
7906 ExprKind::PReduceInitExpr {
7907 init,
7908 block,
7909 list,
7910 progress,
7911 } => {
7912 if let Some(p) = progress {
7913 self.compile_expr(p)?;
7914 } else {
7915 self.emit_op(Op::LoadInt(0), line, Some(root));
7916 }
7917 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7918 self.compile_expr(init)?;
7919 let block_idx = self.add_deferred_block(block.clone());
7920 self.emit_op(Op::PReduceInitWithBlock(block_idx), line, Some(root));
7921 }
7922 ExprKind::PMapReduceExpr {
7923 map_block,
7924 reduce_block,
7925 list,
7926 progress,
7927 } => {
7928 if let Some(p) = progress {
7929 self.compile_expr(p)?;
7930 } else {
7931 self.emit_op(Op::LoadInt(0), line, Some(root));
7932 }
7933 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7934 let map_idx = self.add_deferred_block(map_block.clone());
7935 let reduce_idx = self.add_deferred_block(reduce_block.clone());
7936 self.emit_op(
7937 Op::PMapReduceWithBlocks(map_idx, reduce_idx),
7938 line,
7939 Some(root),
7940 );
7941 }
7942 ExprKind::PcacheExpr {
7943 block,
7944 list,
7945 progress,
7946 } => {
7947 if let Some(p) = progress {
7948 self.compile_expr(p)?;
7949 } else {
7950 self.emit_op(Op::LoadInt(0), line, Some(root));
7951 }
7952 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7953 let block_idx = self.add_deferred_block(block.clone());
7954 self.emit_op(Op::PcacheWithBlock(block_idx), line, Some(root));
7955 }
7956 ExprKind::PselectExpr { receivers, timeout } => {
7957 let n = receivers.len();
7958 if n > u8::MAX as usize {
7959 return Err(CompileError::Unsupported(
7960 "pselect: too many receivers".into(),
7961 ));
7962 }
7963 for r in receivers {
7964 self.compile_expr(r)?;
7965 }
7966 let has_timeout = timeout.is_some();
7967 if let Some(t) = timeout {
7968 self.compile_expr(t)?;
7969 }
7970 self.emit_op(
7971 Op::Pselect {
7972 n_rx: n as u8,
7973 has_timeout,
7974 },
7975 line,
7976 Some(root),
7977 );
7978 }
7979 ExprKind::FanExpr {
7980 count,
7981 block,
7982 progress,
7983 capture,
7984 } => {
7985 if let Some(p) = progress {
7986 self.compile_expr(p)?;
7987 } else {
7988 self.emit_op(Op::LoadInt(0), line, Some(root));
7989 }
7990 let block_idx = self.add_deferred_block(block.clone());
7991 match (count, capture) {
7992 (Some(c), false) => {
7993 self.compile_expr(c)?;
7994 self.emit_op(Op::FanWithBlock(block_idx), line, Some(root));
7995 }
7996 (None, false) => {
7997 self.emit_op(Op::FanWithBlockAuto(block_idx), line, Some(root));
7998 }
7999 (Some(c), true) => {
8000 self.compile_expr(c)?;
8001 self.emit_op(Op::FanCapWithBlock(block_idx), line, Some(root));
8002 }
8003 (None, true) => {
8004 self.emit_op(Op::FanCapWithBlockAuto(block_idx), line, Some(root));
8005 }
8006 }
8007 }
8008 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
8009 let block_idx = self.add_deferred_block(body.clone());
8010 self.emit_op(Op::AsyncBlock(block_idx), line, Some(root));
8011 }
8012 ExprKind::Trace { body } => {
8013 let block_idx = self.add_deferred_block(body.clone());
8014 self.emit_op(Op::TraceBlock(block_idx), line, Some(root));
8015 }
8016 ExprKind::Timer { body } => {
8017 let block_idx = self.add_deferred_block(body.clone());
8018 self.emit_op(Op::TimerBlock(block_idx), line, Some(root));
8019 }
8020 ExprKind::Bench { body, times } => {
8021 self.compile_expr(times)?;
8022 let block_idx = self.add_deferred_block(body.clone());
8023 self.emit_op(Op::BenchBlock(block_idx), line, Some(root));
8024 }
8025 ExprKind::Await(e) => {
8026 self.compile_expr(e)?;
8027 self.emit_op(Op::Await, line, Some(root));
8028 }
8029 ExprKind::Slurp(e) => {
8030 self.compile_expr(e)?;
8031 self.emit_op(
8032 Op::CallBuiltin(BuiltinId::Slurp as u16, 1),
8033 line,
8034 Some(root),
8035 );
8036 }
8037 ExprKind::Capture(e) => {
8038 self.compile_expr(e)?;
8039 self.emit_op(
8040 Op::CallBuiltin(BuiltinId::Capture as u16, 1),
8041 line,
8042 Some(root),
8043 );
8044 }
8045 ExprKind::Qx(e) => {
8046 self.compile_expr(e)?;
8047 self.emit_op(
8048 Op::CallBuiltin(BuiltinId::Readpipe as u16, 1),
8049 line,
8050 Some(root),
8051 );
8052 }
8053 ExprKind::FetchUrl(e) => {
8054 self.compile_expr(e)?;
8055 self.emit_op(
8056 Op::CallBuiltin(BuiltinId::FetchUrl as u16, 1),
8057 line,
8058 Some(root),
8059 );
8060 }
8061 ExprKind::Pchannel { capacity } => {
8062 if let Some(c) = capacity {
8063 self.compile_expr(c)?;
8064 self.emit_op(
8065 Op::CallBuiltin(BuiltinId::Pchannel as u16, 1),
8066 line,
8067 Some(root),
8068 );
8069 } else {
8070 self.emit_op(
8071 Op::CallBuiltin(BuiltinId::Pchannel as u16, 0),
8072 line,
8073 Some(root),
8074 );
8075 }
8076 }
8077 ExprKind::RetryBlock { .. }
8078 | ExprKind::RateLimitBlock { .. }
8079 | ExprKind::EveryBlock { .. }
8080 | ExprKind::GenBlock { .. }
8081 | ExprKind::Yield(_)
8082 | ExprKind::Spinner { .. } => {
8083 let idx = self.chunk.ast_eval_exprs.len() as u16;
8084 self.chunk.ast_eval_exprs.push(root.clone());
8085 self.emit_op(Op::EvalAstExpr(idx), line, Some(root));
8086 }
8087 ExprKind::MyExpr { keyword, decls } => {
8088 if decls.len() == 1 && decls[0].sigil == Sigil::Scalar {
8091 let decl = &decls[0];
8092 if let Some(init) = &decl.initializer {
8093 self.compile_expr(init)?;
8094 } else {
8095 self.chunk.emit(Op::LoadUndef, line);
8096 }
8097 self.emit_op(Op::Dup, line, Some(root));
8099 let name_idx = self.chunk.intern_name(&decl.name);
8100 match keyword.as_str() {
8101 "state" => {
8102 let name = self.chunk.names[name_idx as usize].clone();
8103 self.register_declare(Sigil::Scalar, &name, false);
8104 self.chunk.emit(Op::DeclareStateScalar(name_idx), line);
8105 }
8106 _ => {
8107 self.emit_declare_scalar(name_idx, line, false);
8108 }
8109 }
8110 } else {
8111 return Err(CompileError::Unsupported(
8112 "my/our/state/local in expression context with multiple or non-scalar decls".into(),
8113 ));
8114 }
8115 }
8116 }
8117 Ok(())
8118 }
8119
8120 fn compile_string_part(
8121 &mut self,
8122 part: &StringPart,
8123 line: usize,
8124 parent: Option<&Expr>,
8125 ) -> Result<(), CompileError> {
8126 match part {
8127 StringPart::Literal(s) => {
8128 let idx = self.chunk.add_constant(StrykeValue::string(s.clone()));
8129 self.emit_op(Op::LoadConst(idx), line, parent);
8130 }
8131 StringPart::ScalarVar(name) => {
8132 let idx = self.intern_scalar_var_for_ops(name);
8133 self.emit_get_scalar(idx, line, parent);
8134 }
8135 StringPart::ArrayVar(name) => {
8136 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
8137 self.emit_op(Op::GetArray(idx), line, parent);
8138 self.emit_op(Op::ArrayStringifyListSep, line, parent);
8139 }
8140 StringPart::Expr(e) => {
8141 if matches!(&e.kind, ExprKind::ArraySlice { .. })
8143 || matches!(
8144 &e.kind,
8145 ExprKind::Deref {
8146 kind: Sigil::Array,
8147 ..
8148 }
8149 )
8150 {
8151 self.compile_expr_ctx(e, WantarrayCtx::List)?;
8152 self.emit_op(Op::ArrayStringifyListSep, line, parent);
8153 } else {
8154 self.compile_expr(e)?;
8155 }
8156 }
8157 }
8158 Ok(())
8159 }
8160
8161 fn compile_assign(
8162 &mut self,
8163 target: &Expr,
8164 line: usize,
8165 keep: bool,
8166 ast: Option<&Expr>,
8167 ) -> Result<(), CompileError> {
8168 match &target.kind {
8169 ExprKind::ScalarVar(name) => {
8170 self.check_strict_scalar_access(name, line)?;
8171 self.check_scalar_mutable(name, line)?;
8172 self.check_closure_write_to_outer_my(name, line)?;
8173 let idx = self.intern_scalar_var_for_ops(name);
8174 if keep {
8175 self.emit_set_scalar_keep(idx, line, ast);
8176 } else {
8177 self.emit_set_scalar(idx, line, ast);
8178 }
8179 }
8180 ExprKind::ArrayVar(name) => {
8181 self.check_strict_array_access(name, line)?;
8182 let q = self.qualify_stash_array_name(name);
8183 self.check_array_mutable(&q, line)?;
8184 let idx = self.chunk.intern_name(&q);
8185 self.emit_op(Op::SetArray(idx), line, ast);
8186 if keep {
8187 self.emit_op(Op::GetArray(idx), line, ast);
8188 }
8189 }
8190 ExprKind::HashVar(name) => {
8191 self.check_strict_hash_access(name, line)?;
8192 self.check_hash_mutable(name, line)?;
8193 let idx = self.chunk.intern_name(name);
8194 self.emit_op(Op::SetHash(idx), line, ast);
8195 if keep {
8196 self.emit_op(Op::GetHash(idx), line, ast);
8197 }
8198 }
8199 ExprKind::ArrayElement { array, index } => {
8200 self.check_strict_array_access(array, line)?;
8201 let q = self.qualify_stash_array_name(array);
8202 self.check_array_mutable(&q, line)?;
8203 let idx = self.chunk.intern_name(&q);
8204 self.compile_expr(index)?;
8205 self.emit_op(Op::SetArrayElem(idx), line, ast);
8206 }
8207 ExprKind::ArraySlice { array, indices } => {
8208 if indices.is_empty() {
8209 if self.is_mysync_array(array) {
8210 return Err(CompileError::Unsupported(
8211 "mysync array slice assign".into(),
8212 ));
8213 }
8214 self.check_strict_array_access(array, line)?;
8215 let q = self.qualify_stash_array_name(array);
8216 self.check_array_mutable(&q, line)?;
8217 let arr_idx = self.chunk.intern_name(&q);
8218 self.emit_op(Op::SetNamedArraySlice(arr_idx, 0), line, ast);
8219 if keep {
8220 self.emit_op(Op::MakeArray(0), line, ast);
8221 }
8222 return Ok(());
8223 }
8224 if self.is_mysync_array(array) {
8225 return Err(CompileError::Unsupported(
8226 "mysync array slice assign".into(),
8227 ));
8228 }
8229 self.check_strict_array_access(array, line)?;
8230 let q = self.qualify_stash_array_name(array);
8231 self.check_array_mutable(&q, line)?;
8232 let arr_idx = self.chunk.intern_name(&q);
8233 for ix in indices {
8234 self.compile_array_slice_index_expr(ix)?;
8235 }
8236 self.emit_op(
8237 Op::SetNamedArraySlice(arr_idx, indices.len() as u16),
8238 line,
8239 ast,
8240 );
8241 if keep {
8242 for (ix, index_expr) in indices.iter().enumerate() {
8243 self.compile_array_slice_index_expr(index_expr)?;
8244 self.emit_op(Op::ArraySlicePart(arr_idx), line, ast);
8245 if ix > 0 {
8246 self.emit_op(Op::ArrayConcatTwo, line, ast);
8247 }
8248 }
8249 }
8250 return Ok(());
8251 }
8252 ExprKind::HashElement { hash, key } => {
8253 self.check_strict_hash_access(hash, line)?;
8254 self.check_hash_mutable(hash, line)?;
8255 let idx = self.chunk.intern_name(hash);
8256 self.compile_expr(key)?;
8257 if keep {
8258 self.emit_op(Op::SetHashElemKeep(idx), line, ast);
8259 } else {
8260 self.emit_op(Op::SetHashElem(idx), line, ast);
8261 }
8262 }
8263 ExprKind::HashSlice { hash, keys } => {
8264 if keys.is_empty() {
8265 if self.is_mysync_hash(hash) {
8266 return Err(CompileError::Unsupported("mysync hash slice assign".into()));
8267 }
8268 self.check_strict_hash_access(hash, line)?;
8269 self.check_hash_mutable(hash, line)?;
8270 let hash_idx = self.chunk.intern_name(hash);
8271 self.emit_op(Op::SetHashSlice(hash_idx, 0), line, ast);
8272 if keep {
8273 self.emit_op(Op::MakeArray(0), line, ast);
8274 }
8275 return Ok(());
8276 }
8277 if self.is_mysync_hash(hash) {
8278 return Err(CompileError::Unsupported("mysync hash slice assign".into()));
8279 }
8280 self.check_strict_hash_access(hash, line)?;
8281 self.check_hash_mutable(hash, line)?;
8282 let hash_idx = self.chunk.intern_name(hash);
8283 for key_expr in keys {
8288 self.compile_hash_slice_key_expr(key_expr)?;
8289 }
8290 self.emit_op(Op::SetHashSlice(hash_idx, keys.len() as u16), line, ast);
8291 if keep {
8292 for key_expr in keys {
8293 self.compile_expr(key_expr)?;
8294 self.emit_op(Op::GetHashElem(hash_idx), line, ast);
8295 }
8296 self.emit_op(Op::MakeArray(keys.len() as u16), line, ast);
8297 }
8298 return Ok(());
8299 }
8300 ExprKind::Deref {
8301 expr,
8302 kind: Sigil::Scalar,
8303 } => {
8304 self.compile_expr(expr)?;
8305 if keep {
8306 self.emit_op(Op::SetSymbolicScalarRefKeep, line, ast);
8307 } else {
8308 self.emit_op(Op::SetSymbolicScalarRef, line, ast);
8309 }
8310 }
8311 ExprKind::Deref {
8312 expr,
8313 kind: Sigil::Array,
8314 } => {
8315 self.compile_expr(expr)?;
8316 self.emit_op(Op::SetSymbolicArrayRef, line, ast);
8317 }
8318 ExprKind::Deref {
8319 expr,
8320 kind: Sigil::Hash,
8321 } => {
8322 self.compile_expr(expr)?;
8323 self.emit_op(Op::SetSymbolicHashRef, line, ast);
8324 }
8325 ExprKind::Deref {
8326 expr,
8327 kind: Sigil::Typeglob,
8328 } => {
8329 self.compile_expr(expr)?;
8330 self.emit_op(Op::SetSymbolicTypeglobRef, line, ast);
8331 }
8332 ExprKind::Typeglob(name) => {
8333 let idx = self.chunk.intern_name(name);
8334 if keep {
8335 self.emit_op(Op::TypeglobAssignFromValue(idx), line, ast);
8336 } else {
8337 return Err(CompileError::Unsupported(
8338 "typeglob assign without keep (internal)".into(),
8339 ));
8340 }
8341 }
8342 ExprKind::AnonymousListSlice { source, indices } => {
8343 if let ExprKind::Deref {
8344 expr: inner,
8345 kind: Sigil::Array,
8346 } = &source.kind
8347 {
8348 if indices.is_empty() {
8349 return Err(CompileError::Unsupported(
8350 "assign to empty list slice (internal)".into(),
8351 ));
8352 }
8353 self.compile_arrow_array_base_expr(inner)?;
8354 for ix in indices {
8355 self.compile_array_slice_index_expr(ix)?;
8356 }
8357 self.emit_op(Op::SetArrowArraySlice(indices.len() as u16), line, ast);
8358 if keep {
8359 self.compile_arrow_array_base_expr(inner)?;
8360 for ix in indices {
8361 self.compile_array_slice_index_expr(ix)?;
8362 }
8363 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, ast);
8364 }
8365 return Ok(());
8366 }
8367 return Err(CompileError::Unsupported(
8368 "assign to anonymous list slice (non-@array-deref base)".into(),
8369 ));
8370 }
8371 ExprKind::ArrowDeref {
8372 expr,
8373 index,
8374 kind: DerefKind::Hash,
8375 } => {
8376 self.compile_arrow_hash_base_expr(expr)?;
8377 self.compile_expr(index)?;
8378 if keep {
8379 self.emit_op(Op::SetArrowHashKeep, line, ast);
8380 } else {
8381 self.emit_op(Op::SetArrowHash, line, ast);
8382 }
8383 }
8384 ExprKind::ArrowDeref {
8385 expr,
8386 index,
8387 kind: DerefKind::Array,
8388 } => {
8389 if let ExprKind::List(indices) = &index.kind {
8390 self.compile_arrow_array_base_expr(expr)?;
8395 for ix in indices {
8396 self.compile_array_slice_index_expr(ix)?;
8397 }
8398 self.emit_op(Op::SetArrowArraySlice(indices.len() as u16), line, ast);
8399 if keep {
8400 self.compile_arrow_array_base_expr(expr)?;
8402 for ix in indices {
8403 self.compile_array_slice_index_expr(ix)?;
8404 }
8405 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, ast);
8406 }
8407 return Ok(());
8408 }
8409 if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
8410 self.compile_arrow_array_base_expr(expr)?;
8411 self.compile_expr(index)?;
8412 if keep {
8413 self.emit_op(Op::SetArrowArrayKeep, line, ast);
8414 } else {
8415 self.emit_op(Op::SetArrowArray, line, ast);
8416 }
8417 } else {
8418 self.compile_arrow_array_base_expr(expr)?;
8419 self.compile_array_slice_index_expr(index)?;
8420 self.emit_op(Op::SetArrowArraySlice(1), line, ast);
8421 if keep {
8422 self.compile_arrow_array_base_expr(expr)?;
8423 self.compile_array_slice_index_expr(index)?;
8424 self.emit_op(Op::ArrowArraySlice(1), line, ast);
8425 }
8426 }
8427 }
8428 ExprKind::ArrowDeref {
8429 kind: DerefKind::Call,
8430 ..
8431 } => {
8432 return Err(CompileError::Unsupported(
8433 "Assign to arrow call deref".into(),
8434 ));
8435 }
8436 ExprKind::HashSliceDeref { container, keys } => {
8437 self.compile_expr(container)?;
8438 for key_expr in keys {
8439 self.compile_hash_slice_key_expr(key_expr)?;
8440 }
8441 self.emit_op(Op::SetHashSliceDeref(keys.len() as u16), line, ast);
8442 }
8443 ExprKind::Pos(inner) => {
8444 let Some(inner_e) = inner.as_ref() else {
8445 return Err(CompileError::Unsupported(
8446 "assign to pos() without scalar".into(),
8447 ));
8448 };
8449 if keep {
8450 self.emit_op(Op::Dup, line, ast);
8451 }
8452 match &inner_e.kind {
8453 ExprKind::ScalarVar(name) => {
8454 let stor = self.scalar_storage_name_for_ops(name);
8455 let idx = self.chunk.add_constant(StrykeValue::string(stor));
8456 self.emit_op(Op::LoadConst(idx), line, ast);
8457 }
8458 _ => {
8459 self.compile_expr(inner_e)?;
8460 }
8461 }
8462 self.emit_op(Op::SetRegexPos, line, ast);
8463 }
8464 ExprKind::List(targets) => {
8467 let tmp = self.chunk.intern_name("__list_assign_swap__");
8468 self.emit_op(Op::DeclareArray(tmp), line, ast);
8469 for (i, t) in targets.iter().enumerate() {
8470 self.emit_op(Op::LoadInt(i as i64), line, ast);
8471 self.emit_op(Op::GetArrayElem(tmp), line, ast);
8472 self.compile_assign(t, line, false, ast)?;
8473 }
8474 if keep {
8475 self.emit_op(Op::GetArray(tmp), line, ast);
8476 }
8477 }
8478 _ => {
8479 return Err(CompileError::Unsupported("Assign to complex lvalue".into()));
8480 }
8481 }
8482 Ok(())
8483 }
8484}
8485
8486pub(crate) fn binop_to_vm_op(op: BinOp) -> Option<Op> {
8488 Some(match op {
8489 BinOp::Add => Op::Add,
8490 BinOp::Sub => Op::Sub,
8491 BinOp::Mul => Op::Mul,
8492 BinOp::Div => Op::Div,
8493 BinOp::Mod => Op::Mod,
8494 BinOp::Pow => Op::Pow,
8495 BinOp::Concat => Op::Concat,
8496 BinOp::BitAnd => Op::BitAnd,
8497 BinOp::BitOr => Op::BitOr,
8498 BinOp::BitXor => Op::BitXor,
8499 BinOp::ShiftLeft => Op::Shl,
8500 BinOp::ShiftRight => Op::Shr,
8501 _ => return None,
8502 })
8503}
8504
8505pub(crate) fn scalar_compound_op_to_byte(op: BinOp) -> Option<u8> {
8507 Some(match op {
8508 BinOp::Add => 0,
8509 BinOp::Sub => 1,
8510 BinOp::Mul => 2,
8511 BinOp::Div => 3,
8512 BinOp::Mod => 4,
8513 BinOp::Pow => 5,
8514 BinOp::Concat => 6,
8515 BinOp::BitAnd => 7,
8516 BinOp::BitOr => 8,
8517 BinOp::BitXor => 9,
8518 BinOp::ShiftLeft => 10,
8519 BinOp::ShiftRight => 11,
8520 _ => return None,
8521 })
8522}
8523
8524pub(crate) fn scalar_compound_op_from_byte(b: u8) -> Option<BinOp> {
8525 Some(match b {
8526 0 => BinOp::Add,
8527 1 => BinOp::Sub,
8528 2 => BinOp::Mul,
8529 3 => BinOp::Div,
8530 4 => BinOp::Mod,
8531 5 => BinOp::Pow,
8532 6 => BinOp::Concat,
8533 7 => BinOp::BitAnd,
8534 8 => BinOp::BitOr,
8535 9 => BinOp::BitXor,
8536 10 => BinOp::ShiftLeft,
8537 11 => BinOp::ShiftRight,
8538 _ => return None,
8539 })
8540}
8541
8542#[cfg(test)]
8543mod tests {
8544 use super::*;
8545 use crate::bytecode::{BuiltinId, Op, GP_RUN};
8546 use crate::parse;
8547
8548 fn compile_snippet(code: &str) -> Result<Chunk, CompileError> {
8549 let program = parse(code).expect("parse snippet");
8550 Compiler::new().compile_program(&program)
8551 }
8552
8553 fn assert_last_halt(chunk: &Chunk) {
8554 assert!(
8555 matches!(chunk.ops.last(), Some(Op::Halt)),
8556 "expected Halt last, got {:?}",
8557 chunk.ops.last()
8558 );
8559 }
8560
8561 #[test]
8562 fn compile_empty_program_emits_run_phase_then_halt() {
8563 let chunk = compile_snippet("").expect("compile");
8564 assert_eq!(chunk.ops.len(), 2);
8565 assert!(matches!(chunk.ops[0], Op::SetGlobalPhase(p) if p == GP_RUN));
8566 assert!(matches!(chunk.ops[1], Op::Halt));
8567 }
8568
8569 #[test]
8570 fn compile_struct_method_fn_eq_shorthand() {
8571 compile_snippet("struct CmpS { fn triple($n) = $n * 3 }").expect("compile");
8572 }
8573
8574 #[test]
8575 fn compile_integer_literal_statement() {
8576 let chunk = compile_snippet("42;").expect("compile");
8577 assert!(chunk.ops.iter().any(|o| matches!(o, Op::LoadInt(42))));
8578 assert_last_halt(&chunk);
8579 }
8580
8581 #[test]
8582 fn compile_pos_assign_emits_set_regex_pos() {
8583 let chunk = compile_snippet(r#"$_ = ""; pos = 3;"#).expect("compile");
8584 assert!(
8585 chunk.ops.iter().any(|o| matches!(o, Op::SetRegexPos)),
8586 "expected SetRegexPos in {:?}",
8587 chunk.ops
8588 );
8589 }
8590
8591 #[test]
8592 fn compile_pos_deref_scalar_assign_emits_set_regex_pos() {
8593 let chunk = compile_snippet(
8594 r#"no strict 'vars';
8595 my $s;
8596 my $r = \$s;
8597 pos $$r = 0;"#,
8598 )
8599 .expect("compile");
8600 assert!(
8601 chunk.ops.iter().any(|o| matches!(o, Op::SetRegexPos)),
8602 r"expected SetRegexPos for pos $$r =, got {:?}",
8603 chunk.ops
8604 );
8605 }
8606
8607 #[test]
8608 fn compile_map_expr_comma_emits_map_with_expr() {
8609 let chunk = compile_snippet(
8610 r#"no strict 'vars';
8611 (map $_ + 1, (4, 5)) |> join ','"#,
8612 )
8613 .expect("compile");
8614 assert!(
8615 chunk.ops.iter().any(|o| matches!(o, Op::MapWithExpr(_))),
8616 "expected MapWithExpr, got {:?}",
8617 chunk.ops
8618 );
8619 }
8620
8621 #[test]
8622 fn compile_hash_slice_deref_assign_emits_set_op() {
8623 let code = r#"no strict 'vars';
8624 my $h = { "a" => 1, "b" => 2 };
8625 my $r = $h;
8626 @$r{"a", "b"} = (10, 20);
8627 $r->{"a"} . "," . $r->{"b"};"#;
8628 let chunk = compile_snippet(code).expect("compile");
8629 assert!(
8630 chunk
8631 .ops
8632 .iter()
8633 .any(|o| matches!(o, Op::SetHashSliceDeref(n) if *n == 2)),
8634 "expected SetHashSliceDeref(2), got {:?}",
8635 chunk.ops
8636 );
8637 }
8638
8639 #[test]
8640 fn compile_bare_array_assign_diamond_uses_readline_list() {
8641 let chunk = compile_snippet("@a = <>;").expect("compile");
8642 assert!(
8643 chunk.ops.iter().any(|o| matches!(
8644 o,
8645 Op::CallBuiltin(bid, 0) if *bid == BuiltinId::ReadLineList as u16
8646 )),
8647 "expected ReadLineList for bare @a = <>, got {:?}",
8648 chunk.ops
8649 );
8650 }
8651
8652 #[test]
8653 fn compile_float_literal() {
8654 let chunk = compile_snippet("3.25;").expect("compile");
8655 assert!(chunk
8656 .ops
8657 .iter()
8658 .any(|o| matches!(o, Op::LoadFloat(f) if (*f - 3.25).abs() < 1e-9)));
8659 assert_last_halt(&chunk);
8660 }
8661
8662 #[test]
8663 fn compile_addition() {
8664 let chunk = compile_snippet("1 + 2;").expect("compile");
8665 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Add)));
8666 assert_last_halt(&chunk);
8667 }
8668
8669 #[test]
8670 fn compile_sub_mul_div_mod_pow() {
8671 for (src, op) in [
8672 ("10 - 3;", "Sub"),
8673 ("6 * 7;", "Mul"),
8674 ("8 / 2;", "Div"),
8675 ("9 % 4;", "Mod"),
8676 ("2 ** 8;", "Pow"),
8677 ] {
8678 let chunk = compile_snippet(src).expect(src);
8679 assert!(
8680 chunk.ops.iter().any(|o| std::mem::discriminant(o) == {
8681 let dummy = match op {
8682 "Sub" => Op::Sub,
8683 "Mul" => Op::Mul,
8684 "Div" => Op::Div,
8685 "Mod" => Op::Mod,
8686 "Pow" => Op::Pow,
8687 _ => unreachable!(),
8688 };
8689 std::mem::discriminant(&dummy)
8690 }),
8691 "{} missing {:?}",
8692 src,
8693 op
8694 );
8695 assert_last_halt(&chunk);
8696 }
8697 }
8698
8699 #[test]
8700 fn compile_string_literal_uses_constant_pool() {
8701 let chunk = compile_snippet(r#""hello";"#).expect("compile");
8702 assert!(chunk
8703 .constants
8704 .iter()
8705 .any(|c| c.as_str().as_deref() == Some("hello")));
8706 assert!(chunk.ops.iter().any(|o| matches!(o, Op::LoadConst(_))));
8707 assert_last_halt(&chunk);
8708 }
8709
8710 #[test]
8711 fn compile_substitution_bind_emits_regex_subst() {
8712 let chunk = compile_snippet(r#"my $s = "aa"; $s =~ s/a/b/g;"#).expect("compile");
8713 assert!(
8714 chunk
8715 .ops
8716 .iter()
8717 .any(|o| matches!(o, Op::RegexSubst(_, _, _, _))),
8718 "expected RegexSubst in {:?}",
8719 chunk.ops
8720 );
8721 assert!(!chunk.lvalues.is_empty());
8722 }
8723
8724 #[test]
8725 fn compile_chomp_emits_chomp_in_place() {
8726 let chunk = compile_snippet(r#"my $s = "x\n"; chomp $s;"#).expect("compile");
8727 assert!(
8728 chunk.ops.iter().any(|o| matches!(o, Op::ChompInPlace(_))),
8729 "expected ChompInPlace, got {:?}",
8730 chunk.ops
8731 );
8732 }
8733
8734 #[test]
8735 fn compile_transliterate_bind_emits_regex_transliterate() {
8736 let chunk = compile_snippet(r#"my $u = "abc"; $u =~ tr/a-z/A-Z/;"#).expect("compile");
8737 assert!(
8738 chunk
8739 .ops
8740 .iter()
8741 .any(|o| matches!(o, Op::RegexTransliterate(_, _, _, _))),
8742 "expected RegexTransliterate in {:?}",
8743 chunk.ops
8744 );
8745 assert!(!chunk.lvalues.is_empty());
8746 }
8747
8748 #[test]
8749 fn compile_negation() {
8750 let chunk = compile_snippet("-7;").expect("compile");
8751 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Negate)));
8752 assert_last_halt(&chunk);
8753 }
8754
8755 #[test]
8756 fn compile_my_scalar_declares() {
8757 let chunk = compile_snippet("my $x = 1;").expect("compile");
8758 assert!(chunk
8759 .ops
8760 .iter()
8761 .any(|o| matches!(o, Op::DeclareScalar(_) | Op::DeclareScalarSlot(_, _))));
8762 assert_last_halt(&chunk);
8763 }
8764
8765 #[test]
8766 fn compile_scalar_fetch_and_assign() {
8767 let chunk = compile_snippet("my $a = 1; $a + 0;").expect("compile");
8768 assert!(
8769 chunk
8770 .ops
8771 .iter()
8772 .filter(|o| matches!(
8773 o,
8774 Op::GetScalar(_) | Op::GetScalarPlain(_) | Op::GetScalarSlot(_)
8775 ))
8776 .count()
8777 >= 1
8778 );
8779 assert_last_halt(&chunk);
8780 }
8781
8782 #[test]
8783 fn compile_plain_scalar_read_emits_get_scalar_plain() {
8784 let chunk = compile_snippet("my $a = 1; $a + 0;").expect("compile");
8785 assert!(
8786 chunk
8787 .ops
8788 .iter()
8789 .any(|o| matches!(o, Op::GetScalarPlain(_) | Op::GetScalarSlot(_))),
8790 "expected GetScalarPlain or GetScalarSlot for non-special $a, ops={:?}",
8791 chunk.ops
8792 );
8793 }
8794
8795 #[test]
8796 fn compile_sub_postfix_inc_emits_post_inc_slot() {
8797 let chunk = compile_snippet("fn foo { my $x = 0; $x++; return $x; }").expect("compile");
8798 assert!(
8799 chunk.ops.iter().any(|o| matches!(o, Op::PostIncSlot(_))),
8800 "expected PostIncSlot in compiled sub body, ops={:?}",
8801 chunk.ops
8802 );
8803 }
8804
8805 #[test]
8806 fn compile_comparison_ops_numeric() {
8807 for src in [
8808 "1 < 2;", "1 > 2;", "1 <= 2;", "1 >= 2;", "1 == 2;", "1 != 2;",
8809 ] {
8810 let chunk = compile_snippet(src).expect(src);
8811 assert!(
8812 chunk.ops.iter().any(|o| {
8813 matches!(
8814 o,
8815 Op::NumLt | Op::NumGt | Op::NumLe | Op::NumGe | Op::NumEq | Op::NumNe
8816 )
8817 }),
8818 "{}",
8819 src
8820 );
8821 assert_last_halt(&chunk);
8822 }
8823 }
8824
8825 #[test]
8826 fn compile_string_compare_ops() {
8827 for src in [
8828 r#"'a' lt 'b';"#,
8829 r#"'a' gt 'b';"#,
8830 r#"'a' le 'b';"#,
8831 r#"'a' ge 'b';"#,
8832 ] {
8833 let chunk = compile_snippet(src).expect(src);
8834 assert!(
8835 chunk
8836 .ops
8837 .iter()
8838 .any(|o| matches!(o, Op::StrLt | Op::StrGt | Op::StrLe | Op::StrGe)),
8839 "{}",
8840 src
8841 );
8842 assert_last_halt(&chunk);
8843 }
8844 }
8845
8846 #[test]
8847 fn compile_concat() {
8848 let chunk = compile_snippet(r#"'a' . 'b';"#).expect("compile");
8849 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Concat)));
8850 assert_last_halt(&chunk);
8851 }
8852
8853 #[test]
8854 fn compile_bitwise_ops() {
8855 let chunk = compile_snippet("1 & 2 | 3 ^ 4;").expect("compile");
8856 assert!(chunk.ops.iter().any(|o| matches!(o, Op::BitAnd)));
8857 assert!(chunk.ops.iter().any(|o| matches!(o, Op::BitOr)));
8858 assert!(chunk.ops.iter().any(|o| matches!(o, Op::BitXor)));
8859 assert_last_halt(&chunk);
8860 }
8861
8862 #[test]
8863 fn compile_shift_right() {
8864 let chunk = compile_snippet("8 >> 1;").expect("compile");
8865 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Shr)));
8866 assert_last_halt(&chunk);
8867 }
8868
8869 #[test]
8870 fn compile_shift_left() {
8871 let chunk = compile_snippet("1 << 4;").expect("compile");
8872 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Shl)));
8873 assert_last_halt(&chunk);
8874 }
8875
8876 #[test]
8877 fn compile_log_not_and_bit_not() {
8878 let c1 = compile_snippet("!0;").expect("compile");
8879 assert!(c1.ops.iter().any(|o| matches!(o, Op::LogNot)));
8880 let c2 = compile_snippet("~0;").expect("compile");
8881 assert!(c2.ops.iter().any(|o| matches!(o, Op::BitNot)));
8882 }
8883
8884 #[test]
8885 fn compile_sub_registers_name_and_entry() {
8886 let chunk = compile_snippet("fn foo { return 1; }").expect("compile");
8887 assert!(chunk.names.iter().any(|n| n == "foo"));
8888 assert!(chunk
8889 .sub_entries
8890 .iter()
8891 .any(|&(idx, ip, _)| chunk.names[idx as usize] == "foo" && ip > 0));
8892 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Halt)));
8893 assert!(chunk.ops.iter().any(|o| matches!(o, Op::ReturnValue)));
8894 }
8895
8896 #[test]
8897 fn compile_postinc_scalar() {
8898 let chunk = compile_snippet("my $n = 1; $n++;").expect("compile");
8899 assert!(chunk
8900 .ops
8901 .iter()
8902 .any(|o| matches!(o, Op::PostInc(_) | Op::PostIncSlot(_))));
8903 assert_last_halt(&chunk);
8904 }
8905
8906 #[test]
8907 fn compile_preinc_scalar() {
8908 let chunk = compile_snippet("my $n = 1; ++$n;").expect("compile");
8909 assert!(chunk
8910 .ops
8911 .iter()
8912 .any(|o| matches!(o, Op::PreInc(_) | Op::PreIncSlot(_))));
8913 assert_last_halt(&chunk);
8914 }
8915
8916 #[test]
8917 fn compile_if_expression_value() {
8918 let chunk = compile_snippet("if (1) { 2 } else { 3 }").expect("compile");
8919 assert!(chunk.ops.iter().any(|o| matches!(o, Op::JumpIfFalse(_))));
8920 assert_last_halt(&chunk);
8921 }
8922
8923 #[test]
8924 fn compile_unless_expression_value() {
8925 let chunk = compile_snippet("unless (0) { 1 } else { 2 }").expect("compile");
8926 assert!(chunk.ops.iter().any(|o| matches!(o, Op::JumpIfFalse(_))));
8927 assert_last_halt(&chunk);
8928 }
8929
8930 #[test]
8931 fn compile_array_declare_and_push() {
8932 let chunk = compile_snippet("my @a; push @a, 1;").expect("compile");
8933 assert!(chunk.ops.iter().any(|o| matches!(o, Op::DeclareArray(_))));
8934 assert_last_halt(&chunk);
8935 }
8936
8937 #[test]
8938 fn compile_ternary() {
8939 let chunk = compile_snippet("1 ? 2 : 3;").expect("compile");
8940 assert!(chunk.ops.iter().any(|o| matches!(o, Op::JumpIfFalse(_))));
8941 assert_last_halt(&chunk);
8942 }
8943
8944 #[test]
8945 fn compile_repeat_operator() {
8946 let chunk = compile_snippet(r#"'ab' x 3;"#).expect("compile");
8947 assert!(chunk.ops.iter().any(|o| matches!(o, Op::StringRepeat)));
8948 assert_last_halt(&chunk);
8949 }
8950
8951 #[test]
8952 fn compile_range_to_array() {
8953 let chunk = compile_snippet("my @a = (1..3);").expect("compile");
8954 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Range)));
8955 assert_last_halt(&chunk);
8956 }
8957
8958 #[test]
8960 fn compile_print_if_uses_scalar_flipflop_not_range_list() {
8961 let chunk = compile_snippet("print if 1..2;").expect("compile");
8962 assert!(
8963 chunk
8964 .ops
8965 .iter()
8966 .any(|o| matches!(o, Op::ScalarFlipFlop(_, 0))),
8967 "expected ScalarFlipFlop in bytecode, got:\n{}",
8968 chunk.disassemble()
8969 );
8970 assert!(
8971 !chunk.ops.iter().any(|o| matches!(o, Op::Range)),
8972 "did not expect list Range op in scalar if-condition:\n{}",
8973 chunk.disassemble()
8974 );
8975 }
8976
8977 #[test]
8978 fn compile_print_if_three_dot_scalar_flipflop_sets_exclusive_flag() {
8979 let chunk = compile_snippet("print if 1...2;").expect("compile");
8980 assert!(
8981 chunk
8982 .ops
8983 .iter()
8984 .any(|o| matches!(o, Op::ScalarFlipFlop(_, 1))),
8985 "expected ScalarFlipFlop(..., exclusive=1), got:\n{}",
8986 chunk.disassemble()
8987 );
8988 }
8989
8990 #[test]
8991 fn compile_regex_flipflop_two_dot_emits_regex_flipflop_op() {
8992 let chunk = compile_snippet(r#"print if /a/../b/;"#).expect("compile");
8993 assert!(
8994 chunk
8995 .ops
8996 .iter()
8997 .any(|o| matches!(o, Op::RegexFlipFlop(_, 0, _, _, _, _))),
8998 "expected RegexFlipFlop(.., exclusive=0), got:\n{}",
8999 chunk.disassemble()
9000 );
9001 assert!(
9002 !chunk
9003 .ops
9004 .iter()
9005 .any(|o| matches!(o, Op::ScalarFlipFlop(_, _))),
9006 "regex flip-flop must not use ScalarFlipFlop:\n{}",
9007 chunk.disassemble()
9008 );
9009 }
9010
9011 #[test]
9012 fn compile_regex_flipflop_three_dot_sets_exclusive_flag() {
9013 let chunk = compile_snippet(r#"print if /a/.../b/;"#).expect("compile");
9014 assert!(
9015 chunk
9016 .ops
9017 .iter()
9018 .any(|o| matches!(o, Op::RegexFlipFlop(_, 1, _, _, _, _))),
9019 "expected RegexFlipFlop(..., exclusive=1), got:\n{}",
9020 chunk.disassemble()
9021 );
9022 }
9023
9024 #[test]
9025 fn compile_regex_eof_flipflop_emits_regex_eof_flipflop_op() {
9026 let chunk = compile_snippet(r#"print if /a/..eof;"#).expect("compile");
9027 assert!(
9028 chunk
9029 .ops
9030 .iter()
9031 .any(|o| matches!(o, Op::RegexEofFlipFlop(_, 0, _, _))),
9032 "expected RegexEofFlipFlop(.., exclusive=0), got:\n{}",
9033 chunk.disassemble()
9034 );
9035 assert!(
9036 !chunk
9037 .ops
9038 .iter()
9039 .any(|o| matches!(o, Op::ScalarFlipFlop(_, _))),
9040 "regex/eof flip-flop must not use ScalarFlipFlop:\n{}",
9041 chunk.disassemble()
9042 );
9043 }
9044
9045 #[test]
9046 fn compile_regex_eof_flipflop_three_dot_sets_exclusive_flag() {
9047 let chunk = compile_snippet(r#"print if /a/...eof;"#).expect("compile");
9048 assert!(
9049 chunk
9050 .ops
9051 .iter()
9052 .any(|o| matches!(o, Op::RegexEofFlipFlop(_, 1, _, _))),
9053 "expected RegexEofFlipFlop(..., exclusive=1), got:\n{}",
9054 chunk.disassemble()
9055 );
9056 }
9057
9058 #[test]
9059 fn compile_regex_flipflop_compound_rhs_emits_regex_flip_flop_expr_rhs() {
9060 let chunk = compile_snippet(r#"print if /a/...(/b/ or /c/);"#).expect("compile");
9061 assert!(
9062 chunk
9063 .ops
9064 .iter()
9065 .any(|o| matches!(o, Op::RegexFlipFlopExprRhs(_, _, _, _, _))),
9066 "expected RegexFlipFlopExprRhs for compound RHS, got:\n{}",
9067 chunk.disassemble()
9068 );
9069 assert!(
9070 !chunk
9071 .ops
9072 .iter()
9073 .any(|o| matches!(o, Op::ScalarFlipFlop(_, _))),
9074 "compound regex flip-flop must not use ScalarFlipFlop:\n{}",
9075 chunk.disassemble()
9076 );
9077 }
9078
9079 #[test]
9080 fn compile_print_statement() {
9081 let chunk = compile_snippet("print 1;").expect("compile");
9082 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Print(_, _))));
9083 assert_last_halt(&chunk);
9084 }
9085
9086 #[test]
9087 fn compile_defined_builtin() {
9088 let chunk = compile_snippet("defined 1;").expect("compile");
9089 assert!(chunk
9090 .ops
9091 .iter()
9092 .any(|o| matches!(o, Op::CallBuiltin(id, _) if *id == BuiltinId::Defined as u16)));
9093 assert_last_halt(&chunk);
9094 }
9095
9096 #[test]
9097 fn compile_length_builtin() {
9098 let chunk = compile_snippet("length 'abc';").expect("compile");
9099 assert!(chunk
9100 .ops
9101 .iter()
9102 .any(|o| matches!(o, Op::CallBuiltin(id, _) if *id == BuiltinId::Length as u16)));
9103 assert_last_halt(&chunk);
9104 }
9105
9106 #[test]
9107 fn compile_complex_expr_parentheses() {
9108 let chunk = compile_snippet("(1 + 2) * (3 + 4);").expect("compile");
9109 assert!(chunk.ops.iter().filter(|o| matches!(o, Op::Add)).count() >= 2);
9110 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Mul)));
9111 assert_last_halt(&chunk);
9112 }
9113
9114 #[test]
9115 fn compile_undef_literal() {
9116 let chunk = compile_snippet("undef;").expect("compile");
9117 assert!(chunk.ops.iter().any(|o| matches!(o, Op::LoadUndef)));
9118 assert_last_halt(&chunk);
9119 }
9120
9121 #[test]
9122 fn compile_empty_statement_semicolons() {
9123 let chunk = compile_snippet(";;;").expect("compile");
9124 assert_last_halt(&chunk);
9125 }
9126
9127 #[test]
9128 fn compile_array_elem_preinc_uses_rot_and_set_elem() {
9129 let chunk = compile_snippet("my @a; $a[0] = 0; ++$a[0];").expect("compile");
9130 assert!(
9131 chunk.ops.iter().any(|o| matches!(o, Op::Rot)),
9132 "expected Rot in {:?}",
9133 chunk.ops
9134 );
9135 assert!(
9136 chunk.ops.iter().any(|o| matches!(o, Op::SetArrayElem(_))),
9137 "expected SetArrayElem in {:?}",
9138 chunk.ops
9139 );
9140 assert_last_halt(&chunk);
9141 }
9142
9143 #[test]
9144 fn compile_hash_elem_compound_assign_uses_rot() {
9145 let chunk = compile_snippet("my %h; $h{0} = 1; $h{0} += 2;").expect("compile");
9146 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Rot)));
9147 assert!(
9148 chunk.ops.iter().any(|o| matches!(o, Op::SetHashElem(_))),
9149 "expected SetHashElem"
9150 );
9151 assert_last_halt(&chunk);
9152 }
9153
9154 #[test]
9155 fn compile_postfix_inc_array_elem_emits_rot() {
9156 let chunk = compile_snippet("my @a; $a[1] = 5; $a[1]++;").expect("compile");
9157 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Rot)));
9158 assert_last_halt(&chunk);
9159 }
9160
9161 #[test]
9162 fn compile_tie_stmt_emits_op_tie() {
9163 let chunk = compile_snippet("tie %h, 'Pkg';").expect("compile");
9164 assert!(
9165 chunk.ops.iter().any(|o| matches!(o, Op::Tie { .. })),
9166 "expected Op::Tie in {:?}",
9167 chunk.ops
9168 );
9169 assert_last_halt(&chunk);
9170 }
9171
9172 #[test]
9173 fn compile_format_decl_emits_format_decl_op() {
9174 let chunk = compile_snippet(
9175 r#"
9176format FMT =
9177literal line
9178.
91791;
9180"#,
9181 )
9182 .expect("compile");
9183 assert!(
9184 chunk.ops.iter().any(|o| matches!(o, Op::FormatDecl(0))),
9185 "expected Op::FormatDecl(0), got {:?}",
9186 chunk.ops
9187 );
9188 assert_eq!(chunk.format_decls.len(), 1);
9189 assert_eq!(chunk.format_decls[0].0, "FMT");
9190 assert_eq!(chunk.format_decls[0].1, vec!["literal line".to_string()]);
9191 assert_last_halt(&chunk);
9192 }
9193
9194 #[test]
9195 fn compile_interpolated_string_scalar_only_emits_empty_prefix_and_concat() {
9196 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 1; "$x";"#).expect("compile");
9197 let empty_idx = chunk
9198 .constants
9199 .iter()
9200 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
9201 .expect("empty string in pool") as u16;
9202 assert!(
9203 chunk
9204 .ops
9205 .iter()
9206 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
9207 "expected LoadConst(\"\"), ops={:?}",
9208 chunk.ops
9209 );
9210 assert!(
9211 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
9212 "expected Op::Concat for qq with only a scalar part, ops={:?}",
9213 chunk.ops
9214 );
9215 assert_last_halt(&chunk);
9216 }
9217
9218 #[test]
9219 fn compile_interpolated_string_array_only_emits_stringify_and_concat() {
9220 let chunk = compile_snippet(r#"no strict 'vars'; my @a = (1, 2); "@a";"#).expect("compile");
9221 let empty_idx = chunk
9222 .constants
9223 .iter()
9224 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
9225 .expect("empty string in pool") as u16;
9226 assert!(
9227 chunk
9228 .ops
9229 .iter()
9230 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
9231 "expected LoadConst(\"\"), ops={:?}",
9232 chunk.ops
9233 );
9234 assert!(
9235 chunk
9236 .ops
9237 .iter()
9238 .any(|o| matches!(o, Op::ArrayStringifyListSep)),
9239 "expected ArrayStringifyListSep for array var in qq, ops={:?}",
9240 chunk.ops
9241 );
9242 assert!(
9243 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
9244 "expected Op::Concat after array stringify, ops={:?}",
9245 chunk.ops
9246 );
9247 assert_last_halt(&chunk);
9248 }
9249
9250 #[test]
9251 fn compile_interpolated_string_hash_element_only_emits_empty_prefix_and_concat() {
9252 let chunk =
9253 compile_snippet(r#"no strict 'vars'; my %h = (k => 1); "$h{k}";"#).expect("compile");
9254 let empty_idx = chunk
9255 .constants
9256 .iter()
9257 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
9258 .expect("empty string in pool") as u16;
9259 assert!(
9260 chunk
9261 .ops
9262 .iter()
9263 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
9264 "expected LoadConst(\"\"), ops={:?}",
9265 chunk.ops
9266 );
9267 assert!(
9268 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
9269 "expected Op::Concat for qq with only an expr part, ops={:?}",
9270 chunk.ops
9271 );
9272 assert_last_halt(&chunk);
9273 }
9274
9275 #[test]
9276 fn compile_interpolated_string_leading_literal_has_no_empty_string_prefix() {
9277 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 1; "a$x";"#).expect("compile");
9278 assert!(
9279 !chunk
9280 .constants
9281 .iter()
9282 .any(|c| c.as_str().is_some_and(|s| s.is_empty())),
9283 "literal-first qq must not intern \"\" (only non-literal first parts need it), ops={:?}",
9284 chunk.ops
9285 );
9286 assert!(
9287 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
9288 "expected Op::Concat after literal + scalar, ops={:?}",
9289 chunk.ops
9290 );
9291 assert_last_halt(&chunk);
9292 }
9293
9294 #[test]
9295 fn compile_interpolated_string_two_scalars_empty_prefix_and_two_concats() {
9296 let chunk =
9297 compile_snippet(r#"no strict 'vars'; my $a = 1; my $b = 2; "$a$b";"#).expect("compile");
9298 let empty_idx = chunk
9299 .constants
9300 .iter()
9301 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
9302 .expect("empty string in pool") as u16;
9303 assert!(
9304 chunk
9305 .ops
9306 .iter()
9307 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
9308 "expected LoadConst(\"\") before first scalar qq part, ops={:?}",
9309 chunk.ops
9310 );
9311 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
9312 assert!(
9313 n_concat >= 2,
9314 "expected at least two Op::Concat for two scalar qq parts, got {} in {:?}",
9315 n_concat,
9316 chunk.ops
9317 );
9318 assert_last_halt(&chunk);
9319 }
9320
9321 #[test]
9322 fn compile_interpolated_string_literal_then_two_scalars_has_no_empty_prefix() {
9323 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 7; my $y = 8; "p$x$y";"#)
9324 .expect("compile");
9325 assert!(
9326 !chunk
9327 .constants
9328 .iter()
9329 .any(|c| c.as_str().is_some_and(|s| s.is_empty())),
9330 "literal-first qq must not intern empty string, ops={:?}",
9331 chunk.ops
9332 );
9333 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
9334 assert!(
9335 n_concat >= 2,
9336 "expected two Concats for literal + two scalars, got {} in {:?}",
9337 n_concat,
9338 chunk.ops
9339 );
9340 assert_last_halt(&chunk);
9341 }
9342
9343 #[test]
9344 fn compile_interpolated_string_braced_scalar_trailing_literal_emits_concats() {
9345 let chunk = compile_snippet(r#"no strict 'vars'; my $u = 1; "a${u}z";"#).expect("compile");
9346 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
9347 assert!(
9348 n_concat >= 2,
9349 "expected braced scalar + trailing literal to use multiple Concats, got {} in {:?}",
9350 n_concat,
9351 chunk.ops
9352 );
9353 assert_last_halt(&chunk);
9354 }
9355
9356 #[test]
9357 fn compile_interpolated_string_braced_scalar_sandwiched_emits_concats() {
9358 let chunk = compile_snippet(r#"no strict 'vars'; my $u = 1; "L${u}R";"#).expect("compile");
9359 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
9360 assert!(
9361 n_concat >= 2,
9362 "expected leading literal + braced scalar + trailing literal to use multiple Concats, got {} in {:?}",
9363 n_concat,
9364 chunk.ops
9365 );
9366 assert_last_halt(&chunk);
9367 }
9368
9369 #[test]
9370 fn compile_interpolated_string_mixed_braced_and_plain_scalars_emits_concats() {
9371 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 1; my $y = 2; "a${x}b$y";"#)
9372 .expect("compile");
9373 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
9374 assert!(
9375 n_concat >= 3,
9376 "expected literal/braced/plain qq mix to use at least three Concats, got {} in {:?}",
9377 n_concat,
9378 chunk.ops
9379 );
9380 assert_last_halt(&chunk);
9381 }
9382
9383 #[test]
9384 fn compile_use_overload_emits_use_overload_op() {
9385 let chunk = compile_snippet(r#"use overload '""' => 'as_string';"#).expect("compile");
9386 assert!(
9387 chunk.ops.iter().any(|o| matches!(o, Op::UseOverload(0))),
9388 "expected Op::UseOverload(0), got {:?}",
9389 chunk.ops
9390 );
9391 assert_eq!(chunk.use_overload_entries.len(), 1);
9392 let stringify_key: String = ['"', '"'].iter().collect();
9395 assert_eq!(
9396 chunk.use_overload_entries[0],
9397 vec![(stringify_key, "as_string".to_string())]
9398 );
9399 assert_last_halt(&chunk);
9400 }
9401
9402 #[test]
9403 fn compile_use_overload_empty_list_emits_use_overload_with_no_pairs() {
9404 let chunk = compile_snippet(r#"use overload ();"#).expect("compile");
9405 assert!(
9406 chunk.ops.iter().any(|o| matches!(o, Op::UseOverload(0))),
9407 "expected Op::UseOverload(0), got {:?}",
9408 chunk.ops
9409 );
9410 assert_eq!(chunk.use_overload_entries.len(), 1);
9411 assert!(chunk.use_overload_entries[0].is_empty());
9412 assert_last_halt(&chunk);
9413 }
9414
9415 #[test]
9416 fn compile_use_overload_multiple_pairs_single_op() {
9417 let chunk =
9418 compile_snippet(r#"use overload '+' => 'p_add', '-' => 'p_sub';"#).expect("compile");
9419 assert!(
9420 chunk.ops.iter().any(|o| matches!(o, Op::UseOverload(0))),
9421 "expected Op::UseOverload(0), got {:?}",
9422 chunk.ops
9423 );
9424 assert_eq!(chunk.use_overload_entries.len(), 1);
9425 assert_eq!(
9426 chunk.use_overload_entries[0],
9427 vec![
9428 ("+".to_string(), "p_add".to_string()),
9429 ("-".to_string(), "p_sub".to_string()),
9430 ]
9431 );
9432 assert_last_halt(&chunk);
9433 }
9434
9435 #[test]
9436 fn compile_open_my_fh_emits_declare_open_set() {
9437 let chunk = compile_snippet(r#"open my $fh, "<", "/dev/null";"#).expect("compile");
9438 assert!(
9439 chunk.ops.iter().any(|o| matches!(
9440 o,
9441 Op::CallBuiltin(b, 3) if *b == BuiltinId::Open as u16
9442 )),
9443 "expected Open builtin 3-arg, got {:?}",
9444 chunk.ops
9445 );
9446 assert!(
9447 chunk
9448 .ops
9449 .iter()
9450 .any(|o| matches!(o, Op::SetScalarKeepPlain(_))),
9451 "expected SetScalarKeepPlain after open"
9452 );
9453 assert_last_halt(&chunk);
9454 }
9455
9456 #[test]
9457 fn compile_local_hash_element_emits_local_declare_hash_element() {
9458 let chunk = compile_snippet(r#"local $SIG{__WARN__} = 0;"#).expect("compile");
9459 assert!(
9460 chunk
9461 .ops
9462 .iter()
9463 .any(|o| matches!(o, Op::LocalDeclareHashElement(_))),
9464 "expected LocalDeclareHashElement in {:?}",
9465 chunk.ops
9466 );
9467 assert_last_halt(&chunk);
9468 }
9469
9470 #[test]
9471 fn compile_local_array_element_emits_local_declare_array_element() {
9472 let chunk = compile_snippet(r#"local $a[2] = 9;"#).expect("compile");
9473 assert!(
9474 chunk
9475 .ops
9476 .iter()
9477 .any(|o| matches!(o, Op::LocalDeclareArrayElement(_))),
9478 "expected LocalDeclareArrayElement in {:?}",
9479 chunk.ops
9480 );
9481 assert_last_halt(&chunk);
9482 }
9483
9484 #[test]
9485 fn compile_local_typeglob_emits_local_declare_typeglob() {
9486 let chunk = compile_snippet(r#"local *STDOUT;"#).expect("compile");
9487 assert!(
9488 chunk
9489 .ops
9490 .iter()
9491 .any(|o| matches!(o, Op::LocalDeclareTypeglob(_, None))),
9492 "expected LocalDeclareTypeglob(_, None) in {:?}",
9493 chunk.ops
9494 );
9495 assert_last_halt(&chunk);
9496 }
9497
9498 #[test]
9499 fn compile_local_typeglob_alias_emits_local_declare_typeglob_some_rhs() {
9500 let chunk = compile_snippet(r#"local *FOO = *STDOUT;"#).expect("compile");
9501 assert!(
9502 chunk
9503 .ops
9504 .iter()
9505 .any(|o| matches!(o, Op::LocalDeclareTypeglob(_, Some(_)))),
9506 "expected LocalDeclareTypeglob with rhs in {:?}",
9507 chunk.ops
9508 );
9509 assert_last_halt(&chunk);
9510 }
9511
9512 #[test]
9513 fn compile_local_braced_typeglob_emits_local_declare_typeglob_dynamic() {
9514 let chunk = compile_snippet(r#"no strict 'refs'; my $g = "STDOUT"; local *{ $g };"#)
9515 .expect("compile");
9516 assert!(
9517 chunk
9518 .ops
9519 .iter()
9520 .any(|o| matches!(o, Op::LocalDeclareTypeglobDynamic(None))),
9521 "expected LocalDeclareTypeglobDynamic(None) in {:?}",
9522 chunk.ops
9523 );
9524 assert_last_halt(&chunk);
9525 }
9526
9527 #[test]
9528 fn compile_local_star_deref_typeglob_emits_local_declare_typeglob_dynamic() {
9529 let chunk =
9530 compile_snippet(r#"no strict 'refs'; my $g = "STDOUT"; local *$g;"#).expect("compile");
9531 assert!(
9532 chunk
9533 .ops
9534 .iter()
9535 .any(|o| matches!(o, Op::LocalDeclareTypeglobDynamic(None))),
9536 "expected LocalDeclareTypeglobDynamic(None) for local *scalar glob in {:?}",
9537 chunk.ops
9538 );
9539 assert_last_halt(&chunk);
9540 }
9541
9542 #[test]
9543 fn compile_braced_glob_assign_to_named_glob_emits_copy_dynamic_lhs() {
9544 let chunk = compile_snippet(r#"no strict 'refs'; my $n = "x"; *{ $n } = *STDOUT;"#)
9546 .expect("compile");
9547 assert!(
9548 chunk
9549 .ops
9550 .iter()
9551 .any(|o| matches!(o, Op::CopyTypeglobSlotsDynamicLhs(_))),
9552 "expected CopyTypeglobSlotsDynamicLhs in {:?}",
9553 chunk.ops
9554 );
9555 assert_last_halt(&chunk);
9556 }
9557
9558 #[test]
9565 fn class_field_emits_correct_op_lines_for_subsequent_statements() {
9566 let chunk =
9567 compile_snippet("class Foo {\n x: Int\n}\nmy $y = 1\np $y\n").expect("compile");
9568 let has_line_4 = chunk.lines.contains(&4);
9573 assert!(
9574 has_line_4,
9575 "expected at least one op tagged with source line 4 (my $y = 1), got lines {:?}",
9576 chunk.lines
9577 );
9578 let has_line_5 = chunk.lines.contains(&5);
9580 assert!(
9581 has_line_5,
9582 "expected at least one op tagged with source line 5 (p $y), got lines {:?}",
9583 chunk.lines
9584 );
9585 let has_line_6 = chunk.lines.contains(&6);
9589 assert!(
9590 !has_line_6,
9591 "no op should report line 6 in a 5-line file; lines {:?}",
9592 chunk.lines
9593 );
9594 }
9595
9596 #[test]
9599 fn class_with_two_fields_does_not_drift_op_lines() {
9600 let chunk = compile_snippet(
9601 "class Foo {\n x: Int\n y: Int\n}\nmy $a = 1\nmy $b = 2\np $a\np $b\n",
9602 )
9603 .expect("compile");
9604 for expected in [5, 6, 7, 8] {
9607 assert!(
9608 chunk.lines.contains(&expected),
9609 "expected an op on line {expected}, got {:?}",
9610 chunk.lines
9611 );
9612 }
9613 let max_line = chunk.lines.iter().copied().max().unwrap_or(0);
9615 assert!(
9616 max_line <= 8,
9617 "no op past source line 8: max was {max_line}"
9618 );
9619 }
9620}