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