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