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