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 } => {
4596 if ctx == WantarrayCtx::List {
4597 self.compile_expr_ctx(from, WantarrayCtx::Scalar)?;
4598 self.compile_expr_ctx(to, WantarrayCtx::Scalar)?;
4599 self.emit_op(Op::Range, line, Some(root));
4600 } else if let (ExprKind::Regex(lp, lf), ExprKind::Regex(rp, rf)) =
4601 (&from.kind, &to.kind)
4602 {
4603 let slot = self.chunk.alloc_flip_flop_slot();
4604 let lp_idx = self.chunk.add_constant(PerlValue::string(lp.clone()));
4605 let lf_idx = self.chunk.add_constant(PerlValue::string(lf.clone()));
4606 let rp_idx = self.chunk.add_constant(PerlValue::string(rp.clone()));
4607 let rf_idx = self.chunk.add_constant(PerlValue::string(rf.clone()));
4608 self.emit_op(
4609 Op::RegexFlipFlop(
4610 slot,
4611 u8::from(*exclusive),
4612 lp_idx,
4613 lf_idx,
4614 rp_idx,
4615 rf_idx,
4616 ),
4617 line,
4618 Some(root),
4619 );
4620 } else if let (ExprKind::Regex(lp, lf), ExprKind::Eof(None)) =
4621 (&from.kind, &to.kind)
4622 {
4623 let slot = self.chunk.alloc_flip_flop_slot();
4624 let lp_idx = self.chunk.add_constant(PerlValue::string(lp.clone()));
4625 let lf_idx = self.chunk.add_constant(PerlValue::string(lf.clone()));
4626 self.emit_op(
4627 Op::RegexEofFlipFlop(slot, u8::from(*exclusive), lp_idx, lf_idx),
4628 line,
4629 Some(root),
4630 );
4631 } else if matches!(
4632 (&from.kind, &to.kind),
4633 (ExprKind::Regex(_, _), ExprKind::Eof(Some(_)))
4634 ) {
4635 return Err(CompileError::Unsupported(
4636 "regex flip-flop with eof(HANDLE) is not supported".into(),
4637 ));
4638 } else if let ExprKind::Regex(lp, lf) = &from.kind {
4639 let slot = self.chunk.alloc_flip_flop_slot();
4640 let lp_idx = self.chunk.add_constant(PerlValue::string(lp.clone()));
4641 let lf_idx = self.chunk.add_constant(PerlValue::string(lf.clone()));
4642 if matches!(to.kind, ExprKind::Integer(_) | ExprKind::Float(_)) {
4643 let line_target = match &to.kind {
4644 ExprKind::Integer(n) => *n,
4645 ExprKind::Float(f) => *f as i64,
4646 _ => unreachable!(),
4647 };
4648 let line_cidx = self.chunk.add_constant(PerlValue::integer(line_target));
4649 self.emit_op(
4650 Op::RegexFlipFlopDotLineRhs(
4651 slot,
4652 u8::from(*exclusive),
4653 lp_idx,
4654 lf_idx,
4655 line_cidx,
4656 ),
4657 line,
4658 Some(root),
4659 );
4660 } else {
4661 let rhs_idx = self
4662 .chunk
4663 .add_regex_flip_flop_rhs_expr_entry((**to).clone());
4664 self.emit_op(
4665 Op::RegexFlipFlopExprRhs(
4666 slot,
4667 u8::from(*exclusive),
4668 lp_idx,
4669 lf_idx,
4670 rhs_idx,
4671 ),
4672 line,
4673 Some(root),
4674 );
4675 }
4676 } else {
4677 self.compile_expr(from)?;
4678 self.compile_expr(to)?;
4679 let slot = self.chunk.alloc_flip_flop_slot();
4680 self.emit_op(
4681 Op::ScalarFlipFlop(slot, u8::from(*exclusive)),
4682 line,
4683 Some(root),
4684 );
4685 }
4686 }
4687
4688 ExprKind::Repeat { expr, count } => {
4689 self.compile_expr(expr)?;
4690 self.compile_expr(count)?;
4691 self.emit_op(Op::StringRepeat, line, Some(root));
4692 }
4693
4694 ExprKind::FuncCall { name, args } => match name.as_str() {
4696 "read" | "CORE::read" => {
4698 return Err(CompileError::Unsupported(
4699 "read() needs tree-walker for lvalue buffer arg".into(),
4700 ));
4701 }
4702 "defer__internal" => {
4704 if args.len() != 1 {
4705 return Err(CompileError::Unsupported(
4706 "defer__internal expects exactly one argument".into(),
4707 ));
4708 }
4709 self.compile_expr(&args[0])?;
4711 self.emit_op(Op::DeferBlock, line, Some(root));
4713 }
4714 "deque" => {
4715 if !args.is_empty() {
4716 return Err(CompileError::Unsupported(
4717 "deque() takes no arguments".into(),
4718 ));
4719 }
4720 self.emit_op(
4721 Op::CallBuiltin(BuiltinId::DequeNew as u16, 0),
4722 line,
4723 Some(root),
4724 );
4725 }
4726 "inc" => {
4727 let arg = args.first().cloned().unwrap_or_else(|| Expr {
4728 kind: ExprKind::ScalarVar("_".into()),
4729 line,
4730 });
4731 self.compile_expr(&arg)?;
4732 self.emit_op(Op::Inc, line, Some(root));
4733 }
4734 "dec" => {
4735 let arg = args.first().cloned().unwrap_or_else(|| Expr {
4736 kind: ExprKind::ScalarVar("_".into()),
4737 line,
4738 });
4739 self.compile_expr(&arg)?;
4740 self.emit_op(Op::Dec, line, Some(root));
4741 }
4742 "heap" => {
4743 if args.len() != 1 {
4744 return Err(CompileError::Unsupported(
4745 "heap() expects one comparator sub".into(),
4746 ));
4747 }
4748 self.compile_expr(&args[0])?;
4749 self.emit_op(
4750 Op::CallBuiltin(BuiltinId::HeapNew as u16, 1),
4751 line,
4752 Some(root),
4753 );
4754 }
4755 "pipeline" => {
4756 for arg in args {
4757 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
4758 }
4759 self.emit_op(
4760 Op::CallBuiltin(BuiltinId::Pipeline as u16, args.len() as u8),
4761 line,
4762 Some(root),
4763 );
4764 }
4765 "par_pipeline" => {
4766 for arg in args {
4767 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
4768 }
4769 self.emit_op(
4770 Op::CallBuiltin(BuiltinId::ParPipeline as u16, args.len() as u8),
4771 line,
4772 Some(root),
4773 );
4774 }
4775 "par_pipeline_stream" => {
4776 for arg in args {
4777 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
4778 }
4779 self.emit_op(
4780 Op::CallBuiltin(BuiltinId::ParPipelineStream as u16, args.len() as u8),
4781 line,
4782 Some(root),
4783 );
4784 }
4785 "collect" => {
4791 if args.len() != 1 {
4792 return Err(CompileError::Unsupported(
4793 "collect() expects exactly one argument".into(),
4794 ));
4795 }
4796 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
4797 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
4798 self.emit_op(Op::Call(name_idx, 1, ctx.as_byte()), line, Some(root));
4799 }
4800 "ppool" => {
4801 if args.len() != 1 {
4802 return Err(CompileError::Unsupported(
4803 "ppool() expects one argument (worker count)".into(),
4804 ));
4805 }
4806 self.compile_expr(&args[0])?;
4807 self.emit_op(
4808 Op::CallBuiltin(BuiltinId::Ppool as u16, 1),
4809 line,
4810 Some(root),
4811 );
4812 }
4813 "barrier" => {
4814 if args.len() != 1 {
4815 return Err(CompileError::Unsupported(
4816 "barrier() expects one argument (party count)".into(),
4817 ));
4818 }
4819 self.compile_expr(&args[0])?;
4820 self.emit_op(
4821 Op::CallBuiltin(BuiltinId::BarrierNew as u16, 1),
4822 line,
4823 Some(root),
4824 );
4825 }
4826 "pselect" => {
4827 if args.is_empty() {
4828 return Err(CompileError::Unsupported(
4829 "pselect() expects at least one pchannel receiver".into(),
4830 ));
4831 }
4832 for arg in args {
4833 self.compile_expr(arg)?;
4834 }
4835 self.emit_op(
4836 Op::CallBuiltin(BuiltinId::Pselect as u16, args.len() as u8),
4837 line,
4838 Some(root),
4839 );
4840 }
4841 "ssh" => {
4842 for arg in args {
4843 self.compile_expr(arg)?;
4844 }
4845 self.emit_op(
4846 Op::CallBuiltin(BuiltinId::Ssh as u16, args.len() as u8),
4847 line,
4848 Some(root),
4849 );
4850 }
4851 "rmdir" | "CORE::rmdir" => {
4852 for arg in args {
4853 self.compile_expr(arg)?;
4854 }
4855 self.emit_op(
4856 Op::CallBuiltin(BuiltinId::Rmdir as u16, args.len() as u8),
4857 line,
4858 Some(root),
4859 );
4860 }
4861 "utime" | "CORE::utime" => {
4862 for arg in args {
4863 self.compile_expr(arg)?;
4864 }
4865 self.emit_op(
4866 Op::CallBuiltin(BuiltinId::Utime as u16, args.len() as u8),
4867 line,
4868 Some(root),
4869 );
4870 }
4871 "umask" | "CORE::umask" => {
4872 for arg in args {
4873 self.compile_expr(arg)?;
4874 }
4875 self.emit_op(
4876 Op::CallBuiltin(BuiltinId::Umask as u16, args.len() as u8),
4877 line,
4878 Some(root),
4879 );
4880 }
4881 "getcwd" | "CORE::getcwd" | "Cwd::getcwd" => {
4882 for arg in args {
4883 self.compile_expr(arg)?;
4884 }
4885 self.emit_op(
4886 Op::CallBuiltin(BuiltinId::Getcwd as u16, args.len() as u8),
4887 line,
4888 Some(root),
4889 );
4890 }
4891 "pipe" | "CORE::pipe" => {
4892 if args.len() != 2 {
4893 return Err(CompileError::Unsupported(
4894 "pipe requires exactly two arguments".into(),
4895 ));
4896 }
4897 for arg in args {
4898 self.compile_expr(arg)?;
4899 }
4900 self.emit_op(Op::CallBuiltin(BuiltinId::Pipe as u16, 2), line, Some(root));
4901 }
4902 "uniq"
4903 | "distinct"
4904 | "flatten"
4905 | "set"
4906 | "with_index"
4907 | "list_count"
4908 | "list_size"
4909 | "count"
4910 | "size"
4911 | "cnt"
4912 | "List::Util::uniq"
4913 | "sum"
4914 | "sum0"
4915 | "product"
4916 | "min"
4917 | "max"
4918 | "mean"
4919 | "median"
4920 | "mode"
4921 | "stddev"
4922 | "variance"
4923 | "List::Util::sum"
4924 | "List::Util::sum0"
4925 | "List::Util::product"
4926 | "List::Util::min"
4927 | "List::Util::max"
4928 | "List::Util::minstr"
4929 | "List::Util::maxstr"
4930 | "List::Util::mean"
4931 | "List::Util::median"
4932 | "List::Util::mode"
4933 | "List::Util::stddev"
4934 | "List::Util::variance" => {
4935 for arg in args {
4936 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
4937 }
4938 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
4939 self.emit_op(
4940 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
4941 line,
4942 Some(root),
4943 );
4944 }
4945 "shuffle" | "List::Util::shuffle" => {
4946 for arg in args {
4947 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
4948 }
4949 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
4950 self.emit_op(
4951 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
4952 line,
4953 Some(root),
4954 );
4955 }
4956 "chunked" | "List::Util::chunked" | "windowed" | "List::Util::windowed" => {
4957 match args.len() {
4958 0 => {
4959 return Err(CompileError::Unsupported(
4960 "chunked/windowed need (LIST, N) or unary N (e.g. `|> chunked(2)`)"
4961 .into(),
4962 ));
4963 }
4964 1 => {
4965 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
4967 }
4968 2 => {
4969 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
4970 self.compile_expr(&args[1])?;
4971 }
4972 _ => {
4973 return Err(CompileError::Unsupported(
4974 "chunked/windowed expect exactly two arguments (LIST, N); use a single list expression for the first operand".into(),
4975 ));
4976 }
4977 }
4978 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
4979 self.emit_op(
4980 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
4981 line,
4982 Some(root),
4983 );
4984 }
4985 "take" | "head" | "tail" | "drop" | "List::Util::head" | "List::Util::tail" => {
4986 if args.is_empty() {
4987 return Err(CompileError::Unsupported(
4988 "take/head/tail/drop/List::Util::head|tail expect LIST..., N or unary N"
4989 .into(),
4990 ));
4991 }
4992 if args.len() == 1 {
4993 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
4995 } else {
4996 for a in &args[..args.len() - 1] {
4997 self.compile_expr_ctx(a, WantarrayCtx::List)?;
4998 }
4999 self.compile_expr(&args[args.len() - 1])?;
5000 }
5001 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5002 self.emit_op(
5003 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5004 line,
5005 Some(root),
5006 );
5007 }
5008 "any" | "all" | "none" | "first" | "take_while" | "drop_while" | "tap" | "peek" => {
5009 if args.len() != 2 {
5010 return Err(CompileError::Unsupported(
5011 "any/all/none/first/take_while/drop_while/tap/peek expect BLOCK, LIST"
5012 .into(),
5013 ));
5014 }
5015 if !matches!(&args[0].kind, ExprKind::CodeRef { .. }) {
5016 return Err(CompileError::Unsupported(
5017 "any/all/none/first/take_while/drop_while/tap/peek: first argument must be a { BLOCK }"
5018 .into(),
5019 ));
5020 }
5021 self.compile_expr(&args[0])?;
5022 self.compile_expr_ctx(&args[1], WantarrayCtx::List)?;
5023 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(name));
5024 self.emit_op(Op::Call(name_idx, 2, ctx.as_byte()), line, Some(root));
5025 }
5026 "group_by" | "chunk_by" => {
5027 if args.len() != 2 {
5028 return Err(CompileError::Unsupported(
5029 "group_by/chunk_by expect { BLOCK } or EXPR, LIST".into(),
5030 ));
5031 }
5032 self.compile_expr_ctx(&args[1], WantarrayCtx::List)?;
5033 match &args[0].kind {
5034 ExprKind::CodeRef { body, .. } => {
5035 let block_idx = self.chunk.add_block(body.clone());
5036 self.emit_op(Op::ChunkByWithBlock(block_idx), line, Some(root));
5037 }
5038 _ => {
5039 let idx = self.chunk.add_map_expr_entry(args[0].clone());
5040 self.emit_op(Op::ChunkByWithExpr(idx), line, Some(root));
5041 }
5042 }
5043 if ctx != WantarrayCtx::List {
5044 self.emit_op(Op::StackArrayLen, line, Some(root));
5045 }
5046 }
5047 "zip" | "List::Util::zip" | "List::Util::zip_longest" => {
5048 for arg in args {
5049 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5050 }
5051 let fq = match name.as_str() {
5052 "List::Util::zip_longest" => "List::Util::zip_longest",
5053 "List::Util::zip" => "List::Util::zip",
5054 _ => "zip",
5055 };
5056 let name_idx = self.chunk.intern_name(&self.qualify_sub_key(fq));
5057 self.emit_op(
5058 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5059 line,
5060 Some(root),
5061 );
5062 }
5063 "puniq" => {
5064 if args.is_empty() || args.len() > 2 {
5065 return Err(CompileError::Unsupported(
5066 "puniq expects LIST [, progress => EXPR]".into(),
5067 ));
5068 }
5069 if args.len() == 2 {
5070 self.compile_expr(&args[1])?;
5071 } else {
5072 self.emit_op(Op::LoadInt(0), line, Some(root));
5073 }
5074 self.compile_expr_ctx(&args[0], WantarrayCtx::List)?;
5075 self.emit_op(Op::Puniq, line, Some(root));
5076 if ctx != WantarrayCtx::List {
5077 self.emit_op(Op::StackArrayLen, line, Some(root));
5078 }
5079 }
5080 "pfirst" | "pany" => {
5081 if args.len() < 2 || args.len() > 3 {
5082 return Err(CompileError::Unsupported(
5083 "pfirst/pany expect BLOCK, LIST [, progress => EXPR]".into(),
5084 ));
5085 }
5086 let body = match &args[0].kind {
5087 ExprKind::CodeRef { body, .. } => body,
5088 _ => {
5089 return Err(CompileError::Unsupported(
5090 "pfirst/pany: first argument must be a { BLOCK }".into(),
5091 ));
5092 }
5093 };
5094 if args.len() == 3 {
5095 self.compile_expr(&args[2])?;
5096 } else {
5097 self.emit_op(Op::LoadInt(0), line, Some(root));
5098 }
5099 self.compile_expr_ctx(&args[1], WantarrayCtx::List)?;
5100 let block_idx = self.chunk.add_block(body.clone());
5101 let op = if name == "pfirst" {
5102 Op::PFirstWithBlock(block_idx)
5103 } else {
5104 Op::PAnyWithBlock(block_idx)
5105 };
5106 self.emit_op(op, line, Some(root));
5107 }
5108 _ => {
5109 for arg in args {
5113 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5114 }
5115 let q = self.qualify_sub_key(name);
5116 let name_idx = self.chunk.intern_name(&q);
5117 self.emit_op(
5118 Op::Call(name_idx, args.len() as u8, ctx.as_byte()),
5119 line,
5120 Some(root),
5121 );
5122 }
5123 },
5124
5125 ExprKind::MethodCall {
5127 object,
5128 method,
5129 args,
5130 super_call,
5131 } => {
5132 self.compile_expr(object)?;
5133 for arg in args {
5134 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5135 }
5136 let name_idx = self.chunk.intern_name(method);
5137 if *super_call {
5138 self.emit_op(
5139 Op::MethodCallSuper(name_idx, args.len() as u8, ctx.as_byte()),
5140 line,
5141 Some(root),
5142 );
5143 } else {
5144 self.emit_op(
5145 Op::MethodCall(name_idx, args.len() as u8, ctx.as_byte()),
5146 line,
5147 Some(root),
5148 );
5149 }
5150 }
5151 ExprKind::IndirectCall {
5152 target,
5153 args,
5154 ampersand: _,
5155 pass_caller_arglist,
5156 } => {
5157 self.compile_expr(target)?;
5158 if !pass_caller_arglist {
5159 for a in args {
5160 self.compile_expr_ctx(a, WantarrayCtx::List)?;
5161 }
5162 }
5163 let argc = if *pass_caller_arglist {
5164 0
5165 } else {
5166 args.len() as u8
5167 };
5168 self.emit_op(
5169 Op::IndirectCall(
5170 argc,
5171 ctx.as_byte(),
5172 if *pass_caller_arglist { 1 } else { 0 },
5173 ),
5174 line,
5175 Some(root),
5176 );
5177 }
5178
5179 ExprKind::Print { handle, args } => {
5181 for arg in args {
5182 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5183 }
5184 let h = handle.as_ref().map(|s| self.chunk.intern_name(s));
5185 self.emit_op(Op::Print(h, args.len() as u8), line, Some(root));
5186 }
5187 ExprKind::Say { handle, args } => {
5188 for arg in args {
5189 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5190 }
5191 let h = handle.as_ref().map(|s| self.chunk.intern_name(s));
5192 self.emit_op(Op::Say(h, args.len() as u8), line, Some(root));
5193 }
5194 ExprKind::Printf { args, .. } => {
5195 for arg in args {
5198 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5199 }
5200 self.emit_op(
5201 Op::CallBuiltin(BuiltinId::Printf as u16, args.len() as u8),
5202 line,
5203 Some(root),
5204 );
5205 }
5206
5207 ExprKind::Die(args) => {
5209 for arg in args {
5212 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5213 }
5214 self.emit_op(
5215 Op::CallBuiltin(BuiltinId::Die as u16, args.len() as u8),
5216 line,
5217 Some(root),
5218 );
5219 }
5220 ExprKind::Warn(args) => {
5221 for arg in args {
5222 self.compile_expr_ctx(arg, WantarrayCtx::List)?;
5223 }
5224 self.emit_op(
5225 Op::CallBuiltin(BuiltinId::Warn as u16, args.len() as u8),
5226 line,
5227 Some(root),
5228 );
5229 }
5230 ExprKind::Exit(code) => {
5231 if let Some(c) = code {
5232 self.compile_expr(c)?;
5233 self.emit_op(Op::CallBuiltin(BuiltinId::Exit as u16, 1), line, Some(root));
5234 } else {
5235 self.emit_op(Op::LoadInt(0), line, Some(root));
5236 self.emit_op(Op::CallBuiltin(BuiltinId::Exit as u16, 1), line, Some(root));
5237 }
5238 }
5239
5240 ExprKind::Push { array, values } => {
5242 if let ExprKind::ArrayVar(name) = &array.kind {
5243 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
5244 for v in values {
5245 self.compile_expr_ctx(v, WantarrayCtx::List)?;
5246 self.emit_op(Op::PushArray(idx), line, Some(root));
5247 }
5248 self.emit_op(Op::ArrayLen(idx), line, Some(root));
5249 } else if let ExprKind::Deref {
5250 expr: aref_expr,
5251 kind: Sigil::Array,
5252 } = &array.kind
5253 {
5254 self.compile_expr(aref_expr)?;
5255 for v in values {
5256 self.emit_op(Op::Dup, line, Some(root));
5257 self.compile_expr_ctx(v, WantarrayCtx::List)?;
5258 self.emit_op(Op::PushArrayDeref, line, Some(root));
5259 }
5260 self.emit_op(Op::ArrayDerefLen, line, Some(root));
5261 } else {
5262 let pool = self
5263 .chunk
5264 .add_push_expr_entry(array.as_ref().clone(), values.clone());
5265 self.emit_op(Op::PushExpr(pool), line, Some(root));
5266 }
5267 }
5268 ExprKind::Pop(array) => {
5269 if let ExprKind::ArrayVar(name) = &array.kind {
5270 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
5271 self.emit_op(Op::PopArray(idx), line, Some(root));
5272 } else if let ExprKind::Deref {
5273 expr: aref_expr,
5274 kind: Sigil::Array,
5275 } = &array.kind
5276 {
5277 self.compile_expr(aref_expr)?;
5278 self.emit_op(Op::PopArrayDeref, line, Some(root));
5279 } else {
5280 let pool = self.chunk.add_pop_expr_entry(array.as_ref().clone());
5281 self.emit_op(Op::PopExpr(pool), line, Some(root));
5282 }
5283 }
5284 ExprKind::Shift(array) => {
5285 if let ExprKind::ArrayVar(name) = &array.kind {
5286 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
5287 self.emit_op(Op::ShiftArray(idx), line, Some(root));
5288 } else if let ExprKind::Deref {
5289 expr: aref_expr,
5290 kind: Sigil::Array,
5291 } = &array.kind
5292 {
5293 self.compile_expr(aref_expr)?;
5294 self.emit_op(Op::ShiftArrayDeref, line, Some(root));
5295 } else {
5296 let pool = self.chunk.add_shift_expr_entry(array.as_ref().clone());
5297 self.emit_op(Op::ShiftExpr(pool), line, Some(root));
5298 }
5299 }
5300 ExprKind::Unshift { array, values } => {
5301 if let ExprKind::ArrayVar(name) = &array.kind {
5302 let q = self.qualify_stash_array_name(name);
5303 let name_const = self.chunk.add_constant(PerlValue::string(q));
5304 self.emit_op(Op::LoadConst(name_const), line, Some(root));
5305 for v in values {
5306 self.compile_expr_ctx(v, WantarrayCtx::List)?;
5307 }
5308 let nargs = (1 + values.len()) as u8;
5309 self.emit_op(
5310 Op::CallBuiltin(BuiltinId::Unshift as u16, nargs),
5311 line,
5312 Some(root),
5313 );
5314 } else if let ExprKind::Deref {
5315 expr: aref_expr,
5316 kind: Sigil::Array,
5317 } = &array.kind
5318 {
5319 if values.len() > u8::MAX as usize {
5320 let pool = self
5321 .chunk
5322 .add_unshift_expr_entry(array.as_ref().clone(), values.clone());
5323 self.emit_op(Op::UnshiftExpr(pool), line, Some(root));
5324 } else {
5325 self.compile_expr(aref_expr)?;
5326 for v in values {
5327 self.compile_expr_ctx(v, WantarrayCtx::List)?;
5328 }
5329 self.emit_op(Op::UnshiftArrayDeref(values.len() as u8), line, Some(root));
5330 }
5331 } else {
5332 let pool = self
5333 .chunk
5334 .add_unshift_expr_entry(array.as_ref().clone(), values.clone());
5335 self.emit_op(Op::UnshiftExpr(pool), line, Some(root));
5336 }
5337 }
5338 ExprKind::Splice {
5339 array,
5340 offset,
5341 length,
5342 replacement,
5343 } => {
5344 self.emit_op(Op::WantarrayPush(ctx.as_byte()), line, Some(root));
5345 if let ExprKind::ArrayVar(name) = &array.kind {
5346 let q = self.qualify_stash_array_name(name);
5347 let name_const = self.chunk.add_constant(PerlValue::string(q));
5348 self.emit_op(Op::LoadConst(name_const), line, Some(root));
5349 if let Some(o) = offset {
5350 self.compile_expr(o)?;
5351 } else {
5352 self.emit_op(Op::LoadInt(0), line, Some(root));
5353 }
5354 if let Some(l) = length {
5355 self.compile_expr(l)?;
5356 } else {
5357 self.emit_op(Op::LoadUndef, line, Some(root));
5358 }
5359 for r in replacement {
5360 self.compile_expr(r)?;
5361 }
5362 let nargs = (3 + replacement.len()) as u8;
5363 self.emit_op(
5364 Op::CallBuiltin(BuiltinId::Splice as u16, nargs),
5365 line,
5366 Some(root),
5367 );
5368 } else if let ExprKind::Deref {
5369 expr: aref_expr,
5370 kind: Sigil::Array,
5371 } = &array.kind
5372 {
5373 if replacement.len() > u8::MAX as usize {
5374 let pool = self.chunk.add_splice_expr_entry(
5375 array.as_ref().clone(),
5376 offset.as_deref().cloned(),
5377 length.as_deref().cloned(),
5378 replacement.clone(),
5379 );
5380 self.emit_op(Op::SpliceExpr(pool), line, Some(root));
5381 } else {
5382 self.compile_expr(aref_expr)?;
5383 if let Some(o) = offset {
5384 self.compile_expr(o)?;
5385 } else {
5386 self.emit_op(Op::LoadInt(0), line, Some(root));
5387 }
5388 if let Some(l) = length {
5389 self.compile_expr(l)?;
5390 } else {
5391 self.emit_op(Op::LoadUndef, line, Some(root));
5392 }
5393 for r in replacement {
5394 self.compile_expr(r)?;
5395 }
5396 self.emit_op(
5397 Op::SpliceArrayDeref(replacement.len() as u8),
5398 line,
5399 Some(root),
5400 );
5401 }
5402 } else {
5403 let pool = self.chunk.add_splice_expr_entry(
5404 array.as_ref().clone(),
5405 offset.as_deref().cloned(),
5406 length.as_deref().cloned(),
5407 replacement.clone(),
5408 );
5409 self.emit_op(Op::SpliceExpr(pool), line, Some(root));
5410 }
5411 self.emit_op(Op::WantarrayPop, line, Some(root));
5412 }
5413 ExprKind::ScalarContext(inner) => {
5414 self.compile_expr_ctx(inner, WantarrayCtx::Scalar)?;
5417 self.emit_op(Op::ValueScalarContext, line, Some(root));
5420 }
5421
5422 ExprKind::Delete(inner) => {
5424 if let ExprKind::HashElement { hash, key } = &inner.kind {
5425 self.check_hash_mutable(hash, line)?;
5426 let idx = self.chunk.intern_name(hash);
5427 self.compile_expr(key)?;
5428 self.emit_op(Op::DeleteHashElem(idx), line, Some(root));
5429 } else if let ExprKind::ArrayElement { array, index } = &inner.kind {
5430 self.check_strict_array_access(array, line)?;
5431 let q = self.qualify_stash_array_name(array);
5432 self.check_array_mutable(&q, line)?;
5433 let arr_idx = self.chunk.intern_name(&q);
5434 self.compile_expr(index)?;
5435 self.emit_op(Op::DeleteArrayElem(arr_idx), line, Some(root));
5436 } else if let ExprKind::ArrowDeref {
5437 expr: container,
5438 index,
5439 kind: DerefKind::Hash,
5440 } = &inner.kind
5441 {
5442 self.compile_arrow_hash_base_expr(container)?;
5443 self.compile_expr(index)?;
5444 self.emit_op(Op::DeleteArrowHashElem, line, Some(root));
5445 } else if let ExprKind::ArrowDeref {
5446 expr: container,
5447 index,
5448 kind: DerefKind::Array,
5449 } = &inner.kind
5450 {
5451 if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
5452 self.compile_expr(container)?;
5453 self.compile_expr(index)?;
5454 self.emit_op(Op::DeleteArrowArrayElem, line, Some(root));
5455 } else {
5456 let pool = self.chunk.add_delete_expr_entry(inner.as_ref().clone());
5457 self.emit_op(Op::DeleteExpr(pool), line, Some(root));
5458 }
5459 } else {
5460 let pool = self.chunk.add_delete_expr_entry(inner.as_ref().clone());
5461 self.emit_op(Op::DeleteExpr(pool), line, Some(root));
5462 }
5463 }
5464 ExprKind::Exists(inner) => {
5465 if let ExprKind::HashElement { hash, key } = &inner.kind {
5466 let idx = self.chunk.intern_name(hash);
5467 self.compile_expr(key)?;
5468 self.emit_op(Op::ExistsHashElem(idx), line, Some(root));
5469 } else if let ExprKind::ArrayElement { array, index } = &inner.kind {
5470 self.check_strict_array_access(array, line)?;
5471 let arr_idx = self
5472 .chunk
5473 .intern_name(&self.qualify_stash_array_name(array));
5474 self.compile_expr(index)?;
5475 self.emit_op(Op::ExistsArrayElem(arr_idx), line, Some(root));
5476 } else if let ExprKind::ArrowDeref {
5477 expr: container,
5478 index,
5479 kind: DerefKind::Hash,
5480 } = &inner.kind
5481 {
5482 self.compile_arrow_hash_base_expr(container)?;
5483 self.compile_expr(index)?;
5484 self.emit_op(Op::ExistsArrowHashElem, line, Some(root));
5485 } else if let ExprKind::ArrowDeref {
5486 expr: container,
5487 index,
5488 kind: DerefKind::Array,
5489 } = &inner.kind
5490 {
5491 if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
5492 self.compile_expr(container)?;
5493 self.compile_expr(index)?;
5494 self.emit_op(Op::ExistsArrowArrayElem, line, Some(root));
5495 } else {
5496 let pool = self.chunk.add_exists_expr_entry(inner.as_ref().clone());
5497 self.emit_op(Op::ExistsExpr(pool), line, Some(root));
5498 }
5499 } else {
5500 let pool = self.chunk.add_exists_expr_entry(inner.as_ref().clone());
5501 self.emit_op(Op::ExistsExpr(pool), line, Some(root));
5502 }
5503 }
5504 ExprKind::Keys(inner) => {
5505 if let ExprKind::HashVar(name) = &inner.kind {
5506 let idx = self.chunk.intern_name(name);
5507 if ctx == WantarrayCtx::List {
5508 self.emit_op(Op::HashKeys(idx), line, Some(root));
5509 } else {
5510 self.emit_op(Op::HashKeysScalar(idx), line, Some(root));
5511 }
5512 } else {
5513 self.compile_expr_ctx(inner, WantarrayCtx::List)?;
5514 if ctx == WantarrayCtx::List {
5515 self.emit_op(Op::KeysFromValue, line, Some(root));
5516 } else {
5517 self.emit_op(Op::KeysFromValueScalar, line, Some(root));
5518 }
5519 }
5520 }
5521 ExprKind::Values(inner) => {
5522 if let ExprKind::HashVar(name) = &inner.kind {
5523 let idx = self.chunk.intern_name(name);
5524 if ctx == WantarrayCtx::List {
5525 self.emit_op(Op::HashValues(idx), line, Some(root));
5526 } else {
5527 self.emit_op(Op::HashValuesScalar(idx), line, Some(root));
5528 }
5529 } else {
5530 self.compile_expr_ctx(inner, WantarrayCtx::List)?;
5531 if ctx == WantarrayCtx::List {
5532 self.emit_op(Op::ValuesFromValue, line, Some(root));
5533 } else {
5534 self.emit_op(Op::ValuesFromValueScalar, line, Some(root));
5535 }
5536 }
5537 }
5538 ExprKind::Each(e) => {
5539 self.compile_expr(e)?;
5540 self.emit_op(Op::CallBuiltin(BuiltinId::Each as u16, 1), line, Some(root));
5541 }
5542
5543 ExprKind::Length(e) => {
5545 self.compile_expr(e)?;
5546 self.emit_op(
5547 Op::CallBuiltin(BuiltinId::Length as u16, 1),
5548 line,
5549 Some(root),
5550 );
5551 }
5552 ExprKind::Chomp(e) => {
5553 self.compile_expr(e)?;
5554 let lv = self.chunk.add_lvalue_expr(e.as_ref().clone());
5555 self.emit_op(Op::ChompInPlace(lv), line, Some(root));
5556 }
5557 ExprKind::Chop(e) => {
5558 self.compile_expr(e)?;
5559 let lv = self.chunk.add_lvalue_expr(e.as_ref().clone());
5560 self.emit_op(Op::ChopInPlace(lv), line, Some(root));
5561 }
5562 ExprKind::Defined(e) => {
5563 self.compile_expr(e)?;
5564 self.emit_op(
5565 Op::CallBuiltin(BuiltinId::Defined as u16, 1),
5566 line,
5567 Some(root),
5568 );
5569 }
5570 ExprKind::Abs(e) => {
5571 self.compile_expr(e)?;
5572 self.emit_op(Op::CallBuiltin(BuiltinId::Abs as u16, 1), line, Some(root));
5573 }
5574 ExprKind::Int(e) => {
5575 self.compile_expr(e)?;
5576 self.emit_op(Op::CallBuiltin(BuiltinId::Int as u16, 1), line, Some(root));
5577 }
5578 ExprKind::Sqrt(e) => {
5579 self.compile_expr(e)?;
5580 self.emit_op(Op::CallBuiltin(BuiltinId::Sqrt as u16, 1), line, Some(root));
5581 }
5582 ExprKind::Sin(e) => {
5583 self.compile_expr(e)?;
5584 self.emit_op(Op::CallBuiltin(BuiltinId::Sin as u16, 1), line, Some(root));
5585 }
5586 ExprKind::Cos(e) => {
5587 self.compile_expr(e)?;
5588 self.emit_op(Op::CallBuiltin(BuiltinId::Cos as u16, 1), line, Some(root));
5589 }
5590 ExprKind::Atan2 { y, x } => {
5591 self.compile_expr(y)?;
5592 self.compile_expr(x)?;
5593 self.emit_op(
5594 Op::CallBuiltin(BuiltinId::Atan2 as u16, 2),
5595 line,
5596 Some(root),
5597 );
5598 }
5599 ExprKind::Exp(e) => {
5600 self.compile_expr(e)?;
5601 self.emit_op(Op::CallBuiltin(BuiltinId::Exp as u16, 1), line, Some(root));
5602 }
5603 ExprKind::Log(e) => {
5604 self.compile_expr(e)?;
5605 self.emit_op(Op::CallBuiltin(BuiltinId::Log as u16, 1), line, Some(root));
5606 }
5607 ExprKind::Rand(upper) => {
5608 if let Some(e) = upper {
5609 self.compile_expr(e)?;
5610 self.emit_op(Op::CallBuiltin(BuiltinId::Rand as u16, 1), line, Some(root));
5611 } else {
5612 self.emit_op(Op::CallBuiltin(BuiltinId::Rand as u16, 0), line, Some(root));
5613 }
5614 }
5615 ExprKind::Srand(seed) => {
5616 if let Some(e) = seed {
5617 self.compile_expr(e)?;
5618 self.emit_op(
5619 Op::CallBuiltin(BuiltinId::Srand as u16, 1),
5620 line,
5621 Some(root),
5622 );
5623 } else {
5624 self.emit_op(
5625 Op::CallBuiltin(BuiltinId::Srand as u16, 0),
5626 line,
5627 Some(root),
5628 );
5629 }
5630 }
5631 ExprKind::Chr(e) => {
5632 self.compile_expr(e)?;
5633 self.emit_op(Op::CallBuiltin(BuiltinId::Chr as u16, 1), line, Some(root));
5634 }
5635 ExprKind::Ord(e) => {
5636 self.compile_expr(e)?;
5637 self.emit_op(Op::CallBuiltin(BuiltinId::Ord as u16, 1), line, Some(root));
5638 }
5639 ExprKind::Hex(e) => {
5640 self.compile_expr(e)?;
5641 self.emit_op(Op::CallBuiltin(BuiltinId::Hex as u16, 1), line, Some(root));
5642 }
5643 ExprKind::Oct(e) => {
5644 self.compile_expr(e)?;
5645 self.emit_op(Op::CallBuiltin(BuiltinId::Oct as u16, 1), line, Some(root));
5646 }
5647 ExprKind::Uc(e) => {
5648 self.compile_expr(e)?;
5649 self.emit_op(Op::CallBuiltin(BuiltinId::Uc as u16, 1), line, Some(root));
5650 }
5651 ExprKind::Lc(e) => {
5652 self.compile_expr(e)?;
5653 self.emit_op(Op::CallBuiltin(BuiltinId::Lc as u16, 1), line, Some(root));
5654 }
5655 ExprKind::Ucfirst(e) => {
5656 self.compile_expr(e)?;
5657 self.emit_op(
5658 Op::CallBuiltin(BuiltinId::Ucfirst as u16, 1),
5659 line,
5660 Some(root),
5661 );
5662 }
5663 ExprKind::Lcfirst(e) => {
5664 self.compile_expr(e)?;
5665 self.emit_op(
5666 Op::CallBuiltin(BuiltinId::Lcfirst as u16, 1),
5667 line,
5668 Some(root),
5669 );
5670 }
5671 ExprKind::Fc(e) => {
5672 self.compile_expr(e)?;
5673 self.emit_op(Op::CallBuiltin(BuiltinId::Fc as u16, 1), line, Some(root));
5674 }
5675 ExprKind::Crypt { plaintext, salt } => {
5676 self.compile_expr(plaintext)?;
5677 self.compile_expr(salt)?;
5678 self.emit_op(
5679 Op::CallBuiltin(BuiltinId::Crypt as u16, 2),
5680 line,
5681 Some(root),
5682 );
5683 }
5684 ExprKind::Pos(e) => match e {
5685 None => {
5686 self.emit_op(Op::CallBuiltin(BuiltinId::Pos as u16, 0), line, Some(root));
5687 }
5688 Some(pos_arg) => {
5689 if let ExprKind::ScalarVar(name) = &pos_arg.kind {
5690 let stor = self.scalar_storage_name_for_ops(name);
5691 let idx = self.chunk.add_constant(PerlValue::string(stor));
5692 self.emit_op(Op::LoadConst(idx), line, Some(root));
5693 } else {
5694 self.compile_expr(pos_arg)?;
5695 }
5696 self.emit_op(Op::CallBuiltin(BuiltinId::Pos as u16, 1), line, Some(root));
5697 }
5698 },
5699 ExprKind::Study(e) => {
5700 self.compile_expr(e)?;
5701 self.emit_op(
5702 Op::CallBuiltin(BuiltinId::Study as u16, 1),
5703 line,
5704 Some(root),
5705 );
5706 }
5707 ExprKind::Ref(e) => {
5708 self.compile_expr(e)?;
5709 self.emit_op(Op::CallBuiltin(BuiltinId::Ref as u16, 1), line, Some(root));
5710 }
5711 ExprKind::ScalarReverse(e) => {
5712 self.compile_expr_ctx(e, WantarrayCtx::List)?;
5713 self.emit_op(Op::RevOp, line, Some(root));
5714 }
5715 ExprKind::ReverseExpr(e) => {
5716 self.compile_expr_ctx(e, WantarrayCtx::List)?;
5717 if ctx == WantarrayCtx::List {
5718 self.emit_op(Op::ReverseListOp, line, Some(root));
5719 } else {
5720 self.emit_op(Op::ReverseScalarOp, line, Some(root));
5721 }
5722 }
5723 ExprKind::System(args) => {
5724 for a in args {
5725 self.compile_expr(a)?;
5726 }
5727 self.emit_op(
5728 Op::CallBuiltin(BuiltinId::System as u16, args.len() as u8),
5729 line,
5730 Some(root),
5731 );
5732 }
5733 ExprKind::Exec(args) => {
5734 for a in args {
5735 self.compile_expr(a)?;
5736 }
5737 self.emit_op(
5738 Op::CallBuiltin(BuiltinId::Exec as u16, args.len() as u8),
5739 line,
5740 Some(root),
5741 );
5742 }
5743
5744 ExprKind::Substr {
5746 string,
5747 offset,
5748 length,
5749 replacement,
5750 } => {
5751 if let Some(rep) = replacement {
5752 let idx = self.chunk.add_substr_four_arg_entry(
5753 string.as_ref().clone(),
5754 offset.as_ref().clone(),
5755 length.as_ref().map(|b| b.as_ref().clone()),
5756 rep.as_ref().clone(),
5757 );
5758 self.emit_op(Op::SubstrFourArg(idx), line, Some(root));
5759 } else {
5760 self.compile_expr(string)?;
5761 self.compile_expr(offset)?;
5762 let mut argc: u8 = 2;
5763 if let Some(len) = length {
5764 self.compile_expr(len)?;
5765 argc = 3;
5766 }
5767 self.emit_op(
5768 Op::CallBuiltin(BuiltinId::Substr as u16, argc),
5769 line,
5770 Some(root),
5771 );
5772 }
5773 }
5774 ExprKind::Index {
5775 string,
5776 substr,
5777 position,
5778 } => {
5779 self.compile_expr(string)?;
5780 self.compile_expr(substr)?;
5781 if let Some(pos) = position {
5782 self.compile_expr(pos)?;
5783 self.emit_op(
5784 Op::CallBuiltin(BuiltinId::Index as u16, 3),
5785 line,
5786 Some(root),
5787 );
5788 } else {
5789 self.emit_op(
5790 Op::CallBuiltin(BuiltinId::Index as u16, 2),
5791 line,
5792 Some(root),
5793 );
5794 }
5795 }
5796 ExprKind::Rindex {
5797 string,
5798 substr,
5799 position,
5800 } => {
5801 self.compile_expr(string)?;
5802 self.compile_expr(substr)?;
5803 if let Some(pos) = position {
5804 self.compile_expr(pos)?;
5805 self.emit_op(
5806 Op::CallBuiltin(BuiltinId::Rindex as u16, 3),
5807 line,
5808 Some(root),
5809 );
5810 } else {
5811 self.emit_op(
5812 Op::CallBuiltin(BuiltinId::Rindex as u16, 2),
5813 line,
5814 Some(root),
5815 );
5816 }
5817 }
5818
5819 ExprKind::JoinExpr { separator, list } => {
5820 self.compile_expr(separator)?;
5821 self.compile_expr_ctx(list, WantarrayCtx::List)?;
5823 self.emit_op(Op::CallBuiltin(BuiltinId::Join as u16, 2), line, Some(root));
5824 }
5825 ExprKind::SplitExpr {
5826 pattern,
5827 string,
5828 limit,
5829 } => {
5830 self.compile_expr(pattern)?;
5831 self.compile_expr(string)?;
5832 if let Some(l) = limit {
5833 self.compile_expr(l)?;
5834 self.emit_op(
5835 Op::CallBuiltin(BuiltinId::Split as u16, 3),
5836 line,
5837 Some(root),
5838 );
5839 } else {
5840 self.emit_op(
5841 Op::CallBuiltin(BuiltinId::Split as u16, 2),
5842 line,
5843 Some(root),
5844 );
5845 }
5846 }
5847 ExprKind::Sprintf { format, args } => {
5848 self.compile_expr(format)?;
5851 for a in args {
5852 self.compile_expr_ctx(a, WantarrayCtx::List)?;
5853 }
5854 self.emit_op(
5855 Op::CallBuiltin(BuiltinId::Sprintf as u16, (1 + args.len()) as u8),
5856 line,
5857 Some(root),
5858 );
5859 }
5860
5861 ExprKind::Open { handle, mode, file } => {
5863 if let ExprKind::OpenMyHandle { name } = &handle.kind {
5864 let name_idx = self.chunk.intern_name(name);
5865 self.emit_op(Op::LoadUndef, line, Some(root));
5866 self.emit_declare_scalar(name_idx, line, false);
5867 let h_idx = self.chunk.add_constant(PerlValue::string(name.clone()));
5868 self.emit_op(Op::LoadConst(h_idx), line, Some(root));
5869 self.compile_expr(mode)?;
5870 if let Some(f) = file {
5871 self.compile_expr(f)?;
5872 self.emit_op(Op::CallBuiltin(BuiltinId::Open as u16, 3), line, Some(root));
5873 } else {
5874 self.emit_op(Op::CallBuiltin(BuiltinId::Open as u16, 2), line, Some(root));
5875 }
5876 self.emit_op(Op::SetScalarKeepPlain(name_idx), line, Some(root));
5877 return Ok(());
5878 }
5879 self.compile_expr(handle)?;
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 }
5888 ExprKind::OpenMyHandle { .. } => {
5889 return Err(CompileError::Unsupported(
5890 "open my $fh handle expression".into(),
5891 ));
5892 }
5893 ExprKind::Close(e) => {
5894 self.compile_expr(e)?;
5895 self.emit_op(
5896 Op::CallBuiltin(BuiltinId::Close as u16, 1),
5897 line,
5898 Some(root),
5899 );
5900 }
5901 ExprKind::ReadLine(handle) => {
5902 let bid = if ctx == WantarrayCtx::List {
5903 BuiltinId::ReadLineList
5904 } else {
5905 BuiltinId::ReadLine
5906 };
5907 if let Some(h) = handle {
5908 let idx = self.chunk.add_constant(PerlValue::string(h.clone()));
5909 self.emit_op(Op::LoadConst(idx), line, Some(root));
5910 self.emit_op(Op::CallBuiltin(bid as u16, 1), line, Some(root));
5911 } else {
5912 self.emit_op(Op::CallBuiltin(bid as u16, 0), line, Some(root));
5913 }
5914 }
5915 ExprKind::Eof(e) => {
5916 if let Some(inner) = e {
5917 self.compile_expr(inner)?;
5918 self.emit_op(Op::CallBuiltin(BuiltinId::Eof as u16, 1), line, Some(root));
5919 } else {
5920 self.emit_op(Op::CallBuiltin(BuiltinId::Eof as u16, 0), line, Some(root));
5921 }
5922 }
5923 ExprKind::Opendir { handle, path } => {
5924 self.compile_expr(handle)?;
5925 self.compile_expr(path)?;
5926 self.emit_op(
5927 Op::CallBuiltin(BuiltinId::Opendir as u16, 2),
5928 line,
5929 Some(root),
5930 );
5931 }
5932 ExprKind::Readdir(e) => {
5933 let bid = if ctx == WantarrayCtx::List {
5934 BuiltinId::ReaddirList
5935 } else {
5936 BuiltinId::Readdir
5937 };
5938 self.compile_expr(e)?;
5939 self.emit_op(Op::CallBuiltin(bid as u16, 1), line, Some(root));
5940 }
5941 ExprKind::Closedir(e) => {
5942 self.compile_expr(e)?;
5943 self.emit_op(
5944 Op::CallBuiltin(BuiltinId::Closedir as u16, 1),
5945 line,
5946 Some(root),
5947 );
5948 }
5949 ExprKind::Rewinddir(e) => {
5950 self.compile_expr(e)?;
5951 self.emit_op(
5952 Op::CallBuiltin(BuiltinId::Rewinddir as u16, 1),
5953 line,
5954 Some(root),
5955 );
5956 }
5957 ExprKind::Telldir(e) => {
5958 self.compile_expr(e)?;
5959 self.emit_op(
5960 Op::CallBuiltin(BuiltinId::Telldir as u16, 1),
5961 line,
5962 Some(root),
5963 );
5964 }
5965 ExprKind::Seekdir { handle, position } => {
5966 self.compile_expr(handle)?;
5967 self.compile_expr(position)?;
5968 self.emit_op(
5969 Op::CallBuiltin(BuiltinId::Seekdir as u16, 2),
5970 line,
5971 Some(root),
5972 );
5973 }
5974
5975 ExprKind::FileTest { op, expr } => {
5977 self.compile_expr(expr)?;
5978 self.emit_op(Op::FileTestOp(*op as u8), line, Some(root));
5979 }
5980
5981 ExprKind::Eval(e) => {
5983 self.compile_expr(e)?;
5984 self.emit_op(Op::CallBuiltin(BuiltinId::Eval as u16, 1), line, Some(root));
5985 }
5986 ExprKind::Do(e) => {
5987 if let ExprKind::CodeRef { body, .. } = &e.kind {
5989 let block_idx = self.chunk.add_block(body.clone());
5990 self.emit_op(Op::EvalBlock(block_idx, ctx.as_byte()), line, Some(root));
5991 } else {
5992 self.compile_expr(e)?;
5993 self.emit_op(Op::CallBuiltin(BuiltinId::Do as u16, 1), line, Some(root));
5994 }
5995 }
5996 ExprKind::Require(e) => {
5997 self.compile_expr(e)?;
5998 self.emit_op(
5999 Op::CallBuiltin(BuiltinId::Require as u16, 1),
6000 line,
6001 Some(root),
6002 );
6003 }
6004
6005 ExprKind::Chdir(e) => {
6007 self.compile_expr(e)?;
6008 self.emit_op(
6009 Op::CallBuiltin(BuiltinId::Chdir as u16, 1),
6010 line,
6011 Some(root),
6012 );
6013 }
6014 ExprKind::Mkdir { path, mode } => {
6015 self.compile_expr(path)?;
6016 if let Some(m) = mode {
6017 self.compile_expr(m)?;
6018 self.emit_op(
6019 Op::CallBuiltin(BuiltinId::Mkdir as u16, 2),
6020 line,
6021 Some(root),
6022 );
6023 } else {
6024 self.emit_op(
6025 Op::CallBuiltin(BuiltinId::Mkdir as u16, 1),
6026 line,
6027 Some(root),
6028 );
6029 }
6030 }
6031 ExprKind::Unlink(args) => {
6032 for a in args {
6033 self.compile_expr(a)?;
6034 }
6035 self.emit_op(
6036 Op::CallBuiltin(BuiltinId::Unlink as u16, args.len() as u8),
6037 line,
6038 Some(root),
6039 );
6040 }
6041 ExprKind::Rename { old, new } => {
6042 self.compile_expr(old)?;
6043 self.compile_expr(new)?;
6044 self.emit_op(
6045 Op::CallBuiltin(BuiltinId::Rename as u16, 2),
6046 line,
6047 Some(root),
6048 );
6049 }
6050 ExprKind::Chmod(args) => {
6051 for a in args {
6052 self.compile_expr(a)?;
6053 }
6054 self.emit_op(
6055 Op::CallBuiltin(BuiltinId::Chmod as u16, args.len() as u8),
6056 line,
6057 Some(root),
6058 );
6059 }
6060 ExprKind::Chown(args) => {
6061 for a in args {
6062 self.compile_expr(a)?;
6063 }
6064 self.emit_op(
6065 Op::CallBuiltin(BuiltinId::Chown as u16, args.len() as u8),
6066 line,
6067 Some(root),
6068 );
6069 }
6070 ExprKind::Stat(e) => {
6071 self.compile_expr(e)?;
6072 self.emit_op(Op::CallBuiltin(BuiltinId::Stat as u16, 1), line, Some(root));
6073 }
6074 ExprKind::Lstat(e) => {
6075 self.compile_expr(e)?;
6076 self.emit_op(
6077 Op::CallBuiltin(BuiltinId::Lstat as u16, 1),
6078 line,
6079 Some(root),
6080 );
6081 }
6082 ExprKind::Link { old, new } => {
6083 self.compile_expr(old)?;
6084 self.compile_expr(new)?;
6085 self.emit_op(Op::CallBuiltin(BuiltinId::Link as u16, 2), line, Some(root));
6086 }
6087 ExprKind::Symlink { old, new } => {
6088 self.compile_expr(old)?;
6089 self.compile_expr(new)?;
6090 self.emit_op(
6091 Op::CallBuiltin(BuiltinId::Symlink as u16, 2),
6092 line,
6093 Some(root),
6094 );
6095 }
6096 ExprKind::Readlink(e) => {
6097 self.compile_expr(e)?;
6098 self.emit_op(
6099 Op::CallBuiltin(BuiltinId::Readlink as u16, 1),
6100 line,
6101 Some(root),
6102 );
6103 }
6104 ExprKind::Files(args) => {
6105 for a in args {
6106 self.compile_expr(a)?;
6107 }
6108 self.emit_op(
6109 Op::CallBuiltin(BuiltinId::Files as u16, args.len() as u8),
6110 line,
6111 Some(root),
6112 );
6113 }
6114 ExprKind::Filesf(args) => {
6115 for a in args {
6116 self.compile_expr(a)?;
6117 }
6118 self.emit_op(
6119 Op::CallBuiltin(BuiltinId::Filesf as u16, args.len() as u8),
6120 line,
6121 Some(root),
6122 );
6123 }
6124 ExprKind::FilesfRecursive(args) => {
6125 for a in args {
6126 self.compile_expr(a)?;
6127 }
6128 self.emit_op(
6129 Op::CallBuiltin(BuiltinId::FilesfRecursive as u16, args.len() as u8),
6130 line,
6131 Some(root),
6132 );
6133 }
6134 ExprKind::Dirs(args) => {
6135 for a in args {
6136 self.compile_expr(a)?;
6137 }
6138 self.emit_op(
6139 Op::CallBuiltin(BuiltinId::Dirs as u16, args.len() as u8),
6140 line,
6141 Some(root),
6142 );
6143 }
6144 ExprKind::DirsRecursive(args) => {
6145 for a in args {
6146 self.compile_expr(a)?;
6147 }
6148 self.emit_op(
6149 Op::CallBuiltin(BuiltinId::DirsRecursive as u16, args.len() as u8),
6150 line,
6151 Some(root),
6152 );
6153 }
6154 ExprKind::SymLinks(args) => {
6155 for a in args {
6156 self.compile_expr(a)?;
6157 }
6158 self.emit_op(
6159 Op::CallBuiltin(BuiltinId::SymLinks as u16, args.len() as u8),
6160 line,
6161 Some(root),
6162 );
6163 }
6164 ExprKind::Sockets(args) => {
6165 for a in args {
6166 self.compile_expr(a)?;
6167 }
6168 self.emit_op(
6169 Op::CallBuiltin(BuiltinId::Sockets as u16, args.len() as u8),
6170 line,
6171 Some(root),
6172 );
6173 }
6174 ExprKind::Pipes(args) => {
6175 for a in args {
6176 self.compile_expr(a)?;
6177 }
6178 self.emit_op(
6179 Op::CallBuiltin(BuiltinId::Pipes as u16, args.len() as u8),
6180 line,
6181 Some(root),
6182 );
6183 }
6184 ExprKind::BlockDevices(args) => {
6185 for a in args {
6186 self.compile_expr(a)?;
6187 }
6188 self.emit_op(
6189 Op::CallBuiltin(BuiltinId::BlockDevices as u16, args.len() as u8),
6190 line,
6191 Some(root),
6192 );
6193 }
6194 ExprKind::CharDevices(args) => {
6195 for a in args {
6196 self.compile_expr(a)?;
6197 }
6198 self.emit_op(
6199 Op::CallBuiltin(BuiltinId::CharDevices as u16, args.len() as u8),
6200 line,
6201 Some(root),
6202 );
6203 }
6204 ExprKind::Glob(args) => {
6205 for a in args {
6206 self.compile_expr(a)?;
6207 }
6208 self.emit_op(
6209 Op::CallBuiltin(BuiltinId::Glob as u16, args.len() as u8),
6210 line,
6211 Some(root),
6212 );
6213 }
6214 ExprKind::GlobPar { args, progress } => {
6215 for a in args {
6216 self.compile_expr(a)?;
6217 }
6218 match progress {
6219 None => {
6220 self.emit_op(
6221 Op::CallBuiltin(BuiltinId::GlobPar as u16, args.len() as u8),
6222 line,
6223 Some(root),
6224 );
6225 }
6226 Some(p) => {
6227 self.compile_expr(p)?;
6228 self.emit_op(
6229 Op::CallBuiltin(
6230 BuiltinId::GlobParProgress as u16,
6231 (args.len() + 1) as u8,
6232 ),
6233 line,
6234 Some(root),
6235 );
6236 }
6237 }
6238 }
6239 ExprKind::ParSed { args, progress } => {
6240 for a in args {
6241 self.compile_expr(a)?;
6242 }
6243 match progress {
6244 None => {
6245 self.emit_op(
6246 Op::CallBuiltin(BuiltinId::ParSed as u16, args.len() as u8),
6247 line,
6248 Some(root),
6249 );
6250 }
6251 Some(p) => {
6252 self.compile_expr(p)?;
6253 self.emit_op(
6254 Op::CallBuiltin(
6255 BuiltinId::ParSedProgress as u16,
6256 (args.len() + 1) as u8,
6257 ),
6258 line,
6259 Some(root),
6260 );
6261 }
6262 }
6263 }
6264
6265 ExprKind::Bless { ref_expr, class } => {
6267 self.compile_expr(ref_expr)?;
6268 if let Some(c) = class {
6269 self.compile_expr(c)?;
6270 self.emit_op(
6271 Op::CallBuiltin(BuiltinId::Bless as u16, 2),
6272 line,
6273 Some(root),
6274 );
6275 } else {
6276 self.emit_op(
6277 Op::CallBuiltin(BuiltinId::Bless as u16, 1),
6278 line,
6279 Some(root),
6280 );
6281 }
6282 }
6283 ExprKind::Caller(e) => {
6284 if let Some(inner) = e {
6285 self.compile_expr(inner)?;
6286 self.emit_op(
6287 Op::CallBuiltin(BuiltinId::Caller as u16, 1),
6288 line,
6289 Some(root),
6290 );
6291 } else {
6292 self.emit_op(
6293 Op::CallBuiltin(BuiltinId::Caller as u16, 0),
6294 line,
6295 Some(root),
6296 );
6297 }
6298 }
6299 ExprKind::Wantarray => {
6300 self.emit_op(
6301 Op::CallBuiltin(BuiltinId::Wantarray as u16, 0),
6302 line,
6303 Some(root),
6304 );
6305 }
6306
6307 ExprKind::ScalarRef(e) => match &e.kind {
6309 ExprKind::ScalarVar(name) => {
6310 let idx = self.intern_scalar_var_for_ops(name);
6311 self.emit_op(Op::MakeScalarBindingRef(idx), line, Some(root));
6312 }
6313 ExprKind::ArrayVar(name) => {
6314 self.check_strict_array_access(name, line)?;
6315 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
6316 self.emit_op(Op::MakeArrayBindingRef(idx), line, Some(root));
6317 }
6318 ExprKind::HashVar(name) => {
6319 self.check_strict_hash_access(name, line)?;
6320 let idx = self.chunk.intern_name(name);
6321 self.emit_op(Op::MakeHashBindingRef(idx), line, Some(root));
6322 }
6323 ExprKind::Deref {
6324 expr: inner,
6325 kind: Sigil::Array,
6326 } => {
6327 self.compile_expr(inner)?;
6328 self.emit_op(Op::MakeArrayRefAlias, line, Some(root));
6329 }
6330 ExprKind::Deref {
6331 expr: inner,
6332 kind: Sigil::Hash,
6333 } => {
6334 self.compile_expr(inner)?;
6335 self.emit_op(Op::MakeHashRefAlias, line, Some(root));
6336 }
6337 ExprKind::ArraySlice { .. } | ExprKind::HashSlice { .. } => {
6338 self.compile_expr_ctx(e, WantarrayCtx::List)?;
6339 self.emit_op(Op::MakeArrayRef, line, Some(root));
6340 }
6341 ExprKind::AnonymousListSlice { .. } | ExprKind::HashSliceDeref { .. } => {
6342 self.compile_expr_ctx(e, WantarrayCtx::List)?;
6343 self.emit_op(Op::MakeArrayRef, line, Some(root));
6344 }
6345 _ => {
6346 self.compile_expr(e)?;
6347 self.emit_op(Op::MakeScalarRef, line, Some(root));
6348 }
6349 },
6350 ExprKind::ArrayRef(elems) => {
6351 for e in elems {
6355 self.compile_expr_ctx(e, WantarrayCtx::List)?;
6356 }
6357 self.emit_op(Op::MakeArray(elems.len() as u16), line, Some(root));
6358 self.emit_op(Op::MakeArrayRef, line, Some(root));
6359 }
6360 ExprKind::HashRef(pairs) => {
6361 for (k, v) in pairs {
6364 self.compile_expr(k)?;
6365 self.compile_expr_ctx(v, WantarrayCtx::List)?;
6366 }
6367 self.emit_op(Op::MakeHash((pairs.len() * 2) as u16), line, Some(root));
6368 self.emit_op(Op::MakeHashRef, line, Some(root));
6369 }
6370 ExprKind::CodeRef { body, params } => {
6371 let block_idx = self.chunk.add_block(body.clone());
6372 let sig_idx = self.chunk.add_code_ref_sig(params.clone());
6373 self.emit_op(Op::MakeCodeRef(block_idx, sig_idx), line, Some(root));
6374 }
6375 ExprKind::SubroutineRef(name) => {
6376 let q = self.qualify_sub_key(name);
6378 let name_idx = self.chunk.intern_name(&q);
6379 self.emit_op(Op::Call(name_idx, 0, ctx.as_byte()), line, Some(root));
6380 }
6381 ExprKind::SubroutineCodeRef(name) => {
6382 let name_idx = self.chunk.intern_name(name);
6384 self.emit_op(Op::LoadNamedSubRef(name_idx), line, Some(root));
6385 }
6386 ExprKind::DynamicSubCodeRef(expr) => {
6387 self.compile_expr(expr)?;
6388 self.emit_op(Op::LoadDynamicSubRef, line, Some(root));
6389 }
6390
6391 ExprKind::ArrowDeref { expr, index, kind } => match kind {
6393 DerefKind::Array => {
6394 self.compile_arrow_array_base_expr(expr)?;
6395 let mut used_arrow_slice = false;
6396 if let ExprKind::List(indices) = &index.kind {
6397 for ix in indices {
6398 self.compile_array_slice_index_expr(ix)?;
6399 }
6400 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, Some(root));
6401 used_arrow_slice = true;
6402 } else if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
6403 self.compile_expr(index)?;
6404 self.emit_op(Op::ArrowArray, line, Some(root));
6405 } else {
6406 self.compile_array_slice_index_expr(index)?;
6408 self.emit_op(Op::ArrowArraySlice(1), line, Some(root));
6409 used_arrow_slice = true;
6410 }
6411 if used_arrow_slice && ctx != WantarrayCtx::List {
6412 self.emit_op(Op::ListSliceToScalar, line, Some(root));
6413 }
6414 }
6415 DerefKind::Hash => {
6416 self.compile_arrow_hash_base_expr(expr)?;
6417 self.compile_expr(index)?;
6418 self.emit_op(Op::ArrowHash, line, Some(root));
6419 }
6420 DerefKind::Call => {
6421 self.compile_expr(expr)?;
6422 self.compile_expr_ctx(index, WantarrayCtx::List)?;
6424 self.emit_op(Op::ArrowCall(ctx.as_byte()), line, Some(root));
6425 }
6426 },
6427 ExprKind::Deref { expr, kind } => {
6428 if ctx != WantarrayCtx::List && matches!(kind, Sigil::Array) {
6431 self.compile_expr(expr)?;
6432 self.emit_op(Op::ArrayDerefLen, line, Some(root));
6433 } else if ctx != WantarrayCtx::List && matches!(kind, Sigil::Hash) {
6434 self.compile_expr(expr)?;
6435 self.emit_op(Op::SymbolicDeref(2), line, Some(root));
6436 self.emit_op(Op::ValueScalarContext, line, Some(root));
6437 } else {
6438 self.compile_expr(expr)?;
6439 let b = match kind {
6440 Sigil::Scalar => 0u8,
6441 Sigil::Array => 1,
6442 Sigil::Hash => 2,
6443 Sigil::Typeglob => 3,
6444 };
6445 self.emit_op(Op::SymbolicDeref(b), line, Some(root));
6446 }
6447 }
6448
6449 ExprKind::InterpolatedString(parts) => {
6451 let has_case_escapes = parts.iter().any(|p| {
6453 if let StringPart::Literal(s) = p {
6454 s.contains('\\')
6455 && (s.contains("\\U")
6456 || s.contains("\\L")
6457 || s.contains("\\u")
6458 || s.contains("\\l")
6459 || s.contains("\\Q")
6460 || s.contains("\\E"))
6461 } else {
6462 false
6463 }
6464 });
6465 if parts.is_empty() {
6466 let idx = self.chunk.add_constant(PerlValue::string(String::new()));
6467 self.emit_op(Op::LoadConst(idx), line, Some(root));
6468 } else {
6469 if !matches!(&parts[0], StringPart::Literal(_)) {
6472 let idx = self.chunk.add_constant(PerlValue::string(String::new()));
6473 self.emit_op(Op::LoadConst(idx), line, Some(root));
6474 }
6475 self.compile_string_part(&parts[0], line, Some(root))?;
6476 for part in &parts[1..] {
6477 self.compile_string_part(part, line, Some(root))?;
6478 self.emit_op(Op::Concat, line, Some(root));
6479 }
6480 if !matches!(&parts[0], StringPart::Literal(_)) {
6481 self.emit_op(Op::Concat, line, Some(root));
6482 }
6483 }
6484 if has_case_escapes {
6485 self.emit_op(Op::ProcessCaseEscapes, line, Some(root));
6486 }
6487 }
6488
6489 ExprKind::List(exprs) => {
6491 if ctx == WantarrayCtx::Scalar {
6492 if let Some(last) = exprs.last() {
6494 self.compile_expr_ctx(last, WantarrayCtx::Scalar)?;
6495 } else {
6496 self.emit_op(Op::LoadUndef, line, Some(root));
6497 }
6498 } else {
6499 for e in exprs {
6500 self.compile_expr_ctx(e, ctx)?;
6501 }
6502 if exprs.len() != 1 {
6503 self.emit_op(Op::MakeArray(exprs.len() as u16), line, Some(root));
6504 }
6505 }
6506 }
6507
6508 ExprKind::QW(words) => {
6510 for w in words {
6511 let idx = self.chunk.add_constant(PerlValue::string(w.clone()));
6512 self.emit_op(Op::LoadConst(idx), line, Some(root));
6513 }
6514 self.emit_op(Op::MakeArray(words.len() as u16), line, Some(root));
6515 }
6516
6517 ExprKind::PostfixIf { expr, condition } => {
6519 self.compile_boolean_rvalue_condition(condition)?;
6520 let j = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
6521 self.compile_expr(expr)?;
6522 let end = self.emit_op(Op::Jump(0), line, Some(root));
6523 self.chunk.patch_jump_here(j);
6524 self.emit_op(Op::LoadUndef, line, Some(root));
6525 self.chunk.patch_jump_here(end);
6526 }
6527 ExprKind::PostfixUnless { expr, condition } => {
6528 self.compile_boolean_rvalue_condition(condition)?;
6529 let j = self.emit_op(Op::JumpIfTrue(0), line, Some(root));
6530 self.compile_expr(expr)?;
6531 let end = self.emit_op(Op::Jump(0), line, Some(root));
6532 self.chunk.patch_jump_here(j);
6533 self.emit_op(Op::LoadUndef, line, Some(root));
6534 self.chunk.patch_jump_here(end);
6535 }
6536
6537 ExprKind::PostfixWhile { expr, condition } => {
6539 let is_do_block = matches!(
6541 &expr.kind,
6542 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
6543 );
6544 if is_do_block {
6545 let loop_start = self.chunk.len();
6547 self.compile_expr(expr)?;
6548 self.emit_op(Op::Pop, line, Some(root));
6549 self.compile_boolean_rvalue_condition(condition)?;
6550 self.emit_op(Op::JumpIfTrue(loop_start), line, Some(root));
6551 self.emit_op(Op::LoadUndef, line, Some(root));
6552 } else {
6553 let loop_start = self.chunk.len();
6555 self.compile_boolean_rvalue_condition(condition)?;
6556 let exit_jump = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
6557 self.compile_expr(expr)?;
6558 self.emit_op(Op::Pop, line, Some(root));
6559 self.emit_op(Op::Jump(loop_start), line, Some(root));
6560 self.chunk.patch_jump_here(exit_jump);
6561 self.emit_op(Op::LoadUndef, line, Some(root));
6562 }
6563 }
6564 ExprKind::PostfixUntil { expr, condition } => {
6565 let is_do_block = matches!(
6566 &expr.kind,
6567 ExprKind::Do(inner) if matches!(inner.kind, ExprKind::CodeRef { .. })
6568 );
6569 if is_do_block {
6570 let loop_start = self.chunk.len();
6571 self.compile_expr(expr)?;
6572 self.emit_op(Op::Pop, line, Some(root));
6573 self.compile_boolean_rvalue_condition(condition)?;
6574 self.emit_op(Op::JumpIfFalse(loop_start), line, Some(root));
6575 self.emit_op(Op::LoadUndef, line, Some(root));
6576 } else {
6577 let loop_start = self.chunk.len();
6578 self.compile_boolean_rvalue_condition(condition)?;
6579 let exit_jump = self.emit_op(Op::JumpIfTrue(0), line, Some(root));
6580 self.compile_expr(expr)?;
6581 self.emit_op(Op::Pop, line, Some(root));
6582 self.emit_op(Op::Jump(loop_start), line, Some(root));
6583 self.chunk.patch_jump_here(exit_jump);
6584 self.emit_op(Op::LoadUndef, line, Some(root));
6585 }
6586 }
6587 ExprKind::PostfixForeach { expr, list } => {
6588 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6589 let list_name = self.chunk.intern_name("__pf_foreach_list__");
6590 self.emit_op(Op::DeclareArray(list_name), line, Some(root));
6591 let counter = self.chunk.intern_name("__pf_foreach_i__");
6592 self.emit_op(Op::LoadInt(0), line, Some(root));
6593 self.emit_op(Op::DeclareScalar(counter), line, Some(root));
6594 let underscore = self.chunk.intern_name("_");
6595
6596 let loop_start = self.chunk.len();
6597 self.emit_get_scalar(counter, line, Some(root));
6598 self.emit_op(Op::ArrayLen(list_name), line, Some(root));
6599 self.emit_op(Op::NumLt, line, Some(root));
6600 let exit_jump = self.emit_op(Op::JumpIfFalse(0), line, Some(root));
6601
6602 self.emit_get_scalar(counter, line, Some(root));
6603 self.emit_op(Op::GetArrayElem(list_name), line, Some(root));
6604 self.emit_set_scalar(underscore, line, Some(root));
6605
6606 self.compile_expr(expr)?;
6607 self.emit_op(Op::Pop, line, Some(root));
6608
6609 self.emit_pre_inc(counter, line, Some(root));
6610 self.emit_op(Op::Pop, line, Some(root));
6611 self.emit_op(Op::Jump(loop_start), line, Some(root));
6612 self.chunk.patch_jump_here(exit_jump);
6613 self.emit_op(Op::LoadUndef, line, Some(root));
6614 }
6615
6616 ExprKind::AlgebraicMatch { subject, arms } => {
6617 let idx = self
6618 .chunk
6619 .add_algebraic_match_entry(subject.as_ref().clone(), arms.clone());
6620 self.emit_op(Op::AlgebraicMatch(idx), line, Some(root));
6621 }
6622
6623 ExprKind::Match {
6625 expr,
6626 pattern,
6627 flags,
6628 scalar_g,
6629 delim: _,
6630 } => {
6631 self.compile_expr(expr)?;
6632 let pat_idx = self.chunk.add_constant(PerlValue::string(pattern.clone()));
6633 let flags_idx = self.chunk.add_constant(PerlValue::string(flags.clone()));
6634 let pos_key_idx = if *scalar_g && flags.contains('g') {
6635 if let ExprKind::ScalarVar(n) = &expr.kind {
6636 let stor = self.scalar_storage_name_for_ops(n);
6637 self.chunk.add_constant(PerlValue::string(stor))
6638 } else {
6639 u16::MAX
6640 }
6641 } else {
6642 u16::MAX
6643 };
6644 self.emit_op(
6645 Op::RegexMatch(pat_idx, flags_idx, *scalar_g, pos_key_idx),
6646 line,
6647 Some(root),
6648 );
6649 }
6650
6651 ExprKind::Substitution {
6652 expr,
6653 pattern,
6654 replacement,
6655 flags,
6656 delim: _,
6657 } => {
6658 self.compile_expr(expr)?;
6659 let pat_idx = self.chunk.add_constant(PerlValue::string(pattern.clone()));
6660 let repl_idx = self
6661 .chunk
6662 .add_constant(PerlValue::string(replacement.clone()));
6663 let flags_idx = self.chunk.add_constant(PerlValue::string(flags.clone()));
6664 let lv_idx = self.chunk.add_lvalue_expr(expr.as_ref().clone());
6665 self.emit_op(
6666 Op::RegexSubst(pat_idx, repl_idx, flags_idx, lv_idx),
6667 line,
6668 Some(root),
6669 );
6670 }
6671 ExprKind::Transliterate {
6672 expr,
6673 from,
6674 to,
6675 flags,
6676 delim: _,
6677 } => {
6678 self.compile_expr(expr)?;
6679 let from_idx = self.chunk.add_constant(PerlValue::string(from.clone()));
6680 let to_idx = self.chunk.add_constant(PerlValue::string(to.clone()));
6681 let flags_idx = self.chunk.add_constant(PerlValue::string(flags.clone()));
6682 let lv_idx = self.chunk.add_lvalue_expr(expr.as_ref().clone());
6683 self.emit_op(
6684 Op::RegexTransliterate(from_idx, to_idx, flags_idx, lv_idx),
6685 line,
6686 Some(root),
6687 );
6688 }
6689
6690 ExprKind::Regex(pattern, flags) => {
6692 if ctx == WantarrayCtx::Void {
6693 self.compile_boolean_rvalue_condition(root)?;
6695 } else {
6696 let pat_idx = self.chunk.add_constant(PerlValue::string(pattern.clone()));
6697 let flags_idx = self.chunk.add_constant(PerlValue::string(flags.clone()));
6698 self.emit_op(Op::LoadRegex(pat_idx, flags_idx), line, Some(root));
6699 }
6700 }
6701
6702 ExprKind::MapExpr {
6704 block,
6705 list,
6706 flatten_array_refs,
6707 stream,
6708 } => {
6709 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6710 if *stream {
6711 let block_idx = self.chunk.add_block(block.clone());
6712 if *flatten_array_refs {
6713 self.emit_op(Op::MapsFlatMapWithBlock(block_idx), line, Some(root));
6714 } else {
6715 self.emit_op(Op::MapsWithBlock(block_idx), line, Some(root));
6716 }
6717 } else if let Some(k) = crate::map_grep_fast::detect_map_int_mul(block) {
6718 self.emit_op(Op::MapIntMul(k), line, Some(root));
6719 } else {
6720 let block_idx = self.chunk.add_block(block.clone());
6721 if *flatten_array_refs {
6722 self.emit_op(Op::FlatMapWithBlock(block_idx), line, Some(root));
6723 } else {
6724 self.emit_op(Op::MapWithBlock(block_idx), line, Some(root));
6725 }
6726 }
6727 if ctx != WantarrayCtx::List {
6728 self.emit_op(Op::StackArrayLen, line, Some(root));
6729 }
6730 }
6731 ExprKind::MapExprComma {
6732 expr,
6733 list,
6734 flatten_array_refs,
6735 stream,
6736 } => {
6737 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6738 let idx = self.chunk.add_map_expr_entry(*expr.clone());
6739 if *stream {
6740 if *flatten_array_refs {
6741 self.emit_op(Op::MapsFlatMapWithExpr(idx), line, Some(root));
6742 } else {
6743 self.emit_op(Op::MapsWithExpr(idx), line, Some(root));
6744 }
6745 } else if *flatten_array_refs {
6746 self.emit_op(Op::FlatMapWithExpr(idx), line, Some(root));
6747 } else {
6748 self.emit_op(Op::MapWithExpr(idx), line, Some(root));
6749 }
6750 if ctx != WantarrayCtx::List {
6751 self.emit_op(Op::StackArrayLen, line, Some(root));
6752 }
6753 }
6754 ExprKind::ForEachExpr { block, list } => {
6755 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6756 let block_idx = self.chunk.add_block(block.clone());
6757 self.emit_op(Op::ForEachWithBlock(block_idx), line, Some(root));
6758 }
6759 ExprKind::GrepExpr {
6760 block,
6761 list,
6762 keyword,
6763 } => {
6764 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6765 if keyword.is_stream() {
6766 let block_idx = self.chunk.add_block(block.clone());
6767 self.emit_op(Op::FilterWithBlock(block_idx), line, Some(root));
6768 } else if let Some((m, r)) = crate::map_grep_fast::detect_grep_int_mod_eq(block) {
6769 self.emit_op(Op::GrepIntModEq(m, r), line, Some(root));
6770 } else {
6771 let block_idx = self.chunk.add_block(block.clone());
6772 self.emit_op(Op::GrepWithBlock(block_idx), line, Some(root));
6773 }
6774 if ctx != WantarrayCtx::List {
6775 self.emit_op(Op::StackArrayLen, line, Some(root));
6776 }
6777 }
6778 ExprKind::GrepExprComma {
6779 expr,
6780 list,
6781 keyword,
6782 } => {
6783 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6784 let idx = self.chunk.add_grep_expr_entry(*expr.clone());
6785 if keyword.is_stream() {
6786 self.emit_op(Op::FilterWithExpr(idx), line, Some(root));
6787 } else {
6788 self.emit_op(Op::GrepWithExpr(idx), line, Some(root));
6789 }
6790 if ctx != WantarrayCtx::List {
6791 self.emit_op(Op::StackArrayLen, line, Some(root));
6792 }
6793 }
6794 ExprKind::SortExpr { cmp, list } => {
6795 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6796 match cmp {
6797 Some(crate::ast::SortComparator::Block(block)) => {
6798 if let Some(mode) = detect_sort_block_fast(block) {
6799 let tag = match mode {
6800 crate::sort_fast::SortBlockFast::Numeric => 0u8,
6801 crate::sort_fast::SortBlockFast::String => 1u8,
6802 crate::sort_fast::SortBlockFast::NumericRev => 2u8,
6803 crate::sort_fast::SortBlockFast::StringRev => 3u8,
6804 };
6805 self.emit_op(Op::SortWithBlockFast(tag), line, Some(root));
6806 } else {
6807 let block_idx = self.chunk.add_block(block.clone());
6808 self.emit_op(Op::SortWithBlock(block_idx), line, Some(root));
6809 }
6810 }
6811 Some(crate::ast::SortComparator::Code(code_expr)) => {
6812 self.compile_expr(code_expr)?;
6813 self.emit_op(Op::SortWithCodeComparator(ctx.as_byte()), line, Some(root));
6814 }
6815 None => {
6816 self.emit_op(Op::SortNoBlock, line, Some(root));
6817 }
6818 }
6819 }
6820
6821 ExprKind::PMapExpr {
6823 block,
6824 list,
6825 progress,
6826 flat_outputs,
6827 on_cluster,
6828 stream,
6829 } => {
6830 if *stream {
6831 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6833 let block_idx = self.chunk.add_block(block.clone());
6834 if *flat_outputs {
6835 self.emit_op(Op::PFlatMapsWithBlock(block_idx), line, Some(root));
6836 } else {
6837 self.emit_op(Op::PMapsWithBlock(block_idx), line, Some(root));
6838 }
6839 } else {
6840 if let Some(p) = progress {
6841 self.compile_expr(p)?;
6842 } else {
6843 self.emit_op(Op::LoadInt(0), line, Some(root));
6844 }
6845 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6846 if let Some(cluster_e) = on_cluster {
6847 self.compile_expr(cluster_e)?;
6848 let block_idx = self.chunk.add_block(block.clone());
6849 self.emit_op(
6850 Op::PMapRemote {
6851 block_idx,
6852 flat: u8::from(*flat_outputs),
6853 },
6854 line,
6855 Some(root),
6856 );
6857 } else {
6858 let block_idx = self.chunk.add_block(block.clone());
6859 if *flat_outputs {
6860 self.emit_op(Op::PFlatMapWithBlock(block_idx), line, Some(root));
6861 } else {
6862 self.emit_op(Op::PMapWithBlock(block_idx), line, Some(root));
6863 }
6864 }
6865 }
6866 }
6867 ExprKind::PMapChunkedExpr {
6868 chunk_size,
6869 block,
6870 list,
6871 progress,
6872 } => {
6873 if let Some(p) = progress {
6874 self.compile_expr(p)?;
6875 } else {
6876 self.emit_op(Op::LoadInt(0), line, Some(root));
6877 }
6878 self.compile_expr(chunk_size)?;
6879 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6880 let block_idx = self.chunk.add_block(block.clone());
6881 self.emit_op(Op::PMapChunkedWithBlock(block_idx), line, Some(root));
6882 }
6883 ExprKind::PGrepExpr {
6884 block,
6885 list,
6886 progress,
6887 stream,
6888 } => {
6889 if *stream {
6890 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6891 let block_idx = self.chunk.add_block(block.clone());
6892 self.emit_op(Op::PGrepsWithBlock(block_idx), line, Some(root));
6893 } else {
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_ctx(list, WantarrayCtx::List)?;
6900 let block_idx = self.chunk.add_block(block.clone());
6901 self.emit_op(Op::PGrepWithBlock(block_idx), line, Some(root));
6902 }
6903 }
6904 ExprKind::PForExpr {
6905 block,
6906 list,
6907 progress,
6908 } => {
6909 if let Some(p) = progress {
6910 self.compile_expr(p)?;
6911 } else {
6912 self.emit_op(Op::LoadInt(0), line, Some(root));
6913 }
6914 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6915 let block_idx = self.chunk.add_block(block.clone());
6916 self.emit_op(Op::PForWithBlock(block_idx), line, Some(root));
6917 }
6918 ExprKind::ParLinesExpr {
6919 path,
6920 callback,
6921 progress,
6922 } => {
6923 let idx = self.chunk.add_par_lines_entry(
6924 path.as_ref().clone(),
6925 callback.as_ref().clone(),
6926 progress.as_ref().map(|p| p.as_ref().clone()),
6927 );
6928 self.emit_op(Op::ParLines(idx), line, Some(root));
6929 }
6930 ExprKind::ParWalkExpr {
6931 path,
6932 callback,
6933 progress,
6934 } => {
6935 let idx = self.chunk.add_par_walk_entry(
6936 path.as_ref().clone(),
6937 callback.as_ref().clone(),
6938 progress.as_ref().map(|p| p.as_ref().clone()),
6939 );
6940 self.emit_op(Op::ParWalk(idx), line, Some(root));
6941 }
6942 ExprKind::PwatchExpr { path, callback } => {
6943 let idx = self
6944 .chunk
6945 .add_pwatch_entry(path.as_ref().clone(), callback.as_ref().clone());
6946 self.emit_op(Op::Pwatch(idx), line, Some(root));
6947 }
6948 ExprKind::PSortExpr {
6949 cmp,
6950 list,
6951 progress,
6952 } => {
6953 if let Some(p) = progress {
6954 self.compile_expr(p)?;
6955 } else {
6956 self.emit_op(Op::LoadInt(0), line, Some(root));
6957 }
6958 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6959 if let Some(block) = cmp {
6960 if let Some(mode) = detect_sort_block_fast(block) {
6961 let tag = match mode {
6962 crate::sort_fast::SortBlockFast::Numeric => 0u8,
6963 crate::sort_fast::SortBlockFast::String => 1u8,
6964 crate::sort_fast::SortBlockFast::NumericRev => 2u8,
6965 crate::sort_fast::SortBlockFast::StringRev => 3u8,
6966 };
6967 self.emit_op(Op::PSortWithBlockFast(tag), line, Some(root));
6968 } else {
6969 let block_idx = self.chunk.add_block(block.clone());
6970 self.emit_op(Op::PSortWithBlock(block_idx), line, Some(root));
6971 }
6972 } else {
6973 self.emit_op(Op::PSortNoBlockParallel, line, Some(root));
6974 }
6975 }
6976 ExprKind::ReduceExpr { block, list } => {
6977 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6978 let block_idx = self.chunk.add_block(block.clone());
6979 self.emit_op(Op::ReduceWithBlock(block_idx), line, Some(root));
6980 }
6981 ExprKind::PReduceExpr {
6982 block,
6983 list,
6984 progress,
6985 } => {
6986 if let Some(p) = progress {
6987 self.compile_expr(p)?;
6988 } else {
6989 self.emit_op(Op::LoadInt(0), line, Some(root));
6990 }
6991 self.compile_expr_ctx(list, WantarrayCtx::List)?;
6992 let block_idx = self.chunk.add_block(block.clone());
6993 self.emit_op(Op::PReduceWithBlock(block_idx), line, Some(root));
6994 }
6995 ExprKind::PReduceInitExpr {
6996 init,
6997 block,
6998 list,
6999 progress,
7000 } => {
7001 if let Some(p) = progress {
7002 self.compile_expr(p)?;
7003 } else {
7004 self.emit_op(Op::LoadInt(0), line, Some(root));
7005 }
7006 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7007 self.compile_expr(init)?;
7008 let block_idx = self.chunk.add_block(block.clone());
7009 self.emit_op(Op::PReduceInitWithBlock(block_idx), line, Some(root));
7010 }
7011 ExprKind::PMapReduceExpr {
7012 map_block,
7013 reduce_block,
7014 list,
7015 progress,
7016 } => {
7017 if let Some(p) = progress {
7018 self.compile_expr(p)?;
7019 } else {
7020 self.emit_op(Op::LoadInt(0), line, Some(root));
7021 }
7022 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7023 let map_idx = self.chunk.add_block(map_block.clone());
7024 let reduce_idx = self.chunk.add_block(reduce_block.clone());
7025 self.emit_op(
7026 Op::PMapReduceWithBlocks(map_idx, reduce_idx),
7027 line,
7028 Some(root),
7029 );
7030 }
7031 ExprKind::PcacheExpr {
7032 block,
7033 list,
7034 progress,
7035 } => {
7036 if let Some(p) = progress {
7037 self.compile_expr(p)?;
7038 } else {
7039 self.emit_op(Op::LoadInt(0), line, Some(root));
7040 }
7041 self.compile_expr_ctx(list, WantarrayCtx::List)?;
7042 let block_idx = self.chunk.add_block(block.clone());
7043 self.emit_op(Op::PcacheWithBlock(block_idx), line, Some(root));
7044 }
7045 ExprKind::PselectExpr { receivers, timeout } => {
7046 let n = receivers.len();
7047 if n > u8::MAX as usize {
7048 return Err(CompileError::Unsupported(
7049 "pselect: too many receivers".into(),
7050 ));
7051 }
7052 for r in receivers {
7053 self.compile_expr(r)?;
7054 }
7055 let has_timeout = timeout.is_some();
7056 if let Some(t) = timeout {
7057 self.compile_expr(t)?;
7058 }
7059 self.emit_op(
7060 Op::Pselect {
7061 n_rx: n as u8,
7062 has_timeout,
7063 },
7064 line,
7065 Some(root),
7066 );
7067 }
7068 ExprKind::FanExpr {
7069 count,
7070 block,
7071 progress,
7072 capture,
7073 } => {
7074 if let Some(p) = progress {
7075 self.compile_expr(p)?;
7076 } else {
7077 self.emit_op(Op::LoadInt(0), line, Some(root));
7078 }
7079 let block_idx = self.chunk.add_block(block.clone());
7080 match (count, capture) {
7081 (Some(c), false) => {
7082 self.compile_expr(c)?;
7083 self.emit_op(Op::FanWithBlock(block_idx), line, Some(root));
7084 }
7085 (None, false) => {
7086 self.emit_op(Op::FanWithBlockAuto(block_idx), line, Some(root));
7087 }
7088 (Some(c), true) => {
7089 self.compile_expr(c)?;
7090 self.emit_op(Op::FanCapWithBlock(block_idx), line, Some(root));
7091 }
7092 (None, true) => {
7093 self.emit_op(Op::FanCapWithBlockAuto(block_idx), line, Some(root));
7094 }
7095 }
7096 }
7097 ExprKind::AsyncBlock { body } | ExprKind::SpawnBlock { body } => {
7098 let block_idx = self.chunk.add_block(body.clone());
7099 self.emit_op(Op::AsyncBlock(block_idx), line, Some(root));
7100 }
7101 ExprKind::Trace { body } => {
7102 let block_idx = self.chunk.add_block(body.clone());
7103 self.emit_op(Op::TraceBlock(block_idx), line, Some(root));
7104 }
7105 ExprKind::Timer { body } => {
7106 let block_idx = self.chunk.add_block(body.clone());
7107 self.emit_op(Op::TimerBlock(block_idx), line, Some(root));
7108 }
7109 ExprKind::Bench { body, times } => {
7110 self.compile_expr(times)?;
7111 let block_idx = self.chunk.add_block(body.clone());
7112 self.emit_op(Op::BenchBlock(block_idx), line, Some(root));
7113 }
7114 ExprKind::Await(e) => {
7115 self.compile_expr(e)?;
7116 self.emit_op(Op::Await, line, Some(root));
7117 }
7118 ExprKind::Slurp(e) => {
7119 self.compile_expr(e)?;
7120 self.emit_op(
7121 Op::CallBuiltin(BuiltinId::Slurp as u16, 1),
7122 line,
7123 Some(root),
7124 );
7125 }
7126 ExprKind::Capture(e) => {
7127 self.compile_expr(e)?;
7128 self.emit_op(
7129 Op::CallBuiltin(BuiltinId::Capture as u16, 1),
7130 line,
7131 Some(root),
7132 );
7133 }
7134 ExprKind::Qx(e) => {
7135 self.compile_expr(e)?;
7136 self.emit_op(
7137 Op::CallBuiltin(BuiltinId::Readpipe as u16, 1),
7138 line,
7139 Some(root),
7140 );
7141 }
7142 ExprKind::FetchUrl(e) => {
7143 self.compile_expr(e)?;
7144 self.emit_op(
7145 Op::CallBuiltin(BuiltinId::FetchUrl as u16, 1),
7146 line,
7147 Some(root),
7148 );
7149 }
7150 ExprKind::Pchannel { capacity } => {
7151 if let Some(c) = capacity {
7152 self.compile_expr(c)?;
7153 self.emit_op(
7154 Op::CallBuiltin(BuiltinId::Pchannel as u16, 1),
7155 line,
7156 Some(root),
7157 );
7158 } else {
7159 self.emit_op(
7160 Op::CallBuiltin(BuiltinId::Pchannel as u16, 0),
7161 line,
7162 Some(root),
7163 );
7164 }
7165 }
7166 ExprKind::RetryBlock { .. }
7167 | ExprKind::RateLimitBlock { .. }
7168 | ExprKind::EveryBlock { .. }
7169 | ExprKind::GenBlock { .. }
7170 | ExprKind::Yield(_)
7171 | ExprKind::Spinner { .. } => {
7172 return Err(CompileError::Unsupported(
7173 "retry/rate_limit/every/gen/yield (tree interpreter only)".into(),
7174 ));
7175 }
7176 ExprKind::MyExpr { .. } => {
7177 return Err(CompileError::Unsupported(
7180 "my/our/state/local in expression context (tree interpreter only)".into(),
7181 ));
7182 }
7183 }
7184 Ok(())
7185 }
7186
7187 fn compile_string_part(
7188 &mut self,
7189 part: &StringPart,
7190 line: usize,
7191 parent: Option<&Expr>,
7192 ) -> Result<(), CompileError> {
7193 match part {
7194 StringPart::Literal(s) => {
7195 let idx = self.chunk.add_constant(PerlValue::string(s.clone()));
7196 self.emit_op(Op::LoadConst(idx), line, parent);
7197 }
7198 StringPart::ScalarVar(name) => {
7199 let idx = self.intern_scalar_var_for_ops(name);
7200 self.emit_get_scalar(idx, line, parent);
7201 }
7202 StringPart::ArrayVar(name) => {
7203 let idx = self.chunk.intern_name(&self.qualify_stash_array_name(name));
7204 self.emit_op(Op::GetArray(idx), line, parent);
7205 self.emit_op(Op::ArrayStringifyListSep, line, parent);
7206 }
7207 StringPart::Expr(e) => {
7208 if matches!(&e.kind, ExprKind::ArraySlice { .. })
7210 || matches!(
7211 &e.kind,
7212 ExprKind::Deref {
7213 kind: Sigil::Array,
7214 ..
7215 }
7216 )
7217 {
7218 self.compile_expr_ctx(e, WantarrayCtx::List)?;
7219 self.emit_op(Op::ArrayStringifyListSep, line, parent);
7220 } else {
7221 self.compile_expr(e)?;
7222 }
7223 }
7224 }
7225 Ok(())
7226 }
7227
7228 fn compile_assign(
7229 &mut self,
7230 target: &Expr,
7231 line: usize,
7232 keep: bool,
7233 ast: Option<&Expr>,
7234 ) -> Result<(), CompileError> {
7235 match &target.kind {
7236 ExprKind::ScalarVar(name) => {
7237 self.check_strict_scalar_access(name, line)?;
7238 self.check_scalar_mutable(name, line)?;
7239 let idx = self.intern_scalar_var_for_ops(name);
7240 if keep {
7241 self.emit_set_scalar_keep(idx, line, ast);
7242 } else {
7243 self.emit_set_scalar(idx, line, ast);
7244 }
7245 }
7246 ExprKind::ArrayVar(name) => {
7247 self.check_strict_array_access(name, line)?;
7248 let q = self.qualify_stash_array_name(name);
7249 self.check_array_mutable(&q, line)?;
7250 let idx = self.chunk.intern_name(&q);
7251 self.emit_op(Op::SetArray(idx), line, ast);
7252 if keep {
7253 self.emit_op(Op::GetArray(idx), line, ast);
7254 }
7255 }
7256 ExprKind::HashVar(name) => {
7257 self.check_strict_hash_access(name, line)?;
7258 self.check_hash_mutable(name, line)?;
7259 let idx = self.chunk.intern_name(name);
7260 self.emit_op(Op::SetHash(idx), line, ast);
7261 if keep {
7262 self.emit_op(Op::GetHash(idx), line, ast);
7263 }
7264 }
7265 ExprKind::ArrayElement { array, index } => {
7266 self.check_strict_array_access(array, line)?;
7267 let q = self.qualify_stash_array_name(array);
7268 self.check_array_mutable(&q, line)?;
7269 let idx = self.chunk.intern_name(&q);
7270 self.compile_expr(index)?;
7271 self.emit_op(Op::SetArrayElem(idx), line, ast);
7272 }
7273 ExprKind::ArraySlice { array, indices } => {
7274 if indices.is_empty() {
7275 if self.is_mysync_array(array) {
7276 return Err(CompileError::Unsupported(
7277 "mysync array slice assign (tree interpreter)".into(),
7278 ));
7279 }
7280 self.check_strict_array_access(array, line)?;
7281 let q = self.qualify_stash_array_name(array);
7282 self.check_array_mutable(&q, line)?;
7283 let arr_idx = self.chunk.intern_name(&q);
7284 self.emit_op(Op::SetNamedArraySlice(arr_idx, 0), line, ast);
7285 if keep {
7286 self.emit_op(Op::MakeArray(0), line, ast);
7287 }
7288 return Ok(());
7289 }
7290 if self.is_mysync_array(array) {
7291 return Err(CompileError::Unsupported(
7292 "mysync array slice assign (tree interpreter)".into(),
7293 ));
7294 }
7295 self.check_strict_array_access(array, line)?;
7296 let q = self.qualify_stash_array_name(array);
7297 self.check_array_mutable(&q, line)?;
7298 let arr_idx = self.chunk.intern_name(&q);
7299 for ix in indices {
7300 self.compile_array_slice_index_expr(ix)?;
7301 }
7302 self.emit_op(
7303 Op::SetNamedArraySlice(arr_idx, indices.len() as u16),
7304 line,
7305 ast,
7306 );
7307 if keep {
7308 for (ix, index_expr) in indices.iter().enumerate() {
7309 self.compile_array_slice_index_expr(index_expr)?;
7310 self.emit_op(Op::ArraySlicePart(arr_idx), line, ast);
7311 if ix > 0 {
7312 self.emit_op(Op::ArrayConcatTwo, line, ast);
7313 }
7314 }
7315 }
7316 return Ok(());
7317 }
7318 ExprKind::HashElement { hash, key } => {
7319 self.check_strict_hash_access(hash, line)?;
7320 self.check_hash_mutable(hash, line)?;
7321 let idx = self.chunk.intern_name(hash);
7322 self.compile_expr(key)?;
7323 self.emit_op(Op::SetHashElem(idx), line, ast);
7324 }
7325 ExprKind::HashSlice { hash, keys } => {
7326 if keys.is_empty() {
7327 if self.is_mysync_hash(hash) {
7328 return Err(CompileError::Unsupported(
7329 "mysync hash slice assign (tree interpreter)".into(),
7330 ));
7331 }
7332 self.check_strict_hash_access(hash, line)?;
7333 self.check_hash_mutable(hash, line)?;
7334 let hash_idx = self.chunk.intern_name(hash);
7335 self.emit_op(Op::SetHashSlice(hash_idx, 0), line, ast);
7336 if keep {
7337 self.emit_op(Op::MakeArray(0), line, ast);
7338 }
7339 return Ok(());
7340 }
7341 if self.is_mysync_hash(hash) {
7342 return Err(CompileError::Unsupported(
7343 "mysync hash slice assign (tree interpreter)".into(),
7344 ));
7345 }
7346 self.check_strict_hash_access(hash, line)?;
7347 self.check_hash_mutable(hash, line)?;
7348 let hash_idx = self.chunk.intern_name(hash);
7349 for key_expr in keys {
7354 self.compile_hash_slice_key_expr(key_expr)?;
7355 }
7356 self.emit_op(Op::SetHashSlice(hash_idx, keys.len() as u16), line, ast);
7357 if keep {
7358 for key_expr in keys {
7359 self.compile_expr(key_expr)?;
7360 self.emit_op(Op::GetHashElem(hash_idx), line, ast);
7361 }
7362 self.emit_op(Op::MakeArray(keys.len() as u16), line, ast);
7363 }
7364 return Ok(());
7365 }
7366 ExprKind::Deref {
7367 expr,
7368 kind: Sigil::Scalar,
7369 } => {
7370 self.compile_expr(expr)?;
7371 if keep {
7372 self.emit_op(Op::SetSymbolicScalarRefKeep, line, ast);
7373 } else {
7374 self.emit_op(Op::SetSymbolicScalarRef, line, ast);
7375 }
7376 }
7377 ExprKind::Deref {
7378 expr,
7379 kind: Sigil::Array,
7380 } => {
7381 self.compile_expr(expr)?;
7382 self.emit_op(Op::SetSymbolicArrayRef, line, ast);
7383 }
7384 ExprKind::Deref {
7385 expr,
7386 kind: Sigil::Hash,
7387 } => {
7388 self.compile_expr(expr)?;
7389 self.emit_op(Op::SetSymbolicHashRef, line, ast);
7390 }
7391 ExprKind::Deref {
7392 expr,
7393 kind: Sigil::Typeglob,
7394 } => {
7395 self.compile_expr(expr)?;
7396 self.emit_op(Op::SetSymbolicTypeglobRef, line, ast);
7397 }
7398 ExprKind::Typeglob(name) => {
7399 let idx = self.chunk.intern_name(name);
7400 if keep {
7401 self.emit_op(Op::TypeglobAssignFromValue(idx), line, ast);
7402 } else {
7403 return Err(CompileError::Unsupported(
7404 "typeglob assign without keep (internal)".into(),
7405 ));
7406 }
7407 }
7408 ExprKind::AnonymousListSlice { source, indices } => {
7409 if let ExprKind::Deref {
7410 expr: inner,
7411 kind: Sigil::Array,
7412 } = &source.kind
7413 {
7414 if indices.is_empty() {
7415 return Err(CompileError::Unsupported(
7416 "assign to empty list slice (internal)".into(),
7417 ));
7418 }
7419 self.compile_arrow_array_base_expr(inner)?;
7420 for ix in indices {
7421 self.compile_array_slice_index_expr(ix)?;
7422 }
7423 self.emit_op(Op::SetArrowArraySlice(indices.len() as u16), line, ast);
7424 if keep {
7425 self.compile_arrow_array_base_expr(inner)?;
7426 for ix in indices {
7427 self.compile_array_slice_index_expr(ix)?;
7428 }
7429 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, ast);
7430 }
7431 return Ok(());
7432 }
7433 return Err(CompileError::Unsupported(
7434 "assign to anonymous list slice (non-@array-deref base)".into(),
7435 ));
7436 }
7437 ExprKind::ArrowDeref {
7438 expr,
7439 index,
7440 kind: DerefKind::Hash,
7441 } => {
7442 self.compile_arrow_hash_base_expr(expr)?;
7443 self.compile_expr(index)?;
7444 if keep {
7445 self.emit_op(Op::SetArrowHashKeep, line, ast);
7446 } else {
7447 self.emit_op(Op::SetArrowHash, line, ast);
7448 }
7449 }
7450 ExprKind::ArrowDeref {
7451 expr,
7452 index,
7453 kind: DerefKind::Array,
7454 } => {
7455 if let ExprKind::List(indices) = &index.kind {
7456 self.compile_arrow_array_base_expr(expr)?;
7461 for ix in indices {
7462 self.compile_array_slice_index_expr(ix)?;
7463 }
7464 self.emit_op(Op::SetArrowArraySlice(indices.len() as u16), line, ast);
7465 if keep {
7466 self.compile_arrow_array_base_expr(expr)?;
7468 for ix in indices {
7469 self.compile_array_slice_index_expr(ix)?;
7470 }
7471 self.emit_op(Op::ArrowArraySlice(indices.len() as u16), line, ast);
7472 }
7473 return Ok(());
7474 }
7475 if arrow_deref_arrow_subscript_is_plain_scalar_index(index) {
7476 self.compile_arrow_array_base_expr(expr)?;
7477 self.compile_expr(index)?;
7478 if keep {
7479 self.emit_op(Op::SetArrowArrayKeep, line, ast);
7480 } else {
7481 self.emit_op(Op::SetArrowArray, line, ast);
7482 }
7483 } else {
7484 self.compile_arrow_array_base_expr(expr)?;
7485 self.compile_array_slice_index_expr(index)?;
7486 self.emit_op(Op::SetArrowArraySlice(1), line, ast);
7487 if keep {
7488 self.compile_arrow_array_base_expr(expr)?;
7489 self.compile_array_slice_index_expr(index)?;
7490 self.emit_op(Op::ArrowArraySlice(1), line, ast);
7491 }
7492 }
7493 }
7494 ExprKind::ArrowDeref {
7495 kind: DerefKind::Call,
7496 ..
7497 } => {
7498 return Err(CompileError::Unsupported(
7499 "Assign to arrow call deref (tree interpreter)".into(),
7500 ));
7501 }
7502 ExprKind::HashSliceDeref { container, keys } => {
7503 self.compile_expr(container)?;
7504 for key_expr in keys {
7505 self.compile_hash_slice_key_expr(key_expr)?;
7506 }
7507 self.emit_op(Op::SetHashSliceDeref(keys.len() as u16), line, ast);
7508 }
7509 ExprKind::Pos(inner) => {
7510 let Some(inner_e) = inner.as_ref() else {
7511 return Err(CompileError::Unsupported(
7512 "assign to pos() without scalar".into(),
7513 ));
7514 };
7515 if keep {
7516 self.emit_op(Op::Dup, line, ast);
7517 }
7518 match &inner_e.kind {
7519 ExprKind::ScalarVar(name) => {
7520 let stor = self.scalar_storage_name_for_ops(name);
7521 let idx = self.chunk.add_constant(PerlValue::string(stor));
7522 self.emit_op(Op::LoadConst(idx), line, ast);
7523 }
7524 _ => {
7525 self.compile_expr(inner_e)?;
7526 }
7527 }
7528 self.emit_op(Op::SetRegexPos, line, ast);
7529 }
7530 ExprKind::List(targets) => {
7533 let tmp = self.chunk.intern_name("__list_assign_swap__");
7534 self.emit_op(Op::DeclareArray(tmp), line, ast);
7535 for (i, t) in targets.iter().enumerate() {
7536 self.emit_op(Op::LoadInt(i as i64), line, ast);
7537 self.emit_op(Op::GetArrayElem(tmp), line, ast);
7538 self.compile_assign(t, line, false, ast)?;
7539 }
7540 if keep {
7541 self.emit_op(Op::GetArray(tmp), line, ast);
7542 }
7543 }
7544 _ => {
7545 return Err(CompileError::Unsupported("Assign to complex lvalue".into()));
7546 }
7547 }
7548 Ok(())
7549 }
7550}
7551
7552pub(crate) fn binop_to_vm_op(op: BinOp) -> Option<Op> {
7554 Some(match op {
7555 BinOp::Add => Op::Add,
7556 BinOp::Sub => Op::Sub,
7557 BinOp::Mul => Op::Mul,
7558 BinOp::Div => Op::Div,
7559 BinOp::Mod => Op::Mod,
7560 BinOp::Pow => Op::Pow,
7561 BinOp::Concat => Op::Concat,
7562 BinOp::BitAnd => Op::BitAnd,
7563 BinOp::BitOr => Op::BitOr,
7564 BinOp::BitXor => Op::BitXor,
7565 BinOp::ShiftLeft => Op::Shl,
7566 BinOp::ShiftRight => Op::Shr,
7567 _ => return None,
7568 })
7569}
7570
7571pub(crate) fn scalar_compound_op_to_byte(op: BinOp) -> Option<u8> {
7573 Some(match op {
7574 BinOp::Add => 0,
7575 BinOp::Sub => 1,
7576 BinOp::Mul => 2,
7577 BinOp::Div => 3,
7578 BinOp::Mod => 4,
7579 BinOp::Pow => 5,
7580 BinOp::Concat => 6,
7581 BinOp::BitAnd => 7,
7582 BinOp::BitOr => 8,
7583 BinOp::BitXor => 9,
7584 BinOp::ShiftLeft => 10,
7585 BinOp::ShiftRight => 11,
7586 _ => return None,
7587 })
7588}
7589
7590pub(crate) fn scalar_compound_op_from_byte(b: u8) -> Option<BinOp> {
7591 Some(match b {
7592 0 => BinOp::Add,
7593 1 => BinOp::Sub,
7594 2 => BinOp::Mul,
7595 3 => BinOp::Div,
7596 4 => BinOp::Mod,
7597 5 => BinOp::Pow,
7598 6 => BinOp::Concat,
7599 7 => BinOp::BitAnd,
7600 8 => BinOp::BitOr,
7601 9 => BinOp::BitXor,
7602 10 => BinOp::ShiftLeft,
7603 11 => BinOp::ShiftRight,
7604 _ => return None,
7605 })
7606}
7607
7608#[cfg(test)]
7609mod tests {
7610 use super::*;
7611 use crate::bytecode::{BuiltinId, Op, GP_RUN};
7612 use crate::parse;
7613
7614 fn compile_snippet(code: &str) -> Result<Chunk, CompileError> {
7615 let program = parse(code).expect("parse snippet");
7616 Compiler::new().compile_program(&program)
7617 }
7618
7619 fn assert_last_halt(chunk: &Chunk) {
7620 assert!(
7621 matches!(chunk.ops.last(), Some(Op::Halt)),
7622 "expected Halt last, got {:?}",
7623 chunk.ops.last()
7624 );
7625 }
7626
7627 #[test]
7628 fn compile_empty_program_emits_run_phase_then_halt() {
7629 let chunk = compile_snippet("").expect("compile");
7630 assert_eq!(chunk.ops.len(), 2);
7631 assert!(matches!(chunk.ops[0], Op::SetGlobalPhase(p) if p == GP_RUN));
7632 assert!(matches!(chunk.ops[1], Op::Halt));
7633 }
7634
7635 #[test]
7636 fn compile_integer_literal_statement() {
7637 let chunk = compile_snippet("42;").expect("compile");
7638 assert!(chunk.ops.iter().any(|o| matches!(o, Op::LoadInt(42))));
7639 assert_last_halt(&chunk);
7640 }
7641
7642 #[test]
7643 fn compile_pos_assign_emits_set_regex_pos() {
7644 let chunk = compile_snippet(r#"$_ = ""; pos = 3;"#).expect("compile");
7645 assert!(
7646 chunk.ops.iter().any(|o| matches!(o, Op::SetRegexPos)),
7647 "expected SetRegexPos in {:?}",
7648 chunk.ops
7649 );
7650 }
7651
7652 #[test]
7653 fn compile_pos_deref_scalar_assign_emits_set_regex_pos() {
7654 let chunk = compile_snippet(
7655 r#"no strict 'vars';
7656 my $s;
7657 my $r = \$s;
7658 pos $$r = 0;"#,
7659 )
7660 .expect("compile");
7661 assert!(
7662 chunk.ops.iter().any(|o| matches!(o, Op::SetRegexPos)),
7663 r"expected SetRegexPos for pos $$r =, got {:?}",
7664 chunk.ops
7665 );
7666 }
7667
7668 #[test]
7669 fn compile_map_expr_comma_emits_map_with_expr() {
7670 let chunk = compile_snippet(
7671 r#"no strict 'vars';
7672 (map $_ + 1, (4, 5)) |> join ','"#,
7673 )
7674 .expect("compile");
7675 assert!(
7676 chunk.ops.iter().any(|o| matches!(o, Op::MapWithExpr(_))),
7677 "expected MapWithExpr, got {:?}",
7678 chunk.ops
7679 );
7680 }
7681
7682 #[test]
7683 fn compile_hash_slice_deref_assign_emits_set_op() {
7684 let code = r#"no strict 'vars';
7685 my $h = { "a" => 1, "b" => 2 };
7686 my $r = $h;
7687 @$r{"a", "b"} = (10, 20);
7688 $r->{"a"} . "," . $r->{"b"};"#;
7689 let chunk = compile_snippet(code).expect("compile");
7690 assert!(
7691 chunk
7692 .ops
7693 .iter()
7694 .any(|o| matches!(o, Op::SetHashSliceDeref(n) if *n == 2)),
7695 "expected SetHashSliceDeref(2), got {:?}",
7696 chunk.ops
7697 );
7698 }
7699
7700 #[test]
7701 fn compile_bare_array_assign_diamond_uses_readline_list() {
7702 let chunk = compile_snippet("@a = <>;").expect("compile");
7703 assert!(
7704 chunk.ops.iter().any(|o| matches!(
7705 o,
7706 Op::CallBuiltin(bid, 0) if *bid == BuiltinId::ReadLineList as u16
7707 )),
7708 "expected ReadLineList for bare @a = <>, got {:?}",
7709 chunk.ops
7710 );
7711 }
7712
7713 #[test]
7714 fn compile_float_literal() {
7715 let chunk = compile_snippet("3.25;").expect("compile");
7716 assert!(chunk
7717 .ops
7718 .iter()
7719 .any(|o| matches!(o, Op::LoadFloat(f) if (*f - 3.25).abs() < 1e-9)));
7720 assert_last_halt(&chunk);
7721 }
7722
7723 #[test]
7724 fn compile_addition() {
7725 let chunk = compile_snippet("1 + 2;").expect("compile");
7726 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Add)));
7727 assert_last_halt(&chunk);
7728 }
7729
7730 #[test]
7731 fn compile_sub_mul_div_mod_pow() {
7732 for (src, op) in [
7733 ("10 - 3;", "Sub"),
7734 ("6 * 7;", "Mul"),
7735 ("8 / 2;", "Div"),
7736 ("9 % 4;", "Mod"),
7737 ("2 ** 8;", "Pow"),
7738 ] {
7739 let chunk = compile_snippet(src).expect(src);
7740 assert!(
7741 chunk.ops.iter().any(|o| std::mem::discriminant(o) == {
7742 let dummy = match op {
7743 "Sub" => Op::Sub,
7744 "Mul" => Op::Mul,
7745 "Div" => Op::Div,
7746 "Mod" => Op::Mod,
7747 "Pow" => Op::Pow,
7748 _ => unreachable!(),
7749 };
7750 std::mem::discriminant(&dummy)
7751 }),
7752 "{} missing {:?}",
7753 src,
7754 op
7755 );
7756 assert_last_halt(&chunk);
7757 }
7758 }
7759
7760 #[test]
7761 fn compile_string_literal_uses_constant_pool() {
7762 let chunk = compile_snippet(r#""hello";"#).expect("compile");
7763 assert!(chunk
7764 .constants
7765 .iter()
7766 .any(|c| c.as_str().as_deref() == Some("hello")));
7767 assert!(chunk.ops.iter().any(|o| matches!(o, Op::LoadConst(_))));
7768 assert_last_halt(&chunk);
7769 }
7770
7771 #[test]
7772 fn compile_substitution_bind_emits_regex_subst() {
7773 let chunk = compile_snippet(r#"my $s = "aa"; $s =~ s/a/b/g;"#).expect("compile");
7774 assert!(
7775 chunk
7776 .ops
7777 .iter()
7778 .any(|o| matches!(o, Op::RegexSubst(_, _, _, _))),
7779 "expected RegexSubst in {:?}",
7780 chunk.ops
7781 );
7782 assert!(!chunk.lvalues.is_empty());
7783 }
7784
7785 #[test]
7786 fn compile_chomp_emits_chomp_in_place() {
7787 let chunk = compile_snippet(r#"my $s = "x\n"; chomp $s;"#).expect("compile");
7788 assert!(
7789 chunk.ops.iter().any(|o| matches!(o, Op::ChompInPlace(_))),
7790 "expected ChompInPlace, got {:?}",
7791 chunk.ops
7792 );
7793 }
7794
7795 #[test]
7796 fn compile_transliterate_bind_emits_regex_transliterate() {
7797 let chunk = compile_snippet(r#"my $u = "abc"; $u =~ tr/a-z/A-Z/;"#).expect("compile");
7798 assert!(
7799 chunk
7800 .ops
7801 .iter()
7802 .any(|o| matches!(o, Op::RegexTransliterate(_, _, _, _))),
7803 "expected RegexTransliterate in {:?}",
7804 chunk.ops
7805 );
7806 assert!(!chunk.lvalues.is_empty());
7807 }
7808
7809 #[test]
7810 fn compile_negation() {
7811 let chunk = compile_snippet("-7;").expect("compile");
7812 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Negate)));
7813 assert_last_halt(&chunk);
7814 }
7815
7816 #[test]
7817 fn compile_my_scalar_declares() {
7818 let chunk = compile_snippet("my $x = 1;").expect("compile");
7819 assert!(chunk
7820 .ops
7821 .iter()
7822 .any(|o| matches!(o, Op::DeclareScalar(_) | Op::DeclareScalarSlot(_, _))));
7823 assert_last_halt(&chunk);
7824 }
7825
7826 #[test]
7827 fn compile_scalar_fetch_and_assign() {
7828 let chunk = compile_snippet("my $a = 1; $a + 0;").expect("compile");
7829 assert!(
7830 chunk
7831 .ops
7832 .iter()
7833 .filter(|o| matches!(
7834 o,
7835 Op::GetScalar(_) | Op::GetScalarPlain(_) | Op::GetScalarSlot(_)
7836 ))
7837 .count()
7838 >= 1
7839 );
7840 assert_last_halt(&chunk);
7841 }
7842
7843 #[test]
7844 fn compile_plain_scalar_read_emits_get_scalar_plain() {
7845 let chunk = compile_snippet("my $a = 1; $a + 0;").expect("compile");
7846 assert!(
7847 chunk
7848 .ops
7849 .iter()
7850 .any(|o| matches!(o, Op::GetScalarPlain(_) | Op::GetScalarSlot(_))),
7851 "expected GetScalarPlain or GetScalarSlot for non-special $a, ops={:?}",
7852 chunk.ops
7853 );
7854 }
7855
7856 #[test]
7857 fn compile_sub_postfix_inc_emits_post_inc_slot() {
7858 let chunk = compile_snippet("sub f { my $x = 0; $x++; return $x; }").expect("compile");
7859 assert!(
7860 chunk.ops.iter().any(|o| matches!(o, Op::PostIncSlot(_))),
7861 "expected PostIncSlot in compiled sub body, ops={:?}",
7862 chunk.ops
7863 );
7864 }
7865
7866 #[test]
7867 fn compile_comparison_ops_numeric() {
7868 for src in [
7869 "1 < 2;", "1 > 2;", "1 <= 2;", "1 >= 2;", "1 == 2;", "1 != 2;",
7870 ] {
7871 let chunk = compile_snippet(src).expect(src);
7872 assert!(
7873 chunk.ops.iter().any(|o| {
7874 matches!(
7875 o,
7876 Op::NumLt | Op::NumGt | Op::NumLe | Op::NumGe | Op::NumEq | Op::NumNe
7877 )
7878 }),
7879 "{}",
7880 src
7881 );
7882 assert_last_halt(&chunk);
7883 }
7884 }
7885
7886 #[test]
7887 fn compile_string_compare_ops() {
7888 for src in [
7889 r#"'a' lt 'b';"#,
7890 r#"'a' gt 'b';"#,
7891 r#"'a' le 'b';"#,
7892 r#"'a' ge 'b';"#,
7893 ] {
7894 let chunk = compile_snippet(src).expect(src);
7895 assert!(
7896 chunk
7897 .ops
7898 .iter()
7899 .any(|o| matches!(o, Op::StrLt | Op::StrGt | Op::StrLe | Op::StrGe)),
7900 "{}",
7901 src
7902 );
7903 assert_last_halt(&chunk);
7904 }
7905 }
7906
7907 #[test]
7908 fn compile_concat() {
7909 let chunk = compile_snippet(r#"'a' . 'b';"#).expect("compile");
7910 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Concat)));
7911 assert_last_halt(&chunk);
7912 }
7913
7914 #[test]
7915 fn compile_bitwise_ops() {
7916 let chunk = compile_snippet("1 & 2 | 3 ^ 4;").expect("compile");
7917 assert!(chunk.ops.iter().any(|o| matches!(o, Op::BitAnd)));
7918 assert!(chunk.ops.iter().any(|o| matches!(o, Op::BitOr)));
7919 assert!(chunk.ops.iter().any(|o| matches!(o, Op::BitXor)));
7920 assert_last_halt(&chunk);
7921 }
7922
7923 #[test]
7924 fn compile_shift_right() {
7925 let chunk = compile_snippet("8 >> 1;").expect("compile");
7926 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Shr)));
7927 assert_last_halt(&chunk);
7928 }
7929
7930 #[test]
7931 fn compile_shift_left() {
7932 let chunk = compile_snippet("1 << 4;").expect("compile");
7933 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Shl)));
7934 assert_last_halt(&chunk);
7935 }
7936
7937 #[test]
7938 fn compile_log_not_and_bit_not() {
7939 let c1 = compile_snippet("!0;").expect("compile");
7940 assert!(c1.ops.iter().any(|o| matches!(o, Op::LogNot)));
7941 let c2 = compile_snippet("~0;").expect("compile");
7942 assert!(c2.ops.iter().any(|o| matches!(o, Op::BitNot)));
7943 }
7944
7945 #[test]
7946 fn compile_sub_registers_name_and_entry() {
7947 let chunk = compile_snippet("sub foo { return 1; }").expect("compile");
7948 assert!(chunk.names.iter().any(|n| n == "foo"));
7949 assert!(chunk
7950 .sub_entries
7951 .iter()
7952 .any(|&(idx, ip, _)| chunk.names[idx as usize] == "foo" && ip > 0));
7953 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Halt)));
7954 assert!(chunk.ops.iter().any(|o| matches!(o, Op::ReturnValue)));
7955 }
7956
7957 #[test]
7958 fn compile_postinc_scalar() {
7959 let chunk = compile_snippet("my $n = 1; $n++;").expect("compile");
7960 assert!(chunk
7961 .ops
7962 .iter()
7963 .any(|o| matches!(o, Op::PostInc(_) | Op::PostIncSlot(_))));
7964 assert_last_halt(&chunk);
7965 }
7966
7967 #[test]
7968 fn compile_preinc_scalar() {
7969 let chunk = compile_snippet("my $n = 1; ++$n;").expect("compile");
7970 assert!(chunk
7971 .ops
7972 .iter()
7973 .any(|o| matches!(o, Op::PreInc(_) | Op::PreIncSlot(_))));
7974 assert_last_halt(&chunk);
7975 }
7976
7977 #[test]
7978 fn compile_if_expression_value() {
7979 let chunk = compile_snippet("if (1) { 2 } else { 3 }").expect("compile");
7980 assert!(chunk.ops.iter().any(|o| matches!(o, Op::JumpIfFalse(_))));
7981 assert_last_halt(&chunk);
7982 }
7983
7984 #[test]
7985 fn compile_unless_expression_value() {
7986 let chunk = compile_snippet("unless (0) { 1 } else { 2 }").expect("compile");
7987 assert!(chunk.ops.iter().any(|o| matches!(o, Op::JumpIfFalse(_))));
7988 assert_last_halt(&chunk);
7989 }
7990
7991 #[test]
7992 fn compile_array_declare_and_push() {
7993 let chunk = compile_snippet("my @a; push @a, 1;").expect("compile");
7994 assert!(chunk.ops.iter().any(|o| matches!(o, Op::DeclareArray(_))));
7995 assert_last_halt(&chunk);
7996 }
7997
7998 #[test]
7999 fn compile_ternary() {
8000 let chunk = compile_snippet("1 ? 2 : 3;").expect("compile");
8001 assert!(chunk.ops.iter().any(|o| matches!(o, Op::JumpIfFalse(_))));
8002 assert_last_halt(&chunk);
8003 }
8004
8005 #[test]
8006 fn compile_repeat_operator() {
8007 let chunk = compile_snippet(r#"'ab' x 3;"#).expect("compile");
8008 assert!(chunk.ops.iter().any(|o| matches!(o, Op::StringRepeat)));
8009 assert_last_halt(&chunk);
8010 }
8011
8012 #[test]
8013 fn compile_range_to_array() {
8014 let chunk = compile_snippet("my @a = (1..3);").expect("compile");
8015 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Range)));
8016 assert_last_halt(&chunk);
8017 }
8018
8019 #[test]
8021 fn compile_print_if_uses_scalar_flipflop_not_range_list() {
8022 let chunk = compile_snippet("print if 1..2;").expect("compile");
8023 assert!(
8024 chunk
8025 .ops
8026 .iter()
8027 .any(|o| matches!(o, Op::ScalarFlipFlop(_, 0))),
8028 "expected ScalarFlipFlop in bytecode, got:\n{}",
8029 chunk.disassemble()
8030 );
8031 assert!(
8032 !chunk.ops.iter().any(|o| matches!(o, Op::Range)),
8033 "did not expect list Range op in scalar if-condition:\n{}",
8034 chunk.disassemble()
8035 );
8036 }
8037
8038 #[test]
8039 fn compile_print_if_three_dot_scalar_flipflop_sets_exclusive_flag() {
8040 let chunk = compile_snippet("print if 1...2;").expect("compile");
8041 assert!(
8042 chunk
8043 .ops
8044 .iter()
8045 .any(|o| matches!(o, Op::ScalarFlipFlop(_, 1))),
8046 "expected ScalarFlipFlop(..., exclusive=1), got:\n{}",
8047 chunk.disassemble()
8048 );
8049 }
8050
8051 #[test]
8052 fn compile_regex_flipflop_two_dot_emits_regex_flipflop_op() {
8053 let chunk = compile_snippet(r#"print if /a/../b/;"#).expect("compile");
8054 assert!(
8055 chunk
8056 .ops
8057 .iter()
8058 .any(|o| matches!(o, Op::RegexFlipFlop(_, 0, _, _, _, _))),
8059 "expected RegexFlipFlop(.., exclusive=0), got:\n{}",
8060 chunk.disassemble()
8061 );
8062 assert!(
8063 !chunk
8064 .ops
8065 .iter()
8066 .any(|o| matches!(o, Op::ScalarFlipFlop(_, _))),
8067 "regex flip-flop must not use ScalarFlipFlop:\n{}",
8068 chunk.disassemble()
8069 );
8070 }
8071
8072 #[test]
8073 fn compile_regex_flipflop_three_dot_sets_exclusive_flag() {
8074 let chunk = compile_snippet(r#"print if /a/.../b/;"#).expect("compile");
8075 assert!(
8076 chunk
8077 .ops
8078 .iter()
8079 .any(|o| matches!(o, Op::RegexFlipFlop(_, 1, _, _, _, _))),
8080 "expected RegexFlipFlop(..., exclusive=1), got:\n{}",
8081 chunk.disassemble()
8082 );
8083 }
8084
8085 #[test]
8086 fn compile_regex_eof_flipflop_emits_regex_eof_flipflop_op() {
8087 let chunk = compile_snippet(r#"print if /a/..eof;"#).expect("compile");
8088 assert!(
8089 chunk
8090 .ops
8091 .iter()
8092 .any(|o| matches!(o, Op::RegexEofFlipFlop(_, 0, _, _))),
8093 "expected RegexEofFlipFlop(.., exclusive=0), got:\n{}",
8094 chunk.disassemble()
8095 );
8096 assert!(
8097 !chunk
8098 .ops
8099 .iter()
8100 .any(|o| matches!(o, Op::ScalarFlipFlop(_, _))),
8101 "regex/eof flip-flop must not use ScalarFlipFlop:\n{}",
8102 chunk.disassemble()
8103 );
8104 }
8105
8106 #[test]
8107 fn compile_regex_eof_flipflop_three_dot_sets_exclusive_flag() {
8108 let chunk = compile_snippet(r#"print if /a/...eof;"#).expect("compile");
8109 assert!(
8110 chunk
8111 .ops
8112 .iter()
8113 .any(|o| matches!(o, Op::RegexEofFlipFlop(_, 1, _, _))),
8114 "expected RegexEofFlipFlop(..., exclusive=1), got:\n{}",
8115 chunk.disassemble()
8116 );
8117 }
8118
8119 #[test]
8120 fn compile_regex_flipflop_compound_rhs_emits_regex_flip_flop_expr_rhs() {
8121 let chunk = compile_snippet(r#"print if /a/...(/b/ or /c/);"#).expect("compile");
8122 assert!(
8123 chunk
8124 .ops
8125 .iter()
8126 .any(|o| matches!(o, Op::RegexFlipFlopExprRhs(_, _, _, _, _))),
8127 "expected RegexFlipFlopExprRhs for compound RHS, got:\n{}",
8128 chunk.disassemble()
8129 );
8130 assert!(
8131 !chunk
8132 .ops
8133 .iter()
8134 .any(|o| matches!(o, Op::ScalarFlipFlop(_, _))),
8135 "compound regex flip-flop must not use ScalarFlipFlop:\n{}",
8136 chunk.disassemble()
8137 );
8138 }
8139
8140 #[test]
8141 fn compile_print_statement() {
8142 let chunk = compile_snippet("print 1;").expect("compile");
8143 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Print(_, _))));
8144 assert_last_halt(&chunk);
8145 }
8146
8147 #[test]
8148 fn compile_say_statement() {
8149 let chunk = compile_snippet("say 1;").expect("compile");
8150 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Say(_, _))));
8151 assert_last_halt(&chunk);
8152 }
8153
8154 #[test]
8155 fn compile_defined_builtin() {
8156 let chunk = compile_snippet("defined 1;").expect("compile");
8157 assert!(chunk
8158 .ops
8159 .iter()
8160 .any(|o| matches!(o, Op::CallBuiltin(id, _) if *id == BuiltinId::Defined as u16)));
8161 assert_last_halt(&chunk);
8162 }
8163
8164 #[test]
8165 fn compile_length_builtin() {
8166 let chunk = compile_snippet("length 'abc';").expect("compile");
8167 assert!(chunk
8168 .ops
8169 .iter()
8170 .any(|o| matches!(o, Op::CallBuiltin(id, _) if *id == BuiltinId::Length as u16)));
8171 assert_last_halt(&chunk);
8172 }
8173
8174 #[test]
8175 fn compile_complex_expr_parentheses() {
8176 let chunk = compile_snippet("(1 + 2) * (3 + 4);").expect("compile");
8177 assert!(chunk.ops.iter().filter(|o| matches!(o, Op::Add)).count() >= 2);
8178 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Mul)));
8179 assert_last_halt(&chunk);
8180 }
8181
8182 #[test]
8183 fn compile_undef_literal() {
8184 let chunk = compile_snippet("undef;").expect("compile");
8185 assert!(chunk.ops.iter().any(|o| matches!(o, Op::LoadUndef)));
8186 assert_last_halt(&chunk);
8187 }
8188
8189 #[test]
8190 fn compile_empty_statement_semicolons() {
8191 let chunk = compile_snippet(";;;").expect("compile");
8192 assert_last_halt(&chunk);
8193 }
8194
8195 #[test]
8196 fn compile_array_elem_preinc_uses_rot_and_set_elem() {
8197 let chunk = compile_snippet("my @a; $a[0] = 0; ++$a[0];").expect("compile");
8198 assert!(
8199 chunk.ops.iter().any(|o| matches!(o, Op::Rot)),
8200 "expected Rot in {:?}",
8201 chunk.ops
8202 );
8203 assert!(
8204 chunk.ops.iter().any(|o| matches!(o, Op::SetArrayElem(_))),
8205 "expected SetArrayElem in {:?}",
8206 chunk.ops
8207 );
8208 assert_last_halt(&chunk);
8209 }
8210
8211 #[test]
8212 fn compile_hash_elem_compound_assign_uses_rot() {
8213 let chunk = compile_snippet("my %h; $h{0} = 1; $h{0} += 2;").expect("compile");
8214 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Rot)));
8215 assert!(
8216 chunk.ops.iter().any(|o| matches!(o, Op::SetHashElem(_))),
8217 "expected SetHashElem"
8218 );
8219 assert_last_halt(&chunk);
8220 }
8221
8222 #[test]
8223 fn compile_postfix_inc_array_elem_emits_rot() {
8224 let chunk = compile_snippet("my @a; $a[1] = 5; $a[1]++;").expect("compile");
8225 assert!(chunk.ops.iter().any(|o| matches!(o, Op::Rot)));
8226 assert_last_halt(&chunk);
8227 }
8228
8229 #[test]
8230 fn compile_tie_stmt_emits_op_tie() {
8231 let chunk = compile_snippet("tie %h, 'Pkg';").expect("compile");
8232 assert!(
8233 chunk.ops.iter().any(|o| matches!(o, Op::Tie { .. })),
8234 "expected Op::Tie in {:?}",
8235 chunk.ops
8236 );
8237 assert_last_halt(&chunk);
8238 }
8239
8240 #[test]
8241 fn compile_format_decl_emits_format_decl_op() {
8242 let chunk = compile_snippet(
8243 r#"
8244format FMT =
8245literal line
8246.
82471;
8248"#,
8249 )
8250 .expect("compile");
8251 assert!(
8252 chunk.ops.iter().any(|o| matches!(o, Op::FormatDecl(0))),
8253 "expected Op::FormatDecl(0), got {:?}",
8254 chunk.ops
8255 );
8256 assert_eq!(chunk.format_decls.len(), 1);
8257 assert_eq!(chunk.format_decls[0].0, "FMT");
8258 assert_eq!(chunk.format_decls[0].1, vec!["literal line".to_string()]);
8259 assert_last_halt(&chunk);
8260 }
8261
8262 #[test]
8263 fn compile_interpolated_string_scalar_only_emits_empty_prefix_and_concat() {
8264 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 1; "$x";"#).expect("compile");
8265 let empty_idx = chunk
8266 .constants
8267 .iter()
8268 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
8269 .expect("empty string in pool") as u16;
8270 assert!(
8271 chunk
8272 .ops
8273 .iter()
8274 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
8275 "expected LoadConst(\"\"), ops={:?}",
8276 chunk.ops
8277 );
8278 assert!(
8279 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
8280 "expected Op::Concat for qq with only a scalar part, ops={:?}",
8281 chunk.ops
8282 );
8283 assert_last_halt(&chunk);
8284 }
8285
8286 #[test]
8287 fn compile_interpolated_string_array_only_emits_stringify_and_concat() {
8288 let chunk = compile_snippet(r#"no strict 'vars'; my @a = (1, 2); "@a";"#).expect("compile");
8289 let empty_idx = chunk
8290 .constants
8291 .iter()
8292 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
8293 .expect("empty string in pool") as u16;
8294 assert!(
8295 chunk
8296 .ops
8297 .iter()
8298 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
8299 "expected LoadConst(\"\"), ops={:?}",
8300 chunk.ops
8301 );
8302 assert!(
8303 chunk
8304 .ops
8305 .iter()
8306 .any(|o| matches!(o, Op::ArrayStringifyListSep)),
8307 "expected ArrayStringifyListSep for array var in qq, ops={:?}",
8308 chunk.ops
8309 );
8310 assert!(
8311 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
8312 "expected Op::Concat after array stringify, ops={:?}",
8313 chunk.ops
8314 );
8315 assert_last_halt(&chunk);
8316 }
8317
8318 #[test]
8319 fn compile_interpolated_string_hash_element_only_emits_empty_prefix_and_concat() {
8320 let chunk =
8321 compile_snippet(r#"no strict 'vars'; my %h = (k => 1); "$h{k}";"#).expect("compile");
8322 let empty_idx = chunk
8323 .constants
8324 .iter()
8325 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
8326 .expect("empty string in pool") as u16;
8327 assert!(
8328 chunk
8329 .ops
8330 .iter()
8331 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
8332 "expected LoadConst(\"\"), ops={:?}",
8333 chunk.ops
8334 );
8335 assert!(
8336 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
8337 "expected Op::Concat for qq with only an expr part, ops={:?}",
8338 chunk.ops
8339 );
8340 assert_last_halt(&chunk);
8341 }
8342
8343 #[test]
8344 fn compile_interpolated_string_leading_literal_has_no_empty_string_prefix() {
8345 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 1; "a$x";"#).expect("compile");
8346 assert!(
8347 !chunk
8348 .constants
8349 .iter()
8350 .any(|c| c.as_str().is_some_and(|s| s.is_empty())),
8351 "literal-first qq must not intern \"\" (only non-literal first parts need it), ops={:?}",
8352 chunk.ops
8353 );
8354 assert!(
8355 chunk.ops.iter().any(|o| matches!(o, Op::Concat)),
8356 "expected Op::Concat after literal + scalar, ops={:?}",
8357 chunk.ops
8358 );
8359 assert_last_halt(&chunk);
8360 }
8361
8362 #[test]
8363 fn compile_interpolated_string_two_scalars_empty_prefix_and_two_concats() {
8364 let chunk =
8365 compile_snippet(r#"no strict 'vars'; my $a = 1; my $b = 2; "$a$b";"#).expect("compile");
8366 let empty_idx = chunk
8367 .constants
8368 .iter()
8369 .position(|c| c.as_str().is_some_and(|s| s.is_empty()))
8370 .expect("empty string in pool") as u16;
8371 assert!(
8372 chunk
8373 .ops
8374 .iter()
8375 .any(|o| matches!(o, Op::LoadConst(i) if *i == empty_idx)),
8376 "expected LoadConst(\"\") before first scalar qq part, ops={:?}",
8377 chunk.ops
8378 );
8379 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
8380 assert!(
8381 n_concat >= 2,
8382 "expected at least two Op::Concat for two scalar qq parts, got {} in {:?}",
8383 n_concat,
8384 chunk.ops
8385 );
8386 assert_last_halt(&chunk);
8387 }
8388
8389 #[test]
8390 fn compile_interpolated_string_literal_then_two_scalars_has_no_empty_prefix() {
8391 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 7; my $y = 8; "p$x$y";"#)
8392 .expect("compile");
8393 assert!(
8394 !chunk
8395 .constants
8396 .iter()
8397 .any(|c| c.as_str().is_some_and(|s| s.is_empty())),
8398 "literal-first qq must not intern empty string, ops={:?}",
8399 chunk.ops
8400 );
8401 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
8402 assert!(
8403 n_concat >= 2,
8404 "expected two Concats for literal + two scalars, got {} in {:?}",
8405 n_concat,
8406 chunk.ops
8407 );
8408 assert_last_halt(&chunk);
8409 }
8410
8411 #[test]
8412 fn compile_interpolated_string_braced_scalar_trailing_literal_emits_concats() {
8413 let chunk = compile_snippet(r#"no strict 'vars'; my $u = 1; "a${u}z";"#).expect("compile");
8414 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
8415 assert!(
8416 n_concat >= 2,
8417 "expected braced scalar + trailing literal to use multiple Concats, got {} in {:?}",
8418 n_concat,
8419 chunk.ops
8420 );
8421 assert_last_halt(&chunk);
8422 }
8423
8424 #[test]
8425 fn compile_interpolated_string_braced_scalar_sandwiched_emits_concats() {
8426 let chunk = compile_snippet(r#"no strict 'vars'; my $u = 1; "L${u}R";"#).expect("compile");
8427 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
8428 assert!(
8429 n_concat >= 2,
8430 "expected leading literal + braced scalar + trailing literal to use multiple Concats, got {} in {:?}",
8431 n_concat,
8432 chunk.ops
8433 );
8434 assert_last_halt(&chunk);
8435 }
8436
8437 #[test]
8438 fn compile_interpolated_string_mixed_braced_and_plain_scalars_emits_concats() {
8439 let chunk = compile_snippet(r#"no strict 'vars'; my $x = 1; my $y = 2; "a${x}b$y";"#)
8440 .expect("compile");
8441 let n_concat = chunk.ops.iter().filter(|o| matches!(o, Op::Concat)).count();
8442 assert!(
8443 n_concat >= 3,
8444 "expected literal/braced/plain qq mix to use at least three Concats, got {} in {:?}",
8445 n_concat,
8446 chunk.ops
8447 );
8448 assert_last_halt(&chunk);
8449 }
8450
8451 #[test]
8452 fn compile_use_overload_emits_use_overload_op() {
8453 let chunk = compile_snippet(r#"use overload '""' => 'as_string';"#).expect("compile");
8454 assert!(
8455 chunk.ops.iter().any(|o| matches!(o, Op::UseOverload(0))),
8456 "expected Op::UseOverload(0), got {:?}",
8457 chunk.ops
8458 );
8459 assert_eq!(chunk.use_overload_entries.len(), 1);
8460 let stringify_key: String = ['"', '"'].iter().collect();
8463 assert_eq!(
8464 chunk.use_overload_entries[0],
8465 vec![(stringify_key, "as_string".to_string())]
8466 );
8467 assert_last_halt(&chunk);
8468 }
8469
8470 #[test]
8471 fn compile_use_overload_empty_list_emits_use_overload_with_no_pairs() {
8472 let chunk = compile_snippet(r#"use overload ();"#).expect("compile");
8473 assert!(
8474 chunk.ops.iter().any(|o| matches!(o, Op::UseOverload(0))),
8475 "expected Op::UseOverload(0), got {:?}",
8476 chunk.ops
8477 );
8478 assert_eq!(chunk.use_overload_entries.len(), 1);
8479 assert!(chunk.use_overload_entries[0].is_empty());
8480 assert_last_halt(&chunk);
8481 }
8482
8483 #[test]
8484 fn compile_use_overload_multiple_pairs_single_op() {
8485 let chunk =
8486 compile_snippet(r#"use overload '+' => 'p_add', '-' => 'p_sub';"#).expect("compile");
8487 assert!(
8488 chunk.ops.iter().any(|o| matches!(o, Op::UseOverload(0))),
8489 "expected Op::UseOverload(0), got {:?}",
8490 chunk.ops
8491 );
8492 assert_eq!(chunk.use_overload_entries.len(), 1);
8493 assert_eq!(
8494 chunk.use_overload_entries[0],
8495 vec![
8496 ("+".to_string(), "p_add".to_string()),
8497 ("-".to_string(), "p_sub".to_string()),
8498 ]
8499 );
8500 assert_last_halt(&chunk);
8501 }
8502
8503 #[test]
8504 fn compile_open_my_fh_emits_declare_open_set() {
8505 let chunk = compile_snippet(r#"open my $fh, "<", "/dev/null";"#).expect("compile");
8506 assert!(
8507 chunk.ops.iter().any(|o| matches!(
8508 o,
8509 Op::CallBuiltin(b, 3) if *b == BuiltinId::Open as u16
8510 )),
8511 "expected Open builtin 3-arg, got {:?}",
8512 chunk.ops
8513 );
8514 assert!(
8515 chunk
8516 .ops
8517 .iter()
8518 .any(|o| matches!(o, Op::SetScalarKeepPlain(_))),
8519 "expected SetScalarKeepPlain after open"
8520 );
8521 assert_last_halt(&chunk);
8522 }
8523
8524 #[test]
8525 fn compile_local_hash_element_emits_local_declare_hash_element() {
8526 let chunk = compile_snippet(r#"local $SIG{__WARN__} = 0;"#).expect("compile");
8527 assert!(
8528 chunk
8529 .ops
8530 .iter()
8531 .any(|o| matches!(o, Op::LocalDeclareHashElement(_))),
8532 "expected LocalDeclareHashElement in {:?}",
8533 chunk.ops
8534 );
8535 assert_last_halt(&chunk);
8536 }
8537
8538 #[test]
8539 fn compile_local_array_element_emits_local_declare_array_element() {
8540 let chunk = compile_snippet(r#"local $a[2] = 9;"#).expect("compile");
8541 assert!(
8542 chunk
8543 .ops
8544 .iter()
8545 .any(|o| matches!(o, Op::LocalDeclareArrayElement(_))),
8546 "expected LocalDeclareArrayElement in {:?}",
8547 chunk.ops
8548 );
8549 assert_last_halt(&chunk);
8550 }
8551
8552 #[test]
8553 fn compile_local_typeglob_emits_local_declare_typeglob() {
8554 let chunk = compile_snippet(r#"local *STDOUT;"#).expect("compile");
8555 assert!(
8556 chunk
8557 .ops
8558 .iter()
8559 .any(|o| matches!(o, Op::LocalDeclareTypeglob(_, None))),
8560 "expected LocalDeclareTypeglob(_, None) in {:?}",
8561 chunk.ops
8562 );
8563 assert_last_halt(&chunk);
8564 }
8565
8566 #[test]
8567 fn compile_local_typeglob_alias_emits_local_declare_typeglob_some_rhs() {
8568 let chunk = compile_snippet(r#"local *FOO = *STDOUT;"#).expect("compile");
8569 assert!(
8570 chunk
8571 .ops
8572 .iter()
8573 .any(|o| matches!(o, Op::LocalDeclareTypeglob(_, Some(_)))),
8574 "expected LocalDeclareTypeglob with rhs in {:?}",
8575 chunk.ops
8576 );
8577 assert_last_halt(&chunk);
8578 }
8579
8580 #[test]
8581 fn compile_local_braced_typeglob_emits_local_declare_typeglob_dynamic() {
8582 let chunk = compile_snippet(r#"no strict 'refs'; my $g = "STDOUT"; local *{ $g };"#)
8583 .expect("compile");
8584 assert!(
8585 chunk
8586 .ops
8587 .iter()
8588 .any(|o| matches!(o, Op::LocalDeclareTypeglobDynamic(None))),
8589 "expected LocalDeclareTypeglobDynamic(None) in {:?}",
8590 chunk.ops
8591 );
8592 assert_last_halt(&chunk);
8593 }
8594
8595 #[test]
8596 fn compile_local_star_deref_typeglob_emits_local_declare_typeglob_dynamic() {
8597 let chunk =
8598 compile_snippet(r#"no strict 'refs'; my $g = "STDOUT"; local *$g;"#).expect("compile");
8599 assert!(
8600 chunk
8601 .ops
8602 .iter()
8603 .any(|o| matches!(o, Op::LocalDeclareTypeglobDynamic(None))),
8604 "expected LocalDeclareTypeglobDynamic(None) for local *scalar glob in {:?}",
8605 chunk.ops
8606 );
8607 assert_last_halt(&chunk);
8608 }
8609
8610 #[test]
8611 fn compile_braced_glob_assign_to_named_glob_emits_copy_dynamic_lhs() {
8612 let chunk = compile_snippet(r#"no strict 'refs'; my $n = "x"; *{ $n } = *STDOUT;"#)
8614 .expect("compile");
8615 assert!(
8616 chunk
8617 .ops
8618 .iter()
8619 .any(|o| matches!(o, Op::CopyTypeglobSlotsDynamicLhs(_))),
8620 "expected CopyTypeglobSlotsDynamicLhs in {:?}",
8621 chunk.ops
8622 );
8623 assert_last_halt(&chunk);
8624 }
8625}