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