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