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 ext_handler: Option<ExtensionHandler>,
129 ext_wide_handler: Option<ExtensionWideHandler>,
131 builtin_table: Vec<Option<BuiltinHandler>>,
133 pub host: Option<Box<dyn ShellHost>>,
136 pub awk_host: Option<Box<dyn AwkHost>>,
141 awk_rand_seed: u64,
146 awk_signal: Option<u8>,
152 halted: bool,
154 #[cfg(feature = "jit")]
157 tracing_jit: bool,
158 #[cfg(feature = "jit")]
160 jit: JitCompiler,
161 #[cfg(feature = "jit")]
163 recorder: Option<TraceRecorder>,
164 #[cfg(feature = "jit")]
166 slot_buf: Vec<i64>,
167 #[cfg(feature = "jit")]
169 slot_kinds_buf: Vec<SlotKind>,
170 #[cfg(feature = "jit")]
177 deopt_info: DeoptInfo,
178 #[cfg(feature = "jit")]
184 block_eligible_cached: Option<bool>,
185}
186
187#[derive(Debug)]
189pub enum VMResult {
190 Ok(Value),
191 Halted,
193 Error(String),
195}
196
197impl VM {
198 pub fn new(chunk: Chunk) -> Self {
204 let num_names = chunk.names.len();
205 let mut frames = Vec::with_capacity(32);
206 frames.push(Frame {
207 return_ip: 0,
208 stack_base: 0,
209 slots: Vec::with_capacity(16),
210 });
211 Self {
212 stack: Vec::with_capacity(256),
213 frames,
214 globals: vec![Value::Undef; num_names],
215 ip: 0,
216 chunk,
217 last_status: 0,
218 ext_handler: None,
219 ext_wide_handler: None,
220 builtin_table: Vec::new(),
221 host: None,
222 awk_host: None,
223 awk_rand_seed: 1,
224 awk_signal: None,
225 halted: false,
226 #[cfg(feature = "jit")]
227 tracing_jit: false,
228 #[cfg(feature = "jit")]
229 jit: JitCompiler::new(),
230 #[cfg(feature = "jit")]
231 recorder: None,
232 #[cfg(feature = "jit")]
233 slot_buf: Vec::new(),
234 #[cfg(feature = "jit")]
235 slot_kinds_buf: Vec::new(),
236 #[cfg(feature = "jit")]
237 deopt_info: DeoptInfo::zeroed(),
238 #[cfg(feature = "jit")]
239 block_eligible_cached: None,
240 }
241 }
242
243 #[cfg(feature = "jit")]
252 pub fn enable_tracing_jit(&mut self) {
253 self.tracing_jit = true;
254 }
255
256 #[cfg(feature = "jit")]
259 pub fn disable_tracing_jit(&mut self) {
260 self.tracing_jit = false;
261 self.recorder = None;
262 }
263
264 pub fn reset(&mut self, chunk: Chunk) {
285 self.stack.clear();
286 self.frames.clear();
287 let num_names = chunk.names.len();
288 self.globals.clear();
289 self.globals.resize(num_names, Value::Undef);
290 self.frames.push(Frame {
291 return_ip: 0,
292 stack_base: 0,
293 slots: Vec::with_capacity(16),
294 });
295 self.ip = 0;
296 self.last_status = 0;
297 self.halted = false;
298 self.awk_rand_seed = 1;
299 self.chunk = chunk;
300 #[cfg(feature = "jit")]
301 {
302 self.recorder = None;
303 self.slot_buf.clear();
304 self.slot_kinds_buf.clear();
305 self.deopt_info = DeoptInfo::zeroed();
306 self.block_eligible_cached = None;
307 }
308 }
309
310 pub fn set_shell_host(&mut self, host: Box<dyn ShellHost>) {
312 self.host = Some(host);
313 }
314
315 pub fn set_awk_host(&mut self, host: Box<dyn AwkHost>) {
318 self.awk_host = Some(host);
319 }
320
321 pub fn awk_signal(&self) -> Option<u8> {
327 self.awk_signal
328 }
329
330 pub fn set_extension_handler(&mut self, handler: ExtensionHandler) {
332 self.ext_handler = Some(handler);
333 }
334
335 pub fn set_extension_wide_handler(&mut self, handler: ExtensionWideHandler) {
337 self.ext_wide_handler = Some(handler);
338 }
339
340 pub fn register_builtin(&mut self, id: u16, handler: BuiltinHandler) {
343 let idx = id as usize;
344 if idx >= self.builtin_table.len() {
345 self.builtin_table.resize(idx + 1, None);
346 }
347 self.builtin_table[idx] = Some(handler);
348 }
349
350 pub fn request_halt(&mut self) {
354 self.halted = true;
355 }
356
357 #[cfg(feature = "jit")]
370 #[inline]
371 fn refresh_slot_buffers(&mut self) {
372 let frame = match self.frames.last() {
373 Some(f) => f,
374 None => return,
375 };
376 let n = frame.slots.len();
377 match n {
378 0 => {
379 self.slot_buf.clear();
380 self.slot_kinds_buf.clear();
381 }
382 1 => {
383 let (i, kind) = match &frame.slots[0] {
384 Value::Int(v) => (*v, SlotKind::Int),
385 Value::Float(f) => (f.to_bits() as i64, SlotKind::Float),
386 Value::Bool(b) => (*b as i64, SlotKind::Int),
387 _ => (0, SlotKind::Int),
388 };
389 if self.slot_buf.is_empty() {
390 self.slot_buf.push(i);
391 self.slot_kinds_buf.push(kind);
392 } else {
393 self.slot_buf.truncate(1);
394 self.slot_buf[0] = i;
395 self.slot_kinds_buf.truncate(1);
396 self.slot_kinds_buf[0] = kind;
397 }
398 }
399 _ => {
400 self.slot_buf.clear();
401 self.slot_kinds_buf.clear();
402 self.slot_buf.reserve(n);
403 self.slot_kinds_buf.reserve(n);
404 for v in &frame.slots {
405 let (i, kind) = match v {
406 Value::Int(n) => (*n, SlotKind::Int),
407 Value::Float(f) => (f.to_bits() as i64, SlotKind::Float),
408 Value::Bool(b) => (*b as i64, SlotKind::Int),
409 _ => (0, SlotKind::Int),
410 };
411 self.slot_buf.push(i);
412 self.slot_kinds_buf.push(kind);
413 }
414 }
415 }
416 }
417
418 #[cfg(feature = "jit")]
426 #[inline]
427 fn write_slots_back(&mut self) {
428 let frame = match self.frames.last_mut() {
429 Some(f) => f,
430 None => return,
431 };
432 let n = frame.slots.len().min(self.slot_buf.len());
433 match n {
434 0 => {}
435 1 => match self.slot_kinds_buf.first() {
436 Some(SlotKind::Int) => frame.slots[0] = Value::Int(self.slot_buf[0]),
437 Some(SlotKind::Float) => {
438 frame.slots[0] = Value::Float(f64::from_bits(self.slot_buf[0] as u64));
439 }
440 None => {}
441 },
442 _ => {
443 for i in 0..n {
444 match self.slot_kinds_buf.get(i) {
445 Some(SlotKind::Int) => {
446 frame.slots[i] = Value::Int(self.slot_buf[i]);
447 }
448 Some(SlotKind::Float) => {
449 frame.slots[i] = Value::Float(f64::from_bits(self.slot_buf[i] as u64));
450 }
451 None => {}
452 }
453 }
454 }
455 }
456 }
457
458 #[cfg(feature = "jit")]
465 fn lookup_trace_for_backward(&mut self, anchor_ip: usize, fallthrough_ip: usize) -> usize {
466 self.refresh_slot_buffers();
467 let lookup = self.jit.trace_lookup(
468 &self.chunk,
469 anchor_ip,
470 &mut self.slot_buf,
471 &self.slot_kinds_buf,
472 &mut self.deopt_info,
473 );
474 match lookup {
475 TraceLookup::Ran { resume_ip } => {
476 self.write_slots_back();
477 self.materialize_deopt_frames();
478 self.chain_side_traces(anchor_ip, fallthrough_ip, resume_ip)
481 }
482 TraceLookup::StartRecording => {
483 self.recorder = Some(TraceRecorder {
487 record_anchor_ip: anchor_ip,
488 close_anchor_ip: anchor_ip,
489 fallthrough_ip,
490 ops: Vec::new(),
491 recorded_ips: Vec::new(),
492 slot_kinds_at_anchor: self.slot_kinds_buf.clone(),
493 entered_ips: Vec::new(),
494 aborted: false,
495 });
496 anchor_ip
497 }
498 TraceLookup::NotHot | TraceLookup::GuardMismatch | TraceLookup::Skip => anchor_ip,
499 }
500 }
501
502 #[cfg(feature = "jit")]
520 fn chain_side_traces(
521 &mut self,
522 main_anchor: usize,
523 main_fallthrough: usize,
524 first_resume: usize,
525 ) -> usize {
526 let mut current = first_resume;
527 if current == main_fallthrough {
528 return current;
529 }
530 let max_chain = self.jit.get_config().max_trace_chain;
531 for _ in 0..max_chain {
532 let chained_fallthrough = self
535 .jit
536 .trace_loop_anchors(&self.chunk, current)
537 .map(|(_, fallthrough)| fallthrough);
538
539 self.refresh_slot_buffers();
540 let lookup = self.jit.trace_lookup(
541 &self.chunk,
542 current,
543 &mut self.slot_buf,
544 &self.slot_kinds_buf,
545 &mut self.deopt_info,
546 );
547 match lookup {
548 TraceLookup::Ran { resume_ip } => {
549 self.write_slots_back();
550 self.materialize_deopt_frames();
551 current = resume_ip;
552 if Some(current) == chained_fallthrough {
553 return current;
554 }
555 }
556 TraceLookup::StartRecording => {
557 self.recorder = Some(TraceRecorder {
562 record_anchor_ip: current,
563 close_anchor_ip: main_anchor,
564 fallthrough_ip: main_fallthrough,
565 ops: Vec::new(),
566 recorded_ips: Vec::new(),
567 slot_kinds_at_anchor: self.slot_kinds_buf.clone(),
568 entered_ips: Vec::new(),
569 aborted: false,
570 });
571 return current;
572 }
573 _ => {
574 self.jit.trace_bump_side_exit(&self.chunk, main_anchor);
577 return current;
578 }
579 }
580 }
581 current
582 }
583
584 #[cfg(feature = "jit")]
593 fn materialize_deopt_frames(&mut self) {
594 let stack_count = self.deopt_info.stack_count;
598 for i in 0..stack_count {
599 let raw = self.deopt_info.stack_buf[i];
600 let v = match self.deopt_info.stack_kinds[i] {
601 crate::jit::STACK_KIND_FLOAT => Value::Float(f64::from_bits(raw as u64)),
602 _ => Value::Int(raw),
603 };
604 self.stack.push(v);
605 }
606 let count = self.deopt_info.frame_count;
608 if count == 0 {
609 return;
610 }
611 for i in 0..count {
612 let df = &self.deopt_info.frames[i];
613 let mut slots: Vec<Value> = Vec::with_capacity(df.slot_count);
614 for j in 0..df.slot_count {
615 slots.push(Value::Int(df.slots[j]));
616 }
617 self.frames.push(Frame {
618 return_ip: df.return_ip,
619 stack_base: self.stack.len(),
620 slots,
621 });
622 }
623 }
624
625 #[cfg(feature = "jit")]
629 fn finalize_recorder(&mut self) {
630 let Some(rec) = self.recorder.as_ref() else {
631 return;
632 };
633 let aborted = rec.aborted;
634 let close_anchor = rec.close_anchor_ip;
635 let record_anchor = rec.record_anchor_ip;
636 if !aborted && self.ip == close_anchor {
641 let r = self.recorder.take().unwrap();
642 if self.jit.is_trace_eligible(&r.ops, r.close_anchor_ip) {
643 let installed = self.jit.trace_install_with_kind(
647 &self.chunk,
648 r.record_anchor_ip,
649 r.close_anchor_ip,
650 r.fallthrough_ip,
651 &r.ops,
652 &r.recorded_ips,
653 &r.slot_kinds_at_anchor,
654 );
655 if !installed {
656 self.jit.trace_abort(&self.chunk, r.record_anchor_ip);
657 }
658 } else {
659 self.jit.trace_abort(&self.chunk, r.record_anchor_ip);
660 }
661 } else {
662 let _ = self.recorder.take();
665 self.jit.trace_abort(&self.chunk, record_anchor);
666 }
667 }
668
669 #[inline(always)]
675 pub fn push(&mut self, val: Value) {
676 self.stack.push(val);
677 }
678
679 #[inline(always)]
684 pub fn pop(&mut self) -> Value {
685 self.stack.pop().unwrap_or(Value::Undef)
686 }
687
688 #[inline(always)]
691 pub fn peek(&self) -> &Value {
692 self.stack.last().unwrap_or(&Value::Undef)
693 }
694
695 #[inline(always)]
699 fn arith_int_fast(&mut self, int_op: fn(i64, i64) -> i64, float_op: fn(f64, f64) -> f64) {
700 let len = self.stack.len();
701 if len >= 2 {
702 let b = &self.stack[len - 1];
704 let a = &self.stack[len - 2];
705 let result = match (a, b) {
706 (Value::Int(x), Value::Int(y)) => Value::Int(int_op(*x, *y)),
707 _ => Value::Float(float_op(a.to_float(), b.to_float())),
708 };
709 self.stack.truncate(len - 2);
710 self.stack.push(result);
711 }
712 }
713
714 #[inline(always)]
717 fn cmp_int_fast(&mut self, int_cmp: fn(i64, i64) -> bool, float_cmp: fn(f64, f64) -> bool) {
718 let len = self.stack.len();
719 if len >= 2 {
720 let b = &self.stack[len - 1];
721 let a = &self.stack[len - 2];
722 let result = match (a, b) {
723 (Value::Int(x), Value::Int(y)) => int_cmp(*x, *y),
724 _ => float_cmp(a.to_float(), b.to_float()),
725 };
726 self.stack.truncate(len - 2);
727 self.stack.push(Value::Bool(result));
728 }
729 }
730
731 pub fn run(&mut self) -> VMResult {
755 use crate::awk_builtins as ab;
756 self.awk_signal = None;
760 #[cfg(feature = "jit")]
772 if self.tracing_jit && self.frames.len() == 1 && self.ip == 0 {
773 let eligible = match self.block_eligible_cached {
774 Some(v) => v,
775 None => {
776 let v = self.jit.is_block_eligible(&self.chunk);
777 self.block_eligible_cached = Some(v);
778 v
779 }
780 };
781 if eligible {
782 self.refresh_slot_buffers();
783 if let Some(result_i64) = self.jit.try_run_block_kinded(
784 &self.chunk,
785 &mut self.slot_buf,
786 &self.slot_kinds_buf,
787 ) {
788 match crate::jit::take_awk_div_trap() {
793 1 => return VMResult::Error("division by zero attempted".to_string()),
794 2 => {
795 return VMResult::Error("division by zero attempted in `%'".to_string())
796 }
797 3 => {
798 return VMResult::Error(
799 "lshift: negative values are not allowed".to_string(),
800 )
801 }
802 4 => {
803 return VMResult::Error(
804 "rshift: negative values are not allowed".to_string(),
805 )
806 }
807 5 => {
808 return VMResult::Error(
809 "compl: negative value is not allowed".to_string(),
810 )
811 }
812 _ => {}
813 }
814 self.write_slots_back();
815 self.halted = true;
816 return VMResult::Ok(Value::Int(result_i64));
817 }
818 }
819 }
820
821 let ops = &self.chunk.ops as *const Vec<Op>;
822 let ops = unsafe { &*ops };
826
827 while self.ip < ops.len() && !self.halted {
828 let ip = self.ip;
830 self.ip += 1;
831
832 #[cfg(feature = "jit")]
839 let recorder_was_armed = self.recorder.is_some();
840 #[cfg(feature = "jit")]
841 if self.recorder.is_some() {
842 let cfg = self.jit.get_config();
843 let max_len = cfg.max_trace_len;
844 let max_inline_recursion = cfg.max_inline_recursion;
845 let cur_op = ops[ip].clone();
846 let resolved_entry = if let Op::Call(name_idx, _) = &cur_op {
849 self.chunk.find_sub(*name_idx)
850 } else {
851 None
852 };
853 let rec = self.recorder.as_mut().unwrap();
854 if !rec.aborted {
855 rec.ops.push(cur_op.clone());
856 rec.recorded_ips.push(ip);
857 if rec.ops.len() > max_len {
858 rec.aborted = true;
859 } else {
860 match cur_op {
863 Op::Call(_, _) => match resolved_entry {
864 Some(entry_ip) => {
865 let occurrences = rec
873 .entered_ips
874 .iter()
875 .filter(|&&ip| ip == entry_ip)
876 .count();
877 if occurrences >= max_inline_recursion {
878 rec.aborted = true;
879 } else {
880 rec.entered_ips.push(entry_ip);
881 }
882 }
883 None => rec.aborted = true, },
885 Op::Return | Op::ReturnValue => {
886 if rec.entered_ips.is_empty() {
887 rec.aborted = true; } else {
890 rec.entered_ips.pop();
891 }
892 }
893 Op::CallBuiltin(_, _) => rec.aborted = true,
894 Op::AwkFieldGet
900 | Op::AwkFieldSet
901 | Op::AwkNf
902 | Op::AwkSetRecord
903 | Op::AwkSpecialGet(_)
904 | Op::AwkSpecialSet(_)
905 | Op::AwkPrint(_)
906 | Op::AwkPrintf(_)
907 | Op::AwkSprintf(_)
908 | Op::AwkGetline(_)
909 | Op::AwkLength(_)
910 | Op::AwkSubstr(_)
911 | Op::AwkIndex
912 | Op::AwkSplit(_)
913 | Op::AwkSub(_)
914 | Op::AwkGsub(_)
915 | Op::AwkGensub(_)
916 | Op::AwkMatch
917 | Op::AwkToLower
918 | Op::AwkToUpper
919 | Op::AwkSqrt
920 | Op::AwkSin
921 | Op::AwkCos
922 | Op::AwkExp
923 | Op::AwkLog
924 | Op::AwkAtan2
925 | Op::AwkDiv
926 | Op::AwkMod
927 | Op::AwkDivJit
928 | Op::AwkModJit
929 | Op::AwkSqrtJit
930 | Op::AwkLogJit
931 | Op::AwkLshiftJit
932 | Op::AwkRshiftJit
933 | Op::AwkComplJit
934 | Op::AwkAnd(_)
935 | Op::AwkOr(_)
936 | Op::AwkXor(_)
937 | Op::AwkCompl
938 | Op::AwkLshift
939 | Op::AwkRshift
940 | Op::AwkStrtonum
941 | Op::AwkSystime
942 | Op::AwkRand
943 | Op::AwkSrand(_)
944 | Op::AwkStrftime(_)
945 | Op::AwkMktime(_)
946 | Op::AwkOrd
947 | Op::AwkChr
948 | Op::AwkMkbool
949 | Op::AwkIntdiv
950 | Op::AwkIntdiv0
951 | Op::AwkArrayGet(_)
952 | Op::AwkArraySet(_)
953 | Op::AwkArrayExists(_)
954 | Op::AwkArrayDelete(_)
955 | Op::AwkArrayClear(_)
956 | Op::AwkArrayLen(_) => rec.aborted = true,
957 Op::AwkSignal(_) => rec.aborted = true,
958 _ => {}
959 }
960 }
961 }
962 }
963
964 match &ops[ip] {
965 Op::Nop => {}
966
967 Op::LoadInt(n) => self.push(Value::Int(*n)),
969 Op::LoadFloat(f) => self.push(Value::Float(*f)),
970 Op::LoadConst(idx) => {
971 let val = match self.chunk.constants.get(*idx as usize) {
972 Some(Value::Int(n)) => Value::Int(*n),
973 Some(Value::Float(f)) => Value::Float(*f),
974 Some(Value::Bool(b)) => Value::Bool(*b),
975 Some(other) => other.clone(),
976 None => Value::Undef,
977 };
978 self.push(val);
979 }
980 Op::LoadTrue => self.push(Value::Bool(true)),
981 Op::LoadFalse => self.push(Value::Bool(false)),
982 Op::LoadUndef => self.push(Value::Undef),
983
984 Op::Pop => {
986 self.pop();
987 }
988 Op::Dup => {
989 let val = self.peek().clone();
990 self.push(val);
991 }
992 Op::Dup2 => {
993 let len = self.stack.len();
994 if len >= 2 {
995 let a = self.stack[len - 2].clone();
996 let b = self.stack[len - 1].clone();
997 self.push(a);
998 self.push(b);
999 }
1000 }
1001 Op::Swap => {
1002 let len = self.stack.len();
1003 if len >= 2 {
1004 self.stack.swap(len - 1, len - 2);
1005 }
1006 }
1007 Op::Rot => {
1008 let len = self.stack.len();
1009 if len >= 3 {
1010 self.stack.swap(len - 3, len - 2);
1012 self.stack.swap(len - 2, len - 1);
1013 }
1014 }
1015
1016 Op::GetVar(idx) => {
1018 let val = self.get_var(*idx);
1019 self.push(val);
1020 }
1021 Op::SetVar(idx) => {
1022 let val = self.pop();
1023 self.set_var(*idx, val);
1024 }
1025 Op::DeclareVar(idx) => {
1026 let val = self.pop();
1027 self.set_var(*idx, val);
1028 }
1029 Op::GetSlot(slot) => {
1030 let val = self.get_slot(*slot);
1031 self.push(val);
1032 }
1033 Op::SetSlot(slot) => {
1034 let val = self.pop();
1035 self.set_slot(*slot, val);
1036 }
1037 Op::SlotArrayGet(slot) => {
1038 let index = self.pop().to_int() as usize;
1039 let val = self.get_slot(*slot);
1040 let result = if let Value::Array(ref arr) = val {
1041 arr.get(index).cloned().unwrap_or(Value::Undef)
1042 } else {
1043 Value::Undef
1044 };
1045 self.push(result);
1046 }
1047 Op::SlotArraySet(slot) => {
1048 let index = self.pop().to_int() as usize;
1049 let val = self.pop();
1050 let arr_val = self.get_slot(*slot);
1051 if let Value::Array(mut arr) = arr_val {
1052 if index >= arr.len() {
1053 arr.resize(index + 1, Value::Undef);
1054 }
1055 arr[index] = val;
1056 self.set_slot(*slot, Value::Array(arr));
1057 }
1058 }
1059
1060 Op::Add => self.arith_int_fast(i64::wrapping_add, |a, b| a + b),
1062 Op::Sub => self.arith_int_fast(i64::wrapping_sub, |a, b| a - b),
1063 Op::Mul => self.arith_int_fast(i64::wrapping_mul, |a, b| a * b),
1064 Op::Div => {
1065 let b = self.pop();
1066 let a = self.pop();
1067 let divisor = b.to_float();
1068 self.push(if divisor == 0.0 {
1069 Value::Undef
1070 } else {
1071 Value::Float(a.to_float() / divisor)
1072 });
1073 }
1074 Op::Mod => self.arith_int_fast(|x, y| if y != 0 { x % y } else { 0 }, |a, b| a % b),
1075 Op::Pow => {
1076 let b = self.pop();
1077 let a = self.pop();
1078 self.push(Value::Float(a.to_float().powf(b.to_float())));
1079 }
1080 Op::Negate => {
1081 let val = self.pop();
1082 self.push(match val {
1083 Value::Int(n) => Value::Int(n.wrapping_neg()),
1084 _ => Value::Float(-val.to_float()),
1085 });
1086 }
1087 Op::Inc => {
1088 let val = self.pop();
1089 self.push(match val {
1090 Value::Int(n) => Value::Int(n.wrapping_add(1)),
1091 _ => Value::Int(val.to_int().wrapping_add(1)),
1092 });
1093 }
1094 Op::Dec => {
1095 let val = self.pop();
1096 self.push(match val {
1097 Value::Int(n) => Value::Int(n.wrapping_sub(1)),
1098 _ => Value::Int(val.to_int().wrapping_sub(1)),
1099 });
1100 }
1101
1102 Op::Concat => {
1104 let b = self.pop();
1105 let a = self.pop();
1106 let a_s = a.as_str_cow();
1107 let b_s = b.as_str_cow();
1108 let mut s = String::with_capacity(a_s.len() + b_s.len());
1109 s.push_str(&a_s);
1110 s.push_str(&b_s);
1111 self.push(Value::str(s));
1112 }
1113 Op::StringRepeat => {
1114 let count = self.pop().to_int();
1115 let s = self.pop().to_str();
1116 self.push(Value::str(s.repeat(count.max(0) as usize)));
1117 }
1118 Op::StringLen => {
1119 let s = self.pop();
1120 self.push(Value::Int(s.len() as i64));
1121 }
1122
1123 Op::NumEq => self.cmp_int_fast(|x, y| x == y, |a, b| a == b),
1125 Op::NumNe => self.cmp_int_fast(|x, y| x != y, |a, b| a != b),
1126 Op::NumLt => self.cmp_int_fast(|x, y| x < y, |a, b| a < b),
1127 Op::NumGt => self.cmp_int_fast(|x, y| x > y, |a, b| a > b),
1128 Op::NumLe => self.cmp_int_fast(|x, y| x <= y, |a, b| a <= b),
1129 Op::NumGe => self.cmp_int_fast(|x, y| x >= y, |a, b| a >= b),
1130 Op::Spaceship => {
1131 let len = self.stack.len();
1132 if len >= 2 {
1133 let b = &self.stack[len - 1];
1134 let a = &self.stack[len - 2];
1135 let result = match (a, b) {
1136 (Value::Int(x), Value::Int(y)) => x.cmp(y) as i64,
1137 _ => {
1138 let af = a.to_float();
1139 let bf = b.to_float();
1140 if af < bf {
1141 -1
1142 } else if af > bf {
1143 1
1144 } else {
1145 0
1146 }
1147 }
1148 };
1149 self.stack.truncate(len - 2);
1150 self.stack.push(Value::Int(result));
1151 }
1152 }
1153
1154 Op::StrEq => {
1156 let b = self.pop();
1157 let a = self.pop();
1158 self.push(Value::Bool(a.as_str_cow() == b.as_str_cow()));
1159 }
1160 Op::StrNe => {
1161 let b = self.pop();
1162 let a = self.pop();
1163 self.push(Value::Bool(a.as_str_cow() != b.as_str_cow()));
1164 }
1165 Op::StrLt => {
1166 let b = self.pop();
1167 let a = self.pop();
1168 self.push(Value::Bool(a.as_str_cow() < b.as_str_cow()));
1169 }
1170 Op::StrGt => {
1171 let b = self.pop();
1172 let a = self.pop();
1173 self.push(Value::Bool(a.as_str_cow() > b.as_str_cow()));
1174 }
1175 Op::StrLe => {
1176 let b = self.pop();
1177 let a = self.pop();
1178 self.push(Value::Bool(a.as_str_cow() <= b.as_str_cow()));
1179 }
1180 Op::StrGe => {
1181 let b = self.pop();
1182 let a = self.pop();
1183 self.push(Value::Bool(a.as_str_cow() >= b.as_str_cow()));
1184 }
1185 Op::StrCmp => {
1186 let b = self.pop();
1187 let a = self.pop();
1188 self.push(Value::Int(match a.as_str_cow().cmp(&b.as_str_cow()) {
1189 std::cmp::Ordering::Less => -1,
1190 std::cmp::Ordering::Equal => 0,
1191 std::cmp::Ordering::Greater => 1,
1192 }));
1193 }
1194
1195 Op::LogNot => {
1197 let val = self.pop();
1198 self.push(Value::Bool(!val.is_truthy()));
1199 }
1200 Op::LogAnd => {
1201 let b = self.pop();
1202 let a = self.pop();
1203 self.push(Value::Bool(a.is_truthy() && b.is_truthy()));
1204 }
1205 Op::LogOr => {
1206 let b = self.pop();
1207 let a = self.pop();
1208 self.push(Value::Bool(a.is_truthy() || b.is_truthy()));
1209 }
1210 Op::BitAnd => {
1211 let b = self.pop();
1212 let a = self.pop();
1213 self.push(Value::Int(a.to_int() & b.to_int()));
1214 }
1215 Op::BitOr => {
1216 let b = self.pop();
1217 let a = self.pop();
1218 self.push(Value::Int(a.to_int() | b.to_int()));
1219 }
1220 Op::BitXor => {
1221 let b = self.pop();
1222 let a = self.pop();
1223 self.push(Value::Int(a.to_int() ^ b.to_int()));
1224 }
1225 Op::BitNot => {
1226 let val = self.pop();
1227 self.push(Value::Int(!val.to_int()));
1228 }
1229 Op::Shl => {
1230 let b = self.pop();
1231 let a = self.pop();
1232 self.push(Value::Int(a.to_int() << (b.to_int() as u32 & 63)));
1233 }
1234 Op::Shr => {
1235 let b = self.pop();
1236 let a = self.pop();
1237 self.push(Value::Int(a.to_int() >> (b.to_int() as u32 & 63)));
1238 }
1239
1240 Op::Jump(target) => {
1242 let target = *target;
1243 #[cfg(feature = "jit")]
1244 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1245 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1246 } else {
1247 self.ip = target;
1248 }
1249 #[cfg(not(feature = "jit"))]
1250 {
1251 self.ip = target;
1252 }
1253 }
1254 Op::JumpIfTrue(target) => {
1255 let target = *target;
1256 if self.pop().is_truthy() {
1257 #[cfg(feature = "jit")]
1258 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1259 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1260 } else {
1261 self.ip = target;
1262 }
1263 #[cfg(not(feature = "jit"))]
1264 {
1265 self.ip = target;
1266 }
1267 }
1268 }
1269 Op::JumpIfFalse(target) => {
1270 let target = *target;
1271 if !self.pop().is_truthy() {
1272 #[cfg(feature = "jit")]
1273 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1274 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1275 } else {
1276 self.ip = target;
1277 }
1278 #[cfg(not(feature = "jit"))]
1279 {
1280 self.ip = target;
1281 }
1282 }
1283 }
1284 Op::JumpIfTrueKeep(target) => {
1285 let target = *target;
1286 if self.peek().is_truthy() {
1287 #[cfg(feature = "jit")]
1288 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1289 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1290 } else {
1291 self.ip = target;
1292 }
1293 #[cfg(not(feature = "jit"))]
1294 {
1295 self.ip = target;
1296 }
1297 }
1298 }
1299 Op::JumpIfFalseKeep(target) => {
1300 let target = *target;
1301 if !self.peek().is_truthy() {
1302 #[cfg(feature = "jit")]
1303 if self.tracing_jit && self.recorder.is_none() && target <= ip {
1304 self.ip = self.lookup_trace_for_backward(target, ip + 1);
1305 } else {
1306 self.ip = target;
1307 }
1308 #[cfg(not(feature = "jit"))]
1309 {
1310 self.ip = target;
1311 }
1312 }
1313 }
1314
1315 Op::Call(name_idx, argc) => {
1317 if let Some(entry_ip) = self.chunk.find_sub(*name_idx) {
1318 self.frames.push(Frame {
1319 return_ip: self.ip,
1320 stack_base: self.stack.len() - *argc as usize,
1321 slots: Vec::new(),
1322 });
1323 self.ip = entry_ip;
1324 } else {
1325 return VMResult::Error(format!(
1326 "undefined function: {}",
1327 self.chunk
1328 .names
1329 .get(*name_idx as usize)
1330 .map(|s| s.as_str())
1331 .unwrap_or("?")
1332 ));
1333 }
1334 }
1335 Op::Return => {
1336 if let Some(frame) = self.frames.pop() {
1337 self.stack.truncate(frame.stack_base);
1338 self.ip = frame.return_ip;
1339 } else {
1340 self.halted = true;
1341 }
1342 }
1343 Op::ReturnValue => {
1344 let val = self.pop();
1345 if let Some(frame) = self.frames.pop() {
1346 self.stack.truncate(frame.stack_base);
1347 self.ip = frame.return_ip;
1348 self.push(val);
1349 } else {
1350 self.halted = true;
1351 return VMResult::Ok(val);
1352 }
1353 }
1354
1355 Op::PushFrame => {
1357 self.frames.push(Frame {
1358 return_ip: self.ip,
1359 stack_base: self.stack.len(),
1360 slots: Vec::new(),
1361 });
1362 }
1363 Op::PopFrame => {
1364 if let Some(frame) = self.frames.pop() {
1365 self.stack.truncate(frame.stack_base);
1366 }
1367 }
1368
1369 Op::Print(n) => {
1371 let n = *n;
1372 let start = self.stack.len().saturating_sub(n as usize);
1373 use std::io::Write;
1374 let stdout = std::io::stdout();
1375 let mut lock = stdout.lock();
1376 for v in &self.stack[start..] {
1377 let _ = write!(lock, "{}", v.as_str_cow());
1378 }
1379 self.stack.truncate(start);
1380 }
1381 Op::PrintLn(n) => {
1382 let n = *n;
1383 let start = self.stack.len().saturating_sub(n as usize);
1384 use std::io::Write;
1385 let stdout = std::io::stdout();
1386 let mut lock = stdout.lock();
1387 for v in &self.stack[start..] {
1388 let _ = write!(lock, "{}", v.as_str_cow());
1389 }
1390 let _ = writeln!(lock);
1391 self.stack.truncate(start);
1392 }
1393 Op::ReadLine => {
1394 let mut line = String::new();
1395 let _ = std::io::stdin().read_line(&mut line);
1396 self.push(Value::str(line.trim_end_matches('\n')));
1397 }
1398
1399 Op::PreIncSlot(slot) => {
1401 let val = self.get_slot(*slot).to_int() + 1;
1402 self.set_slot(*slot, Value::Int(val));
1403 self.push(Value::Int(val));
1404 }
1405 Op::PreIncSlotVoid(slot) => {
1406 let val = self.get_slot(*slot).to_int() + 1;
1407 self.set_slot(*slot, Value::Int(val));
1408 }
1409 Op::SlotLtIntJumpIfFalse(slot, limit, target) => {
1410 if self.get_slot(*slot).to_int() >= *limit as i64 {
1411 self.ip = *target;
1412 }
1413 }
1414 Op::SlotIncLtIntJumpBack(slot, limit, target) => {
1415 let val = self.get_slot(*slot).to_int() + 1;
1416 self.set_slot(*slot, Value::Int(val));
1417 if val < *limit as i64 {
1418 self.ip = *target;
1419 }
1420 }
1421 Op::AccumSumLoop(sum_slot, i_slot, limit) => {
1422 let mut sum = self.get_slot(*sum_slot).to_int();
1423 let mut i = self.get_slot(*i_slot).to_int();
1424 let lim = *limit as i64;
1425 while i < lim {
1426 sum += i;
1427 i += 1;
1428 }
1429 self.set_slot(*sum_slot, Value::Int(sum));
1430 self.set_slot(*i_slot, Value::Int(i));
1431 }
1432 Op::AddAssignSlotVoid(a, b) => {
1433 let sum = self.get_slot(*a).to_int() + self.get_slot(*b).to_int();
1434 self.set_slot(*a, Value::Int(sum));
1435 }
1436 Op::PreDecSlot(slot) => {
1437 let val = self.get_slot(*slot).to_int() - 1;
1438 self.set_slot(*slot, Value::Int(val));
1439 self.push(Value::Int(val));
1440 }
1441 Op::PostIncSlot(slot) => {
1442 let old = self.get_slot(*slot).to_int();
1443 self.set_slot(*slot, Value::Int(old + 1));
1444 self.push(Value::Int(old));
1445 }
1446 Op::PostDecSlot(slot) => {
1447 let old = self.get_slot(*slot).to_int();
1448 self.set_slot(*slot, Value::Int(old - 1));
1449 self.push(Value::Int(old));
1450 }
1451
1452 Op::SetStatus => {
1454 self.last_status = self.pop().to_int() as i32;
1455 }
1456 Op::GetStatus => {
1457 self.push(Value::Status(self.last_status));
1458 }
1459
1460 Op::Extended(id, arg) => {
1462 let (id, arg) = (*id, *arg);
1463 if let Some(mut handler) = self.ext_handler.take() {
1464 handler(self, id, arg);
1465 self.ext_handler = Some(handler);
1466 }
1467 }
1468 Op::ExtendedWide(id, payload) => {
1469 let (id, payload) = (*id, *payload);
1470 if crate::awk_builtins::is_awk_op(id) {
1471 self.dispatch_awk(id, payload);
1472 } else if let Some(mut handler) = self.ext_wide_handler.take() {
1473 handler(self, id, payload);
1474 self.ext_wide_handler = Some(handler);
1475 }
1476 }
1477
1478 Op::GetArray(idx) => {
1480 let val = self.get_var(*idx);
1481 self.push(val);
1482 }
1483 Op::SetArray(idx) => {
1484 let val = self.pop();
1485 self.set_var(*idx, val);
1486 }
1487 Op::DeclareArray(idx) => {
1488 self.set_var(*idx, Value::Array(Vec::new()));
1489 }
1490 Op::ArrayGet(arr_idx) => {
1491 let index = self.pop().to_int() as usize;
1492 let idx = *arr_idx as usize;
1493 let val = if idx < self.globals.len() {
1494 if let Value::Array(ref arr) = self.globals[idx] {
1495 arr.get(index).cloned().unwrap_or(Value::Undef)
1496 } else {
1497 Value::Undef
1498 }
1499 } else {
1500 Value::Undef
1501 };
1502 self.push(val);
1503 }
1504 Op::ArraySet(arr_idx) => {
1505 let index = self.pop().to_int() as usize;
1506 let val = self.pop();
1507 let idx = *arr_idx as usize;
1508 if idx >= self.globals.len() {
1509 self.globals.resize(idx + 1, Value::Undef);
1510 }
1511 if let Value::Array(ref mut vec) = self.globals[idx] {
1512 if index >= vec.len() {
1513 vec.resize(index + 1, Value::Undef);
1514 }
1515 vec[index] = val;
1516 }
1517 }
1518 Op::ArrayPush(arr_idx) => {
1519 let val = self.pop();
1520 let idx = *arr_idx as usize;
1521 if idx >= self.globals.len() {
1522 self.globals.resize(idx + 1, Value::Undef);
1523 }
1524 if let Value::Array(ref mut vec) = self.globals[idx] {
1525 vec.push(val);
1526 }
1527 }
1528 Op::ArrayPop(arr_idx) => {
1529 let idx = *arr_idx as usize;
1530 let val = if idx < self.globals.len() {
1531 if let Value::Array(ref mut vec) = self.globals[idx] {
1532 vec.pop().unwrap_or(Value::Undef)
1533 } else {
1534 Value::Undef
1535 }
1536 } else {
1537 Value::Undef
1538 };
1539 self.push(val);
1540 }
1541 Op::ArrayShift(arr_idx) => {
1542 let idx = *arr_idx as usize;
1543 let val = if idx < self.globals.len() {
1544 if let Value::Array(ref mut vec) = self.globals[idx] {
1545 if vec.is_empty() {
1546 Value::Undef
1547 } else {
1548 vec.remove(0)
1549 }
1550 } else {
1551 Value::Undef
1552 }
1553 } else {
1554 Value::Undef
1555 };
1556 self.push(val);
1557 }
1558 Op::ArrayLen(arr_idx) => {
1559 let idx = *arr_idx as usize;
1560 let len = if idx < self.globals.len() {
1561 if let Value::Array(ref vec) = self.globals[idx] {
1562 vec.len() as i64
1563 } else {
1564 0
1565 }
1566 } else {
1567 0
1568 };
1569 self.push(Value::Int(len));
1570 }
1571 Op::MakeArray(n) => {
1572 let n = *n;
1573 let start = self.stack.len().saturating_sub(n as usize);
1574 let elements: Vec<Value> = self.stack.drain(start..).collect();
1575 self.push(Value::Array(elements));
1576 }
1577
1578 Op::GetHash(idx) => {
1580 let val = self.get_var(*idx);
1581 self.push(val);
1582 }
1583 Op::SetHash(idx) => {
1584 let val = self.pop();
1585 self.set_var(*idx, val);
1586 }
1587 Op::DeclareHash(idx) => {
1588 self.set_var(*idx, Value::Hash(std::collections::HashMap::new()));
1589 }
1590 Op::HashGet(hash_idx) => {
1591 let key_val = self.pop();
1592 let key = key_val.as_str_cow();
1593 let idx = *hash_idx as usize;
1594 let val = if idx < self.globals.len() {
1595 if let Value::Hash(ref map) = self.globals[idx] {
1596 map.get(key.as_ref()).cloned().unwrap_or(Value::Undef)
1597 } else {
1598 Value::Undef
1599 }
1600 } else {
1601 Value::Undef
1602 };
1603 self.push(val);
1604 }
1605 Op::HashSet(hash_idx) => {
1606 let key = self.pop().to_str();
1607 let val = self.pop();
1608 let idx = *hash_idx as usize;
1609 if idx >= self.globals.len() {
1610 self.globals.resize(idx + 1, Value::Undef);
1611 }
1612 if let Value::Hash(ref mut map) = self.globals[idx] {
1613 map.insert(key, val);
1614 }
1615 }
1616 Op::HashDelete(hash_idx) => {
1617 let key_val = self.pop();
1618 let key = key_val.as_str_cow();
1619 let idx = *hash_idx as usize;
1620 let val = if idx < self.globals.len() {
1621 if let Value::Hash(ref mut map) = self.globals[idx] {
1622 map.remove(key.as_ref()).unwrap_or(Value::Undef)
1623 } else {
1624 Value::Undef
1625 }
1626 } else {
1627 Value::Undef
1628 };
1629 self.push(val);
1630 }
1631 Op::HashExists(hash_idx) => {
1632 let key_val = self.pop();
1633 let key = key_val.as_str_cow();
1634 let idx = *hash_idx as usize;
1635 let val = if idx < self.globals.len() {
1636 if let Value::Hash(ref map) = self.globals[idx] {
1637 map.contains_key(key.as_ref())
1638 } else {
1639 false
1640 }
1641 } else {
1642 false
1643 };
1644 self.push(Value::Bool(val));
1645 }
1646 Op::HashKeys(hash_idx) => {
1647 let idx = *hash_idx as usize;
1648 let arr = if idx < self.globals.len() {
1649 if let Value::Hash(ref map) = self.globals[idx] {
1650 let mut keys = Vec::with_capacity(map.len());
1651 keys.extend(map.keys().map(|k| Value::str(k.as_str())));
1652 keys
1653 } else {
1654 Vec::new()
1655 }
1656 } else {
1657 Vec::new()
1658 };
1659 self.push(Value::Array(arr));
1660 }
1661 Op::HashValues(hash_idx) => {
1662 let idx = *hash_idx as usize;
1663 let arr = if idx < self.globals.len() {
1664 if let Value::Hash(ref map) = self.globals[idx] {
1665 let mut vals = Vec::with_capacity(map.len());
1666 vals.extend(map.values().cloned());
1667 vals
1668 } else {
1669 Vec::new()
1670 }
1671 } else {
1672 Vec::new()
1673 };
1674 self.push(Value::Array(arr));
1675 }
1676 Op::MakeHash(n) => {
1677 let n = *n;
1678 let start = self.stack.len().saturating_sub(n as usize);
1679 let pairs: Vec<Value> = self.stack.drain(start..).collect();
1680 let mut map = std::collections::HashMap::with_capacity(pairs.len() / 2);
1681 let mut iter = pairs.into_iter();
1682 while let Some(key) = iter.next() {
1683 if let Some(val) = iter.next() {
1684 map.insert(key.to_str(), val);
1685 }
1686 }
1687 self.push(Value::Hash(map));
1688 }
1689
1690 Op::Range => {
1692 let to = self.pop().to_int();
1693 let from = self.pop().to_int();
1694 let cap = (to - from + 1).max(0) as usize;
1695 let mut arr = Vec::with_capacity(cap);
1696 arr.extend((from..=to).map(Value::Int));
1697 self.push(Value::Array(arr));
1698 }
1699 Op::RangeStep => {
1700 let step = self.pop().to_int();
1701 let to = self.pop().to_int();
1702 let from = self.pop().to_int();
1703 let cap = if step > 0 {
1704 ((to - from) / step + 1).max(0) as usize
1705 } else if step < 0 {
1706 ((from - to) / (-step) + 1).max(0) as usize
1707 } else {
1708 0
1709 };
1710 let mut arr = Vec::with_capacity(cap);
1711 if step > 0 {
1712 let mut i = from;
1713 while i <= to {
1714 arr.push(Value::Int(i));
1715 i += step;
1716 }
1717 } else if step < 0 {
1718 let mut i = from;
1719 while i >= to {
1720 arr.push(Value::Int(i));
1721 i += step;
1722 }
1723 }
1724 self.push(Value::Array(arr));
1725 }
1726
1727 Op::TestFile(test_type) => {
1729 let test_type = *test_type;
1730 let path = self.pop().to_str();
1731 let result = match test_type {
1732 crate::op::file_test::EXISTS => std::path::Path::new(&path).exists(),
1733 crate::op::file_test::IS_FILE => std::path::Path::new(&path).is_file(),
1734 crate::op::file_test::IS_DIR => std::path::Path::new(&path).is_dir(),
1735 crate::op::file_test::IS_SYMLINK => {
1736 std::path::Path::new(&path).is_symlink()
1737 }
1738 crate::op::file_test::IS_READABLE | crate::op::file_test::IS_WRITABLE => {
1739 std::path::Path::new(&path).exists()
1740 }
1741 crate::op::file_test::IS_EXECUTABLE => {
1742 #[cfg(unix)]
1743 {
1744 use std::os::unix::fs::PermissionsExt;
1745 std::fs::metadata(&path)
1746 .map(|m| m.permissions().mode() & 0o111 != 0)
1747 .unwrap_or(false)
1748 }
1749 #[cfg(not(unix))]
1750 {
1751 std::path::Path::new(&path).exists()
1752 }
1753 }
1754 crate::op::file_test::IS_NONEMPTY => std::fs::metadata(&path)
1755 .map(|m| m.len() > 0)
1756 .unwrap_or(false),
1757 crate::op::file_test::IS_SOCKET => {
1758 #[cfg(unix)]
1759 {
1760 use std::os::unix::fs::FileTypeExt;
1761 std::fs::symlink_metadata(&path)
1762 .map(|m| m.file_type().is_socket())
1763 .unwrap_or(false)
1764 }
1765 #[cfg(not(unix))]
1766 {
1767 false
1768 }
1769 }
1770 crate::op::file_test::IS_FIFO => {
1771 #[cfg(unix)]
1772 {
1773 use std::os::unix::fs::FileTypeExt;
1774 std::fs::symlink_metadata(&path)
1775 .map(|m| m.file_type().is_fifo())
1776 .unwrap_or(false)
1777 }
1778 #[cfg(not(unix))]
1779 {
1780 false
1781 }
1782 }
1783 crate::op::file_test::IS_BLOCK_DEV => {
1784 #[cfg(unix)]
1785 {
1786 use std::os::unix::fs::FileTypeExt;
1787 std::fs::symlink_metadata(&path)
1788 .map(|m| m.file_type().is_block_device())
1789 .unwrap_or(false)
1790 }
1791 #[cfg(not(unix))]
1792 {
1793 false
1794 }
1795 }
1796 crate::op::file_test::IS_CHAR_DEV => {
1797 #[cfg(unix)]
1798 {
1799 use std::os::unix::fs::FileTypeExt;
1800 std::fs::symlink_metadata(&path)
1801 .map(|m| m.file_type().is_char_device())
1802 .unwrap_or(false)
1803 }
1804 #[cfg(not(unix))]
1805 {
1806 false
1807 }
1808 }
1809 _ => false,
1810 };
1811 self.push(Value::Bool(result));
1812 }
1813
1814 Op::Exec(argc) => {
1815 let argc = *argc;
1816 let start = self.stack.len().saturating_sub(argc as usize);
1817 let args: Vec<String> = self
1823 .stack
1824 .drain(start..)
1825 .flat_map(|v| match v {
1826 Value::Array(items) => {
1827 items.into_iter().map(|i| i.to_str()).collect::<Vec<_>>()
1828 }
1829 other => vec![other.to_str()],
1830 })
1831 .collect();
1832 if let Some(cmd) = args.first() {
1833 let name_idx = self.chunk.names.iter().position(|n| n == cmd);
1835 if let Some(name_idx) = name_idx {
1836 if let Some(entry_ip) = self.chunk.find_sub(name_idx as u16) {
1837 for arg in &args[1..] {
1839 self.push(Value::str(arg));
1840 }
1841 self.frames.push(Frame {
1843 return_ip: self.ip,
1844 stack_base: self.stack.len() - (args.len() - 1),
1845 slots: Vec::with_capacity(8),
1846 });
1847 self.ip = entry_ip;
1848 continue;
1849 }
1850 }
1851
1852 match cmd.as_str() {
1853 "true" => self.push(Value::Status(0)),
1854 "false" => self.push(Value::Status(1)),
1855 "echo" => {
1856 println!("{}", args[1..].join(" "));
1857 self.push(Value::Status(0));
1858 }
1859 "test" | "[" => {
1860 self.push(Value::Status(0));
1861 }
1862 _ => {
1863 let status = if let Some(h) = self.host.as_mut() {
1873 h.exec(args.clone())
1874 } else {
1875 use std::process::{Command, Stdio};
1876 Command::new(cmd)
1877 .args(&args[1..])
1878 .stdout(Stdio::inherit())
1879 .stderr(Stdio::inherit())
1880 .status()
1881 .map(|s| s.code().unwrap_or(1))
1882 .unwrap_or(127)
1883 };
1884 self.push(Value::Status(status));
1885 }
1886 }
1887 } else {
1888 self.push(Value::Status(0));
1889 }
1890 }
1891 Op::ExecBg(argc) => {
1892 let argc = *argc;
1893 let start = self.stack.len().saturating_sub(argc as usize);
1894 let args: Vec<String> = self
1896 .stack
1897 .drain(start..)
1898 .flat_map(|v| match v {
1899 Value::Array(items) => {
1900 items.into_iter().map(|i| i.to_str()).collect::<Vec<_>>()
1901 }
1902 other => vec![other.to_str()],
1903 })
1904 .collect();
1905 if let Some(cmd) = args.first() {
1906 if let Some(h) = self.host.as_mut() {
1915 let _ = h.exec_bg(args.clone());
1916 } else {
1917 use std::process::{Command, Stdio};
1918 let _ = Command::new(cmd)
1919 .args(&args[1..])
1920 .stdout(Stdio::null())
1921 .stderr(Stdio::null())
1922 .spawn();
1923 }
1924 }
1925 self.push(Value::Status(0));
1926 }
1927
1928 Op::PipelineBegin(n) => {
1930 let n = *n;
1931 if let Some(h) = self.host.as_mut() {
1932 h.pipeline_begin(n);
1933 }
1934 }
1935 Op::PipelineStage => {
1936 if let Some(h) = self.host.as_mut() {
1937 h.pipeline_stage();
1938 }
1939 }
1940 Op::PipelineEnd => {
1941 let status = if let Some(h) = self.host.as_mut() {
1942 h.pipeline_end()
1943 } else {
1944 self.last_status
1945 };
1946 self.last_status = status;
1947 self.push(Value::Status(status));
1948 }
1949 Op::SubshellBegin => {
1950 if let Some(h) = self.host.as_mut() {
1951 h.subshell_begin();
1952 }
1953 }
1954 Op::SubshellEnd => {
1955 if let Some(h) = self.host.as_mut() {
1956 if let Some(status) = h.subshell_end() {
1957 self.last_status = status;
1958 }
1959 }
1960 }
1961 Op::Redirect(fd, op) => {
1962 let fd = *fd;
1963 let op = *op;
1964 let target = self.pop().to_str();
1965 if let Some(h) = self.host.as_mut() {
1966 h.redirect(fd, op, &target);
1967 }
1968 }
1969 Op::HereDoc(idx) => {
1970 let content = self
1971 .chunk
1972 .constants
1973 .get(*idx as usize)
1974 .map(|v| v.to_str())
1975 .unwrap_or_default();
1976 if let Some(h) = self.host.as_mut() {
1977 h.heredoc(&content);
1978 }
1979 }
1980 Op::HereString => {
1981 let s = self.pop().to_str();
1982 if let Some(h) = self.host.as_mut() {
1983 h.herestring(&s);
1984 }
1985 }
1986 Op::CmdSubst(idx) => {
1987 let result = match self.chunk.sub_chunks.get(*idx as usize) {
1988 Some(sub) => {
1989 let sub_ref: *const Chunk = sub;
1991 let sub_ref = unsafe { &*sub_ref };
1993 if let Some(h) = self.host.as_mut() {
1994 h.cmd_subst(sub_ref)
1995 } else {
1996 String::new()
1997 }
1998 }
1999 None => String::new(),
2000 };
2001 self.push(Value::str(result));
2002 }
2003 Op::ProcessSubIn(idx) => {
2004 let result = match self.chunk.sub_chunks.get(*idx as usize) {
2005 Some(sub) => {
2006 let sub_ref: *const Chunk = sub;
2007 let sub_ref = unsafe { &*sub_ref };
2008 if let Some(h) = self.host.as_mut() {
2009 h.process_sub_in(sub_ref)
2010 } else {
2011 String::new()
2012 }
2013 }
2014 None => String::new(),
2015 };
2016 self.push(Value::str(result));
2017 }
2018 Op::ProcessSubOut(idx) => {
2019 let result = match self.chunk.sub_chunks.get(*idx as usize) {
2020 Some(sub) => {
2021 let sub_ref: *const Chunk = sub;
2022 let sub_ref = unsafe { &*sub_ref };
2023 if let Some(h) = self.host.as_mut() {
2024 h.process_sub_out(sub_ref)
2025 } else {
2026 String::new()
2027 }
2028 }
2029 None => String::new(),
2030 };
2031 self.push(Value::str(result));
2032 }
2033 Op::Glob | Op::GlobRecursive => {
2034 let recursive = matches!(&ops[ip], Op::GlobRecursive);
2035 let pat_val = self.pop();
2036 let pattern = pat_val.to_str();
2037 let matches: Vec<String> = if let Some(h) = self.host.as_mut() {
2038 h.glob(&pattern, recursive)
2039 } else {
2040 glob::glob(&pattern)
2041 .into_iter()
2042 .flat_map(|paths| paths.filter_map(|p| p.ok()))
2043 .map(|p| p.to_string_lossy().into_owned())
2044 .collect()
2045 };
2046 let arr: Vec<Value> = matches.into_iter().map(Value::str).collect();
2047 self.push(Value::Array(arr));
2048 }
2049 Op::TrapSet(idx) => {
2050 let sig = self.pop().to_str();
2052 if let Some(sub) = self.chunk.sub_chunks.get(*idx as usize) {
2053 let sub_ref: *const Chunk = sub;
2054 let sub_ref = unsafe { &*sub_ref };
2055 if let Some(h) = self.host.as_mut() {
2056 h.trap_set(&sig, sub_ref);
2057 }
2058 }
2059 }
2060 Op::TrapCheck => {
2061 if let Some(h) = self.host.as_mut() {
2062 h.trap_check();
2063 }
2064 }
2065 Op::TildeExpand => {
2066 let s = self.pop().to_str();
2067 let result = if let Some(h) = self.host.as_mut() {
2068 h.tilde_expand(&s)
2069 } else {
2070 s
2071 };
2072 self.push(Value::str(result));
2073 }
2074 Op::BraceExpand => {
2075 let s = self.pop().to_str();
2076 let result = if let Some(h) = self.host.as_mut() {
2077 h.brace_expand(&s)
2078 } else {
2079 vec![s]
2080 };
2081 let arr: Vec<Value> = result.into_iter().map(Value::str).collect();
2082 self.push(Value::Array(arr));
2083 }
2084 Op::WordSplit => {
2085 let s = self.pop().to_str();
2086 let result = if let Some(h) = self.host.as_mut() {
2087 h.word_split(&s)
2088 } else {
2089 s.split_whitespace().map(|w| w.to_string()).collect()
2090 };
2091 let arr: Vec<Value> = result.into_iter().map(Value::str).collect();
2092 self.push(Value::Array(arr));
2093 }
2094 Op::ExpandParam(modifier) => {
2095 let m = *modifier;
2101 let argc = match m {
2102 crate::op::param_mod::DEFAULT
2103 | crate::op::param_mod::ASSIGN
2104 | crate::op::param_mod::ERROR
2105 | crate::op::param_mod::ALTERNATE
2106 | crate::op::param_mod::STRIP_SHORT
2107 | crate::op::param_mod::STRIP_LONG
2108 | crate::op::param_mod::RSTRIP_SHORT
2109 | crate::op::param_mod::RSTRIP_LONG => 1,
2110 crate::op::param_mod::SUBST_FIRST
2111 | crate::op::param_mod::SUBST_ALL
2112 | crate::op::param_mod::SLICE => 2,
2113 _ => 0,
2114 };
2115 let mut args: Vec<Value> = Vec::with_capacity(argc);
2116 for _ in 0..argc {
2117 args.push(self.pop());
2118 }
2119 args.reverse();
2120 let name = self.pop().to_str();
2121 let result = if let Some(h) = self.host.as_mut() {
2122 h.expand_param(&name, m, &args)
2123 } else {
2124 Value::str("")
2125 };
2126 self.push(result);
2127 }
2128 Op::StrMatch => {
2129 let pat = self.pop().to_str();
2130 let s = self.pop().to_str();
2131 let result = if let Some(h) = self.host.as_mut() {
2132 h.str_match(&s, &pat)
2133 } else {
2134 s == pat
2135 };
2136 self.push(Value::Bool(result));
2137 }
2138 Op::RegexMatch => {
2139 let re = self.pop().to_str();
2140 let s = self.pop().to_str();
2141 let result = if let Some(h) = self.host.as_mut() {
2142 h.regex_match(&s, &re)
2143 } else {
2144 false
2145 };
2146 self.push(Value::Bool(result));
2147 }
2148 Op::WithRedirectsBegin(n) => {
2149 let n = *n;
2150 if let Some(h) = self.host.as_mut() {
2151 h.with_redirects_begin(n);
2152 }
2153 }
2154 Op::WithRedirectsEnd => {
2155 if let Some(h) = self.host.as_mut() {
2156 h.with_redirects_end();
2157 }
2158 }
2159 Op::CallFunction(name_idx, argc) => {
2160 let name = self
2161 .chunk
2162 .names
2163 .get(*name_idx as usize)
2164 .cloned()
2165 .unwrap_or_default();
2166 let argc = *argc as usize;
2167 let start = self.stack.len().saturating_sub(argc);
2168 let args: Vec<String> = self
2170 .stack
2171 .drain(start..)
2172 .flat_map(|v| match v {
2173 Value::Array(items) => {
2174 items.into_iter().map(|i| i.to_str()).collect::<Vec<_>>()
2175 }
2176 other => vec![other.to_str()],
2177 })
2178 .collect();
2179 let status = if let Some(h) = self.host.as_mut() {
2180 match h.call_function(&name, args.clone()) {
2181 Some(s) => s,
2182 None => {
2183 let mut full = Vec::with_capacity(args.len() + 1);
2184 full.push(name.clone());
2185 full.extend(args);
2186 h.exec(full)
2187 }
2188 }
2189 } else {
2190 let nidx = *name_idx;
2192 if let Some(entry_ip) = self.chunk.find_sub(nidx) {
2193 for arg in &args {
2194 self.push(Value::str(arg));
2195 }
2196 self.frames.push(Frame {
2197 return_ip: self.ip,
2198 stack_base: self.stack.len() - args.len(),
2199 slots: Vec::with_capacity(8),
2200 });
2201 self.ip = entry_ip;
2202 continue;
2203 }
2204 let mut full = Vec::with_capacity(args.len() + 1);
2205 full.push(name);
2206 full.extend(args);
2207 use std::process::Command;
2208 Command::new(&full[0])
2209 .args(&full[1..])
2210 .status()
2211 .map(|s| s.code().unwrap_or(1))
2212 .unwrap_or(127)
2213 };
2214 self.last_status = status;
2215 self.push(Value::Status(status));
2216 }
2217
2218 Op::ConcatConstLoop(const_idx, s_slot, i_slot, limit) => {
2220 let c_str = self
2221 .chunk
2222 .constants
2223 .get(*const_idx as usize)
2224 .map(|v| v.as_str_cow())
2225 .unwrap_or(std::borrow::Cow::Borrowed(""));
2226 let mut s = self.get_slot(*s_slot).to_str();
2227 let mut i = self.get_slot(*i_slot).to_int();
2228 let lim = *limit as i64;
2229 let iters = (lim - i).max(0) as usize;
2230 s.reserve(c_str.len() * iters);
2231 while i < lim {
2232 s.push_str(&c_str);
2233 i += 1;
2234 }
2235 self.set_slot(*s_slot, Value::str(s));
2236 self.set_slot(*i_slot, Value::Int(i));
2237 }
2238 Op::PushIntRangeLoop(arr_idx, i_slot, limit) => {
2239 let mut i = self.get_slot(*i_slot).to_int();
2240 let lim = *limit as i64;
2241 let arr = self.get_var(*arr_idx);
2242 let mut vec = if let Value::Array(v) = arr {
2243 v
2244 } else {
2245 Vec::new()
2246 };
2247 vec.reserve((lim - i).max(0) as usize);
2248 while i < lim {
2249 vec.push(Value::Int(i));
2250 i += 1;
2251 }
2252 self.set_var(*arr_idx, Value::Array(vec));
2253 self.set_slot(*i_slot, Value::Int(i));
2254 }
2255
2256 Op::MapBlock(_)
2258 | Op::GrepBlock(_)
2259 | Op::SortBlock(_)
2260 | Op::SortDefault
2261 | Op::ForEachBlock(_) => {}
2262
2263 Op::CallBuiltin(id, argc) => {
2265 let (id, argc) = (*id, *argc);
2266 if let Some(Some(handler)) = self.builtin_table.get(id as usize) {
2267 let result = handler(self, argc);
2268 self.push(result);
2269 }
2270 }
2271
2272 Op::AwkFieldGet => self.dispatch_awk(ab::AWK_FIELD_GET, 0),
2275 Op::AwkFieldSet => self.dispatch_awk(ab::AWK_FIELD_SET, 0),
2276 Op::AwkNf => self.dispatch_awk(ab::AWK_NF, 0),
2277 Op::AwkSetRecord => self.dispatch_awk(ab::AWK_SET_RECORD, 0),
2278 Op::AwkSpecialGet(n) => self.dispatch_awk(ab::AWK_SPECIAL_GET, *n as usize),
2279 Op::AwkSpecialSet(n) => self.dispatch_awk(ab::AWK_SPECIAL_SET, *n as usize),
2280 Op::AwkPrint(argc) => self.dispatch_awk(ab::AWK_PRINT, *argc as usize),
2281 Op::AwkPrintf(argc) => self.dispatch_awk(ab::AWK_PRINTF, *argc as usize),
2282 Op::AwkSprintf(argc) => self.dispatch_awk(ab::AWK_SPRINTF, *argc as usize),
2283 Op::AwkGetline(src) => self.dispatch_awk(ab::AWK_GETLINE, *src as usize),
2284 Op::AwkLength(argc) => self.dispatch_awk(ab::AWK_LENGTH, *argc as usize),
2285 Op::AwkSubstr(argc) => self.dispatch_awk(ab::AWK_SUBSTR, *argc as usize),
2286 Op::AwkIndex => self.dispatch_awk(ab::AWK_INDEX, 0),
2287 Op::AwkSplit(argc) => self.dispatch_awk(ab::AWK_SPLIT, *argc as usize),
2288 Op::AwkSub(argc) => self.dispatch_awk(ab::AWK_SUB, *argc as usize),
2289 Op::AwkGsub(argc) => self.dispatch_awk(ab::AWK_GSUB, *argc as usize),
2290 Op::AwkMatch => self.dispatch_awk(ab::AWK_MATCH, 0),
2291 Op::AwkToLower => self.dispatch_awk(ab::AWK_TOLOWER, 0),
2292 Op::AwkToUpper => self.dispatch_awk(ab::AWK_TOUPPER, 0),
2293 Op::AwkInt => self.dispatch_awk(ab::AWK_INT, 0),
2294 Op::AwkSqrt => self.dispatch_awk(ab::AWK_SQRT, 0),
2295 Op::AwkSin => self.dispatch_awk(ab::AWK_SIN, 0),
2296 Op::AwkCos => self.dispatch_awk(ab::AWK_COS, 0),
2297 Op::AwkExp => self.dispatch_awk(ab::AWK_EXP, 0),
2298 Op::AwkLog => self.dispatch_awk(ab::AWK_LOG, 0),
2299 Op::AwkAtan2 => self.dispatch_awk(ab::AWK_ATAN2, 0),
2300 Op::AwkDiv => {
2304 let b = self.pop();
2305 let a = self.pop();
2306 let divisor = b.to_float();
2307 if divisor == 0.0 {
2308 return VMResult::Error("division by zero attempted".to_string());
2309 }
2310 self.push(Value::Float(a.to_float() / divisor));
2311 }
2312 Op::AwkMod => {
2313 let b = self.pop();
2314 let a = self.pop();
2315 let divisor = b.to_float();
2316 if divisor == 0.0 {
2317 return VMResult::Error("division by zero attempted in `%'".to_string());
2318 }
2319 self.push(Value::Float(a.to_float() % divisor));
2320 }
2321 Op::AwkDivJit => {
2325 let b = self.pop();
2326 let a = self.pop();
2327 let divisor = b.to_float();
2328 if divisor == 0.0 {
2329 return VMResult::Error("division by zero attempted".to_string());
2330 }
2331 self.push(Value::Float(a.to_float() / divisor));
2332 }
2333 Op::AwkModJit => {
2334 let b = self.pop();
2335 let a = self.pop();
2336 let divisor = b.to_float();
2337 if divisor == 0.0 {
2338 return VMResult::Error("division by zero attempted in `%'".to_string());
2339 }
2340 self.push(Value::Float(a.to_float() % divisor));
2341 }
2342 Op::AwkSqrtJit => {
2347 let a = self.pop().to_float();
2348 if a < 0.0 {
2349 eprintln!("awk: warning: sqrt: received negative argument {a}");
2350 self.push(Value::Float(f64::NAN));
2351 } else {
2352 self.push(Value::Float(a.sqrt()));
2353 }
2354 }
2355 Op::AwkLogJit => {
2360 let a = self.pop().to_float();
2361 if a < 0.0 {
2362 eprintln!("awk: warning: log: received negative argument {a}");
2363 self.push(Value::Float(f64::NAN));
2364 } else {
2365 self.push(Value::Float(a.ln()));
2366 }
2367 }
2368 Op::AwkLshiftJit => {
2372 let n = self.pop().to_float();
2373 let a = self.pop().to_float();
2374 if a < 0.0 || n < 0.0 {
2375 return VMResult::Error(
2376 "lshift: negative values are not allowed".to_string(),
2377 );
2378 }
2379 let shifted = (a as i64).wrapping_shl((n as u32) & 0x3f);
2380 self.push(Value::Float(shifted as f64));
2381 }
2382 Op::AwkRshiftJit => {
2384 let n = self.pop().to_float();
2385 let a = self.pop().to_float();
2386 if a < 0.0 || n < 0.0 {
2387 return VMResult::Error(
2388 "rshift: negative values are not allowed".to_string(),
2389 );
2390 }
2391 let shifted = ((a as i64) as u64).wrapping_shr((n as u32) & 0x3f);
2392 self.push(Value::Float(shifted as f64));
2393 }
2394 Op::AwkComplJit => {
2398 let a = self.pop().to_float();
2399 if a < 0.0 {
2400 return VMResult::Error("compl: negative value is not allowed".to_string());
2401 }
2402 let v = !(a as i64);
2403 self.push(Value::Float(v as f64));
2404 }
2405 Op::AwkGetFieldNum(field_idx) => {
2410 let v = crate::jit::fusevm_jit_awk_get_field_num(*field_idx as i64);
2411 self.push(Value::Float(v));
2412 }
2413 Op::PowFloat => {
2414 let b = self.pop();
2415 let a = self.pop();
2416 self.push(Value::Float(a.to_float().powf(b.to_float())));
2417 }
2418 Op::SqrtFloat => {
2419 let a = self.pop();
2420 self.push(Value::Float(a.to_float().sqrt()));
2421 }
2422 Op::SinFloat => {
2423 let a = self.pop();
2424 self.push(Value::Float(a.to_float().sin()));
2425 }
2426 Op::CosFloat => {
2427 let a = self.pop();
2428 self.push(Value::Float(a.to_float().cos()));
2429 }
2430 Op::ExpFloat => {
2431 let a = self.pop();
2432 self.push(Value::Float(a.to_float().exp()));
2433 }
2434 Op::Atan2Float => {
2435 let x = self.pop();
2436 let y = self.pop();
2437 self.push(Value::Float(y.to_float().atan2(x.to_float())));
2438 }
2439 Op::LogFloat => {
2440 let a = self.pop();
2441 self.push(Value::Float(a.to_float().ln()));
2442 }
2443 Op::AbsFloat => {
2444 let a = self.pop();
2445 self.push(Value::Float(a.to_float().abs()));
2446 }
2447 Op::TruncInt => {
2448 let a = self.pop();
2449 self.push(Value::Int(a.to_int()));
2450 }
2451 Op::CeilFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().ceil())); }
2452 Op::FloorFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().floor())); }
2453 Op::TruncFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().trunc())); }
2454 Op::RoundFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().round_ties_even())); }
2455 Op::TanFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().tan())); }
2456 Op::AsinFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().asin())); }
2457 Op::AcosFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().acos())); }
2458 Op::AtanFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().atan())); }
2459 Op::SinhFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().sinh())); }
2460 Op::CoshFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().cosh())); }
2461 Op::TanhFloat => { let a = self.pop(); self.push(Value::Float(a.to_float().tanh())); }
2462 Op::Log2Float => { let a = self.pop(); self.push(Value::Float(a.to_float().log2())); }
2463 Op::Log10Float => { let a = self.pop(); self.push(Value::Float(a.to_float().log10())); }
2464 Op::AbsInt => { let a = self.pop(); self.push(Value::Int(a.to_int().wrapping_abs())); }
2465 Op::GcdInt => {
2466 let b = self.pop().to_int().unsigned_abs();
2467 let a = self.pop().to_int().unsigned_abs();
2468 let mut x = a; let mut y = b;
2469 while y != 0 { let t = x % y; x = y; y = t; }
2470 self.push(Value::Int(x as i64));
2471 }
2472 Op::LcmInt => {
2473 let b = self.pop().to_int().unsigned_abs();
2474 let a = self.pop().to_int().unsigned_abs();
2475 if a == 0 || b == 0 { self.push(Value::Int(0)); } else {
2476 let mut x = a; let mut y = b;
2477 while y != 0 { let t = x % y; x = y; y = t; }
2478 let prod = (a / x).saturating_mul(b);
2479 self.push(Value::Int(prod.min(i64::MAX as u64) as i64));
2480 }
2481 }
2482 Op::TimeInt => {
2483 let secs = std::time::SystemTime::now()
2484 .duration_since(std::time::UNIX_EPOCH)
2485 .map(|d| d.as_secs() as i64)
2486 .unwrap_or(0);
2487 self.push(Value::Int(secs));
2488 }
2489 Op::AwkArrayGet(n) => self.dispatch_awk(ab::AWK_ARRAY_GET, *n as usize),
2490 Op::AwkArraySet(n) => self.dispatch_awk(ab::AWK_ARRAY_SET, *n as usize),
2491 Op::AwkArrayExists(n) => self.dispatch_awk(ab::AWK_ARRAY_EXISTS, *n as usize),
2492 Op::AwkArrayDelete(n) => self.dispatch_awk(ab::AWK_ARRAY_DELETE, *n as usize),
2493 Op::AwkArrayClear(n) => self.dispatch_awk(ab::AWK_ARRAY_CLEAR, *n as usize),
2494 Op::AwkArrayLen(n) => self.dispatch_awk(ab::AWK_ARRAY_LEN, *n as usize),
2495 Op::AwkAnd(argc) => self.dispatch_awk(ab::AWK_AND, *argc as usize),
2496 Op::AwkOr(argc) => self.dispatch_awk(ab::AWK_OR, *argc as usize),
2497 Op::AwkXor(argc) => self.dispatch_awk(ab::AWK_XOR, *argc as usize),
2498 Op::AwkCompl => self.dispatch_awk(ab::AWK_COMPL, 0),
2499 Op::AwkLshift => self.dispatch_awk(ab::AWK_LSHIFT, 0),
2500 Op::AwkRshift => self.dispatch_awk(ab::AWK_RSHIFT, 0),
2501 Op::AwkStrtonum => self.dispatch_awk(ab::AWK_STRTONUM, 0),
2502 Op::AwkSystime => self.dispatch_awk(ab::AWK_SYSTIME, 0),
2503 Op::AwkRand => self.dispatch_awk(ab::AWK_RAND, 0),
2504 Op::AwkSrand(argc) => self.dispatch_awk(ab::AWK_SRAND, *argc as usize),
2505 Op::AwkStrftime(argc) => self.dispatch_awk(ab::AWK_STRFTIME, *argc as usize),
2506 Op::AwkMktime(argc) => self.dispatch_awk(ab::AWK_MKTIME, *argc as usize),
2507 Op::AwkOrd => self.dispatch_awk(ab::AWK_ORD, 1),
2508 Op::AwkChr => self.dispatch_awk(ab::AWK_CHR, 1),
2509 Op::AwkMkbool => self.dispatch_awk(ab::AWK_MKBOOL, 1),
2510 Op::AwkIntdiv => self.dispatch_awk(ab::AWK_INTDIV, 2),
2511 Op::AwkIntdiv0 => self.dispatch_awk(ab::AWK_INTDIV0, 2),
2512 Op::AwkGensub(argc) => self.dispatch_awk(ab::AWK_GENSUB, *argc as usize),
2513 Op::AwkSignal(code) => {
2514 self.awk_signal = Some(*code);
2517 self.halted = true;
2518 }
2519 }
2520
2521 #[cfg(feature = "jit")]
2534 if recorder_was_armed && self.recorder.is_some() {
2535 let aborted = self.recorder.as_ref().map_or(false, |r| r.aborted);
2536 let close_ip = self
2539 .recorder
2540 .as_ref()
2541 .map(|r| r.close_anchor_ip)
2542 .unwrap_or(0);
2543 let was_jump = matches!(
2544 &ops[ip],
2545 Op::Jump(_)
2546 | Op::JumpIfTrue(_)
2547 | Op::JumpIfFalse(_)
2548 | Op::JumpIfTrueKeep(_)
2549 | Op::JumpIfFalseKeep(_)
2550 );
2551 let landed_at_anchor = self.ip == close_ip;
2552 if aborted || (was_jump && landed_at_anchor) {
2553 self.finalize_recorder();
2554 }
2555 }
2556 }
2557
2558 if let Some(val) = self.stack.pop() {
2559 VMResult::Ok(val)
2560 } else {
2561 VMResult::Halted
2562 }
2563 }
2564
2565 fn dispatch_awk(&mut self, id: u16, payload: usize) {
2578 use crate::awk_builtins as ab;
2579 use crate::awk_host::AwkLvalue;
2580
2581 let mut host = match self.awk_host.take() {
2585 Some(h) => h,
2586 None => {
2587 self.dispatch_awk_stub(id, payload);
2588 return;
2589 }
2590 };
2591
2592 macro_rules! pop_n {
2594 ($n:expr) => {{
2595 let n = $n;
2596 let mut v: Vec<Value> = (0..n).map(|_| self.pop()).collect();
2597 v.reverse();
2598 v
2599 }};
2600 }
2601 let name_at = |vm: &Self, idx: usize| -> String {
2602 vm.chunk.names.get(idx).cloned().unwrap_or_default()
2603 };
2604
2605 match id {
2606 ab::AWK_FIELD_GET => {
2607 let i = self.pop().to_int();
2608 let v = host.field_get(i);
2609 self.push(v);
2610 }
2611 ab::AWK_FIELD_SET => {
2612 let i = self.pop().to_int();
2613 let v = self.pop();
2614 host.field_set(i, v);
2615 }
2616 ab::AWK_NF => {
2617 let n = host.nf();
2618 self.push(Value::Int(n));
2619 }
2620 ab::AWK_SET_RECORD => {
2621 let v = self.pop();
2622 host.set_record(v);
2623 }
2624 ab::AWK_SPECIAL_GET => {
2625 let name = name_at(self, payload);
2626 let v = host.special_get(&name);
2627 self.push(v);
2628 }
2629 ab::AWK_SPECIAL_SET => {
2630 let name = name_at(self, payload);
2631 let v = self.pop();
2632 host.special_set(&name, v);
2633 }
2634 ab::AWK_PRINT => {
2635 let args = pop_n!(payload);
2636 host.print(&args);
2637 }
2638 ab::AWK_PRINTF => {
2639 let mut args = pop_n!(payload);
2640 let fmt = if args.is_empty() {
2641 String::new()
2642 } else {
2643 args.remove(0).to_str()
2644 };
2645 host.printf(&fmt, &args);
2646 }
2647 ab::AWK_SPRINTF => {
2648 let mut args = pop_n!(payload);
2649 let fmt = if args.is_empty() {
2650 String::new()
2651 } else {
2652 args.remove(0).to_str()
2653 };
2654 let v = host.sprintf(&fmt, &args);
2655 self.push(v);
2656 }
2657 ab::AWK_GETLINE => {
2658 let operand = match payload {
2660 ab::getline_source::FILE
2661 | ab::getline_source::FILE_VAR
2662 | ab::getline_source::CMD
2663 | ab::getline_source::CMD_VAR => Some(self.pop().to_str()),
2664 _ => None,
2665 };
2666 let status = host.getline(payload, operand.as_deref(), None);
2667 self.push(Value::Int(status));
2668 }
2669 ab::AWK_LENGTH => {
2670 let arg = if payload == 0 { None } else { Some(self.pop()) };
2671 let n = host.length(arg.as_ref());
2672 self.push(Value::Int(n));
2673 }
2674 ab::AWK_SUBSTR => {
2675 let args = pop_n!(payload);
2676 let s = args.first().cloned().unwrap_or(Value::str(""));
2677 let m = args.get(1).map(|v| v.to_int()).unwrap_or(1);
2678 let n = args.get(2).map(|v| v.to_int());
2679 let v = host.substr(&s, m, n);
2680 self.push(v);
2681 }
2682 ab::AWK_INDEX => {
2683 let t = self.pop();
2684 let s = self.pop();
2685 let r = host.index(&s, &t);
2686 self.push(Value::Int(r));
2687 }
2688 ab::AWK_SPLIT => {
2689 let args = pop_n!(payload);
2690 let s = args.first().cloned().unwrap_or(Value::str(""));
2691 let arr = args.get(1).map(|v| v.to_str()).unwrap_or_default();
2692 let fs = args.get(2);
2693 let n = host.split(&s, &arr, fs);
2694 self.push(Value::Int(n));
2695 }
2696 ab::AWK_SUB | ab::AWK_GSUB => {
2697 let args = pop_n!(payload);
2701 let re = args.first().cloned().unwrap_or(Value::str(""));
2702 let repl = args.get(1).cloned().unwrap_or(Value::str(""));
2703 let target = args
2704 .get(2)
2705 .map(|v| AwkLvalue::Var(v.to_str()))
2706 .unwrap_or(AwkLvalue::Field(0));
2707 let n = if id == ab::AWK_SUB {
2708 host.sub(&re, &repl, &target)
2709 } else {
2710 host.gsub(&re, &repl, &target)
2711 };
2712 self.push(Value::Int(n));
2713 }
2714 ab::AWK_MATCH => {
2715 let re = self.pop();
2716 let s = self.pop();
2717 let r = host.match_re(&s, &re);
2718 self.push(Value::Int(r));
2719 }
2720 ab::AWK_GENSUB => {
2721 let args = pop_n!(payload);
2723 let re = args.first().cloned().unwrap_or(Value::str(""));
2724 let repl = args.get(1).cloned().unwrap_or(Value::str(""));
2725 let how = args.get(2).cloned().unwrap_or(Value::str("g"));
2726 let target = args.get(3);
2727 let v = host.gensub(&re, &repl, &how, target);
2728 self.push(v);
2729 }
2730 ab::AWK_TOLOWER => {
2731 let s = self.pop();
2732 let v = host.tolower(&s);
2733 self.push(v);
2734 }
2735 ab::AWK_TOUPPER => {
2736 let s = self.pop();
2737 let v = host.toupper(&s);
2738 self.push(v);
2739 }
2740 ab::AWK_INT => {
2741 let x = self.pop();
2742 let v = host.int(&x);
2743 self.push(v);
2744 }
2745 ab::AWK_SQRT => {
2746 let x = self.pop();
2747 let v = host.sqrt(&x);
2748 self.push(v);
2749 }
2750 ab::AWK_SIN => {
2751 let x = self.pop();
2752 let v = host.sin(&x);
2753 self.push(v);
2754 }
2755 ab::AWK_COS => {
2756 let x = self.pop();
2757 let v = host.cos(&x);
2758 self.push(v);
2759 }
2760 ab::AWK_EXP => {
2761 let x = self.pop();
2762 let v = host.exp(&x);
2763 self.push(v);
2764 }
2765 ab::AWK_LOG => {
2766 let x = self.pop();
2767 let v = host.log(&x);
2768 self.push(v);
2769 }
2770 ab::AWK_ATAN2 => {
2771 let x = self.pop();
2772 let y = self.pop();
2773 let v = host.atan2(&y, &x);
2774 self.push(v);
2775 }
2776 ab::AWK_AND => {
2777 let args = pop_n!(payload);
2778 let v = host.and(&args);
2779 self.push(v);
2780 }
2781 ab::AWK_OR => {
2782 let args = pop_n!(payload);
2783 let v = host.or(&args);
2784 self.push(v);
2785 }
2786 ab::AWK_XOR => {
2787 let args = pop_n!(payload);
2788 let v = host.xor(&args);
2789 self.push(v);
2790 }
2791 ab::AWK_COMPL => {
2792 let v = self.pop();
2793 let r = host.compl(&v);
2794 self.push(r);
2795 }
2796 ab::AWK_LSHIFT => {
2797 let n = self.pop();
2798 let v = self.pop();
2799 let r = host.lshift(&v, &n);
2800 self.push(r);
2801 }
2802 ab::AWK_RSHIFT => {
2803 let n = self.pop();
2804 let v = self.pop();
2805 let r = host.rshift(&v, &n);
2806 self.push(r);
2807 }
2808 ab::AWK_STRTONUM => {
2809 let s = self.pop();
2810 let r = host.strtonum(&s);
2811 self.push(r);
2812 }
2813 ab::AWK_SYSTIME => {
2814 let r = host.systime();
2815 self.push(r);
2816 }
2817 ab::AWK_RAND => {
2818 let r = crate::awk_host::awk_rand(&mut self.awk_rand_seed);
2819 self.push(Value::Float(r));
2820 }
2821 ab::AWK_SRAND => {
2822 let n = if payload >= 1 {
2823 Some(self.pop().to_float() as u32 as u64)
2824 } else {
2825 None
2826 };
2827 let r = crate::awk_host::awk_srand(&mut self.awk_rand_seed, n);
2828 self.push(Value::Float(r));
2829 }
2830 ab::AWK_STRFTIME => {
2831 let args = pop_n!(payload);
2832 let r = host.strftime(&args);
2833 self.push(r);
2834 }
2835 ab::AWK_MKTIME => {
2836 let args = pop_n!(payload);
2837 let r = host.mktime(&args);
2838 self.push(r);
2839 }
2840 ab::AWK_ORD => {
2841 let a = self.pop();
2842 let r = host.ord(&a);
2843 self.push(r);
2844 }
2845 ab::AWK_CHR => {
2846 let a = self.pop();
2847 let r = host.chr(&a);
2848 self.push(r);
2849 }
2850 ab::AWK_MKBOOL => {
2851 let a = self.pop();
2852 let r = host.mkbool(&a);
2853 self.push(r);
2854 }
2855 ab::AWK_INTDIV => {
2856 let b = self.pop();
2857 let a = self.pop();
2858 let r = host.intdiv(&a, &b);
2859 self.push(r);
2860 }
2861 ab::AWK_INTDIV0 => {
2862 let b = self.pop();
2863 let a = self.pop();
2864 let r = host.intdiv0(&a, &b);
2865 self.push(r);
2866 }
2867 ab::AWK_ARRAY_GET => {
2868 let name = name_at(self, payload);
2869 let key = self.pop();
2870 let v = host.array_get(&name, &key);
2871 self.push(v);
2872 }
2873 ab::AWK_ARRAY_SET => {
2874 let name = name_at(self, payload);
2875 let key = self.pop();
2876 let v = self.pop();
2877 host.array_set(&name, &key, v);
2878 }
2879 ab::AWK_ARRAY_EXISTS => {
2880 let name = name_at(self, payload);
2881 let key = self.pop();
2882 let b = host.array_exists(&name, &key);
2883 self.push(Value::Bool(b));
2884 }
2885 ab::AWK_ARRAY_DELETE => {
2886 let name = name_at(self, payload);
2887 let key = self.pop();
2888 host.array_delete(&name, &key);
2889 }
2890 ab::AWK_ARRAY_CLEAR => {
2891 let name = name_at(self, payload);
2892 host.array_clear(&name);
2893 }
2894 ab::AWK_ARRAY_LEN => {
2895 let name = name_at(self, payload);
2896 let n = host.array_len(&name);
2897 self.push(Value::Int(n));
2898 }
2899 _ => self.push(Value::Undef),
2902 }
2903
2904 self.awk_host = Some(host);
2905 }
2906
2907 fn dispatch_awk_stub(&mut self, id: u16, payload: usize) {
2911 use crate::awk_builtins as ab;
2912 use crate::awk_host::{
2913 awk_canon_nan, awk_chr, awk_compl, awk_fold_and, awk_fold_or, awk_fold_xor, awk_index,
2914 awk_int, awk_intdiv, awk_intdiv0, awk_length, awk_lshift, awk_mkbool, awk_mktime,
2915 awk_ord, awk_rand, awk_rshift, awk_srand, awk_strftime, awk_strtonum, awk_substr,
2916 awk_systime, awk_tolower, awk_toupper,
2917 };
2918 match id {
2919 ab::AWK_FIELD_GET => {
2921 self.pop();
2922 self.push(Value::str(""));
2923 }
2924 ab::AWK_LENGTH if payload > 0 => {
2928 let s = self.pop();
2929 self.push(Value::Int(awk_length(Some(&s))));
2930 }
2931 ab::AWK_NF | ab::AWK_LENGTH | ab::AWK_ARRAY_LEN => {
2932 self.push(Value::Int(0));
2933 }
2934 ab::AWK_SPECIAL_GET => self.push(Value::Undef),
2935 ab::AWK_SPRINTF => {
2936 for _ in 0..payload {
2937 self.pop();
2938 }
2939 self.push(Value::str(""));
2940 }
2941 ab::AWK_SUBSTR => {
2945 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
2946 args.reverse();
2947 let s = args.first().cloned().unwrap_or(Value::str(""));
2948 let m = args.get(1).map(|v| v.to_int()).unwrap_or(1);
2949 let n = args.get(2).map(|v| v.to_int());
2950 self.push(awk_substr(&s, m, n));
2951 }
2952 ab::AWK_TOLOWER => {
2953 let s = self.pop();
2954 self.push(awk_tolower(&s));
2955 }
2956 ab::AWK_TOUPPER => {
2957 let s = self.pop();
2958 self.push(awk_toupper(&s));
2959 }
2960 ab::AWK_INT => {
2963 let x = self.pop();
2964 self.push(awk_int(&x));
2965 }
2966 ab::AWK_SQRT => {
2967 let x = self.pop();
2968 self.push(Value::Float(x.to_float().sqrt()));
2969 }
2970 ab::AWK_SIN => {
2971 let x = self.pop();
2972 self.push(Value::Float(awk_canon_nan(x.to_float().sin())));
2973 }
2974 ab::AWK_COS => {
2975 let x = self.pop();
2976 self.push(Value::Float(awk_canon_nan(x.to_float().cos())));
2977 }
2978 ab::AWK_EXP => {
2979 let x = self.pop();
2980 self.push(Value::Float(awk_canon_nan(x.to_float().exp())));
2981 }
2982 ab::AWK_LOG => {
2983 let x = self.pop();
2984 self.push(Value::Float(x.to_float().ln()));
2985 }
2986 ab::AWK_ATAN2 => {
2987 let x = self.pop();
2988 let y = self.pop();
2989 self.push(Value::Float(awk_canon_nan(
2990 y.to_float().atan2(x.to_float()),
2991 )));
2992 }
2993 ab::AWK_AND => {
2995 let args: Vec<Value> = {
2996 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
2997 v.reverse();
2998 v
2999 };
3000 self.push(Value::Int(awk_fold_and(&args)));
3001 }
3002 ab::AWK_OR => {
3003 let args: Vec<Value> = {
3004 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
3005 v.reverse();
3006 v
3007 };
3008 self.push(Value::Int(awk_fold_or(&args)));
3009 }
3010 ab::AWK_XOR => {
3011 let args: Vec<Value> = {
3012 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
3013 v.reverse();
3014 v
3015 };
3016 self.push(Value::Int(awk_fold_xor(&args)));
3017 }
3018 ab::AWK_COMPL => {
3019 let v = self.pop();
3020 self.push(Value::Int(awk_compl(&v)));
3021 }
3022 ab::AWK_LSHIFT => {
3023 let n = self.pop();
3024 let v = self.pop();
3025 self.push(Value::Int(awk_lshift(&v, &n)));
3026 }
3027 ab::AWK_RSHIFT => {
3028 let n = self.pop();
3029 let v = self.pop();
3030 self.push(Value::Int(awk_rshift(&v, &n)));
3031 }
3032 ab::AWK_STRTONUM => {
3033 let s = self.pop();
3034 self.push(Value::Float(awk_strtonum(&s.to_str())));
3035 }
3036 ab::AWK_SYSTIME => {
3037 self.push(Value::Float(awk_systime()));
3038 }
3039 ab::AWK_RAND => {
3040 let r = awk_rand(&mut self.awk_rand_seed);
3041 self.push(Value::Float(r));
3042 }
3043 ab::AWK_SRAND => {
3044 let n = if payload >= 1 {
3045 Some(self.pop().to_float() as u32 as u64)
3046 } else {
3047 None
3048 };
3049 let r = awk_srand(&mut self.awk_rand_seed, n);
3050 self.push(Value::Float(r));
3051 }
3052 ab::AWK_STRFTIME => {
3053 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
3054 args.reverse();
3055 self.push(awk_strftime(&args));
3056 }
3057 ab::AWK_MKTIME => {
3058 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
3059 args.reverse();
3060 self.push(awk_mktime(&args));
3061 }
3062 ab::AWK_ORD => {
3063 let a = self.pop();
3064 self.push(awk_ord(&a));
3065 }
3066 ab::AWK_CHR => {
3067 let a = self.pop();
3068 self.push(awk_chr(&a));
3069 }
3070 ab::AWK_MKBOOL => {
3071 let a = self.pop();
3072 self.push(awk_mkbool(&a));
3073 }
3074 ab::AWK_INTDIV => {
3075 let b = self.pop();
3076 let a = self.pop();
3077 self.push(awk_intdiv(&a, &b));
3078 }
3079 ab::AWK_INTDIV0 => {
3080 let b = self.pop();
3081 let a = self.pop();
3082 self.push(awk_intdiv0(&a, &b));
3083 }
3084 ab::AWK_INDEX => {
3085 let t = self.pop();
3086 let s = self.pop();
3087 self.push(Value::Int(awk_index(&s, &t)));
3088 }
3089 ab::AWK_MATCH => {
3090 self.pop();
3091 self.pop();
3092 self.push(Value::Int(0));
3093 }
3094 ab::AWK_SPLIT | ab::AWK_SUB | ab::AWK_GSUB => {
3095 for _ in 0..payload {
3096 self.pop();
3097 }
3098 self.push(Value::Int(0));
3099 }
3100 ab::AWK_GENSUB => {
3101 for _ in 0..payload {
3102 self.pop();
3103 }
3104 self.push(Value::str(""));
3105 }
3106 ab::AWK_GETLINE => {
3107 if matches!(
3108 payload,
3109 ab::getline_source::FILE
3110 | ab::getline_source::FILE_VAR
3111 | ab::getline_source::CMD
3112 | ab::getline_source::CMD_VAR
3113 ) {
3114 self.pop();
3115 }
3116 self.push(Value::Int(0));
3117 }
3118 ab::AWK_ARRAY_GET => {
3119 self.pop();
3120 self.push(Value::str(""));
3121 }
3122 ab::AWK_ARRAY_EXISTS => {
3123 self.pop();
3124 self.push(Value::Bool(false));
3125 }
3126 ab::AWK_FIELD_SET | ab::AWK_ARRAY_SET => {
3128 self.pop();
3129 self.pop();
3130 }
3131 ab::AWK_SET_RECORD | ab::AWK_SPECIAL_SET | ab::AWK_ARRAY_DELETE => {
3132 self.pop();
3133 }
3134 ab::AWK_PRINT | ab::AWK_PRINTF => {
3135 for _ in 0..payload {
3136 self.pop();
3137 }
3138 }
3139 ab::AWK_ARRAY_CLEAR => {}
3140 _ => {}
3141 }
3142 }
3143
3144 fn get_var(&self, idx: u16) -> Value {
3145 self.globals
3146 .get(idx as usize)
3147 .cloned()
3148 .unwrap_or(Value::Undef)
3149 }
3150
3151 fn set_var(&mut self, idx: u16, val: Value) {
3152 let idx = idx as usize;
3153 if idx >= self.globals.len() {
3154 self.globals.resize(idx + 1, Value::Undef);
3155 }
3156 self.globals[idx] = val;
3157 }
3158
3159 pub fn get_slot(&self, slot: u16) -> Value {
3166 self.frames
3167 .last()
3168 .and_then(|f| f.slots.get(slot as usize))
3169 .cloned()
3170 .unwrap_or(Value::Undef)
3171 }
3172
3173 pub fn set_slot(&mut self, slot: u16, val: Value) {
3179 if let Some(frame) = self.frames.last_mut() {
3180 let idx = slot as usize;
3181 if idx >= frame.slots.len() {
3182 frame.slots.resize(idx + 1, Value::Undef);
3183 }
3184 frame.slots[idx] = val;
3185 }
3186 }
3187}
3188
3189pub struct VMPool {
3218 pool: Vec<VM>,
3219}
3220
3221impl VMPool {
3222 pub fn new() -> Self {
3224 Self { pool: Vec::new() }
3225 }
3226
3227 pub fn with_capacity(cap: usize) -> Self {
3229 Self {
3230 pool: Vec::with_capacity(cap),
3231 }
3232 }
3233
3234 pub fn acquire(&mut self, chunk: Chunk) -> VM {
3239 if let Some(mut vm) = self.pool.pop() {
3240 vm.reset(chunk);
3241 vm
3242 } else {
3243 VM::new(chunk)
3244 }
3245 }
3246
3247 pub fn release(&mut self, vm: VM) {
3250 self.pool.push(vm);
3251 }
3252
3253 pub fn with<F, T>(&mut self, chunk: Chunk, f: F) -> T
3256 where
3257 F: FnOnce(&mut VM) -> T,
3258 {
3259 let mut vm = self.acquire(chunk);
3260 let r = f(&mut vm);
3261 self.release(vm);
3262 r
3263 }
3264
3265 pub fn len(&self) -> usize {
3268 self.pool.len()
3269 }
3270
3271 pub fn is_empty(&self) -> bool {
3273 self.pool.is_empty()
3274 }
3275}
3276
3277impl Default for VMPool {
3278 fn default() -> Self {
3279 Self::new()
3280 }
3281}
3282
3283#[cfg(test)]
3284mod tests {
3285 use super::*;
3286 use crate::chunk::ChunkBuilder;
3287
3288 #[test]
3289 fn test_arithmetic() {
3290 let mut b = ChunkBuilder::new();
3291 b.emit(Op::LoadInt(10), 1);
3292 b.emit(Op::LoadInt(32), 1);
3293 b.emit(Op::Add, 1);
3294 let mut vm = VM::new(b.build());
3295 match vm.run() {
3296 VMResult::Ok(Value::Int(42)) => {}
3297 other => panic!("expected Int(42), got {:?}", other),
3298 }
3299 }
3300
3301 #[test]
3302 fn test_jump() {
3303 let mut b = ChunkBuilder::new();
3304 b.emit(Op::LoadInt(1), 1);
3305 b.emit(Op::Jump(3), 1);
3306 b.emit(Op::LoadInt(999), 1); b.emit(Op::LoadInt(2), 1);
3309 b.emit(Op::Add, 1);
3310 let mut vm = VM::new(b.build());
3311 match vm.run() {
3312 VMResult::Ok(Value::Int(3)) => {}
3313 other => panic!("expected Int(3), got {:?}", other),
3314 }
3315 }
3316
3317 #[test]
3318 fn test_fused_sum_loop() {
3319 let mut b = ChunkBuilder::new();
3321 b.emit(Op::PushFrame, 1);
3322 b.emit(Op::LoadInt(0), 1);
3323 b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
3325 b.emit(Op::SetSlot(1), 1); b.emit(Op::AccumSumLoop(0, 1, 100), 1);
3327 b.emit(Op::GetSlot(0), 1);
3328
3329 let mut vm = VM::new(b.build());
3330 match vm.run() {
3331 VMResult::Ok(Value::Int(4950)) => {}
3332 other => panic!("expected Int(4950), got {:?}", other),
3333 }
3334 }
3335
3336 #[test]
3337 fn test_function_call() {
3338 let mut b = ChunkBuilder::new();
3339 let double_name = b.add_name("double");
3340
3341 b.emit(Op::LoadInt(21), 1);
3343 b.emit(Op::Call(double_name, 1), 1);
3344 let end_jump = b.emit(Op::Jump(0), 1); let double_ip = b.current_pos();
3348 b.add_sub_entry(double_name, double_ip);
3349 b.emit(Op::LoadInt(2), 2);
3350 b.emit(Op::Mul, 2);
3351 b.emit(Op::ReturnValue, 2);
3352
3353 b.patch_jump(end_jump, b.current_pos());
3354
3355 let mut vm = VM::new(b.build());
3356 match vm.run() {
3357 VMResult::Ok(Value::Int(42)) => {}
3358 other => panic!("expected Int(42), got {:?}", other),
3359 }
3360 }
3361
3362 #[test]
3363 fn test_builtin_cache() {
3364 let mut b = ChunkBuilder::new();
3365 b.emit(Op::LoadInt(10), 1);
3366 b.emit(Op::CallBuiltin(0, 1), 1);
3367 let mut vm = VM::new(b.build());
3368 vm.register_builtin(0, |vm, _argc| {
3369 let val = vm.pop();
3370 Value::Int(val.to_int() * 2)
3371 });
3372 match vm.run() {
3373 VMResult::Ok(Value::Int(20)) => {}
3374 other => panic!("expected Int(20), got {:?}", other),
3375 }
3376 }
3377
3378 fn run_one(ops: Vec<Op>) -> VMResult {
3381 let mut b = ChunkBuilder::new();
3382 for op in ops {
3383 b.emit(op, 1);
3384 }
3385 VM::new(b.build()).run()
3386 }
3387
3388 fn expect_int(ops: Vec<Op>, want: i64) {
3389 match run_one(ops) {
3390 VMResult::Ok(Value::Int(n)) => assert_eq!(n, want),
3391 other => panic!("expected Int({}), got {:?}", want, other),
3392 }
3393 }
3394
3395 fn expect_bool(ops: Vec<Op>, want: bool) {
3396 match run_one(ops) {
3397 VMResult::Ok(Value::Bool(b)) => assert_eq!(b, want),
3398 other => panic!("expected Bool({}), got {:?}", want, other),
3399 }
3400 }
3401
3402 #[test]
3405 fn arithmetic_sub_mul_div_mod() {
3406 expect_int(vec![Op::LoadInt(20), Op::LoadInt(8), Op::Sub], 12);
3407 expect_int(vec![Op::LoadInt(6), Op::LoadInt(7), Op::Mul], 42);
3408 expect_int(vec![Op::LoadInt(20), Op::LoadInt(3), Op::Mod], 2);
3409 match run_one(vec![Op::LoadInt(20), Op::LoadInt(5), Op::Div]) {
3411 VMResult::Ok(Value::Float(f)) => assert!((f - 4.0).abs() < 1e-9),
3412 VMResult::Ok(Value::Int(4)) => {} other => panic!("got {:?}", other),
3414 }
3415 }
3416
3417 #[test]
3418 fn arithmetic_negate_and_inc_dec() {
3419 expect_int(vec![Op::LoadInt(5), Op::Negate], -5);
3420 expect_int(vec![Op::LoadInt(5), Op::Inc], 6);
3421 expect_int(vec![Op::LoadInt(5), Op::Dec], 4);
3422 }
3423
3424 #[test]
3425 fn arithmetic_pow_returns_float() {
3426 match run_one(vec![Op::LoadInt(3), Op::LoadInt(4), Op::Pow]) {
3427 VMResult::Ok(Value::Float(f)) => assert!((f - 81.0).abs() < 1e-9),
3428 VMResult::Ok(Value::Int(81)) => {} other => panic!("got {:?}", other),
3430 }
3431 }
3432
3433 #[test]
3436 fn num_comparisons_produce_booleans() {
3437 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(1), Op::NumEq], true);
3438 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumEq], false);
3439 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumLt], true);
3440 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumGt], false);
3441 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumLe], true);
3442 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumGe], true);
3443 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumNe], false);
3444 }
3445
3446 #[test]
3447 fn spaceship_returns_neg_zero_pos() {
3448 expect_int(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Spaceship], -1);
3449 expect_int(vec![Op::LoadInt(2), Op::LoadInt(2), Op::Spaceship], 0);
3450 expect_int(vec![Op::LoadInt(3), Op::LoadInt(2), Op::Spaceship], 1);
3451 }
3452
3453 #[test]
3454 fn string_comparisons() {
3455 let mut b = ChunkBuilder::new();
3456 let a = b.add_constant(Value::str("alpha"));
3457 let z = b.add_constant(Value::str("beta"));
3458 b.emit(Op::LoadConst(a), 1);
3459 b.emit(Op::LoadConst(z), 1);
3460 b.emit(Op::StrLt, 1);
3461 let mut vm = VM::new(b.build());
3462 assert!(matches!(vm.run(), VMResult::Ok(Value::Bool(true))));
3463 }
3464
3465 #[test]
3466 fn string_eq_and_ne() {
3467 let mut b = ChunkBuilder::new();
3468 let s1 = b.add_constant(Value::str("hi"));
3469 let s2 = b.add_constant(Value::str("hi"));
3470 b.emit(Op::LoadConst(s1), 1);
3471 b.emit(Op::LoadConst(s2), 1);
3472 b.emit(Op::StrEq, 1);
3473 assert!(matches!(
3474 VM::new(b.build()).run(),
3475 VMResult::Ok(Value::Bool(true))
3476 ));
3477 }
3478
3479 #[test]
3482 fn pop_discards_top() {
3483 expect_int(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Pop], 1);
3485 }
3486
3487 #[test]
3488 fn dup_duplicates_top() {
3489 expect_int(vec![Op::LoadInt(5), Op::Dup, Op::Add], 10);
3491 }
3492
3493 #[test]
3494 fn swap_exchanges_top_two() {
3495 expect_int(vec![Op::LoadInt(10), Op::LoadInt(3), Op::Swap, Op::Sub], -7);
3499 }
3500
3501 #[test]
3502 fn dup2_duplicates_top_two_values() {
3503 expect_int(
3505 vec![Op::LoadInt(3), Op::LoadInt(4), Op::Dup2, Op::Add, Op::Add],
3506 11,
3507 );
3508 }
3509
3510 #[test]
3513 fn log_not_inverts_truthiness() {
3514 expect_bool(vec![Op::LoadInt(0), Op::LogNot], true);
3515 expect_bool(vec![Op::LoadInt(1), Op::LogNot], false);
3516 expect_bool(vec![Op::LoadTrue, Op::LogNot], false);
3517 expect_bool(vec![Op::LoadFalse, Op::LogNot], true);
3518 }
3519
3520 #[test]
3521 fn bitwise_ops() {
3522 expect_int(
3523 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitAnd],
3524 0b1000,
3525 );
3526 expect_int(
3527 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitOr],
3528 0b1110,
3529 );
3530 expect_int(
3531 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitXor],
3532 0b0110,
3533 );
3534 expect_int(vec![Op::LoadInt(1), Op::LoadInt(4), Op::Shl], 16);
3535 expect_int(vec![Op::LoadInt(64), Op::LoadInt(2), Op::Shr], 16);
3536 }
3537
3538 #[test]
3539 fn bit_not_inverts_bits() {
3540 expect_int(vec![Op::LoadInt(0), Op::BitNot], -1);
3541 }
3542
3543 #[test]
3546 fn concat_joins_strings() {
3547 let mut b = ChunkBuilder::new();
3548 let h = b.add_constant(Value::str("hello "));
3549 let w = b.add_constant(Value::str("world"));
3550 b.emit(Op::LoadConst(h), 1);
3551 b.emit(Op::LoadConst(w), 1);
3552 b.emit(Op::Concat, 1);
3553 match VM::new(b.build()).run() {
3554 VMResult::Ok(v) => assert_eq!(v.to_str(), "hello world"),
3555 other => panic!("got {:?}", other),
3556 }
3557 }
3558
3559 #[test]
3560 fn string_repeat_op() {
3561 let mut b = ChunkBuilder::new();
3562 let s = b.add_constant(Value::str("ab"));
3563 b.emit(Op::LoadConst(s), 1);
3564 b.emit(Op::LoadInt(3), 1);
3565 b.emit(Op::StringRepeat, 1);
3566 match VM::new(b.build()).run() {
3567 VMResult::Ok(v) => assert_eq!(v.to_str(), "ababab"),
3568 other => panic!("got {:?}", other),
3569 }
3570 }
3571
3572 #[test]
3573 fn string_len_returns_int() {
3574 let mut b = ChunkBuilder::new();
3575 let s = b.add_constant(Value::str("abcd"));
3576 b.emit(Op::LoadConst(s), 1);
3577 b.emit(Op::StringLen, 1);
3578 match VM::new(b.build()).run() {
3579 VMResult::Ok(Value::Int(4)) => {}
3580 other => panic!("got {:?}", other),
3581 }
3582 }
3583
3584 #[test]
3587 fn load_true_false_undef() {
3588 assert!(matches!(
3589 run_one(vec![Op::LoadTrue]),
3590 VMResult::Ok(Value::Bool(true))
3591 ));
3592 assert!(matches!(
3593 run_one(vec![Op::LoadFalse]),
3594 VMResult::Ok(Value::Bool(false))
3595 ));
3596 assert!(matches!(
3597 run_one(vec![Op::LoadUndef]),
3598 VMResult::Ok(Value::Undef)
3599 ));
3600 }
3601
3602 #[test]
3603 fn load_const_string() {
3604 let mut b = ChunkBuilder::new();
3605 let c = b.add_constant(Value::str("xyz"));
3606 b.emit(Op::LoadConst(c), 1);
3607 match VM::new(b.build()).run() {
3608 VMResult::Ok(v) => assert_eq!(v.to_str(), "xyz"),
3609 other => panic!("got {:?}", other),
3610 }
3611 }
3612
3613 #[test]
3616 fn jump_if_true_taken() {
3617 let mut b = ChunkBuilder::new();
3619 b.emit(Op::LoadTrue, 1);
3620 let j = b.emit(Op::JumpIfTrue(0), 1);
3621 b.emit(Op::LoadInt(0), 1);
3622 b.patch_jump(j, b.current_pos());
3623 b.emit(Op::LoadInt(1), 1);
3624 assert!(matches!(
3625 VM::new(b.build()).run(),
3626 VMResult::Ok(Value::Int(1))
3627 ));
3628 }
3629
3630 #[test]
3631 fn jump_if_false_not_taken_for_true() {
3632 let mut b = ChunkBuilder::new();
3633 b.emit(Op::LoadTrue, 1);
3634 let j = b.emit(Op::JumpIfFalse(0), 1);
3635 b.emit(Op::LoadInt(7), 1); b.patch_jump(j, b.current_pos());
3637 match VM::new(b.build()).run() {
3638 VMResult::Ok(Value::Int(7)) => {}
3639 other => panic!("got {:?}", other),
3640 }
3641 }
3642
3643 #[test]
3646 fn push_pop_frame_with_slots() {
3647 let mut b = ChunkBuilder::new();
3651 b.emit(Op::PushFrame, 1);
3652 b.emit(Op::LoadInt(99), 1);
3653 b.emit(Op::SetSlot(0), 1);
3654 b.emit(Op::GetSlot(0), 1);
3655 match VM::new(b.build()).run() {
3656 VMResult::Ok(Value::Int(99)) => {}
3657 other => panic!("got {:?}", other),
3658 }
3659 }
3660
3661 #[test]
3664 fn vm_push_pop_peek_round_trip() {
3665 let chunk = ChunkBuilder::new().build();
3666 let mut vm = VM::new(chunk);
3667 vm.push(Value::Int(1));
3668 vm.push(Value::Int(2));
3669 assert_eq!(*vm.peek(), Value::Int(2));
3670 assert_eq!(vm.pop(), Value::Int(2));
3671 assert_eq!(vm.pop(), Value::Int(1));
3672 }
3673
3674 #[test]
3677 fn register_builtin_overwrites_existing_handler() {
3678 let mut b = ChunkBuilder::new();
3679 b.emit(Op::LoadInt(1), 1);
3680 b.emit(Op::CallBuiltin(0, 1), 1);
3681 let mut vm = VM::new(b.build());
3682 vm.register_builtin(0, |vm, _| {
3683 vm.pop();
3684 Value::Int(111)
3685 });
3686 vm.register_builtin(0, |vm, _| {
3688 vm.pop();
3689 Value::Int(222)
3690 });
3691 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(222))));
3692 }
3693
3694 #[test]
3695 fn register_builtin_grows_table_to_high_id() {
3696 let chunk = ChunkBuilder::new().build();
3698 let mut vm = VM::new(chunk);
3699 vm.register_builtin(500, |_, _| Value::Int(0));
3700 vm.register_builtin(1, |_, _| Value::Int(0));
3702 }
3703
3704 #[test]
3707 fn reset_clears_state_and_runs_new_chunk() {
3708 let mut b1 = ChunkBuilder::new();
3709 b1.emit(Op::LoadInt(1), 1);
3710 let mut vm = VM::new(b1.build());
3711 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(1))));
3712
3713 let mut b2 = ChunkBuilder::new();
3714 b2.emit(Op::LoadInt(2), 1);
3715 b2.emit(Op::LoadInt(3), 1);
3716 b2.emit(Op::Add, 1);
3717 vm.reset(b2.build());
3718 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(5))));
3719 }
3720
3721 #[test]
3724 fn extension_handler_invoked_with_payload() {
3725 use std::sync::{Arc, Mutex};
3726 let captured: Arc<Mutex<Option<(u16, u8)>>> = Arc::new(Mutex::new(None));
3727 let captured_cl = Arc::clone(&captured);
3728
3729 let mut b = ChunkBuilder::new();
3730 b.emit(Op::Extended(7, 42), 1);
3731 let mut vm = VM::new(b.build());
3732 vm.set_extension_handler(Box::new(move |vm, id, arg| {
3733 *captured_cl.lock().unwrap() = Some((id, arg));
3734 vm.push(Value::Int(123));
3735 }));
3736 match vm.run() {
3737 VMResult::Ok(Value::Int(123)) => {}
3738 other => panic!("got {:?}", other),
3739 }
3740 assert_eq!(*captured.lock().unwrap(), Some((7, 42)));
3741 }
3742
3743 #[test]
3744 fn extension_wide_handler_invoked_with_payload() {
3745 use std::sync::{Arc, Mutex};
3746 let captured: Arc<Mutex<Option<(u16, usize)>>> = Arc::new(Mutex::new(None));
3747 let captured_cl = Arc::clone(&captured);
3748 let mut b = ChunkBuilder::new();
3749 b.emit(Op::ExtendedWide(9, 9999), 1);
3750 let mut vm = VM::new(b.build());
3751 vm.set_extension_wide_handler(Box::new(move |vm, id, payload| {
3752 *captured_cl.lock().unwrap() = Some((id, payload));
3753 vm.push(Value::Int(0));
3754 }));
3755 let _ = vm.run();
3756 assert_eq!(*captured.lock().unwrap(), Some((9, 9999)));
3757 }
3758
3759 #[test]
3762 fn vmpool_new_default_and_with_capacity_start_empty() {
3763 let p = VMPool::new();
3764 assert!(p.is_empty());
3765 assert_eq!(p.len(), 0);
3766 let p = VMPool::with_capacity(8);
3767 assert!(p.is_empty());
3768 let p: VMPool = Default::default();
3769 assert!(p.is_empty());
3770 }
3771
3772 #[test]
3773 fn vmpool_release_then_acquire_reuses_vm() {
3774 let mut pool = VMPool::new();
3775 let chunk1 = {
3776 let mut b = ChunkBuilder::new();
3777 b.emit(Op::LoadInt(1), 1);
3778 b.build()
3779 };
3780 let vm = pool.acquire(chunk1);
3781 assert_eq!(pool.len(), 0);
3782 pool.release(vm);
3783 assert_eq!(pool.len(), 1);
3784
3785 let chunk2 = {
3786 let mut b = ChunkBuilder::new();
3787 b.emit(Op::LoadInt(2), 1);
3788 b.build()
3789 };
3790 let mut vm = pool.acquire(chunk2);
3791 assert_eq!(pool.len(), 0);
3792 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(2))));
3793 }
3794
3795 #[test]
3796 fn vmpool_with_returns_value_and_recycles_vm() {
3797 let mut pool = VMPool::new();
3798 let chunk = {
3799 let mut b = ChunkBuilder::new();
3800 b.emit(Op::LoadInt(10), 1);
3801 b.emit(Op::LoadInt(5), 1);
3802 b.emit(Op::Add, 1);
3803 b.build()
3804 };
3805 let result = pool.with(chunk, |vm| match vm.run() {
3806 VMResult::Ok(Value::Int(n)) => n,
3807 other => panic!("got {:?}", other),
3808 });
3809 assert_eq!(result, 15);
3810 assert_eq!(pool.len(), 1, "VM should be returned to pool after with()");
3811 }
3812
3813 #[derive(Default)]
3818 struct RecordingAwkHost {
3819 record: String,
3820 fields: Vec<String>,
3821 printed: Vec<Vec<String>>,
3822 field_sets: Vec<(i64, String)>,
3823 special_sets: Vec<(String, String)>,
3824 array: std::collections::HashMap<String, String>,
3825 }
3826
3827 impl crate::awk_host::AwkHost for RecordingAwkHost {
3828 fn field_get(&mut self, i: i64) -> Value {
3829 Value::str(self.fields.get(i as usize).cloned().unwrap_or_default())
3830 }
3831 fn field_set(&mut self, i: i64, v: Value) {
3832 self.field_sets.push((i, v.to_str()));
3833 }
3834 fn nf(&mut self) -> i64 {
3835 self.fields.len() as i64
3836 }
3837 fn set_record(&mut self, v: Value) {
3838 self.record = v.to_str();
3839 self.fields = self.record.split(' ').map(|s| s.to_string()).collect();
3840 }
3841 fn special_get(&mut self, name: &str) -> Value {
3842 match name {
3843 "NR" => Value::Int(7),
3844 _ => Value::str(""),
3845 }
3846 }
3847 fn special_set(&mut self, name: &str, v: Value) {
3848 self.special_sets.push((name.to_string(), v.to_str()));
3849 }
3850 fn print(&mut self, args: &[Value]) {
3851 self.printed.push(args.iter().map(|v| v.to_str()).collect());
3852 }
3853 fn array_get(&mut self, _arr: &str, key: &Value) -> Value {
3854 Value::str(self.array.get(&key.to_str()).cloned().unwrap_or_default())
3855 }
3856 fn array_set(&mut self, _arr: &str, key: &Value, v: Value) {
3857 self.array.insert(key.to_str(), v.to_str());
3858 }
3859 }
3860
3861 fn awk_op(b: &mut ChunkBuilder, id: u16, payload: usize) {
3862 b.emit(Op::ExtendedWide(id, payload), 1);
3863 }
3864
3865 #[test]
3866 fn awk_field_get_routes_to_host() {
3867 use crate::awk_builtins::*;
3868 let chunk = {
3869 let mut b = ChunkBuilder::new();
3870 b.emit(Op::LoadInt(2), 1); awk_op(&mut b, AWK_FIELD_GET, 0);
3872 b.build()
3873 };
3874 let mut vm = VM::new(chunk);
3875 let host = RecordingAwkHost {
3876 fields: vec!["a".into(), "b".into(), "c".into()],
3877 ..Default::default()
3878 };
3879 vm.set_awk_host(Box::new(host));
3880 match vm.run() {
3881 VMResult::Ok(v) => assert_eq!(v.to_str(), "c"),
3882 other => panic!("got {:?}", other),
3883 }
3884 }
3885
3886 #[test]
3887 fn awk_print_pops_args_in_source_order() {
3888 use crate::awk_builtins::*;
3889 let chunk = {
3890 let mut b = ChunkBuilder::new();
3891 let x = b.add_constant(Value::str("x"));
3892 let y = b.add_constant(Value::str("y"));
3893 b.emit(Op::LoadConst(x), 1);
3894 b.emit(Op::LoadConst(y), 1);
3895 awk_op(&mut b, AWK_PRINT, 2);
3896 b.build()
3897 };
3898 let mut vm = VM::new(chunk);
3899 struct H(std::sync::Arc<std::sync::Mutex<Vec<Vec<String>>>>);
3901 impl crate::awk_host::AwkHost for H {
3902 fn print(&mut self, args: &[Value]) {
3903 self.0
3904 .lock()
3905 .unwrap()
3906 .push(args.iter().map(|v| v.to_str()).collect());
3907 }
3908 }
3909 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
3910 vm.set_awk_host(Box::new(H(sink.clone())));
3911 let _ = vm.run();
3912 assert_eq!(
3913 sink.lock().unwrap().as_slice(),
3914 &[vec!["x".to_string(), "y".to_string()]]
3915 );
3916 }
3917
3918 #[test]
3919 fn awk_field_set_pops_value_and_index() {
3920 use crate::awk_builtins::*;
3921 let chunk = {
3922 let mut b = ChunkBuilder::new();
3923 let v = b.add_constant(Value::str("Z"));
3924 b.emit(Op::LoadConst(v), 1); b.emit(Op::LoadInt(3), 1); awk_op(&mut b, AWK_FIELD_SET, 0);
3927 b.build()
3928 };
3929 struct H(std::sync::Arc<std::sync::Mutex<Vec<(i64, String)>>>);
3930 impl crate::awk_host::AwkHost for H {
3931 fn field_set(&mut self, i: i64, v: Value) {
3932 self.0.lock().unwrap().push((i, v.to_str()));
3933 }
3934 }
3935 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
3936 let mut vm = VM::new(chunk);
3937 vm.set_awk_host(Box::new(H(sink.clone())));
3938 let _ = vm.run();
3939 assert_eq!(sink.lock().unwrap().as_slice(), &[(3i64, "Z".to_string())]);
3940 }
3941
3942 #[test]
3943 fn awk_special_get_and_array_roundtrip() {
3944 use crate::awk_builtins::*;
3945 let chunk = {
3946 let mut b = ChunkBuilder::new();
3947 let arr = b.add_name("counts");
3948 let k = b.add_constant(Value::str("k"));
3949 let val = b.add_constant(Value::str("42"));
3950 b.emit(Op::LoadConst(val), 1);
3952 b.emit(Op::LoadConst(k), 1);
3953 awk_op(&mut b, AWK_ARRAY_SET, arr as usize);
3954 b.emit(Op::LoadConst(k), 1);
3956 awk_op(&mut b, AWK_ARRAY_GET, arr as usize);
3957 b.build()
3958 };
3959 let mut vm = VM::new(chunk);
3960 vm.set_awk_host(Box::new(RecordingAwkHost::default()));
3961 match vm.run() {
3962 VMResult::Ok(v) => assert_eq!(v.to_str(), "42"),
3963 other => panic!("got {:?}", other),
3964 }
3965 }
3966
3967 #[test]
3968 fn awk_ops_are_inert_without_host_but_keep_stack_balanced() {
3969 use crate::awk_builtins::*;
3970 let chunk = {
3972 let mut b = ChunkBuilder::new();
3973 b.emit(Op::LoadInt(1), 1);
3974 awk_op(&mut b, AWK_FIELD_GET, 0);
3975 b.build()
3976 };
3977 let mut vm = VM::new(chunk);
3978 match vm.run() {
3979 VMResult::Ok(v) => assert_eq!(v.to_str(), ""),
3980 other => panic!("got {:?}", other),
3981 }
3982 }
3983
3984 #[test]
3991 fn first_class_awk_field_get_routes_to_host() {
3992 let chunk = {
3993 let mut b = ChunkBuilder::new();
3994 b.emit(Op::LoadInt(2), 1); b.emit(Op::AwkFieldGet, 1);
3996 b.build()
3997 };
3998 let mut vm = VM::new(chunk);
3999 let host = RecordingAwkHost {
4000 fields: vec!["a".into(), "b".into(), "c".into()],
4001 ..Default::default()
4002 };
4003 vm.set_awk_host(Box::new(host));
4004 match vm.run() {
4005 VMResult::Ok(v) => assert_eq!(v.to_str(), "c"),
4006 other => panic!("got {:?}", other),
4007 }
4008 }
4009
4010 #[test]
4011 fn first_class_awk_print_pops_args_in_source_order() {
4012 struct H(std::sync::Arc<std::sync::Mutex<Vec<Vec<String>>>>);
4013 impl crate::awk_host::AwkHost for H {
4014 fn print(&mut self, args: &[Value]) {
4015 self.0
4016 .lock()
4017 .unwrap()
4018 .push(args.iter().map(|v| v.to_str()).collect());
4019 }
4020 }
4021 let chunk = {
4022 let mut b = ChunkBuilder::new();
4023 let x = b.add_constant(Value::str("x"));
4024 let y = b.add_constant(Value::str("y"));
4025 b.emit(Op::LoadConst(x), 1);
4026 b.emit(Op::LoadConst(y), 1);
4027 b.emit(Op::AwkPrint(2), 1);
4028 b.build()
4029 };
4030 let mut vm = VM::new(chunk);
4031 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
4032 vm.set_awk_host(Box::new(H(sink.clone())));
4033 let _ = vm.run();
4034 assert_eq!(
4035 sink.lock().unwrap().as_slice(),
4036 &[vec!["x".to_string(), "y".to_string()]]
4037 );
4038 }
4039
4040 #[test]
4041 fn first_class_awk_array_roundtrip_matches_extendedwide() {
4042 fn run_variant(first_class: bool) -> String {
4045 use crate::awk_builtins::*;
4046 let mut b = ChunkBuilder::new();
4047 let arr = b.add_name("counts");
4048 let k = b.add_constant(Value::str("k"));
4049 let val = b.add_constant(Value::str("42"));
4050 b.emit(Op::LoadConst(val), 1);
4051 b.emit(Op::LoadConst(k), 1);
4052 if first_class {
4053 b.emit(Op::AwkArraySet(arr), 1);
4054 } else {
4055 b.emit(Op::ExtendedWide(AWK_ARRAY_SET, arr as usize), 1);
4056 }
4057 b.emit(Op::LoadConst(k), 1);
4058 if first_class {
4059 b.emit(Op::AwkArrayGet(arr), 1);
4060 } else {
4061 b.emit(Op::ExtendedWide(AWK_ARRAY_GET, arr as usize), 1);
4062 }
4063 let mut vm = VM::new(b.build());
4064 vm.set_awk_host(Box::new(RecordingAwkHost::default()));
4065 match vm.run() {
4066 VMResult::Ok(v) => v.to_str(),
4067 other => panic!("got {:?}", other),
4068 }
4069 }
4070 assert_eq!(run_variant(true), "42");
4071 assert_eq!(run_variant(true), run_variant(false));
4072 }
4073
4074 #[test]
4075 fn first_class_awk_ops_inert_without_host() {
4076 let chunk = {
4078 let mut b = ChunkBuilder::new();
4079 b.emit(Op::LoadInt(1), 1);
4080 b.emit(Op::AwkFieldGet, 1);
4081 b.build()
4082 };
4083 let mut vm = VM::new(chunk);
4084 match vm.run() {
4085 VMResult::Ok(v) => assert_eq!(v.to_str(), ""),
4086 other => panic!("got {:?}", other),
4087 }
4088 }
4089
4090 fn run_native(chunk: crate::chunk::Chunk) -> Value {
4096 let mut vm = VM::new(chunk);
4097 match vm.run() {
4098 VMResult::Ok(v) => v,
4099 other => panic!("got {:?}", other),
4100 }
4101 }
4102
4103 #[test]
4104 fn awk_substr_executes_natively_without_host() {
4105 let chunk = {
4107 let mut b = ChunkBuilder::new();
4108 let s = b.add_constant(Value::str("hello"));
4109 b.emit(Op::LoadConst(s), 1);
4110 b.emit(Op::LoadInt(2), 1);
4111 b.emit(Op::LoadInt(3), 1);
4112 b.emit(Op::AwkSubstr(3), 1);
4113 b.build()
4114 };
4115 assert_eq!(run_native(chunk).to_str(), "ell");
4116 }
4117
4118 #[test]
4119 fn awk_substr_two_arg_to_end_without_host() {
4120 let chunk = {
4122 let mut b = ChunkBuilder::new();
4123 let s = b.add_constant(Value::str("hello"));
4124 b.emit(Op::LoadConst(s), 1);
4125 b.emit(Op::LoadInt(2), 1);
4126 b.emit(Op::AwkSubstr(2), 1);
4127 b.build()
4128 };
4129 assert_eq!(run_native(chunk).to_str(), "ello");
4130 }
4131
4132 #[test]
4133 fn awk_tolower_toupper_execute_natively_without_host() {
4134 let lower = {
4135 let mut b = ChunkBuilder::new();
4136 let s = b.add_constant(Value::str("MiXeD"));
4137 b.emit(Op::LoadConst(s), 1);
4138 b.emit(Op::AwkToLower, 1);
4139 b.build()
4140 };
4141 assert_eq!(run_native(lower).to_str(), "mixed");
4142 let upper = {
4143 let mut b = ChunkBuilder::new();
4144 let s = b.add_constant(Value::str("MiXeD"));
4145 b.emit(Op::LoadConst(s), 1);
4146 b.emit(Op::AwkToUpper, 1);
4147 b.build()
4148 };
4149 assert_eq!(run_native(upper).to_str(), "MIXED");
4150 }
4151
4152 #[test]
4153 fn awk_index_executes_natively_without_host() {
4154 let chunk = {
4156 let mut b = ChunkBuilder::new();
4157 let s = b.add_constant(Value::str("hello"));
4158 let t = b.add_constant(Value::str("ll"));
4159 b.emit(Op::LoadConst(s), 1);
4160 b.emit(Op::LoadConst(t), 1);
4161 b.emit(Op::AwkIndex, 1);
4162 b.build()
4163 };
4164 assert_eq!(run_native(chunk).to_int(), 3);
4165 }
4166
4167 #[test]
4168 fn awk_length_scalar_executes_natively_without_host() {
4169 let chunk = {
4171 let mut b = ChunkBuilder::new();
4172 let s = b.add_constant(Value::str("héllo"));
4173 b.emit(Op::LoadConst(s), 1);
4174 b.emit(Op::AwkLength(1), 1);
4175 b.build()
4176 };
4177 assert_eq!(run_native(chunk).to_int(), 5);
4178 }
4179
4180 #[test]
4181 fn awk_native_string_ops_match_host_path() {
4182 use crate::awk_host::{awk_index, awk_substr, awk_tolower};
4184 assert_eq!(awk_substr(&Value::str("hello"), 2, Some(3)).to_str(), "ell");
4185 assert_eq!(awk_index(&Value::str("hello"), &Value::str("z")), 0);
4186 assert_eq!(awk_tolower(&Value::str("ABC")).to_str(), "abc");
4187 }
4188
4189 #[test]
4194 fn awk_int_truncates_toward_zero_without_host() {
4195 for (input, want) in [(3.7_f64, 3_i64), (-3.7, -3), (0.0, 0)] {
4196 let chunk = {
4197 let mut b = ChunkBuilder::new();
4198 let x = b.add_constant(Value::Float(input));
4199 b.emit(Op::LoadConst(x), 1);
4200 b.emit(Op::AwkInt, 1);
4201 b.build()
4202 };
4203 assert_eq!(run_native(chunk).to_int(), want, "int({input})");
4204 }
4205 }
4206
4207 #[test]
4208 fn awk_sqrt_exp_log_execute_natively_without_host() {
4209 let sqrt = {
4210 let mut b = ChunkBuilder::new();
4211 let x = b.add_constant(Value::Float(16.0));
4212 b.emit(Op::LoadConst(x), 1);
4213 b.emit(Op::AwkSqrt, 1);
4214 b.build()
4215 };
4216 assert_eq!(run_native(sqrt).to_float(), 4.0);
4217 let log = {
4218 let mut b = ChunkBuilder::new();
4219 let x = b.add_constant(Value::Float(std::f64::consts::E));
4220 b.emit(Op::LoadConst(x), 1);
4221 b.emit(Op::AwkLog, 1);
4222 b.build()
4223 };
4224 assert!((run_native(log).to_float() - 1.0).abs() < 1e-12);
4225 let exp = {
4226 let mut b = ChunkBuilder::new();
4227 let x = b.add_constant(Value::Float(0.0));
4228 b.emit(Op::LoadConst(x), 1);
4229 b.emit(Op::AwkExp, 1);
4230 b.build()
4231 };
4232 assert_eq!(run_native(exp).to_float(), 1.0);
4233 }
4234
4235 #[test]
4236 fn awk_sin_cos_execute_natively_without_host() {
4237 let sin = {
4238 let mut b = ChunkBuilder::new();
4239 let x = b.add_constant(Value::Float(0.0));
4240 b.emit(Op::LoadConst(x), 1);
4241 b.emit(Op::AwkSin, 1);
4242 b.build()
4243 };
4244 assert_eq!(run_native(sin).to_float(), 0.0);
4245 let cos = {
4246 let mut b = ChunkBuilder::new();
4247 let x = b.add_constant(Value::Float(0.0));
4248 b.emit(Op::LoadConst(x), 1);
4249 b.emit(Op::AwkCos, 1);
4250 b.build()
4251 };
4252 assert_eq!(run_native(cos).to_float(), 1.0);
4253 }
4254
4255 #[test]
4256 fn awk_atan2_pops_y_then_x_without_host() {
4257 let chunk = {
4259 let mut b = ChunkBuilder::new();
4260 let y = b.add_constant(Value::Float(1.0));
4261 let x = b.add_constant(Value::Float(1.0));
4262 b.emit(Op::LoadConst(y), 1);
4263 b.emit(Op::LoadConst(x), 1);
4264 b.emit(Op::AwkAtan2, 1);
4265 b.build()
4266 };
4267 assert!((run_native(chunk).to_float() - std::f64::consts::FRAC_PI_4).abs() < 1e-12);
4268 }
4269
4270 fn run_native_int(chunk: crate::chunk::Chunk) -> i64 {
4275 run_native(chunk).to_int()
4276 }
4277
4278 #[test]
4279 fn awk_and_or_xor_execute_natively_without_host() {
4280 let mk = |op: Op| {
4282 let mut b = ChunkBuilder::new();
4283 b.emit(Op::LoadInt(12), 1);
4284 b.emit(Op::LoadInt(10), 1);
4285 b.emit(op, 1);
4286 b.build()
4287 };
4288 assert_eq!(run_native_int(mk(Op::AwkAnd(2))), 8);
4289 assert_eq!(run_native_int(mk(Op::AwkOr(2))), 14);
4290 assert_eq!(run_native_int(mk(Op::AwkXor(2))), 6);
4291 }
4292
4293 #[test]
4294 fn awk_and_is_variadic_without_host() {
4295 let chunk = {
4297 let mut b = ChunkBuilder::new();
4298 b.emit(Op::LoadInt(15), 1);
4299 b.emit(Op::LoadInt(9), 1);
4300 b.emit(Op::LoadInt(5), 1);
4301 b.emit(Op::AwkAnd(3), 1);
4302 b.build()
4303 };
4304 assert_eq!(run_native_int(chunk), 1);
4305 }
4306
4307 #[test]
4308 fn awk_compl_matches_awkrs_i64_wrap_without_host() {
4309 let chunk = {
4311 let mut b = ChunkBuilder::new();
4312 b.emit(Op::LoadInt(0), 1);
4313 b.emit(Op::AwkCompl, 1);
4314 b.build()
4315 };
4316 assert_eq!(run_native_int(chunk), -1);
4317 }
4318
4319 #[test]
4320 fn awk_lshift_rshift_execute_natively_without_host() {
4321 let lshift = {
4323 let mut b = ChunkBuilder::new();
4324 b.emit(Op::LoadInt(1), 1);
4325 b.emit(Op::LoadInt(4), 1);
4326 b.emit(Op::AwkLshift, 1);
4327 b.build()
4328 };
4329 assert_eq!(run_native_int(lshift), 16);
4330 let rshift = {
4331 let mut b = ChunkBuilder::new();
4332 b.emit(Op::LoadInt(256), 1);
4333 b.emit(Op::LoadInt(4), 1);
4334 b.emit(Op::AwkRshift, 1);
4335 b.build()
4336 };
4337 assert_eq!(run_native_int(rshift), 16);
4338 }
4339
4340 #[test]
4341 fn awk_bitwise_free_fns_match_gawk_semantics() {
4342 use crate::awk_host::{awk_compl, awk_fold_and, awk_fold_or, awk_fold_xor, awk_lshift};
4343 assert_eq!(awk_fold_and(&[Value::Int(12), Value::Int(10)]), 8);
4344 assert_eq!(awk_fold_or(&[Value::Int(12), Value::Int(10)]), 14);
4345 assert_eq!(awk_fold_xor(&[Value::Int(12), Value::Int(10)]), 6);
4346 assert_eq!(awk_compl(&Value::Int(0)), -1);
4347 assert_eq!(awk_lshift(&Value::Int(1), &Value::Int(64)), 1);
4349 }
4350
4351 #[test]
4352 fn awk_mktime_utc_executes_natively_without_host() {
4353 let chunk = {
4355 let mut b = ChunkBuilder::new();
4356 let s = b.add_constant(Value::str("2020 01 01 00 00 00"));
4357 b.emit(Op::LoadConst(s), 1);
4358 b.emit(Op::LoadInt(1), 1); b.emit(Op::AwkMktime(2), 1);
4360 b.build()
4361 };
4362 assert_eq!(run_native(chunk).to_float(), 1_577_836_800.0);
4363 }
4364
4365 #[test]
4366 fn awk_mktime_bad_datespec_returns_minus_one_without_host() {
4367 let chunk = {
4369 let mut b = ChunkBuilder::new();
4370 let s = b.add_constant(Value::str("2020 01 01"));
4371 b.emit(Op::LoadConst(s), 1);
4372 b.emit(Op::AwkMktime(1), 1);
4373 b.build()
4374 };
4375 assert_eq!(run_native(chunk).to_float(), -1.0);
4376 }
4377
4378 #[test]
4379 fn awk_strftime_utc_executes_natively_without_host() {
4380 let chunk = {
4382 let mut b = ChunkBuilder::new();
4383 let fmt = b.add_constant(Value::str("%Y-%m-%d"));
4384 b.emit(Op::LoadConst(fmt), 1);
4385 b.emit(Op::LoadInt(0), 1); b.emit(Op::LoadInt(1), 1); b.emit(Op::AwkStrftime(3), 1);
4388 b.build()
4389 };
4390 assert_eq!(run_native(chunk).to_str(), "1970-01-01");
4391 }
4392
4393 #[test]
4394 fn awk_strftime_mktime_free_fns_match_awkrs() {
4395 use crate::awk_host::{awk_mktime, awk_strftime};
4396 assert_eq!(
4398 awk_mktime(&[Value::str("2020 01 01 00 00 00"), Value::Int(1)]).to_float(),
4399 1_577_836_800.0
4400 );
4401 assert_eq!(awk_mktime(&[Value::str("garbage")]).to_float(), -1.0);
4402 assert_eq!(
4403 awk_strftime(&[Value::str("%H:%M:%S"), Value::Int(0), Value::Int(1)]).to_str(),
4404 "00:00:00"
4405 );
4406 }
4407
4408 #[test]
4409 fn awk_ord_executes_natively_without_host() {
4410 let chunk = {
4412 let mut b = ChunkBuilder::new();
4413 let s = b.add_constant(Value::str("ABC"));
4414 b.emit(Op::LoadConst(s), 1);
4415 b.emit(Op::AwkOrd, 1);
4416 b.build()
4417 };
4418 assert_eq!(run_native(chunk).to_float(), 65.0);
4419
4420 let empty = {
4421 let mut b = ChunkBuilder::new();
4422 let s = b.add_constant(Value::str(""));
4423 b.emit(Op::LoadConst(s), 1);
4424 b.emit(Op::AwkOrd, 1);
4425 b.build()
4426 };
4427 assert_eq!(run_native(empty).to_float(), 0.0);
4428 }
4429
4430 #[test]
4431 fn awk_chr_executes_natively_without_host() {
4432 let chunk = {
4434 let mut b = ChunkBuilder::new();
4435 b.emit(Op::LoadInt(65), 1);
4436 b.emit(Op::AwkChr, 1);
4437 b.build()
4438 };
4439 assert_eq!(run_native(chunk).to_str(), "A");
4440
4441 let bad = {
4442 let mut b = ChunkBuilder::new();
4443 b.emit(Op::LoadInt(0xD800), 1); b.emit(Op::AwkChr, 1);
4445 b.build()
4446 };
4447 assert_eq!(run_native(bad).to_str(), "");
4448 }
4449
4450 #[test]
4451 fn awk_mkbool_executes_natively_without_host() {
4452 let truthy = {
4454 let mut b = ChunkBuilder::new();
4455 b.emit(Op::LoadInt(7), 1);
4456 b.emit(Op::AwkMkbool, 1);
4457 b.build()
4458 };
4459 assert_eq!(run_native(truthy).to_float(), 1.0);
4460
4461 let zero = {
4462 let mut b = ChunkBuilder::new();
4463 b.emit(Op::LoadInt(0), 1);
4464 b.emit(Op::AwkMkbool, 1);
4465 b.build()
4466 };
4467 assert_eq!(run_native(zero).to_float(), 0.0);
4468 }
4469
4470 #[test]
4471 fn awk_intdiv_executes_natively_without_host() {
4472 let chunk = {
4474 let mut b = ChunkBuilder::new();
4475 b.emit(Op::LoadInt(17), 1);
4476 b.emit(Op::LoadInt(5), 1);
4477 b.emit(Op::AwkIntdiv, 1);
4478 b.build()
4479 };
4480 assert_eq!(run_native(chunk).to_float(), 3.0);
4481
4482 let div0 = {
4483 let mut b = ChunkBuilder::new();
4484 b.emit(Op::LoadInt(17), 1);
4485 b.emit(Op::LoadInt(0), 1);
4486 b.emit(Op::AwkIntdiv, 1);
4487 b.build()
4488 };
4489 assert!(matches!(run_native(div0), Value::Undef));
4490 }
4491
4492 #[test]
4493 fn awk_intdiv0_executes_natively_without_host() {
4494 let chunk = {
4496 let mut b = ChunkBuilder::new();
4497 b.emit(Op::LoadInt(17), 1);
4498 b.emit(Op::LoadInt(5), 1);
4499 b.emit(Op::AwkIntdiv0, 1);
4500 b.build()
4501 };
4502 assert_eq!(run_native(chunk).to_float(), 3.0);
4503
4504 let div0 = {
4505 let mut b = ChunkBuilder::new();
4506 b.emit(Op::LoadInt(17), 1);
4507 b.emit(Op::LoadInt(0), 1);
4508 b.emit(Op::AwkIntdiv0, 1);
4509 b.build()
4510 };
4511 assert_eq!(run_native(div0).to_float(), 0.0);
4512 }
4513
4514 #[test]
4515 fn awk_div_mod_compute_and_trap_on_zero() {
4516 let div = {
4521 let mut b = ChunkBuilder::new();
4522 b.emit(Op::LoadFloat(7.0), 1);
4523 b.emit(Op::LoadFloat(2.0), 1);
4524 b.emit(Op::AwkDiv, 1);
4525 b.build()
4526 };
4527 assert_eq!(run_native(div).to_float(), 3.5);
4528
4529 let md = {
4530 let mut b = ChunkBuilder::new();
4531 b.emit(Op::LoadFloat(7.0), 1);
4532 b.emit(Op::LoadFloat(3.0), 1);
4533 b.emit(Op::AwkMod, 1);
4534 b.build()
4535 };
4536 assert_eq!(run_native(md).to_float(), 1.0);
4537
4538 let div0 = {
4539 let mut b = ChunkBuilder::new();
4540 b.emit(Op::LoadFloat(1.0), 1);
4541 b.emit(Op::LoadFloat(0.0), 1);
4542 b.emit(Op::AwkDiv, 1);
4543 b.build()
4544 };
4545 match VM::new(div0).run() {
4546 VMResult::Error(m) => assert_eq!(m, "division by zero attempted"),
4547 other => panic!("expected div-by-zero trap, got {:?}", other),
4548 }
4549
4550 let mod0 = {
4551 let mut b = ChunkBuilder::new();
4552 b.emit(Op::LoadFloat(1.0), 1);
4553 b.emit(Op::LoadFloat(0.0), 1);
4554 b.emit(Op::AwkMod, 1);
4555 b.build()
4556 };
4557 match VM::new(mod0).run() {
4558 VMResult::Error(m) => assert_eq!(m, "division by zero attempted in `%'"),
4559 other => panic!("expected mod-by-zero trap, got {:?}", other),
4560 }
4561 }
4562
4563 #[test]
4564 fn awk_signal_halts_chunk_and_records_code() {
4565 use crate::awk_builtins::signal;
4566 let chunk = {
4569 let mut b = ChunkBuilder::new();
4570 b.emit(Op::LoadInt(1), 1);
4571 b.emit(Op::AwkSignal(signal::NEXTFILE), 1);
4572 b.emit(Op::LoadInt(99), 1);
4574 b.build()
4575 };
4576 let mut vm = VM::new(chunk);
4577 let r = vm.run();
4578 assert_eq!(vm.awk_signal(), Some(signal::NEXTFILE));
4579 match r {
4582 VMResult::Ok(v) => assert_eq!(v.to_float(), 1.0),
4583 other => panic!("expected Ok(1), got {:?}", other),
4584 }
4585
4586 let plain = {
4588 let mut b = ChunkBuilder::new();
4589 b.emit(Op::LoadInt(5), 1);
4590 b.build()
4591 };
4592 let mut vm2 = VM::new(plain);
4593 let _ = vm2.run();
4594 assert_eq!(vm2.awk_signal(), None);
4595 }
4596
4597 #[test]
4598 fn awk_gensub_stub_is_stack_balanced_without_host() {
4599 let chunk = {
4602 let mut b = ChunkBuilder::new();
4603 let re = b.add_constant(Value::str("x"));
4604 let repl = b.add_constant(Value::str("y"));
4605 let how = b.add_constant(Value::str("g"));
4606 let target = b.add_constant(Value::str("xax"));
4607 b.emit(Op::LoadConst(re), 1);
4608 b.emit(Op::LoadConst(repl), 1);
4609 b.emit(Op::LoadConst(how), 1);
4610 b.emit(Op::LoadConst(target), 1);
4611 b.emit(Op::AwkGensub(4), 1);
4612 b.build()
4613 };
4614 assert_eq!(run_native(chunk).to_str(), "");
4615 }
4616
4617 #[test]
4618 fn awk_char_scalar_free_fns_match_awkrs() {
4619 use crate::awk_host::{awk_chr, awk_intdiv, awk_intdiv0, awk_mkbool, awk_ord};
4620 assert_eq!(awk_ord(&Value::str("z")).to_float(), 122.0);
4621 assert_eq!(awk_chr(&Value::Int(0x1F600)).to_str(), "😀");
4622 assert_eq!(awk_mkbool(&Value::str("0")).to_float(), 0.0);
4623 assert_eq!(awk_mkbool(&Value::str("x")).to_float(), 1.0);
4624 assert_eq!(awk_intdiv(&Value::Int(-7), &Value::Int(2)).to_float(), -3.0);
4625 assert!(matches!(
4626 awk_intdiv(&Value::Int(1), &Value::Int(0)),
4627 Value::Undef
4628 ));
4629 assert_eq!(
4630 awk_intdiv0(&Value::Int(-7), &Value::Int(2)).to_float(),
4631 -3.0
4632 );
4633 assert_eq!(awk_intdiv0(&Value::Int(1), &Value::Int(0)).to_float(), 0.0);
4634 }
4635
4636 #[test]
4637 fn awk_rand_executes_natively_without_host() {
4638 let chunk = {
4640 let mut b = ChunkBuilder::new();
4641 b.emit(Op::AwkRand, 1);
4642 b.build()
4643 };
4644 let v = run_native(chunk).to_float();
4645 assert!((0.0..1.0).contains(&v), "rand() = {v} out of [0,1)");
4646 assert_eq!(
4647 v, 0.51385498046875,
4648 "first rand() from seed=1 must be stable"
4649 );
4650 }
4651
4652 #[test]
4653 fn awk_srand_reseeds_and_returns_prev_seed_without_host() {
4654 let chunk = {
4657 let mut b = ChunkBuilder::new();
4658 b.emit(Op::LoadInt(42), 1);
4659 b.emit(Op::AwkSrand(1), 1); b.emit(Op::Pop, 1);
4661 b.emit(Op::AwkRand, 1);
4662 b.build()
4663 };
4664 assert_eq!(run_native(chunk).to_float(), 0.582305908203125);
4665 }
4666
4667 #[test]
4668 fn awk_srand_no_arg_returns_prev_seed_without_host() {
4669 let chunk = {
4672 let mut b = ChunkBuilder::new();
4673 b.emit(Op::AwkSrand(0), 1);
4674 b.build()
4675 };
4676 assert_eq!(run_native(chunk).to_float(), 1.0);
4677 }
4678
4679 #[test]
4680 fn awk_rand_srand_free_fns_match_awkrs_lcg() {
4681 use crate::awk_host::{awk_rand, awk_srand};
4682 let mut seed: u64 = 1;
4683 assert_eq!(awk_rand(&mut seed), 0.51385498046875);
4684 let prev = awk_srand(&mut seed, Some(42));
4686 assert_eq!(prev, 1103527590.0);
4687 assert_eq!(awk_rand(&mut seed), 0.582305908203125);
4688 }
4689
4690 #[test]
4691 fn awk_systime_executes_natively_without_host() {
4692 let chunk = {
4695 let mut b = ChunkBuilder::new();
4696 b.emit(Op::AwkSystime, 1);
4697 b.build()
4698 };
4699 let v = run_native(chunk).to_float();
4700 assert!(v > 1_577_836_800.0, "systime() = {v} should be past 2020");
4701 }
4702
4703 #[test]
4704 fn awk_systime_free_fn_is_positive() {
4705 use crate::awk_host::awk_systime;
4706 assert!(awk_systime() > 1_577_836_800.0);
4707 }
4708
4709 #[test]
4710 fn awk_strtonum_executes_natively_without_host() {
4711 let chunk = {
4713 let mut b = ChunkBuilder::new();
4714 let s = b.add_constant(Value::str("0x10"));
4715 b.emit(Op::LoadConst(s), 1);
4716 b.emit(Op::AwkStrtonum, 1);
4717 b.build()
4718 };
4719 assert_eq!(run_native(chunk).to_float(), 16.0);
4720 }
4721
4722 #[test]
4723 fn awk_strtonum_octal_and_decimal_prefix_without_host() {
4724 let octal = {
4726 let mut b = ChunkBuilder::new();
4727 let s = b.add_constant(Value::str("010"));
4728 b.emit(Op::LoadConst(s), 1);
4729 b.emit(Op::AwkStrtonum, 1);
4730 b.build()
4731 };
4732 assert_eq!(run_native(octal).to_float(), 8.0);
4733 let prefix = {
4734 let mut b = ChunkBuilder::new();
4735 let s = b.add_constant(Value::str("42abc"));
4736 b.emit(Op::LoadConst(s), 1);
4737 b.emit(Op::AwkStrtonum, 1);
4738 b.build()
4739 };
4740 assert_eq!(run_native(prefix).to_float(), 42.0);
4741 }
4742
4743 #[test]
4744 fn awk_strtonum_free_fn_matches_awkrs_semantics() {
4745 use crate::awk_host::awk_strtonum;
4746 assert_eq!(awk_strtonum(""), 0.0);
4747 assert_eq!(awk_strtonum(" "), 0.0);
4748 assert_eq!(awk_strtonum("0x10"), 16.0);
4749 assert_eq!(awk_strtonum("0Xff"), 255.0);
4750 assert_eq!(awk_strtonum("010"), 8.0);
4751 assert_eq!(awk_strtonum("3.5"), 3.5);
4752 assert_eq!(awk_strtonum("0x"), 0.0);
4754 assert_eq!(awk_strtonum("0xzz"), 0.0);
4755 assert_eq!(awk_strtonum("+0x10"), 0.0);
4757 assert_eq!(awk_strtonum("nan"), 0.0);
4759 assert_eq!(awk_strtonum("inf"), 0.0);
4760 }
4761
4762 #[test]
4763 fn awk_builtin_substr_default_impl_is_posix() {
4764 use crate::awk_host::{AwkHost, DefaultAwkHost};
4765 let mut h = DefaultAwkHost;
4766 assert_eq!(h.substr(&Value::str("hello"), 2, Some(3)).to_str(), "ell");
4767 assert_eq!(h.substr(&Value::str("hello"), 2, None).to_str(), "ello");
4768 assert_eq!(h.substr(&Value::str("hello"), 0, Some(3)).to_str(), "he");
4769 assert_eq!(h.index(&Value::str("hello"), &Value::str("ll")), 3);
4770 assert_eq!(h.index(&Value::str("hello"), &Value::str("z")), 0);
4771 }
4772
4773 #[test]
4774 fn awk_op_range_is_disjoint_from_generic_extended_wide() {
4775 use crate::awk_builtins::*;
4776 assert!(!is_awk_op(0));
4777 assert!(!is_awk_op(AWK_OP_BASE - 1));
4778 assert!(is_awk_op(AWK_FIELD_GET));
4779 assert!(is_awk_op(AWK_ARRAY_LEN));
4780 assert!(!is_awk_op(AWK_OP_END));
4781 }
4782
4783 fn build_unary(op: Op, x: f64) -> crate::Chunk {
4790 let mut b = crate::ChunkBuilder::new();
4791 b.emit(Op::PushFrame, 1);
4792 b.emit(Op::LoadFloat(x), 1);
4793 b.emit(op, 1);
4794 b.build()
4795 }
4796
4797 fn build_binary(op: Op, a: f64, n: f64) -> crate::Chunk {
4798 let mut b = crate::ChunkBuilder::new();
4799 b.emit(Op::PushFrame, 1);
4800 b.emit(Op::LoadFloat(a), 1);
4801 b.emit(Op::LoadFloat(n), 1);
4802 b.emit(op, 1);
4803 b.build()
4804 }
4805
4806 #[test]
4807 fn awk_sqrt_jit_negative_warns_returns_nan() {
4808 let chunk = build_unary(Op::AwkSqrtJit, -4.0);
4809 let mut vm = VM::new(chunk);
4810 match vm.run() {
4811 VMResult::Ok(v) => assert!(v.to_float().is_nan(), "expected NaN, got {v:?}"),
4812 other => panic!("expected Ok(NaN), got {other:?}"),
4813 }
4814 }
4815
4816 #[test]
4817 fn awk_sqrt_jit_positive_returns_sqrt() {
4818 let chunk = build_unary(Op::AwkSqrtJit, 16.0);
4819 let mut vm = VM::new(chunk);
4820 match vm.run() {
4821 VMResult::Ok(v) => assert_eq!(v.to_float(), 4.0),
4822 other => panic!("expected Ok(4.0), got {other:?}"),
4823 }
4824 }
4825
4826 #[test]
4827 fn awk_log_jit_positive_returns_ln() {
4828 let chunk = build_unary(Op::AwkLogJit, std::f64::consts::E);
4829 let mut vm = VM::new(chunk);
4830 match vm.run() {
4831 VMResult::Ok(v) => assert!((v.to_float() - 1.0).abs() < 1e-10),
4832 other => panic!("expected Ok(~1.0), got {other:?}"),
4833 }
4834 }
4835
4836 #[test]
4837 fn awk_log_jit_negative_warns_returns_nan() {
4838 let chunk = build_unary(Op::AwkLogJit, -1.0);
4839 let mut vm = VM::new(chunk);
4840 match vm.run() {
4841 VMResult::Ok(v) => assert!(v.to_float().is_nan()),
4842 other => panic!("expected Ok(NaN), got {other:?}"),
4843 }
4844 }
4845
4846 #[test]
4847 fn awk_lshift_jit_computes_left_shift() {
4848 let chunk = build_binary(Op::AwkLshiftJit, 1.0, 4.0);
4849 let mut vm = VM::new(chunk);
4850 match vm.run() {
4851 VMResult::Ok(v) => assert_eq!(v.to_float(), 16.0, "1 << 4 == 16"),
4852 other => panic!("expected Ok(16.0), got {other:?}"),
4853 }
4854 }
4855
4856 #[test]
4857 fn awk_lshift_jit_negative_amount_errors() {
4858 let chunk = build_binary(Op::AwkLshiftJit, 1.0, -1.0);
4859 let mut vm = VM::new(chunk);
4860 match vm.run() {
4861 VMResult::Error(msg) => assert!(msg.contains("lshift"), "msg = {msg:?}"),
4862 other => panic!("expected Error, got {other:?}"),
4863 }
4864 }
4865
4866 #[test]
4867 fn awk_rshift_jit_computes_right_shift() {
4868 let chunk = build_binary(Op::AwkRshiftJit, 16.0, 2.0);
4869 let mut vm = VM::new(chunk);
4870 match vm.run() {
4871 VMResult::Ok(v) => assert_eq!(v.to_float(), 4.0, "16 >> 2 == 4"),
4872 other => panic!("expected Ok(4.0), got {other:?}"),
4873 }
4874 }
4875
4876 #[test]
4877 fn awk_compl_jit_negates_bits() {
4878 let chunk = build_unary(Op::AwkComplJit, 15.0);
4880 let mut vm = VM::new(chunk);
4881 match vm.run() {
4882 VMResult::Ok(v) => assert_eq!(v.to_float(), -16.0, "compl(15) == -16"),
4883 other => panic!("expected Ok(-16.0), got {other:?}"),
4884 }
4885 }
4886
4887 #[test]
4888 fn awk_compl_jit_negative_errors() {
4889 let chunk = build_unary(Op::AwkComplJit, -1.0);
4890 let mut vm = VM::new(chunk);
4891 match vm.run() {
4892 VMResult::Error(msg) => assert!(msg.contains("compl"), "msg = {msg:?}"),
4893 other => panic!("expected Error, got {other:?}"),
4894 }
4895 }
4896}