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