(&mut self, a: Val, b: Val, chunk: &SSAChunk, slots: &mut [Val])
| 118 | } |
| 119 | |
| 120 | fn exec_mod(&mut self, a: Val, b: Val, chunk: &SSAChunk, slots: &mut [Val]) -> Result<Val, VmErr> { |
| 121 | // `str % args` is printf-style formatting, not modulo. |
| 122 | if a.is_heap() && matches!(self.heap.get(a), HeapObj::Str(_)) { |
| 123 | return self.str_percent_format(a, b, chunk, slots); |
| 124 | } |
| 125 | if a.is_float() || b.is_float() { |
| 126 | let af = self.to_f64_coerce(a).map_err(|_| cold_type("% requires numeric operands"))?; |
| 127 | let bf = self.to_f64_coerce(b).map_err(|_| cold_type("% requires numeric operands"))?; |
| 128 | if bf == 0.0 { return Err(VmErr::ZeroDiv); } |
| 129 | // Floor-division semantics: result takes the divisor's sign. |
| 130 | let r = af - ffloor(af / bf) * bf; |
| 131 | return Ok(Val::float(r)); |
| 132 | } |
| 133 | let (Some(ai), Some(bi)) = (self.as_i128(a), self.as_i128(b)) else { return Err(cold_type("% requires numeric operands")); }; |
| 134 | if bi == 0 { return Err(VmErr::ZeroDiv); } |
| 135 | // Floor-mod on i128: result takes the divisor's sign. `checked_rem` guards against i128::MIN % -1 (which would overflow). |
| 136 | let r = ai.checked_rem(bi).ok_or(cold_overflow())?; |
| 137 | let r = if (r != 0) && ((r < 0) != (bi < 0)) { r + bi } else { r }; |
| 138 | self.int_to_val(Some(r)) |
| 139 | } |
| 140 | |
| 141 | /* printf-style `str % args`: translates each `%[flags][width][.prec]conv` into the `{:spec}` mini-language and reuses `format_value`. A tuple spreads; else one value. */ |
| 142 | fn str_percent_format(&mut self, fmt_val: Val, arg: Val, chunk: &SSAChunk, slots: &mut [Val]) -> Result<Val, VmErr> { |
no test coverage detected