(&mut self, a: Val, b: Val)
| 496 | } |
| 497 | |
| 498 | pub fn mul_vals(&mut self, a: Val, b: Val) -> Result<Val, VmErr> { |
| 499 | if a.is_int() && b.is_int() |
| 500 | && let Some(r) = a.as_int().checked_mul(b.as_int()) |
| 501 | && (Val::INT_MIN..=Val::INT_MAX).contains(&r) { |
| 502 | return Ok(Val::int(r)); |
| 503 | } |
| 504 | if let Some((af, bf)) = coerce_floats(a, b, &self.heap) { return Ok(Val::float(af * bf)); } |
| 505 | // Numeric multiply wins over sequence repetition when both sides are int-like. |
| 506 | if let (Some(ai), Some(bi)) = (as_i128(a, &self.heap), as_i128(b, &self.heap)) { |
| 507 | return self.int_to_val(ai.checked_mul(bi)); |
| 508 | } |
| 509 | // Sequence repetition: str/list/tuple * int (count clamped to i64). |
| 510 | let (seq_val, count) = if a.is_heap() && b.is_int() && !matches!(self.heap.get(a), HeapObj::LongInt(_)) { |
| 511 | (a, b.as_int()) |
| 512 | } else if a.is_int() && b.is_heap() && !matches!(self.heap.get(b), HeapObj::LongInt(_)) { |
| 513 | (b, a.as_int()) |
| 514 | } else { |
| 515 | return Err(VmErr::TypeMsg(s!("unsupported operand type(s) for *: '", str self.type_name(a), "' and '", str self.type_name(b), "'"))); |
| 516 | }; |
| 517 | let n = count.max(0) as usize; |
| 518 | // Charge the fill up front so repeated `[x]*n` is bounded by the op budget, not just heap. |
| 519 | let fill_cost = match self.heap.get(seq_val) { |
| 520 | HeapObj::Str(s) => s.len().checked_mul(n), |
| 521 | HeapObj::List(rc) => rc.borrow().len().checked_mul(n), |
| 522 | HeapObj::Tuple(v) => v.len().checked_mul(n), |
| 523 | _ => None, |
| 524 | }; |
| 525 | if let Some(c) = fill_cost && c <= self.heap.limit() { self.charge_steps(c)?; } |
| 526 | match self.heap.get(seq_val) { |
| 527 | HeapObj::Str(s) => { |
| 528 | let bytes = s.len().checked_mul(n).ok_or(cold_overflow())?; |
| 529 | if bytes > self.heap.limit() { return Err(cold_heap()); } |
| 530 | let r = s.repeat(n); |
| 531 | return self.heap.alloc(HeapObj::Str(r)); |
| 532 | } |
| 533 | HeapObj::List(rc) => { |
| 534 | let src = rc.borrow().clone(); |
| 535 | // Empty source: result is empty for any n, so skip the n-iteration loop. |
| 536 | if src.is_empty() { return self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(Vec::new())))); } |
| 537 | let cap = src.len().checked_mul(n).ok_or(cold_overflow())?; |
| 538 | if cap > self.heap.limit() { return Err(cold_heap()); } |
| 539 | let mut out = Vec::with_capacity(cap); |
| 540 | for _ in 0..n { out.extend_from_slice(&src); } |
| 541 | return self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(out)))); |
| 542 | } |
| 543 | HeapObj::Tuple(v) => { |
| 544 | let src = v.clone(); |
| 545 | // Empty source: result is empty for any n, so skip the n-iteration loop. |
| 546 | if src.is_empty() { return self.heap.alloc(HeapObj::Tuple(Vec::new())); } |
| 547 | let cap = src.len().checked_mul(n).ok_or(cold_overflow())?; |
| 548 | if cap > self.heap.limit() { return Err(cold_heap()); } |
| 549 | let mut out = Vec::with_capacity(cap); |
| 550 | for _ in 0..n { out.extend_from_slice(&src); } |
| 551 | return self.heap.alloc(HeapObj::Tuple(out)); |
| 552 | } |
| 553 | _ => {} |
| 554 | } |
| 555 | Err(VmErr::TypeMsg(s!("unsupported operand type(s) for *: '", str self.type_name(a), "' and '", str self.type_name(b), "'"))) |
no test coverage detected