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