1use crate::awk_host::AwkHost;
27use crate::chunk::Chunk;
28use crate::host::ShellHost;
29#[cfg(feature = "jit")]
30use crate::jit::{DeoptInfo, JitCompiler, SlotKind, TraceLookup};
31use crate::op::Op;
32use crate::value::Value;
33
34#[cfg(feature = "jit")]
65struct TraceRecorder {
66 record_anchor_ip: usize,
69 close_anchor_ip: usize,
73 fallthrough_ip: usize,
76 ops: Vec<Op>,
78 recorded_ips: Vec<usize>,
83 slot_kinds_at_anchor: Vec<SlotKind>,
85 entered_ips: Vec<usize>,
89 aborted: bool,
92}
93
94#[derive(Debug, Clone)]
96pub struct Frame {
97 pub return_ip: usize,
99 pub stack_base: usize,
101 pub slots: Vec<Value>,
103}
104
105pub type ExtensionHandler = Box<dyn FnMut(&mut VM, u16, u8) + Send>;
108pub type ExtensionWideHandler = Box<dyn FnMut(&mut VM, u16, usize) + Send>;
110pub type BuiltinHandler = fn(&mut VM, u8) -> Value;
112
113pub struct VM {
115 pub stack: Vec<Value>,
117 pub frames: Vec<Frame>,
119 pub globals: Vec<Value>,
121 pub ip: usize,
123 pub chunk: Chunk,
125 pub last_status: i32,
127 output_sink: Option<OutputSink>,
130 input_source: Option<InputSource>,
133 numeric_hook: Option<NumericHook>,
137 fixnum_range: Option<(i64, i64)>,
143 #[cfg(feature = "jit")]
149 slots_all_numeric: bool,
150 ext_handler: Option<ExtensionHandler>,
152 ext_wide_handler: Option<ExtensionWideHandler>,
154 builtin_table: Vec<Option<BuiltinHandler>>,
156 pub host: Option<Box<dyn ShellHost>>,
159 pub awk_host: Option<Box<dyn AwkHost>>,
164 awk_rand_seed: u64,
169 awk_signal: Option<u8>,
175 sched: Option<crate::sched::SchedReq>,
182 halted: bool,
184 #[cfg(feature = "jit")]
187 tracing_jit: bool,
188 #[cfg(feature = "jit")]
190 jit: JitCompiler,
191 #[cfg(feature = "jit")]
193 recorder: Option<TraceRecorder>,
194 #[cfg(feature = "jit")]
196 slot_buf: Vec<i64>,
197 #[cfg(feature = "jit")]
199 slot_kinds_buf: Vec<SlotKind>,
200 #[cfg(feature = "jit")]
207 deopt_info: DeoptInfo,
208 #[cfg(feature = "jit")]
214 block_eligible_cached: Option<bool>,
215 #[cfg(feature = "aot")]
219 aot_result: Option<VMResult>,
220 #[cfg(feature = "aot")]
225 aot_arena: Vec<Value>,
226 #[cfg(feature = "aot")]
231 aot_free: Vec<u32>,
232}
233
234#[derive(Debug)]
236pub enum VMResult {
237 Ok(Value),
238 Halted,
240 Error(String),
242}
243
244pub(crate) enum ExecFlow {
250 Cont,
251 Ret(VMResult),
252}
253
254#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub enum NumOp {
260 Add,
261 Sub,
262 Mul,
263 Div,
264 Mod,
265 Pow,
266 Neg,
267 Lt,
268 Gt,
269 Le,
270 Ge,
271 Eq,
272 Ne,
273}
274
275pub type NumericHook =
297 std::sync::Arc<dyn Fn(NumOp, &Value, &Value) -> Result<Value, String> + Send + Sync>;
298
299pub type OutputSink = Box<dyn FnMut(&str) + Send>;
309
310pub type InputSource = Box<dyn FnMut() -> Option<String> + Send>;
319
320#[inline(always)]
322fn is_native_num(v: &Value) -> bool {
323 matches!(v, Value::Int(_) | Value::Float(_))
324}
325
326impl VM {
327 pub fn new(chunk: Chunk) -> Self {
333 let num_names = chunk.names.len();
334 let mut frames = Vec::with_capacity(32);
335 frames.push(Frame {
336 return_ip: 0,
337 stack_base: 0,
338 slots: Vec::with_capacity(16),
339 });
340 Self {
341 stack: Vec::with_capacity(256),
342 frames,
343 globals: vec![Value::Undef; num_names],
344 ip: 0,
345 chunk,
346 last_status: 0,
347 output_sink: None,
348 input_source: None,
349 numeric_hook: None,
350 fixnum_range: None,
351 #[cfg(feature = "jit")]
352 slots_all_numeric: true,
353 ext_handler: None,
354 ext_wide_handler: None,
355 builtin_table: Vec::new(),
356 host: None,
357 awk_host: None,
358 awk_rand_seed: 1,
359 awk_signal: None,
360 sched: None,
361 halted: false,
362 #[cfg(feature = "jit")]
363 tracing_jit: false,
364 #[cfg(feature = "jit")]
365 jit: JitCompiler::new(),
366 #[cfg(feature = "jit")]
367 recorder: None,
368 #[cfg(feature = "jit")]
369 slot_buf: Vec::new(),
370 #[cfg(feature = "jit")]
371 slot_kinds_buf: Vec::new(),
372 #[cfg(feature = "jit")]
373 deopt_info: DeoptInfo::zeroed(),
374 #[cfg(feature = "jit")]
375 block_eligible_cached: None,
376 #[cfg(feature = "aot")]
377 aot_result: None,
378 #[cfg(feature = "aot")]
379 aot_arena: Vec::new(),
380 #[cfg(feature = "aot")]
381 aot_free: Vec::new(),
382 }
383 }
384
385 pub fn set_numeric_hook(&mut self, hook: NumericHook) {
397 self.numeric_hook = Some(hook);
398 #[cfg(feature = "jit")]
399 {
400 self.block_eligible_cached = None;
402 }
403 }
404
405 pub fn set_output_sink(&mut self, sink: OutputSink) {
411 self.output_sink = Some(sink);
412 }
413
414 pub fn set_input_source(&mut self, source: InputSource) {
418 self.input_source = Some(source);
419 }
420
421 fn emit_output(&mut self, s: &str) {
425 if let Some(sink) = self.output_sink.as_mut() {
426 sink(s);
427 } else {
428 use std::io::Write;
429 let stdout = std::io::stdout();
430 let mut lock = stdout.lock();
431 let _ = lock.write_all(s.as_bytes());
432 }
433 }
434
435 pub fn is_strict_numeric(&self) -> bool {
437 self.numeric_hook.is_some()
438 }
439
440 pub fn set_fixnum_range(&mut self, lo: i64, hi: i64) {
453 self.fixnum_range = Some((lo, hi));
454 #[cfg(feature = "jit")]
455 {
456 self.block_eligible_cached = None;
457 }
458 }
459
460 #[inline(always)]
462 fn in_fixnum_range(&self, n: i64) -> bool {
463 match self.fixnum_range {
464 Some((lo, hi)) => n >= lo && n <= hi,
465 None => true,
466 }
467 }
468
469 #[cfg(feature = "jit")]
478 pub fn enable_tracing_jit(&mut self) {
479 self.tracing_jit = true;
480 }
481
482 #[cfg(feature = "jit")]
485 pub fn disable_tracing_jit(&mut self) {
486 self.tracing_jit = false;
487 self.recorder = None;
488 }
489
490 pub fn reset(&mut self, chunk: Chunk) {
511 self.stack.clear();
512 self.frames.clear();
513 let num_names = chunk.names.len();
514 self.globals.clear();
515 self.globals.resize(num_names, Value::Undef);
516 self.frames.push(Frame {
517 return_ip: 0,
518 stack_base: 0,
519 slots: Vec::with_capacity(16),
520 });
521 self.ip = 0;
522 self.last_status = 0;
523 self.halted = false;
524 self.awk_rand_seed = 1;
525 self.chunk = chunk;
526 #[cfg(feature = "jit")]
527 {
528 self.recorder = None;
529 self.slot_buf.clear();
530 self.slot_kinds_buf.clear();
531 self.deopt_info = DeoptInfo::zeroed();
532 self.block_eligible_cached = None;
533 }
534 }
535
536 pub fn set_shell_host(&mut self, host: Box<dyn ShellHost>) {
538 self.host = Some(host);
539 }
540
541 pub fn set_awk_host(&mut self, host: Box<dyn AwkHost>) {
544 self.awk_host = Some(host);
545 }
546
547 pub fn awk_signal(&self) -> Option<u8> {
553 self.awk_signal
554 }
555
556 pub fn take_sched(&mut self) -> Option<crate::sched::SchedReq> {
561 self.sched.take()
562 }
563
564 pub fn clear_halt(&mut self) {
568 self.halted = false;
569 }
570
571 pub fn set_extension_handler(&mut self, handler: ExtensionHandler) {
573 self.ext_handler = Some(handler);
574 }
575
576 pub fn set_extension_wide_handler(&mut self, handler: ExtensionWideHandler) {
578 self.ext_wide_handler = Some(handler);
579 }
580
581 pub fn register_builtin(&mut self, id: u16, handler: BuiltinHandler) {
584 let idx = id as usize;
585 if idx >= self.builtin_table.len() {
586 self.builtin_table.resize(idx + 1, None);
587 }
588 self.builtin_table[idx] = Some(handler);
589 }
590
591 pub fn run_builtin_by_name(&mut self, name: &str, args: &[String]) -> Option<Value> {
605 let id = crate::shell_builtins::builtin_id(name)?;
606 let handler = match self.builtin_table.get(id as usize) {
607 Some(Some(h)) => *h,
608 _ => return None,
609 };
610 let argc = args.len().min(u8::MAX as usize);
613 for a in &args[..argc] {
614 self.push(Value::Str(std::sync::Arc::new(a.clone())));
615 }
616 Some(handler(self, argc as u8))
617 }
618
619 pub fn request_halt(&mut self) {
623 self.halted = true;
624 }
625
626 #[cfg(feature = "jit")]
639 #[inline]
640 fn refresh_slot_buffers(&mut self) {
641 let frame = match self.frames.last() {
642 Some(f) => f,
643 None => return,
644 };
645 let n = frame.slots.len();
646 let mut all_num = true;
647 match n {
648 0 => {
649 self.slot_buf.clear();
650 self.slot_kinds_buf.clear();
651 }
652 1 => {
653 let (i, kind) = match &frame.slots[0] {
654 Value::Int(v) => (*v, SlotKind::Int),
655 Value::Float(f) => (f.to_bits() as i64, SlotKind::Float),
656 Value::Bool(b) => (*b as i64, SlotKind::Int),
657 _ => {
658 all_num = false;
659 (0, SlotKind::Int)
660 }
661 };
662 if self.slot_buf.is_empty() {
663 self.slot_buf.push(i);
664 self.slot_kinds_buf.push(kind);
665 } else {
666 self.slot_buf.truncate(1);
667 self.slot_buf[0] = i;
668 self.slot_kinds_buf.truncate(1);
669 self.slot_kinds_buf[0] = kind;
670 }
671 }
672 _ => {
673 self.slot_buf.clear();
674 self.slot_kinds_buf.clear();
675 self.slot_buf.reserve(n);
676 self.slot_kinds_buf.reserve(n);
677 for v in &frame.slots {
678 let (i, kind) = match v {
679 Value::Int(n) => (*n, SlotKind::Int),
680 Value::Float(f) => (f.to_bits() as i64, SlotKind::Float),
681 Value::Bool(b) => (*b as i64, SlotKind::Int),
682 _ => {
683 all_num = false;
684 (0, SlotKind::Int)
685 }
686 };
687 self.slot_buf.push(i);
688 self.slot_kinds_buf.push(kind);
689 }
690 }
691 }
692 self.slots_all_numeric = all_num;
693 }
694
695 #[cfg(feature = "jit")]
703 #[inline]
704 fn write_slots_back(&mut self) {
705 let frame = match self.frames.last_mut() {
706 Some(f) => f,
707 None => return,
708 };
709 let n = frame.slots.len().min(self.slot_buf.len());
710 match n {
711 0 => {}
712 1 => match self.slot_kinds_buf.first() {
713 Some(SlotKind::Int) => frame.slots[0] = Value::Int(self.slot_buf[0]),
714 Some(SlotKind::Float) => {
715 frame.slots[0] = Value::Float(f64::from_bits(self.slot_buf[0] as u64));
716 }
717 None => {}
718 },
719 _ => {
720 for i in 0..n {
721 match self.slot_kinds_buf.get(i) {
722 Some(SlotKind::Int) => {
723 frame.slots[i] = Value::Int(self.slot_buf[i]);
724 }
725 Some(SlotKind::Float) => {
726 frame.slots[i] = Value::Float(f64::from_bits(self.slot_buf[i] as u64));
727 }
728 None => {}
729 }
730 }
731 }
732 }
733 }
734
735 #[cfg(feature = "jit")]
742 fn lookup_trace_for_backward(&mut self, anchor_ip: usize, fallthrough_ip: usize) -> usize {
743 self.refresh_slot_buffers();
744 let lookup = self.jit.trace_lookup(
745 &self.chunk,
746 anchor_ip,
747 &mut self.slot_buf,
748 &self.slot_kinds_buf,
749 &mut self.deopt_info,
750 );
751 match lookup {
752 TraceLookup::Ran { resume_ip } => {
753 self.write_slots_back();
754 self.materialize_deopt_frames();
755 self.chain_side_traces(anchor_ip, fallthrough_ip, resume_ip)
758 }
759 TraceLookup::StartRecording => {
760 self.recorder = Some(TraceRecorder {
764 record_anchor_ip: anchor_ip,
765 close_anchor_ip: anchor_ip,
766 fallthrough_ip,
767 ops: Vec::new(),
768 recorded_ips: Vec::new(),
769 slot_kinds_at_anchor: self.slot_kinds_buf.clone(),
770 entered_ips: Vec::new(),
771 aborted: false,
772 });
773 anchor_ip
774 }
775 TraceLookup::NotHot | TraceLookup::GuardMismatch | TraceLookup::Skip => anchor_ip,
776 }
777 }
778
779 #[cfg(feature = "jit")]
797 fn chain_side_traces(
798 &mut self,
799 main_anchor: usize,
800 main_fallthrough: usize,
801 first_resume: usize,
802 ) -> usize {
803 let mut current = first_resume;
804 if current == main_fallthrough {
805 return current;
806 }
807 let max_chain = self.jit.get_config().max_trace_chain;
808 for _ in 0..max_chain {
809 let chained_fallthrough = self
812 .jit
813 .trace_loop_anchors(&self.chunk, current)
814 .map(|(_, fallthrough)| fallthrough);
815
816 self.refresh_slot_buffers();
817 let lookup = self.jit.trace_lookup(
818 &self.chunk,
819 current,
820 &mut self.slot_buf,
821 &self.slot_kinds_buf,
822 &mut self.deopt_info,
823 );
824 match lookup {
825 TraceLookup::Ran { resume_ip } => {
826 self.write_slots_back();
827 self.materialize_deopt_frames();
828 current = resume_ip;
829 if Some(current) == chained_fallthrough {
830 return current;
831 }
832 }
833 TraceLookup::StartRecording => {
834 self.recorder = Some(TraceRecorder {
839 record_anchor_ip: current,
840 close_anchor_ip: main_anchor,
841 fallthrough_ip: main_fallthrough,
842 ops: Vec::new(),
843 recorded_ips: Vec::new(),
844 slot_kinds_at_anchor: self.slot_kinds_buf.clone(),
845 entered_ips: Vec::new(),
846 aborted: false,
847 });
848 return current;
849 }
850 _ => {
851 self.jit.trace_bump_side_exit(&self.chunk, main_anchor);
854 return current;
855 }
856 }
857 }
858 current
859 }
860
861 #[cfg(feature = "jit")]
870 fn materialize_deopt_frames(&mut self) {
871 let stack_count = self.deopt_info.stack_count;
875 for i in 0..stack_count {
876 let raw = self.deopt_info.stack_buf[i];
877 let v = match self.deopt_info.stack_kinds[i] {
878 crate::jit::STACK_KIND_FLOAT => Value::Float(f64::from_bits(raw as u64)),
879 _ => Value::Int(raw),
880 };
881 self.stack.push(v);
882 }
883 let count = self.deopt_info.frame_count;
885 if count == 0 {
886 return;
887 }
888 for i in 0..count {
889 let df = &self.deopt_info.frames[i];
890 let mut slots: Vec<Value> = Vec::with_capacity(df.slot_count);
891 for j in 0..df.slot_count {
892 slots.push(Value::Int(df.slots[j]));
893 }
894 self.frames.push(Frame {
895 return_ip: df.return_ip,
896 stack_base: self.stack.len(),
897 slots,
898 });
899 }
900 }
901
902 #[cfg(feature = "jit")]
906 fn finalize_recorder(&mut self) {
907 let Some(rec) = self.recorder.as_ref() else {
908 return;
909 };
910 let aborted = rec.aborted;
911 let close_anchor = rec.close_anchor_ip;
912 let record_anchor = rec.record_anchor_ip;
913 if !aborted && self.ip == close_anchor {
918 let r = self.recorder.take().unwrap();
919 if self.jit.is_trace_eligible(&r.ops, r.close_anchor_ip) {
920 let installed = self.jit.trace_install_with_kind(
924 &self.chunk,
925 r.record_anchor_ip,
926 r.close_anchor_ip,
927 r.fallthrough_ip,
928 &r.ops,
929 &r.recorded_ips,
930 &r.slot_kinds_at_anchor,
931 );
932 if !installed {
933 self.jit.trace_abort(&self.chunk, r.record_anchor_ip);
934 }
935 } else {
936 self.jit.trace_abort(&self.chunk, r.record_anchor_ip);
937 }
938 } else {
939 let _ = self.recorder.take();
942 self.jit.trace_abort(&self.chunk, record_anchor);
943 }
944 }
945
946 #[inline(always)]
952 pub fn push(&mut self, val: Value) {
953 self.stack.push(val);
954 }
955
956 #[inline(always)]
961 pub fn pop(&mut self) -> Value {
962 self.stack.pop().unwrap_or(Value::Undef)
963 }
964
965 #[inline(always)]
968 pub fn peek(&self) -> &Value {
969 self.stack.last().unwrap_or(&Value::Undef)
970 }
971
972 #[inline(always)]
982 fn arith_int_fast(
983 &mut self,
984 op: NumOp,
985 int_op: fn(i64, i64) -> i64,
986 ck_op: fn(i64, i64) -> Option<i64>,
987 float_op: fn(f64, f64) -> f64,
988 ) -> Option<String> {
989 let len = self.stack.len();
990 if len < 2 {
991 return None;
992 }
993 let b = &self.stack[len - 1];
995 let a = &self.stack[len - 2];
996 let result = match (a, b, &self.numeric_hook) {
997 (Value::Int(x), Value::Int(y), None) => Value::Int(int_op(*x, *y)),
999 (Value::Int(x), Value::Int(y), Some(hook)) => match ck_op(*x, *y) {
1000 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
1002 _ => match hook(op, a, b) {
1005 Ok(v) => v,
1006 Err(e) => return Some(e),
1007 },
1008 },
1009 (a, b, _) if is_native_num(a) && is_native_num(b) => {
1012 Value::Float(float_op(a.to_float(), b.to_float()))
1013 }
1014 (a, b, None) => Value::Float(float_op(a.to_float(), b.to_float())),
1016 (a, b, Some(hook)) => match hook(op, a, b) {
1017 Ok(v) => v,
1018 Err(e) => return Some(e),
1019 },
1020 };
1021 self.stack.truncate(len - 2);
1022 self.stack.push(result);
1023 None
1024 }
1025
1026 #[inline(always)]
1032 fn cmp_int_fast(
1033 &mut self,
1034 op: NumOp,
1035 int_cmp: fn(i64, i64) -> bool,
1036 float_cmp: fn(f64, f64) -> bool,
1037 ) -> Option<String> {
1038 let len = self.stack.len();
1039 if len < 2 {
1040 return None;
1041 }
1042 let b = &self.stack[len - 1];
1043 let a = &self.stack[len - 2];
1044 let result = match (a, b, &self.numeric_hook) {
1045 (Value::Int(x), Value::Int(y), _) => Value::Bool(int_cmp(*x, *y)),
1046 (a, b, _) if is_native_num(a) && is_native_num(b) => {
1047 Value::Bool(float_cmp(a.to_float(), b.to_float()))
1048 }
1049 (a, b, None) => Value::Bool(float_cmp(a.to_float(), b.to_float())),
1050 (a, b, Some(hook)) => match hook(op, a, b) {
1051 Ok(v) => v,
1052 Err(e) => return Some(e),
1053 },
1054 };
1055 self.stack.truncate(len - 2);
1056 self.stack.push(result);
1057 None
1058 }
1059
1060 #[inline(always)]
1064 fn delegate_binary(&mut self, op: NumOp) -> Option<String> {
1065 let b = self.pop();
1066 let a = self.pop();
1067 let hook = match &self.numeric_hook {
1068 Some(h) => h,
1069 None => return None,
1070 };
1071 match hook(op, &a, &b) {
1072 Ok(v) => {
1073 self.push(v);
1074 None
1075 }
1076 Err(e) => Some(e),
1077 }
1078 }
1079
1080 #[inline(always)]
1083 fn negate_strict(&mut self) -> Option<String> {
1084 let val = self.pop();
1085 let result = match (&val, &self.numeric_hook) {
1086 (Value::Int(n), None) => Value::Int(n.wrapping_neg()),
1087 (Value::Int(n), Some(hook)) => match n.checked_neg() {
1088 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
1089 _ => match hook(NumOp::Neg, &val, &Value::Undef) {
1090 Ok(v) => v,
1091 Err(e) => return Some(e),
1092 },
1093 },
1094 (Value::Float(f), _) => Value::Float(-f),
1095 (v, None) => Value::Float(-v.to_float()),
1096 (v, Some(hook)) => match hook(NumOp::Neg, v, &Value::Undef) {
1097 Ok(v) => v,
1098 Err(e) => return Some(e),
1099 },
1100 };
1101 self.push(result);
1102 None
1103 }
1104
1105 pub fn run(&mut self) -> VMResult {
1129 self.awk_signal = None;
1133 self.sched = None;
1136 #[cfg(feature = "jit")]
1151 crate::jit::set_strict_numeric(self.numeric_hook.is_some(), self.fixnum_range);
1152
1153 #[cfg(feature = "jit")]
1154 if self.tracing_jit && self.frames.len() == 1 && self.ip == 0 {
1155 let eligible = match self.block_eligible_cached {
1156 Some(v) => v,
1157 None => {
1158 let v = self.jit.is_block_eligible(&self.chunk);
1159 self.block_eligible_cached = Some(v);
1160 v
1161 }
1162 };
1163 if eligible {
1164 self.refresh_slot_buffers();
1165 let strict_blocked = self.numeric_hook.is_some() && !self.slots_all_numeric;
1171 if !strict_blocked {
1172 if let Some(result) = self.jit.try_run_block_typed_kinded(
1173 &self.chunk,
1174 &mut self.slot_buf,
1175 &self.slot_kinds_buf,
1176 ) {
1177 if crate::jit::take_num_overflow_trap() {
1184 self.ip = 0;
1185 } else {
1186 match crate::jit::take_awk_div_trap() {
1191 1 => {
1192 return VMResult::Error(
1193 "division by zero attempted".to_string(),
1194 )
1195 }
1196 2 => {
1197 return VMResult::Error(
1198 "division by zero attempted in `%'".to_string(),
1199 )
1200 }
1201 3 => {
1202 return VMResult::Error(
1203 "lshift: negative values are not allowed".to_string(),
1204 )
1205 }
1206 4 => {
1207 return VMResult::Error(
1208 "rshift: negative values are not allowed".to_string(),
1209 )
1210 }
1211 5 => {
1212 return VMResult::Error(
1213 "compl: negative value is not allowed".to_string(),
1214 )
1215 }
1216 _ => {}
1217 }
1218 self.write_slots_back();
1219 self.halted = true;
1220 return VMResult::Ok(match result {
1228 crate::jit::BlockNum::Int(n) => Value::Int(n),
1229 crate::jit::BlockNum::Float(f) => Value::Float(f),
1230 });
1231 }
1232 }
1233 }
1234 }
1235 }
1236
1237 let ops = &self.chunk.ops as *const Vec<Op>;
1238 let ops = unsafe { &*ops };
1242
1243 while self.ip < ops.len() && !self.halted {
1244 let ip = self.ip;
1246 self.ip += 1;
1247
1248 #[cfg(feature = "jit")]
1255 let recorder_was_armed = self.recorder.is_some();
1256 #[cfg(not(feature = "jit"))]
1257 let recorder_was_armed = false;
1258 #[cfg(feature = "jit")]
1259 if self.recorder.is_some() {
1260 let cfg = self.jit.get_config();
1261 let max_len = cfg.max_trace_len;
1262 let max_inline_recursion = cfg.max_inline_recursion;
1263 let cur_op = ops[ip].clone();
1264 let resolved_entry = if let Op::Call(name_idx, _) = &cur_op {
1267 self.chunk.find_sub(*name_idx)
1268 } else {
1269 None
1270 };
1271 let rec = self.recorder.as_mut().unwrap();
1272 if !rec.aborted {
1273 rec.ops.push(cur_op.clone());
1274 rec.recorded_ips.push(ip);
1275 if rec.ops.len() > max_len {
1276 rec.aborted = true;
1277 } else {
1278 match cur_op {
1281 Op::Call(_, _) => match resolved_entry {
1282 Some(entry_ip) => {
1283 let occurrences = rec
1291 .entered_ips
1292 .iter()
1293 .filter(|&&ip| ip == entry_ip)
1294 .count();
1295 if occurrences >= max_inline_recursion {
1296 rec.aborted = true;
1297 } else {
1298 rec.entered_ips.push(entry_ip);
1299 }
1300 }
1301 None => rec.aborted = true, },
1303 Op::Return | Op::ReturnValue => {
1304 if rec.entered_ips.is_empty() {
1305 rec.aborted = true; } else {
1308 rec.entered_ips.pop();
1309 }
1310 }
1311 Op::CallBuiltin(_, _) => rec.aborted = true,
1312 Op::AwkFieldGet
1318 | Op::AwkFieldSet
1319 | Op::AwkNf
1320 | Op::AwkSetRecord
1321 | Op::AwkSpecialGet(_)
1322 | Op::AwkSpecialSet(_)
1323 | Op::AwkPrint(_)
1324 | Op::AwkPrintf(_)
1325 | Op::AwkSprintf(_)
1326 | Op::AwkGetline(_)
1327 | Op::AwkLength(_)
1328 | Op::AwkSubstr(_)
1329 | Op::AwkIndex
1330 | Op::AwkSplit(_)
1331 | Op::AwkSub(_)
1332 | Op::AwkGsub(_)
1333 | Op::AwkGensub(_)
1334 | Op::AwkMatch
1335 | Op::AwkToLower
1336 | Op::AwkToUpper
1337 | Op::AwkSqrt
1338 | Op::AwkSin
1339 | Op::AwkCos
1340 | Op::AwkExp
1341 | Op::AwkLog
1342 | Op::AwkAtan2
1343 | Op::AwkDiv
1344 | Op::AwkMod
1345 | Op::AwkDivJit
1346 | Op::AwkModJit
1347 | Op::AwkSqrtJit
1348 | Op::AwkLogJit
1349 | Op::AwkLshiftJit
1350 | Op::AwkRshiftJit
1351 | Op::AwkComplJit
1352 | Op::AwkAnd(_)
1353 | Op::AwkOr(_)
1354 | Op::AwkXor(_)
1355 | Op::AwkCompl
1356 | Op::AwkLshift
1357 | Op::AwkRshift
1358 | Op::AwkStrtonum
1359 | Op::AwkSystime
1360 | Op::AwkRand
1361 | Op::AwkSrand(_)
1362 | Op::AwkStrftime(_)
1363 | Op::AwkMktime(_)
1364 | Op::AwkOrd
1365 | Op::AwkChr
1366 | Op::AwkMkbool
1367 | Op::AwkIntdiv
1368 | Op::AwkIntdiv0
1369 | Op::AwkArrayGet(_)
1370 | Op::AwkArraySet(_)
1371 | Op::AwkArrayExists(_)
1372 | Op::AwkArrayDelete(_)
1373 | Op::AwkArrayClear(_)
1374 | Op::AwkArrayLen(_) => rec.aborted = true,
1375 Op::AwkSignal(_) => rec.aborted = true,
1376 Op::Go(_, _)
1378 | Op::ChanMake
1379 | Op::ChanSend
1380 | Op::ChanRecv
1381 | Op::ChanClose
1382 | Op::Select(_, _)
1383 | Op::CallDynamic(_) => rec.aborted = true,
1384 _ => {}
1385 }
1386 }
1387 }
1388 }
1389
1390 match self.exec_op(ops, ip, recorder_was_armed) {
1391 ExecFlow::Cont => {}
1392 ExecFlow::Ret(r) => return r,
1393 }
1394 }
1395
1396 if let Some(val) = self.stack.pop() {
1397 VMResult::Ok(val)
1398 } else {
1399 VMResult::Halted
1400 }
1401 }
1402
1403 #[cfg_attr(not(feature = "jit"), allow(unused_variables))]
1411 pub(crate) fn exec_op(&mut self, ops: &[Op], ip: usize, recorder_was_armed: bool) -> ExecFlow {
1412 use crate::awk_builtins as ab;
1413 match &ops[ip] {
1414 Op::Nop => {}
1415
1416 Op::LoadInt(n) => self.push(Value::Int(*n)),
1418 Op::LoadFloat(f) => self.push(Value::Float(*f)),
1419 Op::LoadConst(idx) => {
1420 let val = match self.chunk.constants.get(*idx as usize) {
1421 Some(Value::Int(n)) => Value::Int(*n),
1422 Some(Value::Float(f)) => Value::Float(*f),
1423 Some(Value::Bool(b)) => Value::Bool(*b),
1424 Some(other) => other.clone(),
1425 None => Value::Undef,
1426 };
1427 self.push(val);
1428 }
1429 Op::LoadTrue => self.push(Value::Bool(true)),
1430 Op::LoadFalse => self.push(Value::Bool(false)),
1431 Op::LoadUndef => self.push(Value::Undef),
1432
1433 Op::Pop => {
1435 self.pop();
1436 }
1437 Op::Dup => {
1438 let val = self.peek().clone();
1439 self.push(val);
1440 }
1441 Op::Dup2 => {
1442 let len = self.stack.len();
1443 if len >= 2 {
1444 let a = self.stack[len - 2].clone();
1445 let b = self.stack[len - 1].clone();
1446 self.push(a);
1447 self.push(b);
1448 }
1449 }
1450 Op::Swap => {
1451 let len = self.stack.len();
1452 if len >= 2 {
1453 self.stack.swap(len - 1, len - 2);
1454 }
1455 }
1456 Op::Rot => {
1457 let len = self.stack.len();
1458 if len >= 3 {
1459 self.stack.swap(len - 3, len - 2);
1461 self.stack.swap(len - 2, len - 1);
1462 }
1463 }
1464
1465 Op::GetVar(idx) => {
1467 let val = self.get_var(*idx);
1468 self.push(val);
1469 }
1470 Op::SetVar(idx) => {
1471 let val = self.pop();
1472 self.set_var(*idx, val);
1473 }
1474 Op::DeclareVar(idx) => {
1475 let val = self.pop();
1476 self.set_var(*idx, val);
1477 }
1478 Op::GetSlot(slot) => {
1479 let val = self.get_slot(*slot);
1480 self.push(val);
1481 }
1482 Op::SetSlot(slot) => {
1483 let val = self.pop();
1484 self.set_slot(*slot, val);
1485 }
1486 Op::SlotArrayGet(slot) => {
1487 let index = self.pop().to_int() as usize;
1488 let val = self.get_slot(*slot);
1489 let result = if let Value::Array(ref arr) = val {
1490 arr.get(index).cloned().unwrap_or(Value::Undef)
1491 } else {
1492 Value::Undef
1493 };
1494 self.push(result);
1495 }
1496 Op::SlotArraySet(slot) => {
1497 let index = self.pop().to_int() as usize;
1498 let val = self.pop();
1499 let arr_val = self.get_slot(*slot);
1500 if let Value::Array(mut arr) = arr_val {
1501 if index >= arr.len() {
1502 arr.resize(index + 1, Value::Undef);
1503 }
1504 arr[index] = val;
1505 self.set_slot(*slot, Value::Array(arr));
1506 }
1507 }
1508
1509 Op::Add => {
1511 if let Some(e) =
1512 self.arith_int_fast(NumOp::Add, i64::wrapping_add, i64::checked_add, |a, b| {
1513 a + b
1514 })
1515 {
1516 return ExecFlow::Ret(VMResult::Error(e));
1517 }
1518 }
1519 Op::Sub => {
1520 if let Some(e) =
1521 self.arith_int_fast(NumOp::Sub, i64::wrapping_sub, i64::checked_sub, |a, b| {
1522 a - b
1523 })
1524 {
1525 return ExecFlow::Ret(VMResult::Error(e));
1526 }
1527 }
1528 Op::Mul => {
1529 if let Some(e) =
1530 self.arith_int_fast(NumOp::Mul, i64::wrapping_mul, i64::checked_mul, |a, b| {
1531 a * b
1532 })
1533 {
1534 return ExecFlow::Ret(VMResult::Error(e));
1535 }
1536 }
1537 Op::Div => {
1538 if self.numeric_hook.is_some() {
1543 let len = self.stack.len();
1544 if len >= 2
1545 && !(is_native_num(&self.stack[len - 1])
1546 && is_native_num(&self.stack[len - 2]))
1547 {
1548 if let Some(e) = self.delegate_binary(NumOp::Div) {
1549 return ExecFlow::Ret(VMResult::Error(e));
1550 }
1551 return ExecFlow::Cont;
1552 }
1553 }
1554 let b = self.pop();
1555 let a = self.pop();
1556 let divisor = b.to_float();
1557 self.push(if divisor == 0.0 {
1558 Value::Undef
1559 } else {
1560 Value::Float(a.to_float() / divisor)
1561 });
1562 }
1563 Op::Mod => {
1564 if let Some(e) = self.arith_int_fast(
1565 NumOp::Mod,
1566 |x, y| if y != 0 { x % y } else { 0 },
1567 |x, y| if y != 0 { x.checked_rem(y) } else { Some(0) },
1568 |a, b| a % b,
1569 ) {
1570 return ExecFlow::Ret(VMResult::Error(e));
1571 }
1572 }
1573 Op::Pow => {
1574 if self.numeric_hook.is_some() {
1575 let len = self.stack.len();
1576 if len >= 2
1577 && !(is_native_num(&self.stack[len - 1])
1578 && is_native_num(&self.stack[len - 2]))
1579 {
1580 if let Some(e) = self.delegate_binary(NumOp::Pow) {
1581 return ExecFlow::Ret(VMResult::Error(e));
1582 }
1583 return ExecFlow::Cont;
1584 }
1585 }
1586 let b = self.pop();
1587 let a = self.pop();
1588 self.push(Value::Float(a.to_float().powf(b.to_float())));
1589 }
1590 Op::Negate => {
1591 if let Some(e) = self.negate_strict() {
1592 return ExecFlow::Ret(VMResult::Error(e));
1593 }
1594 }
1595 Op::Inc => {
1596 let val = self.pop();
1597 let result = match (&val, &self.numeric_hook) {
1598 (Value::Int(n), None) => Value::Int(n.wrapping_add(1)),
1599 (Value::Int(n), Some(hook)) => match n.checked_add(1) {
1600 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
1601 _ => match hook(NumOp::Add, &val, &Value::Int(1)) {
1602 Ok(v) => v,
1603 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
1604 },
1605 },
1606 (v, None) => Value::Int(v.to_int().wrapping_add(1)),
1607 (v, Some(hook)) => match hook(NumOp::Add, v, &Value::Int(1)) {
1608 Ok(v) => v,
1609 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
1610 },
1611 };
1612 self.push(result);
1613 }
1614 Op::Dec => {
1615 let val = self.pop();
1616 let result = match (&val, &self.numeric_hook) {
1617 (Value::Int(n), None) => Value::Int(n.wrapping_sub(1)),
1618 (Value::Int(n), Some(hook)) => match n.checked_sub(1) {
1619 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
1620 _ => match hook(NumOp::Sub, &val, &Value::Int(1)) {
1621 Ok(v) => v,
1622 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
1623 },
1624 },
1625 (v, None) => Value::Int(v.to_int().wrapping_sub(1)),
1626 (v, Some(hook)) => match hook(NumOp::Sub, v, &Value::Int(1)) {
1627 Ok(v) => v,
1628 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
1629 },
1630 };
1631 self.push(result);
1632 }
1633
1634 Op::Concat => {
1636 let b = self.pop();
1637 let a = self.pop();
1638 let a_s = a.as_str_cow();
1639 let b_s = b.as_str_cow();
1640 let mut s = String::with_capacity(a_s.len() + b_s.len());
1641 s.push_str(&a_s);
1642 s.push_str(&b_s);
1643 self.push(Value::str(s));
1644 }
1645 Op::StringRepeat => {
1646 let count = self.pop().to_int();
1647 let s = self.pop().to_str();
1648 self.push(Value::str(s.repeat(count.max(0) as usize)));
1649 }
1650 Op::StringLen => {
1651 let s = self.pop();
1652 self.push(Value::Int(s.len() as i64));
1653 }
1654
1655 Op::NumEq => {
1657 if let Some(e) = self.cmp_int_fast(NumOp::Eq, |x, y| x == y, |a, b| a == b) {
1658 return ExecFlow::Ret(VMResult::Error(e));
1659 }
1660 }
1661 Op::NumNe => {
1662 if let Some(e) = self.cmp_int_fast(NumOp::Ne, |x, y| x != y, |a, b| a != b) {
1663 return ExecFlow::Ret(VMResult::Error(e));
1664 }
1665 }
1666 Op::NumLt => {
1667 if let Some(e) = self.cmp_int_fast(NumOp::Lt, |x, y| x < y, |a, b| a < b) {
1668 return ExecFlow::Ret(VMResult::Error(e));
1669 }
1670 }
1671 Op::NumGt => {
1672 if let Some(e) = self.cmp_int_fast(NumOp::Gt, |x, y| x > y, |a, b| a > b) {
1673 return ExecFlow::Ret(VMResult::Error(e));
1674 }
1675 }
1676 Op::NumLe => {
1677 if let Some(e) = self.cmp_int_fast(NumOp::Le, |x, y| x <= y, |a, b| a <= b) {
1678 return ExecFlow::Ret(VMResult::Error(e));
1679 }
1680 }
1681 Op::NumGe => {
1682 if let Some(e) = self.cmp_int_fast(NumOp::Ge, |x, y| x >= y, |a, b| a >= b) {
1683 return ExecFlow::Ret(VMResult::Error(e));
1684 }
1685 }
1686 Op::Spaceship => {
1687 let len = self.stack.len();
1688 if len >= 2 {
1689 let b = &self.stack[len - 1];
1690 let a = &self.stack[len - 2];
1691 let result = match (a, b) {
1692 (Value::Int(x), Value::Int(y)) => x.cmp(y) as i64,
1693 _ => {
1694 let af = a.to_float();
1695 let bf = b.to_float();
1696 if af < bf {
1697 -1
1698 } else if af > bf {
1699 1
1700 } else {
1701 0
1702 }
1703 }
1704 };
1705 self.stack.truncate(len - 2);
1706 self.stack.push(Value::Int(result));
1707 }
1708 }
1709
1710 Op::StrEq => {
1712 let b = self.pop();
1713 let a = self.pop();
1714 self.push(Value::Bool(a.as_str_cow() == b.as_str_cow()));
1715 }
1716 Op::StrNe => {
1717 let b = self.pop();
1718 let a = self.pop();
1719 self.push(Value::Bool(a.as_str_cow() != b.as_str_cow()));
1720 }
1721 Op::StrLt => {
1722 let b = self.pop();
1723 let a = self.pop();
1724 self.push(Value::Bool(a.as_str_cow() < b.as_str_cow()));
1725 }
1726 Op::StrGt => {
1727 let b = self.pop();
1728 let a = self.pop();
1729 self.push(Value::Bool(a.as_str_cow() > b.as_str_cow()));
1730 }
1731 Op::StrLe => {
1732 let b = self.pop();
1733 let a = self.pop();
1734 self.push(Value::Bool(a.as_str_cow() <= b.as_str_cow()));
1735 }
1736 Op::StrGe => {
1737 let b = self.pop();
1738 let a = self.pop();
1739 self.push(Value::Bool(a.as_str_cow() >= b.as_str_cow()));
1740 }
1741 Op::StrCmp => {
1742 let b = self.pop();
1743 let a = self.pop();
1744 self.push(Value::Int(match a.as_str_cow().cmp(&b.as_str_cow()) {
1745 std::cmp::Ordering::Less => -1,
1746 std::cmp::Ordering::Equal => 0,
1747 std::cmp::Ordering::Greater => 1,
1748 }));
1749 }
1750
1751 Op::LogNot => {
1753 let val = self.pop();
1754 self.push(Value::Bool(!val.is_truthy()));
1755 }
1756 Op::LogAnd => {
1757 let b = self.pop();
1758 let a = self.pop();
1759 self.push(Value::Bool(a.is_truthy() && b.is_truthy()));
1760 }
1761 Op::LogOr => {
1762 let b = self.pop();
1763 let a = self.pop();
1764 self.push(Value::Bool(a.is_truthy() || b.is_truthy()));
1765 }
1766 Op::BitAnd => {
1767 let b = self.pop();
1768 let a = self.pop();
1769 self.push(Value::Int(a.to_int() & b.to_int()));
1770 }
1771 Op::BitOr => {
1772 let b = self.pop();
1773 let a = self.pop();
1774 self.push(Value::Int(a.to_int() | b.to_int()));
1775 }
1776 Op::BitXor => {
1777 let b = self.pop();
1778 let a = self.pop();
1779 self.push(Value::Int(a.to_int() ^ b.to_int()));
1780 }
1781 Op::BitNot => {
1782 let val = self.pop();
1783 self.push(Value::Int(!val.to_int()));
1784 }
1785 Op::Shl => {
1786 let b = self.pop();
1787 let a = self.pop();
1788 self.push(Value::Int(a.to_int() << (b.to_int() as u32 & 63)));
1789 }
1790 Op::Shr => {
1791 let b = self.pop();
1792 let a = self.pop();
1793 self.push(Value::Int(a.to_int() >> (b.to_int() as u32 & 63)));
1794 }
1795
1796 Op::Jump(target) => {
1798 let target = *target;
1799 #[cfg(feature = "jit")]
1800 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1801 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1802 } else {
1803 self.ip = target;
1804 }
1805 #[cfg(not(feature = "jit"))]
1806 {
1807 self.ip = target;
1808 }
1809 }
1810 Op::JumpIfTrue(target) => {
1811 let target = *target;
1812 if self.pop().is_truthy() {
1813 #[cfg(feature = "jit")]
1814 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1815 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1816 } else {
1817 self.ip = target;
1818 }
1819 #[cfg(not(feature = "jit"))]
1820 {
1821 self.ip = target;
1822 }
1823 }
1824 }
1825 Op::JumpIfFalse(target) => {
1826 let target = *target;
1827 if !self.pop().is_truthy() {
1828 #[cfg(feature = "jit")]
1829 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1830 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1831 } else {
1832 self.ip = target;
1833 }
1834 #[cfg(not(feature = "jit"))]
1835 {
1836 self.ip = target;
1837 }
1838 }
1839 }
1840 Op::JumpIfTrueKeep(target) => {
1841 let target = *target;
1842 if self.peek().is_truthy() {
1843 #[cfg(feature = "jit")]
1844 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1845 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1846 } else {
1847 self.ip = target;
1848 }
1849 #[cfg(not(feature = "jit"))]
1850 {
1851 self.ip = target;
1852 }
1853 }
1854 }
1855 Op::JumpIfFalseKeep(target) => {
1856 let target = *target;
1857 if !self.peek().is_truthy() {
1858 #[cfg(feature = "jit")]
1859 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1860 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1861 } else {
1862 self.ip = target;
1863 }
1864 #[cfg(not(feature = "jit"))]
1865 {
1866 self.ip = target;
1867 }
1868 }
1869 }
1870
1871 Op::Call(name_idx, argc) => {
1873 if let Some(entry_ip) = self.chunk.find_sub(*name_idx) {
1874 self.frames.push(Frame {
1875 return_ip: self.ip,
1876 stack_base: self.stack.len() - *argc as usize,
1877 slots: Vec::new(),
1878 });
1879 self.ip = entry_ip;
1880 } else {
1881 return ExecFlow::Ret(VMResult::Error(format!(
1882 "undefined function: {}",
1883 self.chunk
1884 .names
1885 .get(*name_idx as usize)
1886 .map(|s| s.as_str())
1887 .unwrap_or("?")
1888 )));
1889 }
1890 }
1891 Op::Return => {
1892 if let Some(frame) = self.frames.pop() {
1893 self.stack.truncate(frame.stack_base);
1894 self.ip = frame.return_ip;
1895 } else {
1896 self.halted = true;
1897 }
1898 }
1899 Op::ReturnValue => {
1900 let val = self.pop();
1901 if let Some(frame) = self.frames.pop() {
1902 self.stack.truncate(frame.stack_base);
1903 self.ip = frame.return_ip;
1904 self.push(val);
1905 } else {
1906 self.halted = true;
1907 return ExecFlow::Ret(VMResult::Ok(val));
1908 }
1909 }
1910
1911 Op::PushFrame => {
1913 self.frames.push(Frame {
1914 return_ip: self.ip,
1915 stack_base: self.stack.len(),
1916 slots: Vec::new(),
1917 });
1918 }
1919 Op::PopFrame => {
1920 if let Some(frame) = self.frames.pop() {
1921 self.stack.truncate(frame.stack_base);
1922 }
1923 }
1924
1925 Op::Print(n) => {
1927 let n = *n;
1928 let start = self.stack.len().saturating_sub(n as usize);
1929 let mut out = String::new();
1930 for v in &self.stack[start..] {
1931 out.push_str(&v.as_str_cow());
1932 }
1933 self.stack.truncate(start);
1934 self.emit_output(&out);
1935 }
1936 Op::PrintLn(n) => {
1937 let n = *n;
1938 let start = self.stack.len().saturating_sub(n as usize);
1939 let mut out = String::new();
1940 for v in &self.stack[start..] {
1941 out.push_str(&v.as_str_cow());
1942 }
1943 out.push('\n');
1944 self.stack.truncate(start);
1945 self.emit_output(&out);
1946 }
1947 Op::ReadLine => {
1948 if let Some(src) = self.input_source.as_mut() {
1949 match src() {
1950 Some(l) => self.push(Value::str(l)),
1951 None => self.push(Value::Undef),
1952 }
1953 } else {
1954 let mut line = String::new();
1955 let _ = std::io::stdin().read_line(&mut line);
1956 self.push(Value::str(line.trim_end_matches('\n')));
1957 }
1958 }
1959
1960 Op::PreIncSlot(slot) => {
1962 let val = self.get_slot(*slot).to_int() + 1;
1963 self.set_slot(*slot, Value::Int(val));
1964 self.push(Value::Int(val));
1965 }
1966 Op::PreIncSlotVoid(slot) => {
1967 let val = self.get_slot(*slot).to_int() + 1;
1968 self.set_slot(*slot, Value::Int(val));
1969 }
1970 Op::SlotLtIntJumpIfFalse(slot, limit, target) => {
1971 if self.get_slot(*slot).to_int() >= *limit as i64 {
1972 self.ip = *target;
1973 }
1974 }
1975 Op::SlotIncLtIntJumpBack(slot, limit, target) => {
1976 let val = self.get_slot(*slot).to_int() + 1;
1977 self.set_slot(*slot, Value::Int(val));
1978 if val < *limit as i64 {
1979 self.ip = *target;
1980 }
1981 }
1982 Op::AccumSumLoop(sum_slot, i_slot, limit) => {
1983 let mut sum = self.get_slot(*sum_slot).to_int();
1984 let mut i = self.get_slot(*i_slot).to_int();
1985 let lim = *limit as i64;
1986 while i < lim {
1987 sum += i;
1988 i += 1;
1989 }
1990 self.set_slot(*sum_slot, Value::Int(sum));
1991 self.set_slot(*i_slot, Value::Int(i));
1992 }
1993 Op::AddAssignSlotVoid(a, b) => {
1994 let sum = self.get_slot(*a).to_int() + self.get_slot(*b).to_int();
1995 self.set_slot(*a, Value::Int(sum));
1996 }
1997 Op::PreDecSlot(slot) => {
1998 let val = self.get_slot(*slot).to_int() - 1;
1999 self.set_slot(*slot, Value::Int(val));
2000 self.push(Value::Int(val));
2001 }
2002 Op::PostIncSlot(slot) => {
2003 let old = self.get_slot(*slot).to_int();
2004 self.set_slot(*slot, Value::Int(old + 1));
2005 self.push(Value::Int(old));
2006 }
2007 Op::PostDecSlot(slot) => {
2008 let old = self.get_slot(*slot).to_int();
2009 self.set_slot(*slot, Value::Int(old - 1));
2010 self.push(Value::Int(old));
2011 }
2012
2013 Op::SetStatus => {
2015 self.last_status = self.pop().to_int() as i32;
2016 }
2017 Op::GetStatus => {
2018 self.push(Value::Status(self.last_status));
2019 }
2020
2021 Op::Extended(id, arg) => {
2023 let (id, arg) = (*id, *arg);
2024 if let Some(mut handler) = self.ext_handler.take() {
2025 handler(self, id, arg);
2026 self.ext_handler = Some(handler);
2027 }
2028 }
2029 Op::ExtendedWide(id, payload) => {
2030 let (id, payload) = (*id, *payload);
2031 if crate::awk_builtins::is_awk_op(id) {
2032 self.dispatch_awk(id, payload);
2033 } else if let Some(mut handler) = self.ext_wide_handler.take() {
2034 handler(self, id, payload);
2035 self.ext_wide_handler = Some(handler);
2036 }
2037 }
2038
2039 Op::GetArray(idx) => {
2041 let val = self.get_var(*idx);
2042 self.push(val);
2043 }
2044 Op::SetArray(idx) => {
2045 let val = self.pop();
2046 self.set_var(*idx, val);
2047 }
2048 Op::DeclareArray(idx) => {
2049 self.set_var(*idx, Value::Array(Vec::new()));
2050 }
2051 Op::ArrayGet(arr_idx) => {
2052 let index = self.pop().to_int() as usize;
2053 let idx = *arr_idx as usize;
2054 let val = if idx < self.globals.len() {
2055 if let Value::Array(ref arr) = self.globals[idx] {
2056 arr.get(index).cloned().unwrap_or(Value::Undef)
2057 } else {
2058 Value::Undef
2059 }
2060 } else {
2061 Value::Undef
2062 };
2063 self.push(val);
2064 }
2065 Op::ArraySet(arr_idx) => {
2066 let index = self.pop().to_int() as usize;
2067 let val = self.pop();
2068 let idx = *arr_idx as usize;
2069 if idx >= self.globals.len() {
2070 self.globals.resize(idx + 1, Value::Undef);
2071 }
2072 if let Value::Array(ref mut vec) = self.globals[idx] {
2073 if index >= vec.len() {
2074 vec.resize(index + 1, Value::Undef);
2075 }
2076 vec[index] = val;
2077 }
2078 }
2079 Op::ArrayPush(arr_idx) => {
2080 let val = self.pop();
2081 let idx = *arr_idx as usize;
2082 if idx >= self.globals.len() {
2083 self.globals.resize(idx + 1, Value::Undef);
2084 }
2085 if let Value::Array(ref mut vec) = self.globals[idx] {
2086 vec.push(val);
2087 }
2088 }
2089 Op::ArrayPop(arr_idx) => {
2090 let idx = *arr_idx as usize;
2091 let val = if idx < self.globals.len() {
2092 if let Value::Array(ref mut vec) = self.globals[idx] {
2093 vec.pop().unwrap_or(Value::Undef)
2094 } else {
2095 Value::Undef
2096 }
2097 } else {
2098 Value::Undef
2099 };
2100 self.push(val);
2101 }
2102 Op::ArrayShift(arr_idx) => {
2103 let idx = *arr_idx as usize;
2104 let val = if idx < self.globals.len() {
2105 if let Value::Array(ref mut vec) = self.globals[idx] {
2106 if vec.is_empty() {
2107 Value::Undef
2108 } else {
2109 vec.remove(0)
2110 }
2111 } else {
2112 Value::Undef
2113 }
2114 } else {
2115 Value::Undef
2116 };
2117 self.push(val);
2118 }
2119 Op::ArrayLen(arr_idx) => {
2120 let idx = *arr_idx as usize;
2121 let len = if idx < self.globals.len() {
2122 if let Value::Array(ref vec) = self.globals[idx] {
2123 vec.len() as i64
2124 } else {
2125 0
2126 }
2127 } else {
2128 0
2129 };
2130 self.push(Value::Int(len));
2131 }
2132 Op::MakeArray(n) => {
2133 let n = *n;
2134 let start = self.stack.len().saturating_sub(n as usize);
2135 let elements: Vec<Value> = self.stack.drain(start..).collect();
2136 self.push(Value::Array(elements));
2137 }
2138
2139 Op::GetHash(idx) => {
2141 let val = self.get_var(*idx);
2142 self.push(val);
2143 }
2144 Op::SetHash(idx) => {
2145 let val = self.pop();
2146 self.set_var(*idx, val);
2147 }
2148 Op::DeclareHash(idx) => {
2149 self.set_var(*idx, Value::Hash(std::collections::HashMap::new()));
2150 }
2151 Op::HashGet(hash_idx) => {
2152 let key_val = self.pop();
2153 let key = key_val.as_str_cow();
2154 let idx = *hash_idx as usize;
2155 let val = if idx < self.globals.len() {
2156 if let Value::Hash(ref map) = self.globals[idx] {
2157 map.get(key.as_ref()).cloned().unwrap_or(Value::Undef)
2158 } else {
2159 Value::Undef
2160 }
2161 } else {
2162 Value::Undef
2163 };
2164 self.push(val);
2165 }
2166 Op::HashSet(hash_idx) => {
2167 let key = self.pop().to_str();
2168 let val = self.pop();
2169 let idx = *hash_idx as usize;
2170 if idx >= self.globals.len() {
2171 self.globals.resize(idx + 1, Value::Undef);
2172 }
2173 if let Value::Hash(ref mut map) = self.globals[idx] {
2174 map.insert(key, val);
2175 }
2176 }
2177 Op::HashDelete(hash_idx) => {
2178 let key_val = self.pop();
2179 let key = key_val.as_str_cow();
2180 let idx = *hash_idx as usize;
2181 let val = if idx < self.globals.len() {
2182 if let Value::Hash(ref mut map) = self.globals[idx] {
2183 map.remove(key.as_ref()).unwrap_or(Value::Undef)
2184 } else {
2185 Value::Undef
2186 }
2187 } else {
2188 Value::Undef
2189 };
2190 self.push(val);
2191 }
2192 Op::HashExists(hash_idx) => {
2193 let key_val = self.pop();
2194 let key = key_val.as_str_cow();
2195 let idx = *hash_idx as usize;
2196 let val = if idx < self.globals.len() {
2197 if let Value::Hash(ref map) = self.globals[idx] {
2198 map.contains_key(key.as_ref())
2199 } else {
2200 false
2201 }
2202 } else {
2203 false
2204 };
2205 self.push(Value::Bool(val));
2206 }
2207 Op::HashKeys(hash_idx) => {
2208 let idx = *hash_idx as usize;
2209 let arr = if idx < self.globals.len() {
2210 if let Value::Hash(ref map) = self.globals[idx] {
2211 let mut keys = Vec::with_capacity(map.len());
2212 keys.extend(map.keys().map(|k| Value::str(k.as_str())));
2213 keys
2214 } else {
2215 Vec::new()
2216 }
2217 } else {
2218 Vec::new()
2219 };
2220 self.push(Value::Array(arr));
2221 }
2222 Op::HashValues(hash_idx) => {
2223 let idx = *hash_idx as usize;
2224 let arr = if idx < self.globals.len() {
2225 if let Value::Hash(ref map) = self.globals[idx] {
2226 let mut vals = Vec::with_capacity(map.len());
2227 vals.extend(map.values().cloned());
2228 vals
2229 } else {
2230 Vec::new()
2231 }
2232 } else {
2233 Vec::new()
2234 };
2235 self.push(Value::Array(arr));
2236 }
2237 Op::MakeHash(n) => {
2238 let n = *n;
2239 let start = self.stack.len().saturating_sub(n as usize);
2240 let pairs: Vec<Value> = self.stack.drain(start..).collect();
2241 let mut map = std::collections::HashMap::with_capacity(pairs.len() / 2);
2242 let mut iter = pairs.into_iter();
2243 while let Some(key) = iter.next() {
2244 if let Some(val) = iter.next() {
2245 map.insert(key.to_str(), val);
2246 }
2247 }
2248 self.push(Value::Hash(map));
2249 }
2250
2251 Op::Range => {
2253 let to = self.pop().to_int();
2254 let from = self.pop().to_int();
2255 let cap = (to - from + 1).max(0) as usize;
2256 let mut arr = Vec::with_capacity(cap);
2257 arr.extend((from..=to).map(Value::Int));
2258 self.push(Value::Array(arr));
2259 }
2260 Op::RangeStep => {
2261 let step = self.pop().to_int();
2262 let to = self.pop().to_int();
2263 let from = self.pop().to_int();
2264 let cap = if step > 0 {
2265 ((to - from) / step + 1).max(0) as usize
2266 } else if step < 0 {
2267 ((from - to) / (-step) + 1).max(0) as usize
2268 } else {
2269 0
2270 };
2271 let mut arr = Vec::with_capacity(cap);
2272 if step > 0 {
2273 let mut i = from;
2274 while i <= to {
2275 arr.push(Value::Int(i));
2276 i += step;
2277 }
2278 } else if step < 0 {
2279 let mut i = from;
2280 while i >= to {
2281 arr.push(Value::Int(i));
2282 i += step;
2283 }
2284 }
2285 self.push(Value::Array(arr));
2286 }
2287
2288 Op::TestFile(test_type) => {
2290 let test_type = *test_type;
2291 let path = self.pop().to_str();
2292 let result = match test_type {
2293 crate::op::file_test::EXISTS => std::path::Path::new(&path).exists(),
2294 crate::op::file_test::IS_FILE => std::path::Path::new(&path).is_file(),
2295 crate::op::file_test::IS_DIR => std::path::Path::new(&path).is_dir(),
2296 crate::op::file_test::IS_SYMLINK => std::path::Path::new(&path).is_symlink(),
2297 crate::op::file_test::IS_READABLE | crate::op::file_test::IS_WRITABLE => {
2298 std::path::Path::new(&path).exists()
2299 }
2300 crate::op::file_test::IS_EXECUTABLE => {
2301 #[cfg(unix)]
2302 {
2303 use std::os::unix::fs::PermissionsExt;
2304 std::fs::metadata(&path)
2305 .map(|m| m.permissions().mode() & 0o111 != 0)
2306 .unwrap_or(false)
2307 }
2308 #[cfg(not(unix))]
2309 {
2310 std::path::Path::new(&path).exists()
2311 }
2312 }
2313 crate::op::file_test::IS_NONEMPTY => std::fs::metadata(&path)
2314 .map(|m| m.len() > 0)
2315 .unwrap_or(false),
2316 crate::op::file_test::IS_SOCKET => {
2317 #[cfg(unix)]
2318 {
2319 use std::os::unix::fs::FileTypeExt;
2320 std::fs::symlink_metadata(&path)
2321 .map(|m| m.file_type().is_socket())
2322 .unwrap_or(false)
2323 }
2324 #[cfg(not(unix))]
2325 {
2326 false
2327 }
2328 }
2329 crate::op::file_test::IS_FIFO => {
2330 #[cfg(unix)]
2331 {
2332 use std::os::unix::fs::FileTypeExt;
2333 std::fs::symlink_metadata(&path)
2334 .map(|m| m.file_type().is_fifo())
2335 .unwrap_or(false)
2336 }
2337 #[cfg(not(unix))]
2338 {
2339 false
2340 }
2341 }
2342 crate::op::file_test::IS_BLOCK_DEV => {
2343 #[cfg(unix)]
2344 {
2345 use std::os::unix::fs::FileTypeExt;
2346 std::fs::symlink_metadata(&path)
2347 .map(|m| m.file_type().is_block_device())
2348 .unwrap_or(false)
2349 }
2350 #[cfg(not(unix))]
2351 {
2352 false
2353 }
2354 }
2355 crate::op::file_test::IS_CHAR_DEV => {
2356 #[cfg(unix)]
2357 {
2358 use std::os::unix::fs::FileTypeExt;
2359 std::fs::symlink_metadata(&path)
2360 .map(|m| m.file_type().is_char_device())
2361 .unwrap_or(false)
2362 }
2363 #[cfg(not(unix))]
2364 {
2365 false
2366 }
2367 }
2368 _ => false,
2369 };
2370 self.push(Value::Bool(result));
2371 }
2372
2373 Op::Exec(argc) => {
2374 let argc = *argc;
2375 let start = self.stack.len().saturating_sub(argc as usize);
2376 let args: Vec<String> = self
2382 .stack
2383 .drain(start..)
2384 .flat_map(|v| match v {
2385 Value::Array(items) => {
2386 items.into_iter().map(|i| i.to_str()).collect::<Vec<_>>()
2387 }
2388 other => vec![other.to_str()],
2389 })
2390 .collect();
2391 if let Some(cmd) = args.first() {
2392 let name_idx = self.chunk.names.iter().position(|n| n == cmd);
2394 if let Some(name_idx) = name_idx {
2395 if let Some(entry_ip) = self.chunk.find_sub(name_idx as u16) {
2396 for arg in &args[1..] {
2398 self.push(Value::str(arg));
2399 }
2400 self.frames.push(Frame {
2402 return_ip: self.ip,
2403 stack_base: self.stack.len() - (args.len() - 1),
2404 slots: Vec::with_capacity(8),
2405 });
2406 self.ip = entry_ip;
2407 return ExecFlow::Cont;
2408 }
2409 }
2410
2411 match cmd.as_str() {
2412 "true" => self.push(Value::Status(0)),
2413 "false" => self.push(Value::Status(1)),
2414 "echo" => {
2415 println!("{}", args[1..].join(" "));
2416 self.push(Value::Status(0));
2417 }
2418 "test" | "[" => {
2419 self.push(Value::Status(0));
2420 }
2421 _ => {
2422 let status = if let Some(h) = self.host.as_mut() {
2432 h.exec(args.clone())
2433 } else {
2434 #[cfg(not(target_arch = "wasm32"))]
2435 {
2436 use std::process::{Command, Stdio};
2437 Command::new(cmd)
2438 .args(&args[1..])
2439 .stdout(Stdio::inherit())
2440 .stderr(Stdio::inherit())
2441 .status()
2442 .map(|s| s.code().unwrap_or(1))
2443 .unwrap_or(127)
2444 }
2445 #[cfg(target_arch = "wasm32")]
2448 {
2449 let _ = cmd;
2450 127
2451 }
2452 };
2453 self.push(Value::Status(status));
2454 }
2455 }
2456 } else {
2457 self.push(Value::Status(0));
2458 }
2459 }
2460 Op::ExecBg(argc) => {
2461 let argc = *argc;
2462 let start = self.stack.len().saturating_sub(argc as usize);
2463 let args: Vec<String> = self
2465 .stack
2466 .drain(start..)
2467 .flat_map(|v| match v {
2468 Value::Array(items) => {
2469 items.into_iter().map(|i| i.to_str()).collect::<Vec<_>>()
2470 }
2471 other => vec![other.to_str()],
2472 })
2473 .collect();
2474 if let Some(cmd) = args.first() {
2475 if let Some(h) = self.host.as_mut() {
2484 let _ = h.exec_bg(args.clone());
2485 } else {
2486 #[cfg(not(target_arch = "wasm32"))]
2487 {
2488 use std::process::{Command, Stdio};
2489 let _ = Command::new(cmd)
2490 .args(&args[1..])
2491 .stdout(Stdio::null())
2492 .stderr(Stdio::null())
2493 .spawn();
2494 }
2495 #[cfg(target_arch = "wasm32")]
2497 {
2498 let _ = cmd;
2499 }
2500 }
2501 }
2502 self.push(Value::Status(0));
2503 }
2504
2505 Op::PipelineBegin(n) => {
2507 let n = *n;
2508 if let Some(h) = self.host.as_mut() {
2509 h.pipeline_begin(n);
2510 }
2511 }
2512 Op::PipelineStage => {
2513 if let Some(h) = self.host.as_mut() {
2514 h.pipeline_stage();
2515 }
2516 }
2517 Op::PipelineEnd => {
2518 let status = if let Some(h) = self.host.as_mut() {
2519 h.pipeline_end()
2520 } else {
2521 self.last_status
2522 };
2523 self.last_status = status;
2524 self.push(Value::Status(status));
2525 }
2526 Op::SubshellBegin => {
2527 if let Some(h) = self.host.as_mut() {
2528 h.subshell_begin();
2529 }
2530 }
2531 Op::SubshellEnd => {
2532 if let Some(h) = self.host.as_mut() {
2533 if let Some(status) = h.subshell_end() {
2534 self.last_status = status;
2535 }
2536 }
2537 }
2538 Op::Redirect(fd, op) => {
2539 let fd = *fd;
2540 let op = *op;
2541 let target = self.pop().to_str();
2542 if let Some(h) = self.host.as_mut() {
2543 h.redirect(fd, op, &target);
2544 }
2545 }
2546 Op::HereDoc(idx) => {
2547 let content = self
2548 .chunk
2549 .constants
2550 .get(*idx as usize)
2551 .map(|v| v.to_str())
2552 .unwrap_or_default();
2553 if let Some(h) = self.host.as_mut() {
2554 h.heredoc(&content);
2555 }
2556 }
2557 Op::HereString => {
2558 let s = self.pop().to_str();
2559 if let Some(h) = self.host.as_mut() {
2560 h.herestring(&s);
2561 }
2562 }
2563 Op::CmdSubst(idx) => {
2564 let result = match self.chunk.sub_chunks.get(*idx as usize) {
2565 Some(sub) => {
2566 let sub_ref: *const Chunk = sub;
2568 let sub_ref = unsafe { &*sub_ref };
2570 if let Some(h) = self.host.as_mut() {
2571 h.cmd_subst(sub_ref)
2572 } else {
2573 String::new()
2574 }
2575 }
2576 None => String::new(),
2577 };
2578 self.push(Value::str(result));
2579 }
2580 Op::ProcessSubIn(idx) => {
2581 let result = match self.chunk.sub_chunks.get(*idx as usize) {
2582 Some(sub) => {
2583 let sub_ref: *const Chunk = sub;
2584 let sub_ref = unsafe { &*sub_ref };
2585 if let Some(h) = self.host.as_mut() {
2586 h.process_sub_in(sub_ref)
2587 } else {
2588 String::new()
2589 }
2590 }
2591 None => String::new(),
2592 };
2593 self.push(Value::str(result));
2594 }
2595 Op::ProcessSubOut(idx) => {
2596 let result = match self.chunk.sub_chunks.get(*idx as usize) {
2597 Some(sub) => {
2598 let sub_ref: *const Chunk = sub;
2599 let sub_ref = unsafe { &*sub_ref };
2600 if let Some(h) = self.host.as_mut() {
2601 h.process_sub_out(sub_ref)
2602 } else {
2603 String::new()
2604 }
2605 }
2606 None => String::new(),
2607 };
2608 self.push(Value::str(result));
2609 }
2610 Op::Glob | Op::GlobRecursive => {
2611 let recursive = matches!(&ops[ip], Op::GlobRecursive);
2612 let pat_val = self.pop();
2613 let pattern = pat_val.to_str();
2614 let matches: Vec<String> = if let Some(h) = self.host.as_mut() {
2615 h.glob(&pattern, recursive)
2616 } else {
2617 glob::glob(&pattern)
2618 .into_iter()
2619 .flat_map(|paths| paths.filter_map(|p| p.ok()))
2620 .map(|p| p.to_string_lossy().into_owned())
2621 .collect()
2622 };
2623 let arr: Vec<Value> = matches.into_iter().map(Value::str).collect();
2624 self.push(Value::Array(arr));
2625 }
2626 Op::TrapSet(idx) => {
2627 let sig = self.pop().to_str();
2629 if let Some(sub) = self.chunk.sub_chunks.get(*idx as usize) {
2630 let sub_ref: *const Chunk = sub;
2631 let sub_ref = unsafe { &*sub_ref };
2632 if let Some(h) = self.host.as_mut() {
2633 h.trap_set(&sig, sub_ref);
2634 }
2635 }
2636 }
2637 Op::TrapCheck => {
2638 if let Some(h) = self.host.as_mut() {
2639 h.trap_check();
2640 }
2641 }
2642 Op::TildeExpand => {
2643 let s = self.pop().to_str();
2644 let result = if let Some(h) = self.host.as_mut() {
2645 h.tilde_expand(&s)
2646 } else {
2647 s
2648 };
2649 self.push(Value::str(result));
2650 }
2651 Op::BraceExpand => {
2652 let s = self.pop().to_str();
2653 let result = if let Some(h) = self.host.as_mut() {
2654 h.brace_expand(&s)
2655 } else {
2656 vec![s]
2657 };
2658 let arr: Vec<Value> = result.into_iter().map(Value::str).collect();
2659 self.push(Value::Array(arr));
2660 }
2661 Op::WordSplit => {
2662 let s = self.pop().to_str();
2663 let result = if let Some(h) = self.host.as_mut() {
2664 h.word_split(&s)
2665 } else {
2666 s.split_whitespace().map(|w| w.to_string()).collect()
2667 };
2668 let arr: Vec<Value> = result.into_iter().map(Value::str).collect();
2669 self.push(Value::Array(arr));
2670 }
2671 Op::ExpandParam(modifier) => {
2672 let m = *modifier;
2678 let argc = match m {
2679 crate::op::param_mod::DEFAULT
2680 | crate::op::param_mod::ASSIGN
2681 | crate::op::param_mod::ERROR
2682 | crate::op::param_mod::ALTERNATE
2683 | crate::op::param_mod::STRIP_SHORT
2684 | crate::op::param_mod::STRIP_LONG
2685 | crate::op::param_mod::RSTRIP_SHORT
2686 | crate::op::param_mod::RSTRIP_LONG => 1,
2687 crate::op::param_mod::SUBST_FIRST
2688 | crate::op::param_mod::SUBST_ALL
2689 | crate::op::param_mod::SLICE => 2,
2690 _ => 0,
2691 };
2692 let mut args: Vec<Value> = Vec::with_capacity(argc);
2693 for _ in 0..argc {
2694 args.push(self.pop());
2695 }
2696 args.reverse();
2697 let name = self.pop().to_str();
2698 let result = if let Some(h) = self.host.as_mut() {
2699 h.expand_param(&name, m, &args)
2700 } else {
2701 Value::str("")
2702 };
2703 self.push(result);
2704 }
2705 Op::StrMatch => {
2706 let pat = self.pop().to_str();
2707 let s = self.pop().to_str();
2708 let result = if let Some(h) = self.host.as_mut() {
2709 h.str_match(&s, &pat)
2710 } else {
2711 s == pat
2712 };
2713 self.push(Value::Bool(result));
2714 }
2715 Op::RegexMatch => {
2716 let re = self.pop().to_str();
2717 let s = self.pop().to_str();
2718 let result = if let Some(h) = self.host.as_mut() {
2719 h.regex_match(&s, &re)
2720 } else {
2721 false
2722 };
2723 self.push(Value::Bool(result));
2724 }
2725 Op::WithRedirectsBegin(n) => {
2726 let n = *n;
2727 if let Some(h) = self.host.as_mut() {
2728 h.with_redirects_begin(n);
2729 }
2730 }
2731 Op::WithRedirectsEnd => {
2732 if let Some(h) = self.host.as_mut() {
2733 h.with_redirects_end();
2734 }
2735 }
2736 Op::CallFunction(name_idx, argc) => {
2737 let name = self
2738 .chunk
2739 .names
2740 .get(*name_idx as usize)
2741 .cloned()
2742 .unwrap_or_default();
2743 let argc = *argc as usize;
2744 let start = self.stack.len().saturating_sub(argc);
2745 let args: Vec<String> = self
2747 .stack
2748 .drain(start..)
2749 .flat_map(|v| match v {
2750 Value::Array(items) => {
2751 items.into_iter().map(|i| i.to_str()).collect::<Vec<_>>()
2752 }
2753 other => vec![other.to_str()],
2754 })
2755 .collect();
2756 let status = if self.host.is_some() {
2757 let cf = self
2759 .host
2760 .as_mut()
2761 .unwrap()
2762 .call_function(&name, args.clone());
2763 match cf {
2764 Some(s) => s,
2765 None => {
2766 if let Some(v) = self.run_builtin_by_name(&name, &args) {
2775 v.to_int() as i32
2776 } else {
2777 let mut full = Vec::with_capacity(args.len() + 1);
2778 full.push(name.clone());
2779 full.extend(args);
2780 self.host.as_mut().unwrap().exec(full)
2781 }
2782 }
2783 }
2784 } else {
2785 let nidx = *name_idx;
2787 if let Some(entry_ip) = self.chunk.find_sub(nidx) {
2788 for arg in &args {
2789 self.push(Value::str(arg));
2790 }
2791 self.frames.push(Frame {
2792 return_ip: self.ip,
2793 stack_base: self.stack.len() - args.len(),
2794 slots: Vec::with_capacity(8),
2795 });
2796 self.ip = entry_ip;
2797 return ExecFlow::Cont;
2798 }
2799 let mut full = Vec::with_capacity(args.len() + 1);
2800 full.push(name);
2801 full.extend(args);
2802 #[cfg(not(target_arch = "wasm32"))]
2803 {
2804 use std::process::Command;
2805 Command::new(&full[0])
2806 .args(&full[1..])
2807 .status()
2808 .map(|s| s.code().unwrap_or(1))
2809 .unwrap_or(127)
2810 }
2811 #[cfg(target_arch = "wasm32")]
2814 {
2815 let _ = &full;
2816 127
2817 }
2818 };
2819 self.last_status = status;
2820 self.push(Value::Status(status));
2821 }
2822
2823 Op::ConcatConstLoop(const_idx, s_slot, i_slot, limit) => {
2825 let c_str = self
2826 .chunk
2827 .constants
2828 .get(*const_idx as usize)
2829 .map(|v| v.as_str_cow())
2830 .unwrap_or(std::borrow::Cow::Borrowed(""));
2831 let mut s = self.get_slot(*s_slot).to_str();
2832 let mut i = self.get_slot(*i_slot).to_int();
2833 let lim = *limit as i64;
2834 let iters = (lim - i).max(0) as usize;
2835 s.reserve(c_str.len() * iters);
2836 while i < lim {
2837 s.push_str(&c_str);
2838 i += 1;
2839 }
2840 self.set_slot(*s_slot, Value::str(s));
2841 self.set_slot(*i_slot, Value::Int(i));
2842 }
2843 Op::PushIntRangeLoop(arr_idx, i_slot, limit) => {
2844 let mut i = self.get_slot(*i_slot).to_int();
2845 let lim = *limit as i64;
2846 let arr = self.get_var(*arr_idx);
2847 let mut vec = if let Value::Array(v) = arr {
2848 v
2849 } else {
2850 Vec::new()
2851 };
2852 vec.reserve((lim - i).max(0) as usize);
2853 while i < lim {
2854 vec.push(Value::Int(i));
2855 i += 1;
2856 }
2857 self.set_var(*arr_idx, Value::Array(vec));
2858 self.set_slot(*i_slot, Value::Int(i));
2859 }
2860
2861 Op::MapBlock(_)
2863 | Op::GrepBlock(_)
2864 | Op::SortBlock(_)
2865 | Op::SortDefault
2866 | Op::ForEachBlock(_) => {}
2867
2868 Op::CallBuiltin(id, argc) => {
2870 let (id, argc) = (*id, *argc);
2871 if let Some(Some(handler)) = self.builtin_table.get(id as usize) {
2872 let result = handler(self, argc);
2873 self.push(result);
2874 }
2875 }
2876
2877 Op::AwkFieldGet => self.dispatch_awk(ab::AWK_FIELD_GET, 0),
2880 Op::AwkFieldSet => self.dispatch_awk(ab::AWK_FIELD_SET, 0),
2881 Op::AwkNf => self.dispatch_awk(ab::AWK_NF, 0),
2882 Op::AwkSetRecord => self.dispatch_awk(ab::AWK_SET_RECORD, 0),
2883 Op::AwkSpecialGet(n) => self.dispatch_awk(ab::AWK_SPECIAL_GET, *n as usize),
2884 Op::AwkSpecialSet(n) => self.dispatch_awk(ab::AWK_SPECIAL_SET, *n as usize),
2885 Op::AwkPrint(argc) => self.dispatch_awk(ab::AWK_PRINT, *argc as usize),
2886 Op::AwkPrintf(argc) => self.dispatch_awk(ab::AWK_PRINTF, *argc as usize),
2887 Op::AwkSprintf(argc) => self.dispatch_awk(ab::AWK_SPRINTF, *argc as usize),
2888 Op::AwkGetline(src) => self.dispatch_awk(ab::AWK_GETLINE, *src as usize),
2889 Op::AwkLength(argc) => self.dispatch_awk(ab::AWK_LENGTH, *argc as usize),
2890 Op::AwkSubstr(argc) => self.dispatch_awk(ab::AWK_SUBSTR, *argc as usize),
2891 Op::AwkIndex => self.dispatch_awk(ab::AWK_INDEX, 0),
2892 Op::AwkSplit(argc) => self.dispatch_awk(ab::AWK_SPLIT, *argc as usize),
2893 Op::AwkSub(argc) => self.dispatch_awk(ab::AWK_SUB, *argc as usize),
2894 Op::AwkGsub(argc) => self.dispatch_awk(ab::AWK_GSUB, *argc as usize),
2895 Op::AwkMatch => self.dispatch_awk(ab::AWK_MATCH, 0),
2896 Op::AwkToLower => self.dispatch_awk(ab::AWK_TOLOWER, 0),
2897 Op::AwkToUpper => self.dispatch_awk(ab::AWK_TOUPPER, 0),
2898 Op::AwkInt => self.dispatch_awk(ab::AWK_INT, 0),
2899 Op::AwkSqrt => self.dispatch_awk(ab::AWK_SQRT, 0),
2900 Op::AwkSin => self.dispatch_awk(ab::AWK_SIN, 0),
2901 Op::AwkCos => self.dispatch_awk(ab::AWK_COS, 0),
2902 Op::AwkExp => self.dispatch_awk(ab::AWK_EXP, 0),
2903 Op::AwkLog => self.dispatch_awk(ab::AWK_LOG, 0),
2904 Op::AwkAtan2 => self.dispatch_awk(ab::AWK_ATAN2, 0),
2905 Op::AwkDiv => {
2909 let b = self.pop();
2910 let a = self.pop();
2911 let divisor = b.to_float();
2912 if divisor == 0.0 {
2913 return ExecFlow::Ret(VMResult::Error(
2914 "division by zero attempted".to_string(),
2915 ));
2916 }
2917 self.push(Value::Float(a.to_float() / divisor));
2918 }
2919 Op::AwkMod => {
2920 let b = self.pop();
2921 let a = self.pop();
2922 let divisor = b.to_float();
2923 if divisor == 0.0 {
2924 return ExecFlow::Ret(VMResult::Error(
2925 "division by zero attempted in `%'".to_string(),
2926 ));
2927 }
2928 self.push(Value::Float(a.to_float() % divisor));
2929 }
2930 Op::AwkDivJit => {
2934 let b = self.pop();
2935 let a = self.pop();
2936 let divisor = b.to_float();
2937 if divisor == 0.0 {
2938 return ExecFlow::Ret(VMResult::Error(
2939 "division by zero attempted".to_string(),
2940 ));
2941 }
2942 self.push(Value::Float(a.to_float() / divisor));
2943 }
2944 Op::AwkModJit => {
2945 let b = self.pop();
2946 let a = self.pop();
2947 let divisor = b.to_float();
2948 if divisor == 0.0 {
2949 return ExecFlow::Ret(VMResult::Error(
2950 "division by zero attempted in `%'".to_string(),
2951 ));
2952 }
2953 self.push(Value::Float(a.to_float() % divisor));
2954 }
2955 Op::AwkSqrtJit => {
2960 let a = self.pop().to_float();
2961 if a < 0.0 {
2962 eprintln!("awk: warning: sqrt: received negative argument {a}");
2963 self.push(Value::Float(f64::NAN));
2964 } else {
2965 self.push(Value::Float(a.sqrt()));
2966 }
2967 }
2968 Op::AwkLogJit => {
2973 let a = self.pop().to_float();
2974 if a < 0.0 {
2975 eprintln!("awk: warning: log: received negative argument {a}");
2976 self.push(Value::Float(f64::NAN));
2977 } else {
2978 self.push(Value::Float(a.ln()));
2979 }
2980 }
2981 Op::AwkLshiftJit => {
2985 let n = self.pop().to_float();
2986 let a = self.pop().to_float();
2987 if a < 0.0 || n < 0.0 {
2988 return ExecFlow::Ret(VMResult::Error(
2989 "lshift: negative values are not allowed".to_string(),
2990 ));
2991 }
2992 let shifted = (a as i64).wrapping_shl((n as u32) & 0x3f);
2993 self.push(Value::Float(shifted as f64));
2994 }
2995 Op::AwkRshiftJit => {
2997 let n = self.pop().to_float();
2998 let a = self.pop().to_float();
2999 if a < 0.0 || n < 0.0 {
3000 return ExecFlow::Ret(VMResult::Error(
3001 "rshift: negative values are not allowed".to_string(),
3002 ));
3003 }
3004 let shifted = ((a as i64) as u64).wrapping_shr((n as u32) & 0x3f);
3005 self.push(Value::Float(shifted as f64));
3006 }
3007 Op::AwkComplJit => {
3011 let a = self.pop().to_float();
3012 if a < 0.0 {
3013 return ExecFlow::Ret(VMResult::Error(
3014 "compl: negative value is not allowed".to_string(),
3015 ));
3016 }
3017 let v = !(a as i64);
3018 self.push(Value::Float(v as f64));
3019 }
3020 Op::AwkGetFieldNum(field_idx) => {
3025 let v = crate::jit::fusevm_jit_awk_get_field_num(*field_idx as i64);
3026 self.push(Value::Float(v));
3027 }
3028 Op::PowFloat => {
3029 let b = self.pop();
3030 let a = self.pop();
3031 self.push(Value::Float(a.to_float().powf(b.to_float())));
3032 }
3033 Op::SqrtFloat => {
3034 let a = self.pop();
3035 self.push(Value::Float(a.to_float().sqrt()));
3036 }
3037 Op::SinFloat => {
3038 let a = self.pop();
3039 self.push(Value::Float(a.to_float().sin()));
3040 }
3041 Op::CosFloat => {
3042 let a = self.pop();
3043 self.push(Value::Float(a.to_float().cos()));
3044 }
3045 Op::ExpFloat => {
3046 let a = self.pop();
3047 self.push(Value::Float(a.to_float().exp()));
3048 }
3049 Op::Atan2Float => {
3050 let x = self.pop();
3051 let y = self.pop();
3052 self.push(Value::Float(y.to_float().atan2(x.to_float())));
3053 }
3054 Op::LogFloat => {
3055 let a = self.pop();
3056 self.push(Value::Float(a.to_float().ln()));
3057 }
3058 Op::AbsFloat => {
3059 let a = self.pop();
3060 self.push(Value::Float(a.to_float().abs()));
3061 }
3062 Op::TruncInt => {
3063 let a = self.pop();
3064 self.push(Value::Int(a.to_int()));
3065 }
3066 Op::CeilFloat => {
3067 let a = self.pop();
3068 self.push(Value::Float(a.to_float().ceil()));
3069 }
3070 Op::FloorFloat => {
3071 let a = self.pop();
3072 self.push(Value::Float(a.to_float().floor()));
3073 }
3074 Op::TruncFloat => {
3075 let a = self.pop();
3076 self.push(Value::Float(a.to_float().trunc()));
3077 }
3078 Op::RoundFloat => {
3079 let a = self.pop();
3080 self.push(Value::Float(a.to_float().round_ties_even()));
3081 }
3082 Op::TanFloat => {
3083 let a = self.pop();
3084 self.push(Value::Float(a.to_float().tan()));
3085 }
3086 Op::AsinFloat => {
3087 let a = self.pop();
3088 self.push(Value::Float(a.to_float().asin()));
3089 }
3090 Op::AcosFloat => {
3091 let a = self.pop();
3092 self.push(Value::Float(a.to_float().acos()));
3093 }
3094 Op::AtanFloat => {
3095 let a = self.pop();
3096 self.push(Value::Float(a.to_float().atan()));
3097 }
3098 Op::SinhFloat => {
3099 let a = self.pop();
3100 self.push(Value::Float(a.to_float().sinh()));
3101 }
3102 Op::CoshFloat => {
3103 let a = self.pop();
3104 self.push(Value::Float(a.to_float().cosh()));
3105 }
3106 Op::TanhFloat => {
3107 let a = self.pop();
3108 self.push(Value::Float(a.to_float().tanh()));
3109 }
3110 Op::Log2Float => {
3111 let a = self.pop();
3112 self.push(Value::Float(a.to_float().log2()));
3113 }
3114 Op::Log10Float => {
3115 let a = self.pop();
3116 self.push(Value::Float(a.to_float().log10()));
3117 }
3118 Op::AbsInt => {
3119 let a = self.pop();
3120 self.push(Value::Int(a.to_int().wrapping_abs()));
3121 }
3122 Op::GcdInt => {
3123 let b = self.pop().to_int().unsigned_abs();
3124 let a = self.pop().to_int().unsigned_abs();
3125 let mut x = a;
3126 let mut y = b;
3127 while y != 0 {
3128 let t = x % y;
3129 x = y;
3130 y = t;
3131 }
3132 self.push(Value::Int(x as i64));
3133 }
3134 Op::LcmInt => {
3135 let b = self.pop().to_int().unsigned_abs();
3136 let a = self.pop().to_int().unsigned_abs();
3137 if a == 0 || b == 0 {
3138 self.push(Value::Int(0));
3139 } else {
3140 let mut x = a;
3141 let mut y = b;
3142 while y != 0 {
3143 let t = x % y;
3144 x = y;
3145 y = t;
3146 }
3147 let prod = (a / x).saturating_mul(b);
3148 self.push(Value::Int(prod.min(i64::MAX as u64) as i64));
3149 }
3150 }
3151 Op::TimeInt => {
3152 self.push(Value::Int(crate::sysclock::unix_secs()));
3153 }
3154 Op::AwkArrayGet(n) => self.dispatch_awk(ab::AWK_ARRAY_GET, *n as usize),
3155 Op::AwkArraySet(n) => self.dispatch_awk(ab::AWK_ARRAY_SET, *n as usize),
3156 Op::AwkArrayExists(n) => self.dispatch_awk(ab::AWK_ARRAY_EXISTS, *n as usize),
3157 Op::AwkArrayDelete(n) => self.dispatch_awk(ab::AWK_ARRAY_DELETE, *n as usize),
3158 Op::AwkArrayClear(n) => self.dispatch_awk(ab::AWK_ARRAY_CLEAR, *n as usize),
3159 Op::AwkArrayLen(n) => self.dispatch_awk(ab::AWK_ARRAY_LEN, *n as usize),
3160 Op::AwkAnd(argc) => self.dispatch_awk(ab::AWK_AND, *argc as usize),
3161 Op::AwkOr(argc) => self.dispatch_awk(ab::AWK_OR, *argc as usize),
3162 Op::AwkXor(argc) => self.dispatch_awk(ab::AWK_XOR, *argc as usize),
3163 Op::AwkCompl => self.dispatch_awk(ab::AWK_COMPL, 0),
3164 Op::AwkLshift => self.dispatch_awk(ab::AWK_LSHIFT, 0),
3165 Op::AwkRshift => self.dispatch_awk(ab::AWK_RSHIFT, 0),
3166 Op::AwkStrtonum => self.dispatch_awk(ab::AWK_STRTONUM, 0),
3167 Op::AwkSystime => self.dispatch_awk(ab::AWK_SYSTIME, 0),
3168 Op::AwkRand => self.dispatch_awk(ab::AWK_RAND, 0),
3169 Op::AwkSrand(argc) => self.dispatch_awk(ab::AWK_SRAND, *argc as usize),
3170 Op::AwkStrftime(argc) => self.dispatch_awk(ab::AWK_STRFTIME, *argc as usize),
3171 Op::AwkMktime(argc) => self.dispatch_awk(ab::AWK_MKTIME, *argc as usize),
3172 Op::AwkOrd => self.dispatch_awk(ab::AWK_ORD, 1),
3173 Op::AwkChr => self.dispatch_awk(ab::AWK_CHR, 1),
3174 Op::AwkMkbool => self.dispatch_awk(ab::AWK_MKBOOL, 1),
3175 Op::AwkIntdiv => self.dispatch_awk(ab::AWK_INTDIV, 2),
3176 Op::AwkIntdiv0 => self.dispatch_awk(ab::AWK_INTDIV0, 2),
3177 Op::AwkGensub(argc) => self.dispatch_awk(ab::AWK_GENSUB, *argc as usize),
3178 Op::AwkSignal(code) => {
3179 self.awk_signal = Some(*code);
3182 self.halted = true;
3183 }
3184
3185 Op::Go(name_idx, argc) => {
3190 let n = *argc as usize;
3191 let mut args = Vec::with_capacity(n);
3192 for _ in 0..n {
3193 args.push(self.pop());
3194 }
3195 args.reverse();
3196 self.sched = Some(crate::sched::SchedReq::Go {
3197 name_idx: *name_idx,
3198 args,
3199 });
3200 self.halted = true;
3201 }
3202 Op::ChanMake => {
3203 let cap = self.pop().to_int().max(0) as usize;
3204 self.sched = Some(crate::sched::SchedReq::Make { cap });
3205 self.halted = true;
3206 }
3207 Op::ChanSend => {
3208 let val = self.pop();
3209 let ch = self.pop().to_int();
3210 self.sched = Some(crate::sched::SchedReq::Send { ch, val });
3211 self.halted = true;
3212 }
3213 Op::ChanRecv => {
3214 let ch = self.pop().to_int();
3215 self.sched = Some(crate::sched::SchedReq::Recv { ch });
3216 self.halted = true;
3217 }
3218 Op::ChanClose => {
3219 let ch = self.pop().to_int();
3220 self.sched = Some(crate::sched::SchedReq::Close { ch });
3221 self.halted = true;
3222 }
3223 Op::CallDynamic(argc) => {
3224 let name_idx = self.pop().to_int() as u16;
3226 if let Some(entry_ip) = self.chunk.find_sub(name_idx) {
3227 self.frames.push(Frame {
3228 return_ip: self.ip,
3229 stack_base: self.stack.len() - *argc as usize,
3230 slots: Vec::new(),
3231 });
3232 self.ip = entry_ip;
3233 } else {
3234 return ExecFlow::Ret(VMResult::Error(
3235 "call of a nil or unknown function value".to_string(),
3236 ));
3237 }
3238 }
3239 Op::Select(num_cases, has_default) => {
3240 let n = *num_cases as usize;
3241 let mut raw = Vec::with_capacity(n * 3);
3242 for _ in 0..n * 3 {
3243 raw.push(self.pop());
3244 }
3245 raw.reverse();
3246 let cases = raw
3247 .chunks_exact(3)
3248 .map(|c| crate::sched::SelectCase {
3249 ch: c[0].to_int(),
3250 recv: c[1].to_int() != 0,
3251 val: c[2].clone(),
3252 })
3253 .collect();
3254 self.sched = Some(crate::sched::SchedReq::Select {
3255 cases,
3256 has_default: *has_default != 0,
3257 });
3258 self.halted = true;
3259 }
3260 }
3261
3262 #[cfg(feature = "jit")]
3275 if recorder_was_armed && self.recorder.is_some() {
3276 let aborted = self.recorder.as_ref().map_or(false, |r| r.aborted);
3277 let close_ip = self
3280 .recorder
3281 .as_ref()
3282 .map(|r| r.close_anchor_ip)
3283 .unwrap_or(0);
3284 let was_jump = matches!(
3285 &ops[ip],
3286 Op::Jump(_)
3287 | Op::JumpIfTrue(_)
3288 | Op::JumpIfFalse(_)
3289 | Op::JumpIfTrueKeep(_)
3290 | Op::JumpIfFalseKeep(_)
3291 );
3292 let landed_at_anchor = self.ip == close_ip;
3293 if aborted || (was_jump && landed_at_anchor) {
3294 self.finalize_recorder();
3295 }
3296 }
3297 ExecFlow::Cont
3298 }
3299
3300 #[cfg(feature = "aot")]
3310 pub(crate) fn aot_exec_op(&mut self, ip: usize) -> i64 {
3311 let ops = &self.chunk.ops as *const Vec<Op>;
3314 let ops = unsafe { &*ops };
3315 self.ip = ip + 1;
3316 match self.exec_op(ops, ip, false) {
3317 ExecFlow::Ret(r) => {
3318 self.aot_result = Some(r);
3319 return -1;
3320 }
3321 ExecFlow::Cont => {}
3322 }
3323 if self.halted {
3324 return -1;
3325 }
3326 self.ip as i64
3327 }
3328
3329 #[cfg(feature = "aot")]
3333 pub(crate) fn aot_finish(&mut self) {
3334 if self.aot_result.is_none() {
3335 self.aot_result = Some(match self.stack.pop() {
3336 Some(v) => VMResult::Ok(v),
3337 None => VMResult::Halted,
3338 });
3339 }
3340 }
3341
3342 #[cfg(feature = "aot")]
3347 pub(crate) fn aot_set_int_result(&mut self, n: i64) {
3348 self.aot_result = Some(VMResult::Ok(Value::Int(n)));
3349 }
3350
3351 #[cfg(feature = "aot")]
3354 pub(crate) fn aot_set_float_result(&mut self, f: f64) {
3355 self.aot_result = Some(VMResult::Ok(Value::Float(f)));
3356 }
3357
3358 #[cfg(feature = "aot")]
3361 fn aot_alloc(&mut self, v: Value) -> i64 {
3362 if let Some(h) = self.aot_free.pop() {
3363 self.aot_arena[h as usize] = v;
3364 h as i64
3365 } else {
3366 let h = self.aot_arena.len() as i64;
3367 self.aot_arena.push(v);
3368 h
3369 }
3370 }
3371
3372 #[cfg(feature = "aot")]
3375 fn aot_take(&mut self, handle: i64) -> Value {
3376 match self.aot_arena.get_mut(handle as usize) {
3377 Some(slot) => {
3378 let v = std::mem::replace(slot, Value::Undef);
3379 self.aot_free.push(handle as u32);
3380 v
3381 }
3382 None => Value::Undef,
3383 }
3384 }
3385
3386 #[cfg(feature = "aot")]
3391 pub(crate) fn aot_box(&mut self) -> i64 {
3392 let v = self.stack.pop().unwrap_or(Value::Undef);
3393 self.aot_alloc(v)
3394 }
3395
3396 #[cfg(feature = "aot")]
3400 pub(crate) fn aot_unbox(&mut self, handle: i64) {
3401 let v = self.aot_take(handle);
3402 self.stack.push(v);
3403 }
3404
3405 #[cfg(feature = "aot")]
3408 pub(crate) fn aot_clone(&mut self, handle: i64) -> i64 {
3409 let v = self
3410 .aot_arena
3411 .get(handle as usize)
3412 .cloned()
3413 .unwrap_or(Value::Undef);
3414 self.aot_alloc(v)
3415 }
3416
3417 #[cfg(feature = "aot")]
3420 pub(crate) fn aot_free(&mut self, handle: i64) {
3421 if handle >= 0 && (handle as usize) < self.aot_arena.len() {
3422 self.aot_arena[handle as usize] = Value::Undef;
3423 self.aot_free.push(handle as u32);
3424 }
3425 }
3426
3427 #[cfg(feature = "aot")]
3430 pub(crate) fn aot_set_obj_result(&mut self, handle: i64) {
3431 let v = self.aot_take(handle);
3432 self.aot_result = Some(VMResult::Ok(v));
3433 }
3434
3435 #[cfg(feature = "aot")]
3439 pub(crate) fn aot_store_slot_obj(&mut self, idx: u32, handle: i64) {
3440 let v = self
3441 .aot_arena
3442 .get(handle as usize)
3443 .cloned()
3444 .unwrap_or(Value::Undef);
3445 self.set_slot(idx as u16, v);
3446 }
3447
3448 #[cfg(feature = "aot")]
3450 pub(crate) fn aot_store_global_obj(&mut self, idx: u32, handle: i64) {
3451 let v = self
3452 .aot_arena
3453 .get(handle as usize)
3454 .cloned()
3455 .unwrap_or(Value::Undef);
3456 self.set_var(idx as u16, v);
3457 }
3458
3459 #[cfg(feature = "aot")]
3463 pub(crate) fn aot_push_int(&mut self, n: i64) {
3464 self.stack.push(Value::Int(n));
3465 }
3466
3467 #[cfg(feature = "aot")]
3469 pub(crate) fn aot_push_float(&mut self, f: f64) {
3470 self.stack.push(Value::Float(f));
3471 }
3472
3473 #[cfg(feature = "aot")]
3475 pub(crate) fn aot_push_bool(&mut self, n: i64) {
3476 self.stack.push(Value::Bool(n != 0));
3477 }
3478
3479 #[cfg(feature = "aot")]
3483 pub(crate) fn aot_store_slot_int(&mut self, idx: u32, n: i64) {
3484 self.set_slot(idx as u16, Value::Int(n));
3485 }
3486
3487 #[cfg(feature = "aot")]
3489 pub(crate) fn aot_store_slot_float(&mut self, idx: u32, f: f64) {
3490 self.set_slot(idx as u16, Value::Float(f));
3491 }
3492
3493 #[cfg(feature = "aot")]
3495 pub(crate) fn aot_store_global_int(&mut self, idx: u32, n: i64) {
3496 self.set_var(idx as u16, Value::Int(n));
3497 }
3498
3499 #[cfg(feature = "aot")]
3501 pub(crate) fn aot_store_global_float(&mut self, idx: u32, f: f64) {
3502 self.set_var(idx as u16, Value::Float(f));
3503 }
3504
3505 #[cfg(feature = "aot")]
3510 pub(crate) fn aot_resume(&mut self, ip: u32) {
3511 self.ip = ip as usize;
3512 let r = self.run();
3513 self.aot_result = Some(r);
3514 }
3515
3516 #[cfg(feature = "aot")]
3520 pub(crate) fn aot_pop_float(&mut self) -> f64 {
3521 self.stack.pop().map(|v| v.to_float()).unwrap_or(0.0)
3522 }
3523
3524 #[cfg(feature = "aot")]
3527 pub(crate) fn aot_pop_int(&mut self) -> i64 {
3528 self.stack.pop().map(|v| v.to_int()).unwrap_or(0)
3529 }
3530
3531 #[cfg(feature = "aot")]
3533 pub(crate) fn aot_push_status(&mut self, n: i64) {
3534 self.stack.push(Value::Status(n as i32));
3535 }
3536
3537 #[cfg(feature = "aot")]
3539 pub(crate) fn aot_set_status_result(&mut self, n: i64) {
3540 self.aot_result = Some(VMResult::Ok(Value::Status(n as i32)));
3541 }
3542
3543 #[cfg(feature = "aot")]
3547 pub(crate) fn aot_set_error(&mut self, code: u32) {
3548 let msg = match code {
3549 0 => "division by zero attempted",
3550 1 => "division by zero attempted in `%'",
3551 2 => "lshift: negative values are not allowed",
3552 3 => "rshift: negative values are not allowed",
3553 4 => "compl: negative value is not allowed",
3554 _ => "aot: runtime error",
3555 };
3556 self.aot_result = Some(VMResult::Error(msg.to_string()));
3557 }
3558
3559 #[cfg(feature = "aot")]
3561 pub(crate) fn take_aot_result(&mut self) -> VMResult {
3562 self.aot_result.take().unwrap_or(VMResult::Halted)
3563 }
3564
3565 fn dispatch_awk(&mut self, id: u16, payload: usize) {
3578 use crate::awk_builtins as ab;
3579 use crate::awk_host::AwkLvalue;
3580
3581 let mut host = match self.awk_host.take() {
3585 Some(h) => h,
3586 None => {
3587 self.dispatch_awk_stub(id, payload);
3588 return;
3589 }
3590 };
3591
3592 macro_rules! pop_n {
3594 ($n:expr) => {{
3595 let n = $n;
3596 let mut v: Vec<Value> = (0..n).map(|_| self.pop()).collect();
3597 v.reverse();
3598 v
3599 }};
3600 }
3601 let name_at = |vm: &Self, idx: usize| -> String {
3602 vm.chunk.names.get(idx).cloned().unwrap_or_default()
3603 };
3604
3605 match id {
3606 ab::AWK_FIELD_GET => {
3607 let i = self.pop().to_int();
3608 let v = host.field_get(i);
3609 self.push(v);
3610 }
3611 ab::AWK_FIELD_SET => {
3612 let i = self.pop().to_int();
3613 let v = self.pop();
3614 host.field_set(i, v);
3615 }
3616 ab::AWK_NF => {
3617 let n = host.nf();
3618 self.push(Value::Int(n));
3619 }
3620 ab::AWK_SET_RECORD => {
3621 let v = self.pop();
3622 host.set_record(v);
3623 }
3624 ab::AWK_SPECIAL_GET => {
3625 let name = name_at(self, payload);
3626 let v = host.special_get(&name);
3627 self.push(v);
3628 }
3629 ab::AWK_SPECIAL_SET => {
3630 let name = name_at(self, payload);
3631 let v = self.pop();
3632 host.special_set(&name, v);
3633 }
3634 ab::AWK_PRINT => {
3635 let args = pop_n!(payload);
3636 host.print(&args);
3637 }
3638 ab::AWK_PRINTF => {
3639 let mut args = pop_n!(payload);
3640 let fmt = if args.is_empty() {
3641 String::new()
3642 } else {
3643 args.remove(0).to_str()
3644 };
3645 host.printf(&fmt, &args);
3646 }
3647 ab::AWK_SPRINTF => {
3648 let mut args = pop_n!(payload);
3649 let fmt = if args.is_empty() {
3650 String::new()
3651 } else {
3652 args.remove(0).to_str()
3653 };
3654 let v = host.sprintf(&fmt, &args);
3655 self.push(v);
3656 }
3657 ab::AWK_GETLINE => {
3658 let operand = match payload {
3660 ab::getline_source::FILE
3661 | ab::getline_source::FILE_VAR
3662 | ab::getline_source::CMD
3663 | ab::getline_source::CMD_VAR => Some(self.pop().to_str()),
3664 _ => None,
3665 };
3666 let status = host.getline(payload, operand.as_deref(), None);
3667 self.push(Value::Int(status));
3668 }
3669 ab::AWK_LENGTH => {
3670 let arg = if payload == 0 { None } else { Some(self.pop()) };
3671 let n = host.length(arg.as_ref());
3672 self.push(Value::Int(n));
3673 }
3674 ab::AWK_SUBSTR => {
3675 let args = pop_n!(payload);
3676 let s = args.first().cloned().unwrap_or(Value::str(""));
3677 let m = args.get(1).map(|v| v.to_int()).unwrap_or(1);
3678 let n = args.get(2).map(|v| v.to_int());
3679 let v = host.substr(&s, m, n);
3680 self.push(v);
3681 }
3682 ab::AWK_INDEX => {
3683 let t = self.pop();
3684 let s = self.pop();
3685 let r = host.index(&s, &t);
3686 self.push(Value::Int(r));
3687 }
3688 ab::AWK_SPLIT => {
3689 let args = pop_n!(payload);
3690 let s = args.first().cloned().unwrap_or(Value::str(""));
3691 let arr = args.get(1).map(|v| v.to_str()).unwrap_or_default();
3692 let fs = args.get(2);
3693 let n = host.split(&s, &arr, fs);
3694 self.push(Value::Int(n));
3695 }
3696 ab::AWK_SUB | ab::AWK_GSUB => {
3697 let args = pop_n!(payload);
3701 let re = args.first().cloned().unwrap_or(Value::str(""));
3702 let repl = args.get(1).cloned().unwrap_or(Value::str(""));
3703 let target = args
3704 .get(2)
3705 .map(|v| AwkLvalue::Var(v.to_str()))
3706 .unwrap_or(AwkLvalue::Field(0));
3707 let n = if id == ab::AWK_SUB {
3708 host.sub(&re, &repl, &target)
3709 } else {
3710 host.gsub(&re, &repl, &target)
3711 };
3712 self.push(Value::Int(n));
3713 }
3714 ab::AWK_MATCH => {
3715 let re = self.pop();
3716 let s = self.pop();
3717 let r = host.match_re(&s, &re);
3718 self.push(Value::Int(r));
3719 }
3720 ab::AWK_GENSUB => {
3721 let args = pop_n!(payload);
3723 let re = args.first().cloned().unwrap_or(Value::str(""));
3724 let repl = args.get(1).cloned().unwrap_or(Value::str(""));
3725 let how = args.get(2).cloned().unwrap_or(Value::str("g"));
3726 let target = args.get(3);
3727 let v = host.gensub(&re, &repl, &how, target);
3728 self.push(v);
3729 }
3730 ab::AWK_TOLOWER => {
3731 let s = self.pop();
3732 let v = host.tolower(&s);
3733 self.push(v);
3734 }
3735 ab::AWK_TOUPPER => {
3736 let s = self.pop();
3737 let v = host.toupper(&s);
3738 self.push(v);
3739 }
3740 ab::AWK_INT => {
3741 let x = self.pop();
3742 let v = host.int(&x);
3743 self.push(v);
3744 }
3745 ab::AWK_SQRT => {
3746 let x = self.pop();
3747 let v = host.sqrt(&x);
3748 self.push(v);
3749 }
3750 ab::AWK_SIN => {
3751 let x = self.pop();
3752 let v = host.sin(&x);
3753 self.push(v);
3754 }
3755 ab::AWK_COS => {
3756 let x = self.pop();
3757 let v = host.cos(&x);
3758 self.push(v);
3759 }
3760 ab::AWK_EXP => {
3761 let x = self.pop();
3762 let v = host.exp(&x);
3763 self.push(v);
3764 }
3765 ab::AWK_LOG => {
3766 let x = self.pop();
3767 let v = host.log(&x);
3768 self.push(v);
3769 }
3770 ab::AWK_ATAN2 => {
3771 let x = self.pop();
3772 let y = self.pop();
3773 let v = host.atan2(&y, &x);
3774 self.push(v);
3775 }
3776 ab::AWK_AND => {
3777 let args = pop_n!(payload);
3778 let v = host.and(&args);
3779 self.push(v);
3780 }
3781 ab::AWK_OR => {
3782 let args = pop_n!(payload);
3783 let v = host.or(&args);
3784 self.push(v);
3785 }
3786 ab::AWK_XOR => {
3787 let args = pop_n!(payload);
3788 let v = host.xor(&args);
3789 self.push(v);
3790 }
3791 ab::AWK_COMPL => {
3792 let v = self.pop();
3793 let r = host.compl(&v);
3794 self.push(r);
3795 }
3796 ab::AWK_LSHIFT => {
3797 let n = self.pop();
3798 let v = self.pop();
3799 let r = host.lshift(&v, &n);
3800 self.push(r);
3801 }
3802 ab::AWK_RSHIFT => {
3803 let n = self.pop();
3804 let v = self.pop();
3805 let r = host.rshift(&v, &n);
3806 self.push(r);
3807 }
3808 ab::AWK_STRTONUM => {
3809 let s = self.pop();
3810 let r = host.strtonum(&s);
3811 self.push(r);
3812 }
3813 ab::AWK_SYSTIME => {
3814 let r = host.systime();
3815 self.push(r);
3816 }
3817 ab::AWK_RAND => {
3818 let r = crate::awk_host::awk_rand(&mut self.awk_rand_seed);
3819 self.push(Value::Float(r));
3820 }
3821 ab::AWK_SRAND => {
3822 let n = if payload >= 1 {
3823 Some(self.pop().to_float() as u32 as u64)
3824 } else {
3825 None
3826 };
3827 let r = crate::awk_host::awk_srand(&mut self.awk_rand_seed, n);
3828 self.push(Value::Float(r));
3829 }
3830 ab::AWK_STRFTIME => {
3831 let args = pop_n!(payload);
3832 let r = host.strftime(&args);
3833 self.push(r);
3834 }
3835 ab::AWK_MKTIME => {
3836 let args = pop_n!(payload);
3837 let r = host.mktime(&args);
3838 self.push(r);
3839 }
3840 ab::AWK_ORD => {
3841 let a = self.pop();
3842 let r = host.ord(&a);
3843 self.push(r);
3844 }
3845 ab::AWK_CHR => {
3846 let a = self.pop();
3847 let r = host.chr(&a);
3848 self.push(r);
3849 }
3850 ab::AWK_MKBOOL => {
3851 let a = self.pop();
3852 let r = host.mkbool(&a);
3853 self.push(r);
3854 }
3855 ab::AWK_INTDIV => {
3856 let b = self.pop();
3857 let a = self.pop();
3858 let r = host.intdiv(&a, &b);
3859 self.push(r);
3860 }
3861 ab::AWK_INTDIV0 => {
3862 let b = self.pop();
3863 let a = self.pop();
3864 let r = host.intdiv0(&a, &b);
3865 self.push(r);
3866 }
3867 ab::AWK_ARRAY_GET => {
3868 let name = name_at(self, payload);
3869 let key = self.pop();
3870 let v = host.array_get(&name, &key);
3871 self.push(v);
3872 }
3873 ab::AWK_ARRAY_SET => {
3874 let name = name_at(self, payload);
3875 let key = self.pop();
3876 let v = self.pop();
3877 host.array_set(&name, &key, v);
3878 }
3879 ab::AWK_ARRAY_EXISTS => {
3880 let name = name_at(self, payload);
3881 let key = self.pop();
3882 let b = host.array_exists(&name, &key);
3883 self.push(Value::Bool(b));
3884 }
3885 ab::AWK_ARRAY_DELETE => {
3886 let name = name_at(self, payload);
3887 let key = self.pop();
3888 host.array_delete(&name, &key);
3889 }
3890 ab::AWK_ARRAY_CLEAR => {
3891 let name = name_at(self, payload);
3892 host.array_clear(&name);
3893 }
3894 ab::AWK_ARRAY_LEN => {
3895 let name = name_at(self, payload);
3896 let n = host.array_len(&name);
3897 self.push(Value::Int(n));
3898 }
3899 _ => self.push(Value::Undef),
3902 }
3903
3904 self.awk_host = Some(host);
3905 }
3906
3907 fn dispatch_awk_stub(&mut self, id: u16, payload: usize) {
3911 use crate::awk_builtins as ab;
3912 use crate::awk_host::{
3913 awk_canon_nan, awk_chr, awk_compl, awk_fold_and, awk_fold_or, awk_fold_xor, awk_index,
3914 awk_int, awk_intdiv, awk_intdiv0, awk_length, awk_lshift, awk_mkbool, awk_mktime,
3915 awk_ord, awk_rand, awk_rshift, awk_srand, awk_strftime, awk_strtonum, awk_substr,
3916 awk_systime, awk_tolower, awk_toupper,
3917 };
3918 match id {
3919 ab::AWK_FIELD_GET => {
3921 self.pop();
3922 self.push(Value::str(""));
3923 }
3924 ab::AWK_LENGTH if payload > 0 => {
3928 let s = self.pop();
3929 self.push(Value::Int(awk_length(Some(&s))));
3930 }
3931 ab::AWK_NF | ab::AWK_LENGTH | ab::AWK_ARRAY_LEN => {
3932 self.push(Value::Int(0));
3933 }
3934 ab::AWK_SPECIAL_GET => self.push(Value::Undef),
3935 ab::AWK_SPRINTF => {
3936 for _ in 0..payload {
3937 self.pop();
3938 }
3939 self.push(Value::str(""));
3940 }
3941 ab::AWK_SUBSTR => {
3945 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
3946 args.reverse();
3947 let s = args.first().cloned().unwrap_or(Value::str(""));
3948 let m = args.get(1).map(|v| v.to_int()).unwrap_or(1);
3949 let n = args.get(2).map(|v| v.to_int());
3950 self.push(awk_substr(&s, m, n));
3951 }
3952 ab::AWK_TOLOWER => {
3953 let s = self.pop();
3954 self.push(awk_tolower(&s));
3955 }
3956 ab::AWK_TOUPPER => {
3957 let s = self.pop();
3958 self.push(awk_toupper(&s));
3959 }
3960 ab::AWK_INT => {
3963 let x = self.pop();
3964 self.push(awk_int(&x));
3965 }
3966 ab::AWK_SQRT => {
3967 let x = self.pop();
3968 self.push(Value::Float(x.to_float().sqrt()));
3969 }
3970 ab::AWK_SIN => {
3971 let x = self.pop();
3972 self.push(Value::Float(awk_canon_nan(x.to_float().sin())));
3973 }
3974 ab::AWK_COS => {
3975 let x = self.pop();
3976 self.push(Value::Float(awk_canon_nan(x.to_float().cos())));
3977 }
3978 ab::AWK_EXP => {
3979 let x = self.pop();
3980 self.push(Value::Float(awk_canon_nan(x.to_float().exp())));
3981 }
3982 ab::AWK_LOG => {
3983 let x = self.pop();
3984 self.push(Value::Float(x.to_float().ln()));
3985 }
3986 ab::AWK_ATAN2 => {
3987 let x = self.pop();
3988 let y = self.pop();
3989 self.push(Value::Float(awk_canon_nan(
3990 y.to_float().atan2(x.to_float()),
3991 )));
3992 }
3993 ab::AWK_AND => {
3995 let args: Vec<Value> = {
3996 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
3997 v.reverse();
3998 v
3999 };
4000 self.push(Value::Int(awk_fold_and(&args)));
4001 }
4002 ab::AWK_OR => {
4003 let args: Vec<Value> = {
4004 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4005 v.reverse();
4006 v
4007 };
4008 self.push(Value::Int(awk_fold_or(&args)));
4009 }
4010 ab::AWK_XOR => {
4011 let args: Vec<Value> = {
4012 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4013 v.reverse();
4014 v
4015 };
4016 self.push(Value::Int(awk_fold_xor(&args)));
4017 }
4018 ab::AWK_COMPL => {
4019 let v = self.pop();
4020 self.push(Value::Int(awk_compl(&v)));
4021 }
4022 ab::AWK_LSHIFT => {
4023 let n = self.pop();
4024 let v = self.pop();
4025 self.push(Value::Int(awk_lshift(&v, &n)));
4026 }
4027 ab::AWK_RSHIFT => {
4028 let n = self.pop();
4029 let v = self.pop();
4030 self.push(Value::Int(awk_rshift(&v, &n)));
4031 }
4032 ab::AWK_STRTONUM => {
4033 let s = self.pop();
4034 self.push(Value::Float(awk_strtonum(&s.to_str())));
4035 }
4036 ab::AWK_SYSTIME => {
4037 self.push(Value::Float(awk_systime()));
4038 }
4039 ab::AWK_RAND => {
4040 let r = awk_rand(&mut self.awk_rand_seed);
4041 self.push(Value::Float(r));
4042 }
4043 ab::AWK_SRAND => {
4044 let n = if payload >= 1 {
4045 Some(self.pop().to_float() as u32 as u64)
4046 } else {
4047 None
4048 };
4049 let r = awk_srand(&mut self.awk_rand_seed, n);
4050 self.push(Value::Float(r));
4051 }
4052 ab::AWK_STRFTIME => {
4053 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4054 args.reverse();
4055 self.push(awk_strftime(&args));
4056 }
4057 ab::AWK_MKTIME => {
4058 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4059 args.reverse();
4060 self.push(awk_mktime(&args));
4061 }
4062 ab::AWK_ORD => {
4063 let a = self.pop();
4064 self.push(awk_ord(&a));
4065 }
4066 ab::AWK_CHR => {
4067 let a = self.pop();
4068 self.push(awk_chr(&a));
4069 }
4070 ab::AWK_MKBOOL => {
4071 let a = self.pop();
4072 self.push(awk_mkbool(&a));
4073 }
4074 ab::AWK_INTDIV => {
4075 let b = self.pop();
4076 let a = self.pop();
4077 self.push(awk_intdiv(&a, &b));
4078 }
4079 ab::AWK_INTDIV0 => {
4080 let b = self.pop();
4081 let a = self.pop();
4082 self.push(awk_intdiv0(&a, &b));
4083 }
4084 ab::AWK_INDEX => {
4085 let t = self.pop();
4086 let s = self.pop();
4087 self.push(Value::Int(awk_index(&s, &t)));
4088 }
4089 ab::AWK_MATCH => {
4090 self.pop();
4091 self.pop();
4092 self.push(Value::Int(0));
4093 }
4094 ab::AWK_SPLIT | ab::AWK_SUB | ab::AWK_GSUB => {
4095 for _ in 0..payload {
4096 self.pop();
4097 }
4098 self.push(Value::Int(0));
4099 }
4100 ab::AWK_GENSUB => {
4101 for _ in 0..payload {
4102 self.pop();
4103 }
4104 self.push(Value::str(""));
4105 }
4106 ab::AWK_GETLINE => {
4107 if matches!(
4108 payload,
4109 ab::getline_source::FILE
4110 | ab::getline_source::FILE_VAR
4111 | ab::getline_source::CMD
4112 | ab::getline_source::CMD_VAR
4113 ) {
4114 self.pop();
4115 }
4116 self.push(Value::Int(0));
4117 }
4118 ab::AWK_ARRAY_GET => {
4119 self.pop();
4120 self.push(Value::str(""));
4121 }
4122 ab::AWK_ARRAY_EXISTS => {
4123 self.pop();
4124 self.push(Value::Bool(false));
4125 }
4126 ab::AWK_FIELD_SET | ab::AWK_ARRAY_SET => {
4128 self.pop();
4129 self.pop();
4130 }
4131 ab::AWK_SET_RECORD | ab::AWK_SPECIAL_SET | ab::AWK_ARRAY_DELETE => {
4132 self.pop();
4133 }
4134 ab::AWK_PRINT | ab::AWK_PRINTF => {
4135 for _ in 0..payload {
4136 self.pop();
4137 }
4138 }
4139 ab::AWK_ARRAY_CLEAR => {}
4140 _ => {}
4141 }
4142 }
4143
4144 fn get_var(&self, idx: u16) -> Value {
4145 self.globals
4146 .get(idx as usize)
4147 .cloned()
4148 .unwrap_or(Value::Undef)
4149 }
4150
4151 fn set_var(&mut self, idx: u16, val: Value) {
4152 let idx = idx as usize;
4153 if idx >= self.globals.len() {
4154 self.globals.resize(idx + 1, Value::Undef);
4155 }
4156 self.globals[idx] = val;
4157 }
4158
4159 pub fn get_slot(&self, slot: u16) -> Value {
4166 self.frames
4167 .last()
4168 .and_then(|f| f.slots.get(slot as usize))
4169 .cloned()
4170 .unwrap_or(Value::Undef)
4171 }
4172
4173 pub fn set_slot(&mut self, slot: u16, val: Value) {
4179 if let Some(frame) = self.frames.last_mut() {
4180 let idx = slot as usize;
4181 if idx >= frame.slots.len() {
4182 frame.slots.resize(idx + 1, Value::Undef);
4183 }
4184 frame.slots[idx] = val;
4185 }
4186 }
4187}
4188
4189pub struct VMPool {
4218 pool: Vec<VM>,
4219}
4220
4221impl VMPool {
4222 pub fn new() -> Self {
4224 Self { pool: Vec::new() }
4225 }
4226
4227 pub fn with_capacity(cap: usize) -> Self {
4229 Self {
4230 pool: Vec::with_capacity(cap),
4231 }
4232 }
4233
4234 pub fn acquire(&mut self, chunk: Chunk) -> VM {
4239 if let Some(mut vm) = self.pool.pop() {
4240 vm.reset(chunk);
4241 vm
4242 } else {
4243 VM::new(chunk)
4244 }
4245 }
4246
4247 pub fn release(&mut self, vm: VM) {
4250 self.pool.push(vm);
4251 }
4252
4253 pub fn with<F, T>(&mut self, chunk: Chunk, f: F) -> T
4256 where
4257 F: FnOnce(&mut VM) -> T,
4258 {
4259 let mut vm = self.acquire(chunk);
4260 let r = f(&mut vm);
4261 self.release(vm);
4262 r
4263 }
4264
4265 pub fn len(&self) -> usize {
4268 self.pool.len()
4269 }
4270
4271 pub fn is_empty(&self) -> bool {
4273 self.pool.is_empty()
4274 }
4275}
4276
4277impl Default for VMPool {
4278 fn default() -> Self {
4279 Self::new()
4280 }
4281}
4282
4283#[cfg(test)]
4284mod tests {
4285 use super::*;
4286 use crate::chunk::ChunkBuilder;
4287
4288 #[test]
4289 fn test_arithmetic() {
4290 let mut b = ChunkBuilder::new();
4291 b.emit(Op::LoadInt(10), 1);
4292 b.emit(Op::LoadInt(32), 1);
4293 b.emit(Op::Add, 1);
4294 let mut vm = VM::new(b.build());
4295 match vm.run() {
4296 VMResult::Ok(Value::Int(42)) => {}
4297 other => panic!("expected Int(42), got {:?}", other),
4298 }
4299 }
4300
4301 #[test]
4302 fn test_jump() {
4303 let mut b = ChunkBuilder::new();
4304 b.emit(Op::LoadInt(1), 1);
4305 b.emit(Op::Jump(3), 1);
4306 b.emit(Op::LoadInt(999), 1); b.emit(Op::LoadInt(2), 1);
4309 b.emit(Op::Add, 1);
4310 let mut vm = VM::new(b.build());
4311 match vm.run() {
4312 VMResult::Ok(Value::Int(3)) => {}
4313 other => panic!("expected Int(3), got {:?}", other),
4314 }
4315 }
4316
4317 #[test]
4318 fn test_fused_sum_loop() {
4319 let mut b = ChunkBuilder::new();
4321 b.emit(Op::PushFrame, 1);
4322 b.emit(Op::LoadInt(0), 1);
4323 b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
4325 b.emit(Op::SetSlot(1), 1); b.emit(Op::AccumSumLoop(0, 1, 100), 1);
4327 b.emit(Op::GetSlot(0), 1);
4328
4329 let mut vm = VM::new(b.build());
4330 match vm.run() {
4331 VMResult::Ok(Value::Int(4950)) => {}
4332 other => panic!("expected Int(4950), got {:?}", other),
4333 }
4334 }
4335
4336 #[test]
4337 fn test_call_dynamic() {
4338 let mut b = ChunkBuilder::new();
4339 let dbl = b.add_name("double");
4340 b.emit(Op::LoadInt(21), 1);
4342 b.emit(Op::LoadInt(dbl as i64), 1);
4343 b.emit(Op::CallDynamic(1), 1);
4344 let end = b.emit(Op::Jump(0), 1);
4345 let ip = b.current_pos();
4346 b.add_sub_entry(dbl, ip);
4347 b.emit(Op::LoadInt(2), 2);
4348 b.emit(Op::Mul, 2);
4349 b.emit(Op::ReturnValue, 2);
4350 b.patch_jump(end, b.current_pos());
4351 let mut vm = VM::new(b.build());
4352 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(42))));
4353 }
4354
4355 #[test]
4356 fn test_function_call() {
4357 let mut b = ChunkBuilder::new();
4358 let double_name = b.add_name("double");
4359
4360 b.emit(Op::LoadInt(21), 1);
4362 b.emit(Op::Call(double_name, 1), 1);
4363 let end_jump = b.emit(Op::Jump(0), 1); let double_ip = b.current_pos();
4367 b.add_sub_entry(double_name, double_ip);
4368 b.emit(Op::LoadInt(2), 2);
4369 b.emit(Op::Mul, 2);
4370 b.emit(Op::ReturnValue, 2);
4371
4372 b.patch_jump(end_jump, b.current_pos());
4373
4374 let mut vm = VM::new(b.build());
4375 match vm.run() {
4376 VMResult::Ok(Value::Int(42)) => {}
4377 other => panic!("expected Int(42), got {:?}", other),
4378 }
4379 }
4380
4381 #[test]
4382 fn test_builtin_cache() {
4383 let mut b = ChunkBuilder::new();
4384 b.emit(Op::LoadInt(10), 1);
4385 b.emit(Op::CallBuiltin(0, 1), 1);
4386 let mut vm = VM::new(b.build());
4387 vm.register_builtin(0, |vm, _argc| {
4388 let val = vm.pop();
4389 Value::Int(val.to_int() * 2)
4390 });
4391 match vm.run() {
4392 VMResult::Ok(Value::Int(20)) => {}
4393 other => panic!("expected Int(20), got {:?}", other),
4394 }
4395 }
4396
4397 fn run_one(ops: Vec<Op>) -> VMResult {
4400 let mut b = ChunkBuilder::new();
4401 for op in ops {
4402 b.emit(op, 1);
4403 }
4404 VM::new(b.build()).run()
4405 }
4406
4407 fn expect_int(ops: Vec<Op>, want: i64) {
4408 match run_one(ops) {
4409 VMResult::Ok(Value::Int(n)) => assert_eq!(n, want),
4410 other => panic!("expected Int({}), got {:?}", want, other),
4411 }
4412 }
4413
4414 fn expect_bool(ops: Vec<Op>, want: bool) {
4415 match run_one(ops) {
4416 VMResult::Ok(Value::Bool(b)) => assert_eq!(b, want),
4417 other => panic!("expected Bool({}), got {:?}", want, other),
4418 }
4419 }
4420
4421 #[test]
4424 fn arithmetic_sub_mul_div_mod() {
4425 expect_int(vec![Op::LoadInt(20), Op::LoadInt(8), Op::Sub], 12);
4426 expect_int(vec![Op::LoadInt(6), Op::LoadInt(7), Op::Mul], 42);
4427 expect_int(vec![Op::LoadInt(20), Op::LoadInt(3), Op::Mod], 2);
4428 match run_one(vec![Op::LoadInt(20), Op::LoadInt(5), Op::Div]) {
4430 VMResult::Ok(Value::Float(f)) => assert!((f - 4.0).abs() < 1e-9),
4431 VMResult::Ok(Value::Int(4)) => {} other => panic!("got {:?}", other),
4433 }
4434 }
4435
4436 #[test]
4437 fn arithmetic_negate_and_inc_dec() {
4438 expect_int(vec![Op::LoadInt(5), Op::Negate], -5);
4439 expect_int(vec![Op::LoadInt(5), Op::Inc], 6);
4440 expect_int(vec![Op::LoadInt(5), Op::Dec], 4);
4441 }
4442
4443 #[test]
4444 fn arithmetic_negate_preserves_float_zero_kind_and_sign() {
4445 match run_one(vec![Op::LoadFloat(-0.0), Op::Negate]) {
4448 VMResult::Ok(Value::Float(f)) => assert_eq!(f.to_bits(), 0.0f64.to_bits()),
4449 other => panic!("expected Float(0.0), got {:?}", other),
4450 }
4451 match run_one(vec![Op::LoadFloat(0.0), Op::Negate]) {
4452 VMResult::Ok(Value::Float(f)) => assert_eq!(f.to_bits(), (-0.0f64).to_bits()),
4453 other => panic!("expected Float(-0.0), got {:?}", other),
4454 }
4455 match run_one(vec![Op::LoadFloat(-0.0), Op::LoadInt(0), Op::Sub]) {
4456 VMResult::Ok(Value::Float(f)) => assert_eq!(f.to_bits(), (-0.0f64).to_bits()),
4457 other => panic!("expected Float(-0.0), got {:?}", other),
4458 }
4459 }
4460
4461 #[test]
4462 fn arithmetic_pow_returns_float() {
4463 match run_one(vec![Op::LoadInt(3), Op::LoadInt(4), Op::Pow]) {
4464 VMResult::Ok(Value::Float(f)) => assert!((f - 81.0).abs() < 1e-9),
4465 VMResult::Ok(Value::Int(81)) => {} other => panic!("got {:?}", other),
4467 }
4468 }
4469
4470 #[test]
4473 fn num_comparisons_produce_booleans() {
4474 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(1), Op::NumEq], true);
4475 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumEq], false);
4476 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumLt], true);
4477 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumGt], false);
4478 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumLe], true);
4479 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumGe], true);
4480 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumNe], false);
4481 }
4482
4483 #[test]
4484 fn spaceship_returns_neg_zero_pos() {
4485 expect_int(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Spaceship], -1);
4486 expect_int(vec![Op::LoadInt(2), Op::LoadInt(2), Op::Spaceship], 0);
4487 expect_int(vec![Op::LoadInt(3), Op::LoadInt(2), Op::Spaceship], 1);
4488 }
4489
4490 #[test]
4491 fn string_comparisons() {
4492 let mut b = ChunkBuilder::new();
4493 let a = b.add_constant(Value::str("alpha"));
4494 let z = b.add_constant(Value::str("beta"));
4495 b.emit(Op::LoadConst(a), 1);
4496 b.emit(Op::LoadConst(z), 1);
4497 b.emit(Op::StrLt, 1);
4498 let mut vm = VM::new(b.build());
4499 assert!(matches!(vm.run(), VMResult::Ok(Value::Bool(true))));
4500 }
4501
4502 #[test]
4503 fn string_eq_and_ne() {
4504 let mut b = ChunkBuilder::new();
4505 let s1 = b.add_constant(Value::str("hi"));
4506 let s2 = b.add_constant(Value::str("hi"));
4507 b.emit(Op::LoadConst(s1), 1);
4508 b.emit(Op::LoadConst(s2), 1);
4509 b.emit(Op::StrEq, 1);
4510 assert!(matches!(
4511 VM::new(b.build()).run(),
4512 VMResult::Ok(Value::Bool(true))
4513 ));
4514 }
4515
4516 #[test]
4519 fn pop_discards_top() {
4520 expect_int(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Pop], 1);
4522 }
4523
4524 #[test]
4525 fn dup_duplicates_top() {
4526 expect_int(vec![Op::LoadInt(5), Op::Dup, Op::Add], 10);
4528 }
4529
4530 #[test]
4531 fn swap_exchanges_top_two() {
4532 expect_int(vec![Op::LoadInt(10), Op::LoadInt(3), Op::Swap, Op::Sub], -7);
4536 }
4537
4538 #[test]
4539 fn dup2_duplicates_top_two_values() {
4540 expect_int(
4542 vec![Op::LoadInt(3), Op::LoadInt(4), Op::Dup2, Op::Add, Op::Add],
4543 11,
4544 );
4545 }
4546
4547 #[test]
4550 fn log_not_inverts_truthiness() {
4551 expect_bool(vec![Op::LoadInt(0), Op::LogNot], true);
4552 expect_bool(vec![Op::LoadInt(1), Op::LogNot], false);
4553 expect_bool(vec![Op::LoadTrue, Op::LogNot], false);
4554 expect_bool(vec![Op::LoadFalse, Op::LogNot], true);
4555 }
4556
4557 #[test]
4558 fn bitwise_ops() {
4559 expect_int(
4560 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitAnd],
4561 0b1000,
4562 );
4563 expect_int(
4564 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitOr],
4565 0b1110,
4566 );
4567 expect_int(
4568 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitXor],
4569 0b0110,
4570 );
4571 expect_int(vec![Op::LoadInt(1), Op::LoadInt(4), Op::Shl], 16);
4572 expect_int(vec![Op::LoadInt(64), Op::LoadInt(2), Op::Shr], 16);
4573 }
4574
4575 #[test]
4576 fn bit_not_inverts_bits() {
4577 expect_int(vec![Op::LoadInt(0), Op::BitNot], -1);
4578 }
4579
4580 #[test]
4583 fn concat_joins_strings() {
4584 let mut b = ChunkBuilder::new();
4585 let h = b.add_constant(Value::str("hello "));
4586 let w = b.add_constant(Value::str("world"));
4587 b.emit(Op::LoadConst(h), 1);
4588 b.emit(Op::LoadConst(w), 1);
4589 b.emit(Op::Concat, 1);
4590 match VM::new(b.build()).run() {
4591 VMResult::Ok(v) => assert_eq!(v.to_str(), "hello world"),
4592 other => panic!("got {:?}", other),
4593 }
4594 }
4595
4596 #[test]
4597 fn string_repeat_op() {
4598 let mut b = ChunkBuilder::new();
4599 let s = b.add_constant(Value::str("ab"));
4600 b.emit(Op::LoadConst(s), 1);
4601 b.emit(Op::LoadInt(3), 1);
4602 b.emit(Op::StringRepeat, 1);
4603 match VM::new(b.build()).run() {
4604 VMResult::Ok(v) => assert_eq!(v.to_str(), "ababab"),
4605 other => panic!("got {:?}", other),
4606 }
4607 }
4608
4609 #[test]
4610 fn string_len_returns_int() {
4611 let mut b = ChunkBuilder::new();
4612 let s = b.add_constant(Value::str("abcd"));
4613 b.emit(Op::LoadConst(s), 1);
4614 b.emit(Op::StringLen, 1);
4615 match VM::new(b.build()).run() {
4616 VMResult::Ok(Value::Int(4)) => {}
4617 other => panic!("got {:?}", other),
4618 }
4619 }
4620
4621 #[test]
4624 fn load_true_false_undef() {
4625 assert!(matches!(
4626 run_one(vec![Op::LoadTrue]),
4627 VMResult::Ok(Value::Bool(true))
4628 ));
4629 assert!(matches!(
4630 run_one(vec![Op::LoadFalse]),
4631 VMResult::Ok(Value::Bool(false))
4632 ));
4633 assert!(matches!(
4634 run_one(vec![Op::LoadUndef]),
4635 VMResult::Ok(Value::Undef)
4636 ));
4637 }
4638
4639 #[test]
4640 fn load_const_string() {
4641 let mut b = ChunkBuilder::new();
4642 let c = b.add_constant(Value::str("xyz"));
4643 b.emit(Op::LoadConst(c), 1);
4644 match VM::new(b.build()).run() {
4645 VMResult::Ok(v) => assert_eq!(v.to_str(), "xyz"),
4646 other => panic!("got {:?}", other),
4647 }
4648 }
4649
4650 #[test]
4653 fn jump_if_true_taken() {
4654 let mut b = ChunkBuilder::new();
4656 b.emit(Op::LoadTrue, 1);
4657 let j = b.emit(Op::JumpIfTrue(0), 1);
4658 b.emit(Op::LoadInt(0), 1);
4659 b.patch_jump(j, b.current_pos());
4660 b.emit(Op::LoadInt(1), 1);
4661 assert!(matches!(
4662 VM::new(b.build()).run(),
4663 VMResult::Ok(Value::Int(1))
4664 ));
4665 }
4666
4667 #[test]
4668 fn jump_if_false_not_taken_for_true() {
4669 let mut b = ChunkBuilder::new();
4670 b.emit(Op::LoadTrue, 1);
4671 let j = b.emit(Op::JumpIfFalse(0), 1);
4672 b.emit(Op::LoadInt(7), 1); b.patch_jump(j, b.current_pos());
4674 match VM::new(b.build()).run() {
4675 VMResult::Ok(Value::Int(7)) => {}
4676 other => panic!("got {:?}", other),
4677 }
4678 }
4679
4680 #[test]
4683 fn push_pop_frame_with_slots() {
4684 let mut b = ChunkBuilder::new();
4688 b.emit(Op::PushFrame, 1);
4689 b.emit(Op::LoadInt(99), 1);
4690 b.emit(Op::SetSlot(0), 1);
4691 b.emit(Op::GetSlot(0), 1);
4692 match VM::new(b.build()).run() {
4693 VMResult::Ok(Value::Int(99)) => {}
4694 other => panic!("got {:?}", other),
4695 }
4696 }
4697
4698 #[test]
4701 fn vm_push_pop_peek_round_trip() {
4702 let chunk = ChunkBuilder::new().build();
4703 let mut vm = VM::new(chunk);
4704 vm.push(Value::Int(1));
4705 vm.push(Value::Int(2));
4706 assert_eq!(*vm.peek(), Value::Int(2));
4707 assert_eq!(vm.pop(), Value::Int(2));
4708 assert_eq!(vm.pop(), Value::Int(1));
4709 }
4710
4711 #[test]
4714 fn register_builtin_overwrites_existing_handler() {
4715 let mut b = ChunkBuilder::new();
4716 b.emit(Op::LoadInt(1), 1);
4717 b.emit(Op::CallBuiltin(0, 1), 1);
4718 let mut vm = VM::new(b.build());
4719 vm.register_builtin(0, |vm, _| {
4720 vm.pop();
4721 Value::Int(111)
4722 });
4723 vm.register_builtin(0, |vm, _| {
4725 vm.pop();
4726 Value::Int(222)
4727 });
4728 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(222))));
4729 }
4730
4731 #[test]
4732 fn run_builtin_by_name_dispatches_and_passes_args() {
4733 let id = crate::shell_builtins::builtin_id("true").expect("`true` is a builtin");
4736 let mut vm = VM::new(ChunkBuilder::new().build());
4737 vm.register_builtin(id, |vm, argc| {
4738 let mut got = Vec::new();
4740 for _ in 0..argc {
4741 got.push(vm.pop().to_str());
4742 }
4743 got.reverse();
4744 assert_eq!(got, vec!["x".to_string(), "y".to_string()]);
4745 Value::Int(7)
4746 });
4747 let out = vm.run_builtin_by_name("true", &["x".to_string(), "y".to_string()]);
4748 assert!(matches!(out, Some(Value::Int(7))));
4749 assert!(vm
4751 .run_builtin_by_name("definitely_not_a_builtin_xyz", &[])
4752 .is_none());
4753 }
4754
4755 #[test]
4756 fn register_builtin_grows_table_to_high_id() {
4757 let chunk = ChunkBuilder::new().build();
4759 let mut vm = VM::new(chunk);
4760 vm.register_builtin(500, |_, _| Value::Int(0));
4761 vm.register_builtin(1, |_, _| Value::Int(0));
4763 }
4764
4765 #[test]
4768 fn reset_clears_state_and_runs_new_chunk() {
4769 let mut b1 = ChunkBuilder::new();
4770 b1.emit(Op::LoadInt(1), 1);
4771 let mut vm = VM::new(b1.build());
4772 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(1))));
4773
4774 let mut b2 = ChunkBuilder::new();
4775 b2.emit(Op::LoadInt(2), 1);
4776 b2.emit(Op::LoadInt(3), 1);
4777 b2.emit(Op::Add, 1);
4778 vm.reset(b2.build());
4779 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(5))));
4780 }
4781
4782 #[test]
4785 fn extension_handler_invoked_with_payload() {
4786 use std::sync::{Arc, Mutex};
4787 let captured: Arc<Mutex<Option<(u16, u8)>>> = Arc::new(Mutex::new(None));
4788 let captured_cl = Arc::clone(&captured);
4789
4790 let mut b = ChunkBuilder::new();
4791 b.emit(Op::Extended(7, 42), 1);
4792 let mut vm = VM::new(b.build());
4793 vm.set_extension_handler(Box::new(move |vm, id, arg| {
4794 *captured_cl.lock().unwrap() = Some((id, arg));
4795 vm.push(Value::Int(123));
4796 }));
4797 match vm.run() {
4798 VMResult::Ok(Value::Int(123)) => {}
4799 other => panic!("got {:?}", other),
4800 }
4801 assert_eq!(*captured.lock().unwrap(), Some((7, 42)));
4802 }
4803
4804 #[test]
4805 fn extension_wide_handler_invoked_with_payload() {
4806 use std::sync::{Arc, Mutex};
4807 let captured: Arc<Mutex<Option<(u16, usize)>>> = Arc::new(Mutex::new(None));
4808 let captured_cl = Arc::clone(&captured);
4809 let mut b = ChunkBuilder::new();
4810 b.emit(Op::ExtendedWide(9, 9999), 1);
4811 let mut vm = VM::new(b.build());
4812 vm.set_extension_wide_handler(Box::new(move |vm, id, payload| {
4813 *captured_cl.lock().unwrap() = Some((id, payload));
4814 vm.push(Value::Int(0));
4815 }));
4816 let _ = vm.run();
4817 assert_eq!(*captured.lock().unwrap(), Some((9, 9999)));
4818 }
4819
4820 #[test]
4823 fn vmpool_new_default_and_with_capacity_start_empty() {
4824 let p = VMPool::new();
4825 assert!(p.is_empty());
4826 assert_eq!(p.len(), 0);
4827 let p = VMPool::with_capacity(8);
4828 assert!(p.is_empty());
4829 let p: VMPool = Default::default();
4830 assert!(p.is_empty());
4831 }
4832
4833 #[test]
4834 fn vmpool_release_then_acquire_reuses_vm() {
4835 let mut pool = VMPool::new();
4836 let chunk1 = {
4837 let mut b = ChunkBuilder::new();
4838 b.emit(Op::LoadInt(1), 1);
4839 b.build()
4840 };
4841 let vm = pool.acquire(chunk1);
4842 assert_eq!(pool.len(), 0);
4843 pool.release(vm);
4844 assert_eq!(pool.len(), 1);
4845
4846 let chunk2 = {
4847 let mut b = ChunkBuilder::new();
4848 b.emit(Op::LoadInt(2), 1);
4849 b.build()
4850 };
4851 let mut vm = pool.acquire(chunk2);
4852 assert_eq!(pool.len(), 0);
4853 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(2))));
4854 }
4855
4856 #[test]
4857 fn vmpool_with_returns_value_and_recycles_vm() {
4858 let mut pool = VMPool::new();
4859 let chunk = {
4860 let mut b = ChunkBuilder::new();
4861 b.emit(Op::LoadInt(10), 1);
4862 b.emit(Op::LoadInt(5), 1);
4863 b.emit(Op::Add, 1);
4864 b.build()
4865 };
4866 let result = pool.with(chunk, |vm| match vm.run() {
4867 VMResult::Ok(Value::Int(n)) => n,
4868 other => panic!("got {:?}", other),
4869 });
4870 assert_eq!(result, 15);
4871 assert_eq!(pool.len(), 1, "VM should be returned to pool after with()");
4872 }
4873
4874 #[derive(Default)]
4879 struct RecordingAwkHost {
4880 record: String,
4881 fields: Vec<String>,
4882 printed: Vec<Vec<String>>,
4883 field_sets: Vec<(i64, String)>,
4884 special_sets: Vec<(String, String)>,
4885 array: std::collections::HashMap<String, String>,
4886 }
4887
4888 impl crate::awk_host::AwkHost for RecordingAwkHost {
4889 fn field_get(&mut self, i: i64) -> Value {
4890 Value::str(self.fields.get(i as usize).cloned().unwrap_or_default())
4891 }
4892 fn field_set(&mut self, i: i64, v: Value) {
4893 self.field_sets.push((i, v.to_str()));
4894 }
4895 fn nf(&mut self) -> i64 {
4896 self.fields.len() as i64
4897 }
4898 fn set_record(&mut self, v: Value) {
4899 self.record = v.to_str();
4900 self.fields = self.record.split(' ').map(|s| s.to_string()).collect();
4901 }
4902 fn special_get(&mut self, name: &str) -> Value {
4903 match name {
4904 "NR" => Value::Int(7),
4905 _ => Value::str(""),
4906 }
4907 }
4908 fn special_set(&mut self, name: &str, v: Value) {
4909 self.special_sets.push((name.to_string(), v.to_str()));
4910 }
4911 fn print(&mut self, args: &[Value]) {
4912 self.printed.push(args.iter().map(|v| v.to_str()).collect());
4913 }
4914 fn array_get(&mut self, _arr: &str, key: &Value) -> Value {
4915 Value::str(self.array.get(&key.to_str()).cloned().unwrap_or_default())
4916 }
4917 fn array_set(&mut self, _arr: &str, key: &Value, v: Value) {
4918 self.array.insert(key.to_str(), v.to_str());
4919 }
4920 }
4921
4922 fn awk_op(b: &mut ChunkBuilder, id: u16, payload: usize) {
4923 b.emit(Op::ExtendedWide(id, payload), 1);
4924 }
4925
4926 #[test]
4927 fn awk_field_get_routes_to_host() {
4928 use crate::awk_builtins::*;
4929 let chunk = {
4930 let mut b = ChunkBuilder::new();
4931 b.emit(Op::LoadInt(2), 1); awk_op(&mut b, AWK_FIELD_GET, 0);
4933 b.build()
4934 };
4935 let mut vm = VM::new(chunk);
4936 let host = RecordingAwkHost {
4937 fields: vec!["a".into(), "b".into(), "c".into()],
4938 ..Default::default()
4939 };
4940 vm.set_awk_host(Box::new(host));
4941 match vm.run() {
4942 VMResult::Ok(v) => assert_eq!(v.to_str(), "c"),
4943 other => panic!("got {:?}", other),
4944 }
4945 }
4946
4947 #[test]
4948 fn awk_print_pops_args_in_source_order() {
4949 use crate::awk_builtins::*;
4950 let chunk = {
4951 let mut b = ChunkBuilder::new();
4952 let x = b.add_constant(Value::str("x"));
4953 let y = b.add_constant(Value::str("y"));
4954 b.emit(Op::LoadConst(x), 1);
4955 b.emit(Op::LoadConst(y), 1);
4956 awk_op(&mut b, AWK_PRINT, 2);
4957 b.build()
4958 };
4959 let mut vm = VM::new(chunk);
4960 struct H(std::sync::Arc<std::sync::Mutex<Vec<Vec<String>>>>);
4962 impl crate::awk_host::AwkHost for H {
4963 fn print(&mut self, args: &[Value]) {
4964 self.0
4965 .lock()
4966 .unwrap()
4967 .push(args.iter().map(|v| v.to_str()).collect());
4968 }
4969 }
4970 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
4971 vm.set_awk_host(Box::new(H(sink.clone())));
4972 let _ = vm.run();
4973 assert_eq!(
4974 sink.lock().unwrap().as_slice(),
4975 &[vec!["x".to_string(), "y".to_string()]]
4976 );
4977 }
4978
4979 #[test]
4980 fn awk_field_set_pops_value_and_index() {
4981 use crate::awk_builtins::*;
4982 let chunk = {
4983 let mut b = ChunkBuilder::new();
4984 let v = b.add_constant(Value::str("Z"));
4985 b.emit(Op::LoadConst(v), 1); b.emit(Op::LoadInt(3), 1); awk_op(&mut b, AWK_FIELD_SET, 0);
4988 b.build()
4989 };
4990 struct H(std::sync::Arc<std::sync::Mutex<Vec<(i64, String)>>>);
4991 impl crate::awk_host::AwkHost for H {
4992 fn field_set(&mut self, i: i64, v: Value) {
4993 self.0.lock().unwrap().push((i, v.to_str()));
4994 }
4995 }
4996 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
4997 let mut vm = VM::new(chunk);
4998 vm.set_awk_host(Box::new(H(sink.clone())));
4999 let _ = vm.run();
5000 assert_eq!(sink.lock().unwrap().as_slice(), &[(3i64, "Z".to_string())]);
5001 }
5002
5003 #[test]
5004 fn awk_special_get_and_array_roundtrip() {
5005 use crate::awk_builtins::*;
5006 let chunk = {
5007 let mut b = ChunkBuilder::new();
5008 let arr = b.add_name("counts");
5009 let k = b.add_constant(Value::str("k"));
5010 let val = b.add_constant(Value::str("42"));
5011 b.emit(Op::LoadConst(val), 1);
5013 b.emit(Op::LoadConst(k), 1);
5014 awk_op(&mut b, AWK_ARRAY_SET, arr as usize);
5015 b.emit(Op::LoadConst(k), 1);
5017 awk_op(&mut b, AWK_ARRAY_GET, arr as usize);
5018 b.build()
5019 };
5020 let mut vm = VM::new(chunk);
5021 vm.set_awk_host(Box::new(RecordingAwkHost::default()));
5022 match vm.run() {
5023 VMResult::Ok(v) => assert_eq!(v.to_str(), "42"),
5024 other => panic!("got {:?}", other),
5025 }
5026 }
5027
5028 #[test]
5029 fn awk_ops_are_inert_without_host_but_keep_stack_balanced() {
5030 use crate::awk_builtins::*;
5031 let chunk = {
5033 let mut b = ChunkBuilder::new();
5034 b.emit(Op::LoadInt(1), 1);
5035 awk_op(&mut b, AWK_FIELD_GET, 0);
5036 b.build()
5037 };
5038 let mut vm = VM::new(chunk);
5039 match vm.run() {
5040 VMResult::Ok(v) => assert_eq!(v.to_str(), ""),
5041 other => panic!("got {:?}", other),
5042 }
5043 }
5044
5045 #[test]
5052 fn first_class_awk_field_get_routes_to_host() {
5053 let chunk = {
5054 let mut b = ChunkBuilder::new();
5055 b.emit(Op::LoadInt(2), 1); b.emit(Op::AwkFieldGet, 1);
5057 b.build()
5058 };
5059 let mut vm = VM::new(chunk);
5060 let host = RecordingAwkHost {
5061 fields: vec!["a".into(), "b".into(), "c".into()],
5062 ..Default::default()
5063 };
5064 vm.set_awk_host(Box::new(host));
5065 match vm.run() {
5066 VMResult::Ok(v) => assert_eq!(v.to_str(), "c"),
5067 other => panic!("got {:?}", other),
5068 }
5069 }
5070
5071 #[test]
5072 fn first_class_awk_print_pops_args_in_source_order() {
5073 struct H(std::sync::Arc<std::sync::Mutex<Vec<Vec<String>>>>);
5074 impl crate::awk_host::AwkHost for H {
5075 fn print(&mut self, args: &[Value]) {
5076 self.0
5077 .lock()
5078 .unwrap()
5079 .push(args.iter().map(|v| v.to_str()).collect());
5080 }
5081 }
5082 let chunk = {
5083 let mut b = ChunkBuilder::new();
5084 let x = b.add_constant(Value::str("x"));
5085 let y = b.add_constant(Value::str("y"));
5086 b.emit(Op::LoadConst(x), 1);
5087 b.emit(Op::LoadConst(y), 1);
5088 b.emit(Op::AwkPrint(2), 1);
5089 b.build()
5090 };
5091 let mut vm = VM::new(chunk);
5092 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
5093 vm.set_awk_host(Box::new(H(sink.clone())));
5094 let _ = vm.run();
5095 assert_eq!(
5096 sink.lock().unwrap().as_slice(),
5097 &[vec!["x".to_string(), "y".to_string()]]
5098 );
5099 }
5100
5101 #[test]
5102 fn first_class_awk_array_roundtrip_matches_extendedwide() {
5103 fn run_variant(first_class: bool) -> String {
5106 use crate::awk_builtins::*;
5107 let mut b = ChunkBuilder::new();
5108 let arr = b.add_name("counts");
5109 let k = b.add_constant(Value::str("k"));
5110 let val = b.add_constant(Value::str("42"));
5111 b.emit(Op::LoadConst(val), 1);
5112 b.emit(Op::LoadConst(k), 1);
5113 if first_class {
5114 b.emit(Op::AwkArraySet(arr), 1);
5115 } else {
5116 b.emit(Op::ExtendedWide(AWK_ARRAY_SET, arr as usize), 1);
5117 }
5118 b.emit(Op::LoadConst(k), 1);
5119 if first_class {
5120 b.emit(Op::AwkArrayGet(arr), 1);
5121 } else {
5122 b.emit(Op::ExtendedWide(AWK_ARRAY_GET, arr as usize), 1);
5123 }
5124 let mut vm = VM::new(b.build());
5125 vm.set_awk_host(Box::new(RecordingAwkHost::default()));
5126 match vm.run() {
5127 VMResult::Ok(v) => v.to_str(),
5128 other => panic!("got {:?}", other),
5129 }
5130 }
5131 assert_eq!(run_variant(true), "42");
5132 assert_eq!(run_variant(true), run_variant(false));
5133 }
5134
5135 #[test]
5136 fn first_class_awk_ops_inert_without_host() {
5137 let chunk = {
5139 let mut b = ChunkBuilder::new();
5140 b.emit(Op::LoadInt(1), 1);
5141 b.emit(Op::AwkFieldGet, 1);
5142 b.build()
5143 };
5144 let mut vm = VM::new(chunk);
5145 match vm.run() {
5146 VMResult::Ok(v) => assert_eq!(v.to_str(), ""),
5147 other => panic!("got {:?}", other),
5148 }
5149 }
5150
5151 fn run_native(chunk: crate::chunk::Chunk) -> Value {
5157 let mut vm = VM::new(chunk);
5158 match vm.run() {
5159 VMResult::Ok(v) => v,
5160 other => panic!("got {:?}", other),
5161 }
5162 }
5163
5164 #[test]
5165 fn awk_substr_executes_natively_without_host() {
5166 let chunk = {
5168 let mut b = ChunkBuilder::new();
5169 let s = b.add_constant(Value::str("hello"));
5170 b.emit(Op::LoadConst(s), 1);
5171 b.emit(Op::LoadInt(2), 1);
5172 b.emit(Op::LoadInt(3), 1);
5173 b.emit(Op::AwkSubstr(3), 1);
5174 b.build()
5175 };
5176 assert_eq!(run_native(chunk).to_str(), "ell");
5177 }
5178
5179 #[test]
5180 fn awk_substr_two_arg_to_end_without_host() {
5181 let chunk = {
5183 let mut b = ChunkBuilder::new();
5184 let s = b.add_constant(Value::str("hello"));
5185 b.emit(Op::LoadConst(s), 1);
5186 b.emit(Op::LoadInt(2), 1);
5187 b.emit(Op::AwkSubstr(2), 1);
5188 b.build()
5189 };
5190 assert_eq!(run_native(chunk).to_str(), "ello");
5191 }
5192
5193 #[test]
5194 fn awk_tolower_toupper_execute_natively_without_host() {
5195 let lower = {
5196 let mut b = ChunkBuilder::new();
5197 let s = b.add_constant(Value::str("MiXeD"));
5198 b.emit(Op::LoadConst(s), 1);
5199 b.emit(Op::AwkToLower, 1);
5200 b.build()
5201 };
5202 assert_eq!(run_native(lower).to_str(), "mixed");
5203 let upper = {
5204 let mut b = ChunkBuilder::new();
5205 let s = b.add_constant(Value::str("MiXeD"));
5206 b.emit(Op::LoadConst(s), 1);
5207 b.emit(Op::AwkToUpper, 1);
5208 b.build()
5209 };
5210 assert_eq!(run_native(upper).to_str(), "MIXED");
5211 }
5212
5213 #[test]
5214 fn awk_index_executes_natively_without_host() {
5215 let chunk = {
5217 let mut b = ChunkBuilder::new();
5218 let s = b.add_constant(Value::str("hello"));
5219 let t = b.add_constant(Value::str("ll"));
5220 b.emit(Op::LoadConst(s), 1);
5221 b.emit(Op::LoadConst(t), 1);
5222 b.emit(Op::AwkIndex, 1);
5223 b.build()
5224 };
5225 assert_eq!(run_native(chunk).to_int(), 3);
5226 }
5227
5228 #[test]
5229 fn awk_length_scalar_executes_natively_without_host() {
5230 let chunk = {
5232 let mut b = ChunkBuilder::new();
5233 let s = b.add_constant(Value::str("héllo"));
5234 b.emit(Op::LoadConst(s), 1);
5235 b.emit(Op::AwkLength(1), 1);
5236 b.build()
5237 };
5238 assert_eq!(run_native(chunk).to_int(), 5);
5239 }
5240
5241 #[test]
5242 fn awk_native_string_ops_match_host_path() {
5243 use crate::awk_host::{awk_index, awk_substr, awk_tolower};
5245 assert_eq!(awk_substr(&Value::str("hello"), 2, Some(3)).to_str(), "ell");
5246 assert_eq!(awk_index(&Value::str("hello"), &Value::str("z")), 0);
5247 assert_eq!(awk_tolower(&Value::str("ABC")).to_str(), "abc");
5248 }
5249
5250 #[test]
5255 fn awk_int_truncates_toward_zero_without_host() {
5256 for (input, want) in [(3.7_f64, 3_i64), (-3.7, -3), (0.0, 0)] {
5257 let chunk = {
5258 let mut b = ChunkBuilder::new();
5259 let x = b.add_constant(Value::Float(input));
5260 b.emit(Op::LoadConst(x), 1);
5261 b.emit(Op::AwkInt, 1);
5262 b.build()
5263 };
5264 assert_eq!(run_native(chunk).to_int(), want, "int({input})");
5265 }
5266 }
5267
5268 #[test]
5269 fn awk_sqrt_exp_log_execute_natively_without_host() {
5270 let sqrt = {
5271 let mut b = ChunkBuilder::new();
5272 let x = b.add_constant(Value::Float(16.0));
5273 b.emit(Op::LoadConst(x), 1);
5274 b.emit(Op::AwkSqrt, 1);
5275 b.build()
5276 };
5277 assert_eq!(run_native(sqrt).to_float(), 4.0);
5278 let log = {
5279 let mut b = ChunkBuilder::new();
5280 let x = b.add_constant(Value::Float(std::f64::consts::E));
5281 b.emit(Op::LoadConst(x), 1);
5282 b.emit(Op::AwkLog, 1);
5283 b.build()
5284 };
5285 assert!((run_native(log).to_float() - 1.0).abs() < 1e-12);
5286 let exp = {
5287 let mut b = ChunkBuilder::new();
5288 let x = b.add_constant(Value::Float(0.0));
5289 b.emit(Op::LoadConst(x), 1);
5290 b.emit(Op::AwkExp, 1);
5291 b.build()
5292 };
5293 assert_eq!(run_native(exp).to_float(), 1.0);
5294 }
5295
5296 #[test]
5297 fn awk_sin_cos_execute_natively_without_host() {
5298 let sin = {
5299 let mut b = ChunkBuilder::new();
5300 let x = b.add_constant(Value::Float(0.0));
5301 b.emit(Op::LoadConst(x), 1);
5302 b.emit(Op::AwkSin, 1);
5303 b.build()
5304 };
5305 assert_eq!(run_native(sin).to_float(), 0.0);
5306 let cos = {
5307 let mut b = ChunkBuilder::new();
5308 let x = b.add_constant(Value::Float(0.0));
5309 b.emit(Op::LoadConst(x), 1);
5310 b.emit(Op::AwkCos, 1);
5311 b.build()
5312 };
5313 assert_eq!(run_native(cos).to_float(), 1.0);
5314 }
5315
5316 #[test]
5317 fn awk_atan2_pops_y_then_x_without_host() {
5318 let chunk = {
5320 let mut b = ChunkBuilder::new();
5321 let y = b.add_constant(Value::Float(1.0));
5322 let x = b.add_constant(Value::Float(1.0));
5323 b.emit(Op::LoadConst(y), 1);
5324 b.emit(Op::LoadConst(x), 1);
5325 b.emit(Op::AwkAtan2, 1);
5326 b.build()
5327 };
5328 assert!((run_native(chunk).to_float() - std::f64::consts::FRAC_PI_4).abs() < 1e-12);
5329 }
5330
5331 fn run_native_int(chunk: crate::chunk::Chunk) -> i64 {
5336 run_native(chunk).to_int()
5337 }
5338
5339 #[test]
5340 fn awk_and_or_xor_execute_natively_without_host() {
5341 let mk = |op: Op| {
5343 let mut b = ChunkBuilder::new();
5344 b.emit(Op::LoadInt(12), 1);
5345 b.emit(Op::LoadInt(10), 1);
5346 b.emit(op, 1);
5347 b.build()
5348 };
5349 assert_eq!(run_native_int(mk(Op::AwkAnd(2))), 8);
5350 assert_eq!(run_native_int(mk(Op::AwkOr(2))), 14);
5351 assert_eq!(run_native_int(mk(Op::AwkXor(2))), 6);
5352 }
5353
5354 #[test]
5355 fn awk_and_is_variadic_without_host() {
5356 let chunk = {
5358 let mut b = ChunkBuilder::new();
5359 b.emit(Op::LoadInt(15), 1);
5360 b.emit(Op::LoadInt(9), 1);
5361 b.emit(Op::LoadInt(5), 1);
5362 b.emit(Op::AwkAnd(3), 1);
5363 b.build()
5364 };
5365 assert_eq!(run_native_int(chunk), 1);
5366 }
5367
5368 #[test]
5369 fn awk_compl_matches_awkrs_i64_wrap_without_host() {
5370 let chunk = {
5372 let mut b = ChunkBuilder::new();
5373 b.emit(Op::LoadInt(0), 1);
5374 b.emit(Op::AwkCompl, 1);
5375 b.build()
5376 };
5377 assert_eq!(run_native_int(chunk), -1);
5378 }
5379
5380 #[test]
5381 fn awk_lshift_rshift_execute_natively_without_host() {
5382 let lshift = {
5384 let mut b = ChunkBuilder::new();
5385 b.emit(Op::LoadInt(1), 1);
5386 b.emit(Op::LoadInt(4), 1);
5387 b.emit(Op::AwkLshift, 1);
5388 b.build()
5389 };
5390 assert_eq!(run_native_int(lshift), 16);
5391 let rshift = {
5392 let mut b = ChunkBuilder::new();
5393 b.emit(Op::LoadInt(256), 1);
5394 b.emit(Op::LoadInt(4), 1);
5395 b.emit(Op::AwkRshift, 1);
5396 b.build()
5397 };
5398 assert_eq!(run_native_int(rshift), 16);
5399 }
5400
5401 #[test]
5402 fn awk_bitwise_free_fns_match_gawk_semantics() {
5403 use crate::awk_host::{awk_compl, awk_fold_and, awk_fold_or, awk_fold_xor, awk_lshift};
5404 assert_eq!(awk_fold_and(&[Value::Int(12), Value::Int(10)]), 8);
5405 assert_eq!(awk_fold_or(&[Value::Int(12), Value::Int(10)]), 14);
5406 assert_eq!(awk_fold_xor(&[Value::Int(12), Value::Int(10)]), 6);
5407 assert_eq!(awk_compl(&Value::Int(0)), -1);
5408 assert_eq!(awk_lshift(&Value::Int(1), &Value::Int(64)), 1);
5410 }
5411
5412 #[test]
5413 fn awk_mktime_utc_executes_natively_without_host() {
5414 let chunk = {
5416 let mut b = ChunkBuilder::new();
5417 let s = b.add_constant(Value::str("2020 01 01 00 00 00"));
5418 b.emit(Op::LoadConst(s), 1);
5419 b.emit(Op::LoadInt(1), 1); b.emit(Op::AwkMktime(2), 1);
5421 b.build()
5422 };
5423 assert_eq!(run_native(chunk).to_float(), 1_577_836_800.0);
5424 }
5425
5426 #[test]
5427 fn awk_mktime_bad_datespec_returns_minus_one_without_host() {
5428 let chunk = {
5430 let mut b = ChunkBuilder::new();
5431 let s = b.add_constant(Value::str("2020 01 01"));
5432 b.emit(Op::LoadConst(s), 1);
5433 b.emit(Op::AwkMktime(1), 1);
5434 b.build()
5435 };
5436 assert_eq!(run_native(chunk).to_float(), -1.0);
5437 }
5438
5439 #[test]
5440 fn awk_strftime_utc_executes_natively_without_host() {
5441 let chunk = {
5443 let mut b = ChunkBuilder::new();
5444 let fmt = b.add_constant(Value::str("%Y-%m-%d"));
5445 b.emit(Op::LoadConst(fmt), 1);
5446 b.emit(Op::LoadInt(0), 1); b.emit(Op::LoadInt(1), 1); b.emit(Op::AwkStrftime(3), 1);
5449 b.build()
5450 };
5451 assert_eq!(run_native(chunk).to_str(), "1970-01-01");
5452 }
5453
5454 #[test]
5455 fn awk_strftime_mktime_free_fns_match_awkrs() {
5456 use crate::awk_host::{awk_mktime, awk_strftime};
5457 assert_eq!(
5459 awk_mktime(&[Value::str("2020 01 01 00 00 00"), Value::Int(1)]).to_float(),
5460 1_577_836_800.0
5461 );
5462 assert_eq!(awk_mktime(&[Value::str("garbage")]).to_float(), -1.0);
5463 assert_eq!(
5464 awk_strftime(&[Value::str("%H:%M:%S"), Value::Int(0), Value::Int(1)]).to_str(),
5465 "00:00:00"
5466 );
5467 }
5468
5469 #[test]
5470 fn awk_ord_executes_natively_without_host() {
5471 let chunk = {
5473 let mut b = ChunkBuilder::new();
5474 let s = b.add_constant(Value::str("ABC"));
5475 b.emit(Op::LoadConst(s), 1);
5476 b.emit(Op::AwkOrd, 1);
5477 b.build()
5478 };
5479 assert_eq!(run_native(chunk).to_float(), 65.0);
5480
5481 let empty = {
5482 let mut b = ChunkBuilder::new();
5483 let s = b.add_constant(Value::str(""));
5484 b.emit(Op::LoadConst(s), 1);
5485 b.emit(Op::AwkOrd, 1);
5486 b.build()
5487 };
5488 assert_eq!(run_native(empty).to_float(), 0.0);
5489 }
5490
5491 #[test]
5492 fn awk_chr_executes_natively_without_host() {
5493 let chunk = {
5495 let mut b = ChunkBuilder::new();
5496 b.emit(Op::LoadInt(65), 1);
5497 b.emit(Op::AwkChr, 1);
5498 b.build()
5499 };
5500 assert_eq!(run_native(chunk).to_str(), "A");
5501
5502 let bad = {
5503 let mut b = ChunkBuilder::new();
5504 b.emit(Op::LoadInt(0xD800), 1); b.emit(Op::AwkChr, 1);
5506 b.build()
5507 };
5508 assert_eq!(run_native(bad).to_str(), "");
5509 }
5510
5511 #[test]
5512 fn awk_mkbool_executes_natively_without_host() {
5513 let truthy = {
5515 let mut b = ChunkBuilder::new();
5516 b.emit(Op::LoadInt(7), 1);
5517 b.emit(Op::AwkMkbool, 1);
5518 b.build()
5519 };
5520 assert_eq!(run_native(truthy).to_float(), 1.0);
5521
5522 let zero = {
5523 let mut b = ChunkBuilder::new();
5524 b.emit(Op::LoadInt(0), 1);
5525 b.emit(Op::AwkMkbool, 1);
5526 b.build()
5527 };
5528 assert_eq!(run_native(zero).to_float(), 0.0);
5529 }
5530
5531 #[test]
5532 fn awk_intdiv_executes_natively_without_host() {
5533 let chunk = {
5535 let mut b = ChunkBuilder::new();
5536 b.emit(Op::LoadInt(17), 1);
5537 b.emit(Op::LoadInt(5), 1);
5538 b.emit(Op::AwkIntdiv, 1);
5539 b.build()
5540 };
5541 assert_eq!(run_native(chunk).to_float(), 3.0);
5542
5543 let div0 = {
5544 let mut b = ChunkBuilder::new();
5545 b.emit(Op::LoadInt(17), 1);
5546 b.emit(Op::LoadInt(0), 1);
5547 b.emit(Op::AwkIntdiv, 1);
5548 b.build()
5549 };
5550 assert!(matches!(run_native(div0), Value::Undef));
5551 }
5552
5553 #[test]
5554 fn awk_intdiv0_executes_natively_without_host() {
5555 let chunk = {
5557 let mut b = ChunkBuilder::new();
5558 b.emit(Op::LoadInt(17), 1);
5559 b.emit(Op::LoadInt(5), 1);
5560 b.emit(Op::AwkIntdiv0, 1);
5561 b.build()
5562 };
5563 assert_eq!(run_native(chunk).to_float(), 3.0);
5564
5565 let div0 = {
5566 let mut b = ChunkBuilder::new();
5567 b.emit(Op::LoadInt(17), 1);
5568 b.emit(Op::LoadInt(0), 1);
5569 b.emit(Op::AwkIntdiv0, 1);
5570 b.build()
5571 };
5572 assert_eq!(run_native(div0).to_float(), 0.0);
5573 }
5574
5575 #[test]
5576 fn awk_div_mod_compute_and_trap_on_zero() {
5577 let div = {
5582 let mut b = ChunkBuilder::new();
5583 b.emit(Op::LoadFloat(7.0), 1);
5584 b.emit(Op::LoadFloat(2.0), 1);
5585 b.emit(Op::AwkDiv, 1);
5586 b.build()
5587 };
5588 assert_eq!(run_native(div).to_float(), 3.5);
5589
5590 let md = {
5591 let mut b = ChunkBuilder::new();
5592 b.emit(Op::LoadFloat(7.0), 1);
5593 b.emit(Op::LoadFloat(3.0), 1);
5594 b.emit(Op::AwkMod, 1);
5595 b.build()
5596 };
5597 assert_eq!(run_native(md).to_float(), 1.0);
5598
5599 let div0 = {
5600 let mut b = ChunkBuilder::new();
5601 b.emit(Op::LoadFloat(1.0), 1);
5602 b.emit(Op::LoadFloat(0.0), 1);
5603 b.emit(Op::AwkDiv, 1);
5604 b.build()
5605 };
5606 match VM::new(div0).run() {
5607 VMResult::Error(m) => assert_eq!(m, "division by zero attempted"),
5608 other => panic!("expected div-by-zero trap, got {:?}", other),
5609 }
5610
5611 let mod0 = {
5612 let mut b = ChunkBuilder::new();
5613 b.emit(Op::LoadFloat(1.0), 1);
5614 b.emit(Op::LoadFloat(0.0), 1);
5615 b.emit(Op::AwkMod, 1);
5616 b.build()
5617 };
5618 match VM::new(mod0).run() {
5619 VMResult::Error(m) => assert_eq!(m, "division by zero attempted in `%'"),
5620 other => panic!("expected mod-by-zero trap, got {:?}", other),
5621 }
5622 }
5623
5624 #[test]
5625 fn awk_signal_halts_chunk_and_records_code() {
5626 use crate::awk_builtins::signal;
5627 let chunk = {
5630 let mut b = ChunkBuilder::new();
5631 b.emit(Op::LoadInt(1), 1);
5632 b.emit(Op::AwkSignal(signal::NEXTFILE), 1);
5633 b.emit(Op::LoadInt(99), 1);
5635 b.build()
5636 };
5637 let mut vm = VM::new(chunk);
5638 let r = vm.run();
5639 assert_eq!(vm.awk_signal(), Some(signal::NEXTFILE));
5640 match r {
5643 VMResult::Ok(v) => assert_eq!(v.to_float(), 1.0),
5644 other => panic!("expected Ok(1), got {:?}", other),
5645 }
5646
5647 let plain = {
5649 let mut b = ChunkBuilder::new();
5650 b.emit(Op::LoadInt(5), 1);
5651 b.build()
5652 };
5653 let mut vm2 = VM::new(plain);
5654 let _ = vm2.run();
5655 assert_eq!(vm2.awk_signal(), None);
5656 }
5657
5658 #[test]
5659 fn awk_gensub_stub_is_stack_balanced_without_host() {
5660 let chunk = {
5663 let mut b = ChunkBuilder::new();
5664 let re = b.add_constant(Value::str("x"));
5665 let repl = b.add_constant(Value::str("y"));
5666 let how = b.add_constant(Value::str("g"));
5667 let target = b.add_constant(Value::str("xax"));
5668 b.emit(Op::LoadConst(re), 1);
5669 b.emit(Op::LoadConst(repl), 1);
5670 b.emit(Op::LoadConst(how), 1);
5671 b.emit(Op::LoadConst(target), 1);
5672 b.emit(Op::AwkGensub(4), 1);
5673 b.build()
5674 };
5675 assert_eq!(run_native(chunk).to_str(), "");
5676 }
5677
5678 #[test]
5679 fn awk_char_scalar_free_fns_match_awkrs() {
5680 use crate::awk_host::{awk_chr, awk_intdiv, awk_intdiv0, awk_mkbool, awk_ord};
5681 assert_eq!(awk_ord(&Value::str("z")).to_float(), 122.0);
5682 assert_eq!(awk_chr(&Value::Int(0x1F600)).to_str(), "😀");
5683 assert_eq!(awk_mkbool(&Value::str("0")).to_float(), 0.0);
5684 assert_eq!(awk_mkbool(&Value::str("x")).to_float(), 1.0);
5685 assert_eq!(awk_intdiv(&Value::Int(-7), &Value::Int(2)).to_float(), -3.0);
5686 assert!(matches!(
5687 awk_intdiv(&Value::Int(1), &Value::Int(0)),
5688 Value::Undef
5689 ));
5690 assert_eq!(
5691 awk_intdiv0(&Value::Int(-7), &Value::Int(2)).to_float(),
5692 -3.0
5693 );
5694 assert_eq!(awk_intdiv0(&Value::Int(1), &Value::Int(0)).to_float(), 0.0);
5695 }
5696
5697 #[test]
5698 fn awk_rand_executes_natively_without_host() {
5699 let chunk = {
5701 let mut b = ChunkBuilder::new();
5702 b.emit(Op::AwkRand, 1);
5703 b.build()
5704 };
5705 let v = run_native(chunk).to_float();
5706 assert!((0.0..1.0).contains(&v), "rand() = {v} out of [0,1)");
5707 assert_eq!(
5708 v, 0.51385498046875,
5709 "first rand() from seed=1 must be stable"
5710 );
5711 }
5712
5713 #[test]
5714 fn awk_srand_reseeds_and_returns_prev_seed_without_host() {
5715 let chunk = {
5718 let mut b = ChunkBuilder::new();
5719 b.emit(Op::LoadInt(42), 1);
5720 b.emit(Op::AwkSrand(1), 1); b.emit(Op::Pop, 1);
5722 b.emit(Op::AwkRand, 1);
5723 b.build()
5724 };
5725 assert_eq!(run_native(chunk).to_float(), 0.582305908203125);
5726 }
5727
5728 #[test]
5729 fn awk_srand_no_arg_returns_prev_seed_without_host() {
5730 let chunk = {
5733 let mut b = ChunkBuilder::new();
5734 b.emit(Op::AwkSrand(0), 1);
5735 b.build()
5736 };
5737 assert_eq!(run_native(chunk).to_float(), 1.0);
5738 }
5739
5740 #[test]
5741 fn awk_rand_srand_free_fns_match_awkrs_lcg() {
5742 use crate::awk_host::{awk_rand, awk_srand};
5743 let mut seed: u64 = 1;
5744 assert_eq!(awk_rand(&mut seed), 0.51385498046875);
5745 let prev = awk_srand(&mut seed, Some(42));
5747 assert_eq!(prev, 1103527590.0);
5748 assert_eq!(awk_rand(&mut seed), 0.582305908203125);
5749 }
5750
5751 #[test]
5752 fn awk_systime_executes_natively_without_host() {
5753 let chunk = {
5756 let mut b = ChunkBuilder::new();
5757 b.emit(Op::AwkSystime, 1);
5758 b.build()
5759 };
5760 let v = run_native(chunk).to_float();
5761 assert!(v > 1_577_836_800.0, "systime() = {v} should be past 2020");
5762 }
5763
5764 #[test]
5765 fn awk_systime_free_fn_is_positive() {
5766 use crate::awk_host::awk_systime;
5767 assert!(awk_systime() > 1_577_836_800.0);
5768 }
5769
5770 #[test]
5771 fn awk_strtonum_executes_natively_without_host() {
5772 let chunk = {
5774 let mut b = ChunkBuilder::new();
5775 let s = b.add_constant(Value::str("0x10"));
5776 b.emit(Op::LoadConst(s), 1);
5777 b.emit(Op::AwkStrtonum, 1);
5778 b.build()
5779 };
5780 assert_eq!(run_native(chunk).to_float(), 16.0);
5781 }
5782
5783 #[test]
5784 fn awk_strtonum_octal_and_decimal_prefix_without_host() {
5785 let octal = {
5787 let mut b = ChunkBuilder::new();
5788 let s = b.add_constant(Value::str("010"));
5789 b.emit(Op::LoadConst(s), 1);
5790 b.emit(Op::AwkStrtonum, 1);
5791 b.build()
5792 };
5793 assert_eq!(run_native(octal).to_float(), 8.0);
5794 let prefix = {
5795 let mut b = ChunkBuilder::new();
5796 let s = b.add_constant(Value::str("42abc"));
5797 b.emit(Op::LoadConst(s), 1);
5798 b.emit(Op::AwkStrtonum, 1);
5799 b.build()
5800 };
5801 assert_eq!(run_native(prefix).to_float(), 42.0);
5802 }
5803
5804 #[test]
5805 fn awk_strtonum_free_fn_matches_awkrs_semantics() {
5806 use crate::awk_host::awk_strtonum;
5807 assert_eq!(awk_strtonum(""), 0.0);
5808 assert_eq!(awk_strtonum(" "), 0.0);
5809 assert_eq!(awk_strtonum("0x10"), 16.0);
5810 assert_eq!(awk_strtonum("0Xff"), 255.0);
5811 assert_eq!(awk_strtonum("010"), 8.0);
5812 assert_eq!(awk_strtonum("3.5"), 3.5);
5813 assert_eq!(awk_strtonum("0x"), 0.0);
5815 assert_eq!(awk_strtonum("0xzz"), 0.0);
5816 assert_eq!(awk_strtonum("+0x10"), 0.0);
5818 assert_eq!(awk_strtonum("nan"), 0.0);
5820 assert_eq!(awk_strtonum("inf"), 0.0);
5821 }
5822
5823 #[test]
5824 fn awk_builtin_substr_default_impl_is_posix() {
5825 use crate::awk_host::{AwkHost, DefaultAwkHost};
5826 let mut h = DefaultAwkHost;
5827 assert_eq!(h.substr(&Value::str("hello"), 2, Some(3)).to_str(), "ell");
5828 assert_eq!(h.substr(&Value::str("hello"), 2, None).to_str(), "ello");
5829 assert_eq!(h.substr(&Value::str("hello"), 0, Some(3)).to_str(), "he");
5830 assert_eq!(h.index(&Value::str("hello"), &Value::str("ll")), 3);
5831 assert_eq!(h.index(&Value::str("hello"), &Value::str("z")), 0);
5832 }
5833
5834 #[test]
5835 fn awk_op_range_is_disjoint_from_generic_extended_wide() {
5836 use crate::awk_builtins::*;
5837 assert!(!is_awk_op(0));
5838 assert!(!is_awk_op(AWK_OP_BASE - 1));
5839 assert!(is_awk_op(AWK_FIELD_GET));
5840 assert!(is_awk_op(AWK_ARRAY_LEN));
5841 assert!(!is_awk_op(AWK_OP_END));
5842 }
5843
5844 fn build_unary(op: Op, x: f64) -> crate::Chunk {
5851 let mut b = crate::ChunkBuilder::new();
5852 b.emit(Op::PushFrame, 1);
5853 b.emit(Op::LoadFloat(x), 1);
5854 b.emit(op, 1);
5855 b.build()
5856 }
5857
5858 fn build_binary(op: Op, a: f64, n: f64) -> crate::Chunk {
5859 let mut b = crate::ChunkBuilder::new();
5860 b.emit(Op::PushFrame, 1);
5861 b.emit(Op::LoadFloat(a), 1);
5862 b.emit(Op::LoadFloat(n), 1);
5863 b.emit(op, 1);
5864 b.build()
5865 }
5866
5867 #[test]
5868 fn awk_sqrt_jit_negative_warns_returns_nan() {
5869 let chunk = build_unary(Op::AwkSqrtJit, -4.0);
5870 let mut vm = VM::new(chunk);
5871 match vm.run() {
5872 VMResult::Ok(v) => assert!(v.to_float().is_nan(), "expected NaN, got {v:?}"),
5873 other => panic!("expected Ok(NaN), got {other:?}"),
5874 }
5875 }
5876
5877 #[test]
5878 fn awk_sqrt_jit_positive_returns_sqrt() {
5879 let chunk = build_unary(Op::AwkSqrtJit, 16.0);
5880 let mut vm = VM::new(chunk);
5881 match vm.run() {
5882 VMResult::Ok(v) => assert_eq!(v.to_float(), 4.0),
5883 other => panic!("expected Ok(4.0), got {other:?}"),
5884 }
5885 }
5886
5887 #[test]
5888 fn awk_log_jit_positive_returns_ln() {
5889 let chunk = build_unary(Op::AwkLogJit, std::f64::consts::E);
5890 let mut vm = VM::new(chunk);
5891 match vm.run() {
5892 VMResult::Ok(v) => assert!((v.to_float() - 1.0).abs() < 1e-10),
5893 other => panic!("expected Ok(~1.0), got {other:?}"),
5894 }
5895 }
5896
5897 #[test]
5898 fn awk_log_jit_negative_warns_returns_nan() {
5899 let chunk = build_unary(Op::AwkLogJit, -1.0);
5900 let mut vm = VM::new(chunk);
5901 match vm.run() {
5902 VMResult::Ok(v) => assert!(v.to_float().is_nan()),
5903 other => panic!("expected Ok(NaN), got {other:?}"),
5904 }
5905 }
5906
5907 #[test]
5908 fn awk_lshift_jit_computes_left_shift() {
5909 let chunk = build_binary(Op::AwkLshiftJit, 1.0, 4.0);
5910 let mut vm = VM::new(chunk);
5911 match vm.run() {
5912 VMResult::Ok(v) => assert_eq!(v.to_float(), 16.0, "1 << 4 == 16"),
5913 other => panic!("expected Ok(16.0), got {other:?}"),
5914 }
5915 }
5916
5917 #[test]
5918 fn awk_lshift_jit_negative_amount_errors() {
5919 let chunk = build_binary(Op::AwkLshiftJit, 1.0, -1.0);
5920 let mut vm = VM::new(chunk);
5921 match vm.run() {
5922 VMResult::Error(msg) => assert!(msg.contains("lshift"), "msg = {msg:?}"),
5923 other => panic!("expected Error, got {other:?}"),
5924 }
5925 }
5926
5927 #[test]
5928 fn awk_rshift_jit_computes_right_shift() {
5929 let chunk = build_binary(Op::AwkRshiftJit, 16.0, 2.0);
5930 let mut vm = VM::new(chunk);
5931 match vm.run() {
5932 VMResult::Ok(v) => assert_eq!(v.to_float(), 4.0, "16 >> 2 == 4"),
5933 other => panic!("expected Ok(4.0), got {other:?}"),
5934 }
5935 }
5936
5937 #[test]
5938 fn awk_compl_jit_negates_bits() {
5939 let chunk = build_unary(Op::AwkComplJit, 15.0);
5941 let mut vm = VM::new(chunk);
5942 match vm.run() {
5943 VMResult::Ok(v) => assert_eq!(v.to_float(), -16.0, "compl(15) == -16"),
5944 other => panic!("expected Ok(-16.0), got {other:?}"),
5945 }
5946 }
5947
5948 #[test]
5949 fn awk_compl_jit_negative_errors() {
5950 let chunk = build_unary(Op::AwkComplJit, -1.0);
5951 let mut vm = VM::new(chunk);
5952 match vm.run() {
5953 VMResult::Error(msg) => assert!(msg.contains("compl"), "msg = {msg:?}"),
5954 other => panic!("expected Error, got {other:?}"),
5955 }
5956 }
5957
5958 use std::sync::{Arc, Mutex};
5963
5964 #[test]
5965 fn output_sink_captures_print_and_println() {
5966 let mut b = ChunkBuilder::new();
5968 let a = b.add_constant(Value::str("a"));
5969 let bee = b.add_constant(Value::str("b"));
5970 b.emit(Op::LoadConst(a), 1);
5971 b.emit(Op::Print(1), 1);
5972 b.emit(Op::LoadConst(bee), 1);
5973 b.emit(Op::PrintLn(1), 1);
5974 let captured = Arc::new(Mutex::new(String::new()));
5975 let buf = Arc::clone(&captured);
5976 let mut vm = VM::new(b.build());
5977 vm.set_output_sink(Box::new(move |s: &str| buf.lock().unwrap().push_str(s)));
5978 vm.run();
5979 assert_eq!(*captured.lock().unwrap(), "ab\n");
5980 }
5981
5982 #[test]
5983 fn output_sink_receives_multi_arg_print_concatenated() {
5984 let mut b = ChunkBuilder::new();
5986 let x = b.add_constant(Value::str("x"));
5987 let y = b.add_constant(Value::str("y"));
5988 b.emit(Op::LoadConst(x), 1);
5989 b.emit(Op::LoadInt(1), 1);
5990 b.emit(Op::LoadConst(y), 1);
5991 b.emit(Op::Print(3), 1);
5992 let captured = Arc::new(Mutex::new(String::new()));
5993 let buf = Arc::clone(&captured);
5994 let mut vm = VM::new(b.build());
5995 vm.set_output_sink(Box::new(move |s: &str| buf.lock().unwrap().push_str(s)));
5996 vm.run();
5997 assert_eq!(*captured.lock().unwrap(), "x1y");
5998 }
5999
6000 #[test]
6001 fn input_source_feeds_readline_then_undef_at_eof() {
6002 let mut b = ChunkBuilder::new();
6004 b.emit(Op::ReadLine, 1); b.emit(Op::Pop, 1);
6006 b.emit(Op::ReadLine, 1); b.emit(Op::Pop, 1);
6008 b.emit(Op::ReadLine, 1); let mut pending = vec!["second".to_string(), "first".to_string()];
6010 let mut vm = VM::new(b.build());
6011 vm.set_input_source(Box::new(move || pending.pop()));
6012 match vm.run() {
6013 VMResult::Ok(Value::Undef) => {}
6014 other => panic!("expected Undef at EOF, got {other:?}"),
6015 }
6016 }
6017}