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