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