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