Skip to main content

doge_runtime/ops/
bits.rs

1use bigdecimal::Signed;
2use num_bigint::BigInt;
3
4use crate::error::{DogeError, DogeResult};
5use crate::value::Value;
6
7/// The two Int operands of a bitwise binary operator, or a catchable type error
8/// naming both values — bitwise operators are Int-only.
9fn int_pair<'a>(sym: &str, a: &'a Value, b: &'a Value) -> DogeResult<(&'a BigInt, &'a BigInt)> {
10    match (a, b) {
11        (Value::Int(x), Value::Int(y)) => Ok((x, y)),
12        _ => Err(DogeError::type_error(format!(
13            "cannot {sym} {} and {} (bitwise operators need Ints)",
14            a.describe(),
15            b.describe()
16        ))),
17    }
18}
19
20/// A shift count as a `usize`: a negative count is a catchable value error, and a
21/// count too large to address is one too (a left shift by it could never fit in
22/// memory). `Int` is arbitrary precision, so a *left* shift never drops bits —
23/// only the count itself can be out of range.
24fn shift_count(y: &BigInt) -> DogeResult<usize> {
25    if y.is_negative() {
26        return Err(DogeError::value_error(format!(
27            "cannot shift by {y} — the shift count must be 0 or more"
28        )));
29    }
30    usize::try_from(y).map_err(|_| {
31        DogeError::value_error(format!(
32            "cannot shift by {y} — the shift count is too large"
33        ))
34    })
35}
36
37/// `&` — bitwise AND.
38pub fn bitand(a: Value, b: Value) -> DogeResult {
39    let (x, y) = int_pair("&", &a, &b)?;
40    Ok(Value::int(x & y))
41}
42
43/// `|` — bitwise OR.
44pub fn bitor(a: Value, b: Value) -> DogeResult {
45    let (x, y) = int_pair("|", &a, &b)?;
46    Ok(Value::int(x | y))
47}
48
49/// `^` — bitwise XOR.
50pub fn bitxor(a: Value, b: Value) -> DogeResult {
51    let (x, y) = int_pair("^", &a, &b)?;
52    Ok(Value::int(x ^ y))
53}
54
55/// `<<` — left shift. `Int` is arbitrary precision, so this never drops
56/// significant bits; only a negative or too-large shift count is a catchable error.
57pub fn shl(a: Value, b: Value) -> DogeResult {
58    let (x, y) = int_pair("<<", &a, &b)?;
59    let n = shift_count(y)?;
60    Ok(Value::int(x << n))
61}
62
63/// `>>` — arithmetic (sign-preserving) right shift. A count larger than the value
64/// has bits saturates to the sign fill: `0` for a non-negative value, `-1` for a
65/// negative one.
66pub fn shr(a: Value, b: Value) -> DogeResult {
67    let (x, y) = int_pair(">>", &a, &b)?;
68    if y.is_negative() {
69        return Err(DogeError::value_error(format!(
70            "cannot shift by {y} — the shift count must be 0 or more"
71        )));
72    }
73    // A count that doesn't fit a `usize` shifts every bit out; the result is the
74    // sign fill without materializing an absurd shift.
75    match usize::try_from(y) {
76        Ok(n) => Ok(Value::int(x >> n)),
77        Err(_) => Ok(Value::int(if x.is_negative() {
78            BigInt::from(-1)
79        } else {
80            BigInt::from(0)
81        })),
82    }
83}
84
85/// `~` — bitwise NOT (Int-only).
86pub fn bitnot(a: Value) -> DogeResult {
87    match &a {
88        Value::Int(n) => Ok(Value::int(!n)),
89        _ => Err(DogeError::type_error(format!(
90            "cannot ~ {} (bitwise NOT needs an Int)",
91            a.describe()
92        ))),
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::error::ErrorKind;
100    use crate::ops::compare::values_equal;
101
102    fn int(n: i64) -> Value {
103        Value::int(n)
104    }
105
106    #[test]
107    fn basic_bitwise() {
108        assert!(values_equal(
109            &bitand(int(0b1100), int(0b1010)).unwrap(),
110            &int(0b1000)
111        ));
112        assert!(values_equal(
113            &bitor(int(0b1100), int(0b1010)).unwrap(),
114            &int(0b1110)
115        ));
116        assert!(values_equal(
117            &bitxor(int(0b1100), int(0b1010)).unwrap(),
118            &int(0b0110)
119        ));
120        assert!(values_equal(&bitnot(int(0)).unwrap(), &int(-1)));
121        assert!(values_equal(&bitnot(int(5)).unwrap(), &int(-6)));
122    }
123
124    #[test]
125    fn shifts() {
126        assert!(values_equal(&shl(int(1), int(4)).unwrap(), &int(16)));
127        assert!(values_equal(&shr(int(16), int(4)).unwrap(), &int(1)));
128        // Arithmetic right shift keeps the sign.
129        assert!(values_equal(&shr(int(-8), int(1)).unwrap(), &int(-4)));
130        // A huge right-shift saturates to the sign fill.
131        assert!(values_equal(&shr(int(-8), int(200)).unwrap(), &int(-1)));
132        assert!(values_equal(&shr(int(8), int(200)).unwrap(), &int(0)));
133    }
134
135    #[test]
136    fn left_shift_grows_instead_of_overflowing() {
137        // `1 << 64` used to overflow i64; with arbitrary precision it is 2^64.
138        let two_pow_64: BigInt = BigInt::from(1) << 64u32;
139        assert!(values_equal(
140            &shl(int(1), int(64)).unwrap(),
141            &Value::int(two_pow_64)
142        ));
143    }
144
145    #[test]
146    fn shift_errors() {
147        assert_eq!(
148            shl(int(-1), int(-1)).unwrap_err().kind,
149            ErrorKind::ValueError
150        );
151        assert_eq!(
152            shr(int(1), int(-1)).unwrap_err().kind,
153            ErrorKind::ValueError
154        );
155    }
156
157    #[test]
158    fn non_int_is_a_type_error() {
159        assert_eq!(
160            bitand(int(1), Value::str("x")).unwrap_err().kind,
161            ErrorKind::TypeError
162        );
163        assert_eq!(
164            bitnot(Value::str("x")).unwrap_err().kind,
165            ErrorKind::TypeError
166        );
167    }
168}