use num_bigint::{BigInt, Sign};
use num_integer::Integer as _;
use num_traits::{FromPrimitive, Signed, ToPrimitive, Zero};
use crate::vm::value::Value;
pub fn to_bigint(v: &Value) -> Result<BigInt, String> {
match v {
Value::Int(i) => Ok(BigInt::from(*i)),
Value::BigInt(i) => Ok((**i).clone()),
Value::Float(f) => {
BigInt::from_f64(f.trunc()).ok_or_else(|| "cannot convert float to integer".to_string())
}
_ => Err("expected integer".to_string()),
}
}
#[inline]
pub fn to_f64(v: &Value) -> Result<f64, String> {
match v {
Value::Float(f) => Ok(*f),
Value::Int(i) => Ok(*i as f64),
Value::BigInt(i) => i
.to_f64()
.ok_or_else(|| "integer overflow in float conversion".to_string()),
_ => Err("expected number".to_string()),
}
}
#[inline]
fn big_pair(a: &Value, b: &Value) -> Option<(BigInt, BigInt)> {
let ia = match a {
Value::Int(i) => BigInt::from(*i),
Value::BigInt(i) => (**i).clone(),
_ => return None,
};
let ib = match b {
Value::Int(i) => BigInt::from(*i),
Value::BigInt(i) => (**i).clone(),
_ => return None,
};
Some((ia, ib))
}
#[inline]
pub fn eval_add(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(match ia.checked_add(*ib) {
Some(r) => Value::Int(r),
None => Value::from_bigint(BigInt::from(*ia) + BigInt::from(*ib)),
});
}
if let Some((ia, ib)) = big_pair(a, b) {
return Ok(Value::from_bigint(ia + ib));
}
Ok(Value::Float(to_f64(a)? + to_f64(b)?))
}
#[inline]
pub fn eval_sub(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(match ia.checked_sub(*ib) {
Some(r) => Value::Int(r),
None => Value::from_bigint(BigInt::from(*ia) - BigInt::from(*ib)),
});
}
if let Some((ia, ib)) = big_pair(a, b) {
return Ok(Value::from_bigint(ia - ib));
}
Ok(Value::Float(to_f64(a)? - to_f64(b)?))
}
#[inline]
pub fn eval_mul(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(match ia.checked_mul(*ib) {
Some(r) => Value::Int(r),
None => Value::from_bigint(BigInt::from(*ia) * BigInt::from(*ib)),
});
}
if let Some((ia, ib)) = big_pair(a, b) {
return Ok(Value::from_bigint(ia * ib));
}
Ok(Value::Float(to_f64(a)? * to_f64(b)?))
}
#[inline]
pub fn eval_div(a: &Value, b: &Value) -> Result<Value, String> {
Ok(Value::Float(to_f64(a)? / to_f64(b)?))
}
pub fn eval_idiv(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
if *ib == 0 {
return Err("division by zero".to_string());
}
return Ok(match ia.checked_div_euclid(*ib) {
Some(_) => Value::Int(ia.div_floor(ib)),
None => Value::from_bigint(BigInt::from(*ia).div_floor(&BigInt::from(*ib))),
});
}
if let Some((ia, ib)) = big_pair(a, b) {
if ib.is_zero() {
return Err("division by zero".to_string());
}
return Ok(Value::from_bigint(ia.div_floor(&ib)));
}
let fa = to_f64(a)?;
let fb = to_f64(b)?;
if fb == 0.0 {
return Err("division by zero".to_string());
}
Ok(Value::Float((fa / fb).floor()))
}
pub fn eval_mod(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
if *ib == 0 {
return Err("modulo by zero".to_string());
}
return Ok(if *ib == -1 {
Value::Int(0)
} else {
Value::Int(ia.mod_floor(ib))
});
}
if let Some((ia, ib)) = big_pair(a, b) {
if ib.is_zero() {
return Err("modulo by zero".to_string());
}
return Ok(Value::from_bigint(ia.mod_floor(&ib)));
}
let fa = to_f64(a)?;
let fb = to_f64(b)?;
if fb == 0.0 {
return Err("modulo by zero".to_string());
}
Ok(Value::Float(fa - (fa / fb).floor() * fb))
}
pub fn eval_pow(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b)
&& *ib >= 0
&& let Ok(exp) = u32::try_from(*ib)
{
return Ok(match ia.checked_pow(exp) {
Some(r) => Value::Int(r),
None => Value::from_bigint(BigInt::from(*ia).pow(exp)),
});
}
if let Some((ia, ib)) = big_pair(a, b)
&& ib.sign() != Sign::Minus
&& let Some(exp) = ib.to_u32()
{
return Ok(Value::from_bigint(ia.pow(exp)));
}
Ok(Value::Float(to_f64(a)?.powf(to_f64(b)?)))
}
pub fn eval_bitand(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(Value::Int(ia & ib));
}
Ok(Value::from_bigint(to_bigint(a)? & to_bigint(b)?))
}
pub fn eval_bitor(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(Value::Int(ia | ib));
}
Ok(Value::from_bigint(to_bigint(a)? | to_bigint(b)?))
}
pub fn eval_bitxor(a: &Value, b: &Value) -> Result<Value, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(Value::Int(ia ^ ib));
}
Ok(Value::from_bigint(to_bigint(a)? ^ to_bigint(b)?))
}
fn shift_left(ia: &Value, shift: usize) -> Result<Value, String> {
if let Value::Int(i) = ia {
if *i == 0 {
return Ok(Value::Int(0));
}
let headroom = if *i >= 0 {
i.leading_zeros() - 1
} else {
i.leading_ones() - 1
} as usize;
if shift <= headroom {
return Ok(Value::Int(i << shift));
}
}
Ok(Value::from_bigint(to_bigint(ia)? << shift))
}
fn shift_right(ia: &Value, shift: usize) -> Result<Value, String> {
if let Value::Int(i) = ia {
return Ok(Value::Int(if shift >= 64 {
if *i < 0 { -1 } else { 0 }
} else {
i >> shift
}));
}
Ok(Value::from_bigint(to_bigint(ia)? >> shift))
}
fn shift_amount(b: &Value) -> Result<(bool, usize), String> {
match b {
Value::Int(i) => Ok((*i < 0, i.unsigned_abs() as usize)),
Value::BigInt(_) => Err("shift is too large".to_string()),
Value::Float(f) => {
let t = f.trunc();
Ok((t < 0.0, t.abs() as usize))
}
_ => Err("expected integer".to_string()),
}
}
pub fn eval_shl(a: &Value, b: &Value) -> Result<Value, String> {
if !a.is_number() {
return Err("expected integer".to_string());
}
let (negative, shift) = shift_amount(b)?;
if negative {
shift_right(a, shift)
} else {
shift_left(a, shift)
}
}
pub fn eval_shr(a: &Value, b: &Value) -> Result<Value, String> {
if !a.is_number() {
return Err("expected integer".to_string());
}
let (negative, shift) = shift_amount(b)?;
if negative {
shift_left(a, shift)
} else {
shift_right(a, shift)
}
}
fn low_word(v: &Value) -> Result<u64, String> {
match v {
Value::Int(i) => Ok(*i as u64),
_ => {
let big = to_bigint(v)?;
Ok((big & BigInt::from(u64::MAX)).to_u64().unwrap_or(0))
}
}
}
pub fn eval_lshl(a: &Value, b: &Value) -> Result<Value, String> {
let word = low_word(a)?;
let bits = to_bigint(b)?.to_usize().unwrap_or(usize::MAX);
let res = if bits >= 64 { 0 } else { word << bits };
Ok(Value::from_u64(res))
}
pub fn eval_lshr(a: &Value, b: &Value) -> Result<Value, String> {
let word = low_word(a)?;
let bits = to_bigint(b)?.to_usize().unwrap_or(usize::MAX);
let res = if bits >= 64 { 0 } else { word >> bits };
Ok(Value::from_u64(res))
}
pub fn eval_bitnot(a: &Value) -> Result<Value, String> {
if let Value::Int(i) = a {
return Ok(Value::Int(!i));
}
Ok(Value::from_bigint(!to_bigint(a)?))
}
pub fn eval_unm(a: &Value) -> Result<Value, String> {
match a {
Value::Int(i) => Ok(match i.checked_neg() {
Some(r) => Value::Int(r),
None => Value::from_bigint(-BigInt::from(*i)),
}),
Value::BigInt(b) => Ok(Value::from_bigint(-(**b).clone())),
Value::Float(f) => Ok(Value::Float(-f)),
_ => Err("unary minus expects a number".to_string()),
}
}
#[inline]
fn num_cmp(a: &Value, b: &Value) -> Result<Option<std::cmp::Ordering>, String> {
use std::cmp::Ordering;
Ok(Some(match (a, b) {
(Value::Int(x), Value::Int(y)) => x.cmp(y),
(Value::Int(_), Value::BigInt(y)) => {
if y.is_negative() {
Ordering::Greater
} else {
Ordering::Less
}
}
(Value::BigInt(x), Value::Int(_)) => {
if x.is_negative() {
Ordering::Less
} else {
Ordering::Greater
}
}
(Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
_ => return Ok(to_f64(a)?.partial_cmp(&to_f64(b)?)),
}))
}
macro_rules! compare_op {
($name:ident, $op:tt, $($pat:pat_param)|+) => {
#[inline]
pub fn $name(a: &Value, b: &Value) -> Result<bool, String> {
if let (Value::Int(ia), Value::Int(ib)) = (a, b) {
return Ok(ia $op ib);
}
if let (Value::String(sa), Value::String(sb)) = (a, b) {
return Ok(sa $op sb);
}
Ok(matches!(num_cmp(a, b)?, Some($($pat)|+)))
}
};
}
compare_op!(eval_lt, <, std::cmp::Ordering::Less);
compare_op!(eval_le, <=, std::cmp::Ordering::Less | std::cmp::Ordering::Equal);
compare_op!(eval_gt, >, std::cmp::Ordering::Greater);
compare_op!(eval_ge, >=, std::cmp::Ordering::Greater | std::cmp::Ordering::Equal);