use bigdecimal::Signed;
use num_bigint::BigInt;
use crate::error::{DogeError, DogeResult};
use crate::value::Value;
fn int_pair<'a>(sym: &str, a: &'a Value, b: &'a Value) -> DogeResult<(&'a BigInt, &'a BigInt)> {
match (a, b) {
(Value::Int(x), Value::Int(y)) => Ok((x, y)),
_ => Err(DogeError::type_error(format!(
"cannot {sym} {} and {} (bitwise operators need Ints)",
a.describe(),
b.describe()
))),
}
}
fn shift_count(y: &BigInt) -> DogeResult<usize> {
if y.is_negative() {
return Err(DogeError::value_error(format!(
"cannot shift by {y} — the shift count must be 0 or more"
)));
}
usize::try_from(y).map_err(|_| {
DogeError::value_error(format!(
"cannot shift by {y} — the shift count is too large"
))
})
}
pub fn bitand(a: Value, b: Value) -> DogeResult {
let (x, y) = int_pair("&", &a, &b)?;
Ok(Value::int(x & y))
}
pub fn bitor(a: Value, b: Value) -> DogeResult {
let (x, y) = int_pair("|", &a, &b)?;
Ok(Value::int(x | y))
}
pub fn bitxor(a: Value, b: Value) -> DogeResult {
let (x, y) = int_pair("^", &a, &b)?;
Ok(Value::int(x ^ y))
}
pub fn shl(a: Value, b: Value) -> DogeResult {
let (x, y) = int_pair("<<", &a, &b)?;
let n = shift_count(y)?;
Ok(Value::int(x << n))
}
pub fn shr(a: Value, b: Value) -> DogeResult {
let (x, y) = int_pair(">>", &a, &b)?;
if y.is_negative() {
return Err(DogeError::value_error(format!(
"cannot shift by {y} — the shift count must be 0 or more"
)));
}
match usize::try_from(y) {
Ok(n) => Ok(Value::int(x >> n)),
Err(_) => Ok(Value::int(if x.is_negative() {
BigInt::from(-1)
} else {
BigInt::from(0)
})),
}
}
pub fn bitnot(a: Value) -> DogeResult {
match &a {
Value::Int(n) => Ok(Value::int(!n)),
_ => Err(DogeError::type_error(format!(
"cannot ~ {} (bitwise NOT needs an Int)",
a.describe()
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorKind;
use crate::ops::compare::values_equal;
fn int(n: i64) -> Value {
Value::int(n)
}
#[test]
fn basic_bitwise() {
assert!(values_equal(
&bitand(int(0b1100), int(0b1010)).unwrap(),
&int(0b1000)
));
assert!(values_equal(
&bitor(int(0b1100), int(0b1010)).unwrap(),
&int(0b1110)
));
assert!(values_equal(
&bitxor(int(0b1100), int(0b1010)).unwrap(),
&int(0b0110)
));
assert!(values_equal(&bitnot(int(0)).unwrap(), &int(-1)));
assert!(values_equal(&bitnot(int(5)).unwrap(), &int(-6)));
}
#[test]
fn shifts() {
assert!(values_equal(&shl(int(1), int(4)).unwrap(), &int(16)));
assert!(values_equal(&shr(int(16), int(4)).unwrap(), &int(1)));
assert!(values_equal(&shr(int(-8), int(1)).unwrap(), &int(-4)));
assert!(values_equal(&shr(int(-8), int(200)).unwrap(), &int(-1)));
assert!(values_equal(&shr(int(8), int(200)).unwrap(), &int(0)));
}
#[test]
fn left_shift_grows_instead_of_overflowing() {
let two_pow_64: BigInt = BigInt::from(1) << 64u32;
assert!(values_equal(
&shl(int(1), int(64)).unwrap(),
&Value::int(two_pow_64)
));
}
#[test]
fn shift_errors() {
assert_eq!(
shl(int(-1), int(-1)).unwrap_err().kind,
ErrorKind::ValueError
);
assert_eq!(
shr(int(1), int(-1)).unwrap_err().kind,
ErrorKind::ValueError
);
}
#[test]
fn non_int_is_a_type_error() {
assert_eq!(
bitand(int(1), Value::str("x")).unwrap_err().kind,
ErrorKind::TypeError
);
assert_eq!(
bitnot(Value::str("x")).unwrap_err().kind,
ErrorKind::TypeError
);
}
}