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