use rudb_common::{Error, Result, Value};
use crate::number::integral;
pub(crate) fn value(name: &str, args: &[Value]) -> Option<Result<Value>> {
let answer = match (name, args) {
("&", [left, right]) => both(left, right, |a, b| a & b),
("|", [left, right]) => both(left, right, |a, b| a | b),
("xor", [left, right]) => both(left, right, |a, b| a ^ b),
("~", [only]) => raw(only)
.map(|(bits, width)| back(!bits, width))
.ok_or_else(|| Error::internal(format!("~ of {only}"))),
("<<", [input, shift]) => shift_left(input, shift),
(">>", [input, shift]) => Ok(shift_right(input, shift)),
("bit_count", [only]) => raw(only)
.map(|(bits, _)| {
#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let count = bits.count_ones() as u8 as i8;
Value::TinyInt(count)
})
.ok_or_else(|| Error::internal(format!("bit_count of {only}"))),
_ => return None,
};
Some(answer)
}
#[derive(Clone, Copy)]
struct Width {
bits: u32,
signed: bool,
}
#[expect(clippy::cast_sign_loss)]
fn raw(value: &Value) -> Option<(u128, Width)> {
let signed = |bits| Width { bits, signed: true };
let unsigned = |bits| Width { bits, signed: false };
Some(match *value {
Value::TinyInt(v) => (u128::from(v as u8), signed(8)),
Value::SmallInt(v) => (u128::from(v as u16), signed(16)),
Value::Integer(v) => (u128::from(v as u32), signed(32)),
Value::BigInt(v) => (u128::from(v as u64), signed(64)),
Value::HugeInt(v) => (v as u128, signed(128)),
Value::UTinyInt(v) => (u128::from(v), unsigned(8)),
Value::USmallInt(v) => (u128::from(v), unsigned(16)),
Value::UInteger(v) => (u128::from(v), unsigned(32)),
Value::UBigInt(v) => (u128::from(v), unsigned(64)),
Value::UHugeInt(v) => (v, unsigned(128)),
_ => return None,
})
}
#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
fn back(bits: u128, width: Width) -> Value {
match (width.bits, width.signed) {
(8, true) => Value::TinyInt(bits as u8 as i8),
(16, true) => Value::SmallInt(bits as u16 as i16),
(32, true) => Value::Integer(bits as u32 as i32),
(64, true) => Value::BigInt(bits as u64 as i64),
(_, true) => Value::HugeInt(bits as i128),
(8, false) => Value::UTinyInt(bits as u8),
(16, false) => Value::USmallInt(bits as u16),
(32, false) => Value::UInteger(bits as u32),
(64, false) => Value::UBigInt(bits as u64),
(_, false) => Value::UHugeInt(bits),
}
}
fn both(left: &Value, right: &Value, body: fn(u128, u128) -> u128) -> Result<Value> {
match (raw(left), raw(right)) {
(Some((a, width)), Some((b, _))) => Ok(back(body(a, b), width)),
_ => Err(Error::internal(format!("a bitwise function of {left} and {right}"))),
}
}
fn count(shift: &Value) -> i128 {
integral(shift).unwrap_or(i128::MAX)
}
fn shift_left(input: &Value, shift: &Value) -> Result<Value> {
let Some((bits, width)) = raw(input) else {
return Err(Error::internal(format!("{input} << {shift}")));
};
let negative = width.signed && (bits >> (width.bits - 1)) & 1 == 1;
if negative {
return Err(Error::out_of_range(format!("Cannot left-shift negative number {input}")));
}
let by = count(shift);
if by < 0 {
return Err(Error::out_of_range(format!("Cannot left-shift by negative number {shift}")));
}
let most = i128::from(width.bits) + i128::from(!width.signed);
if by >= most {
if bits == 0 {
return Ok(back(0, width));
}
return Err(Error::out_of_range(format!("Left-shift value {shift} is out of range")));
}
if by == 0 || bits == 0 {
return Ok(input.clone());
}
#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let (by, room) = (by as u32, (most - by - 1) as u32);
if bits >= 1u128 << room {
return Err(Error::out_of_range(format!("Overflow in left shift ({input} << {shift})")));
}
Ok(back(bits << by, width))
}
fn shift_right(input: &Value, shift: &Value) -> Value {
let Some((bits, width)) = raw(input) else {
return input.clone();
};
let by = count(shift);
if by < 0 || by >= i128::from(width.bits) {
return back(0, width);
}
#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let by = by as u32;
if width.signed {
let spare = 128 - width.bits;
#[expect(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
let shifted = (((bits << spare) as i128) >> spare >> by) as u128;
back(shifted, width)
} else {
back(bits >> by, width)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn call(name: &str, args: &[Value]) -> Result<Value> {
value(name, args).expect("a bitwise function")
}
#[test]
fn the_binary_ones_keep_the_type_they_were_given() {
assert_eq!(call("&", &[Value::Integer(5), Value::Integer(3)]).unwrap(), Value::Integer(1));
assert_eq!(call("|", &[Value::Integer(5), Value::Integer(3)]).unwrap(), Value::Integer(7));
assert_eq!(
call("xor", &[Value::TinyInt(5), Value::TinyInt(3)]).unwrap(),
Value::TinyInt(6)
);
assert_eq!(call("~", &[Value::Integer(5)]).unwrap(), Value::Integer(-6));
assert_eq!(call("~", &[Value::UTinyInt(5)]).unwrap(), Value::UTinyInt(250));
}
#[test]
fn a_left_shift_refuses_what_the_pin_refuses() {
let shl = |a: Value, b: Value| call("<<", &[a, b]).map_err(|e| e.to_string());
assert_eq!(shl(Value::Integer(1), Value::Integer(4)), Ok(Value::Integer(16)));
assert_eq!(shl(Value::UTinyInt(1), Value::UTinyInt(7)), Ok(Value::UTinyInt(128)));
assert_eq!(shl(Value::Integer(0), Value::Integer(99)), Ok(Value::Integer(0)));
assert!(shl(Value::Integer(1), Value::Integer(31)).unwrap_err().contains("(1 << 31)"));
assert!(shl(Value::UTinyInt(1), Value::UTinyInt(8)).unwrap_err().contains("(1 << 8)"));
assert!(shl(Value::Integer(1), Value::Integer(70)).unwrap_err().contains("value 70"));
assert!(shl(Value::Integer(-1), Value::Integer(1)).unwrap_err().contains("number -1"));
assert!(shl(Value::Integer(1), Value::Integer(-1)).unwrap_err().contains("by negative"));
}
#[test]
fn a_right_shift_carries_the_sign_and_empties_past_the_width() {
let shr = |a: Value, b: Value| call(">>", &[a, b]).unwrap();
assert_eq!(shr(Value::Integer(256), Value::Integer(4)), Value::Integer(16));
assert_eq!(shr(Value::Integer(-8), Value::Integer(1)), Value::Integer(-4));
assert_eq!(shr(Value::Integer(-8), Value::Integer(70)), Value::Integer(0));
assert_eq!(shr(Value::Integer(8), Value::Integer(-1)), Value::Integer(0));
}
#[test]
fn bit_count_counts_the_twos_complement_and_wraps_at_128() {
assert_eq!(call("bit_count", &[Value::BigInt(-1)]).unwrap(), Value::TinyInt(64));
assert_eq!(call("bit_count", &[Value::HugeInt(-1)]).unwrap(), Value::TinyInt(-128));
assert_eq!(call("bit_count", &[Value::TinyInt(7)]).unwrap(), Value::TinyInt(3));
}
}