Coverage Report

Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/runtime/binary_ops.rs
Line
Count
Source
1
//! Shared binary operation evaluation
2
//! Extracted to reduce duplication across interpreter and REPL
3
4
use crate::frontend::ast::BinaryOp;
5
use crate::runtime::Value;
6
use anyhow::{Result, bail};
7
8
/// Evaluate a binary operation on two values
9
///
10
/// # Examples
11
///
12
/// ```
13
/// use ruchy::runtime::binary_ops::evaluate_binary_op;
14
/// use ruchy::runtime::Value;
15
/// use ruchy::frontend::ast::BinaryOp;
16
///
17
/// let lhs = Value::Int(5);
18
/// let rhs = Value::Int(3);
19
/// let result = evaluate_binary_op(&BinaryOp::Add, &lhs, &rhs).unwrap();
20
/// assert_eq!(result, Value::Int(8));
21
/// ```
22
0
pub fn evaluate_binary_op(op: &BinaryOp, lhs: &Value, rhs: &Value) -> Result<Value> {
23
0
    match op {
24
0
        BinaryOp::Add => evaluate_add(lhs, rhs),
25
0
        BinaryOp::Subtract => evaluate_subtract(lhs, rhs),
26
0
        BinaryOp::Multiply => evaluate_multiply(lhs, rhs),
27
0
        BinaryOp::Divide => evaluate_divide(lhs, rhs),
28
0
        BinaryOp::Modulo => evaluate_modulo(lhs, rhs),
29
0
        BinaryOp::Power => evaluate_power(lhs, rhs),
30
        
31
0
        BinaryOp::Equal => Ok(Value::Bool(values_equal(lhs, rhs))),
32
0
        BinaryOp::NotEqual => Ok(Value::Bool(!values_equal(lhs, rhs))),
33
0
        BinaryOp::Less => evaluate_less(lhs, rhs),
34
0
        BinaryOp::LessEqual => evaluate_less_equal(lhs, rhs),
35
0
        BinaryOp::Greater => evaluate_greater(lhs, rhs),
36
0
        BinaryOp::GreaterEqual => evaluate_greater_equal(lhs, rhs),
37
        
38
0
        BinaryOp::And => evaluate_and(lhs, rhs),
39
0
        BinaryOp::Or => evaluate_or(lhs, rhs),
40
        
41
0
        BinaryOp::BitwiseAnd => evaluate_bitwise_and(lhs, rhs),
42
0
        BinaryOp::BitwiseOr => evaluate_bitwise_or(lhs, rhs),
43
0
        BinaryOp::BitwiseXor => evaluate_bitwise_xor(lhs, rhs),
44
0
        BinaryOp::LeftShift => evaluate_left_shift(lhs, rhs),
45
        
46
0
        BinaryOp::NullCoalesce => Ok(if matches!(lhs, Value::Unit) { 
47
0
            rhs.clone() 
48
        } else { 
49
0
            lhs.clone() 
50
        }),
51
    }
52
0
}
53
54
0
fn evaluate_add(lhs: &Value, rhs: &Value) -> Result<Value> {
55
0
    match (lhs, rhs) {
56
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a + b)),
57
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
58
0
        (Value::String(a), Value::String(b)) => {
59
0
            let mut result = String::with_capacity(a.len() + b.len());
60
0
            result.push_str(a);
61
0
            result.push_str(b);
62
0
            Ok(Value::String(result))
63
        }
64
0
        (Value::List(a), Value::List(b)) => {
65
0
            let mut result = Vec::with_capacity(a.len() + b.len());
66
0
            result.extend(a.iter().cloned());
67
0
            result.extend(b.iter().cloned());
68
0
            Ok(Value::List(result))
69
        }
70
0
        _ => bail!("Cannot add {:?} and {:?}", lhs, rhs),
71
    }
72
0
}
73
74
0
fn evaluate_subtract(lhs: &Value, rhs: &Value) -> Result<Value> {
75
0
    match (lhs, rhs) {
76
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a - b)),
77
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)),
78
0
        _ => bail!("Cannot subtract {:?} and {:?}", lhs, rhs),
79
    }
80
0
}
81
82
0
fn evaluate_multiply(lhs: &Value, rhs: &Value) -> Result<Value> {
83
0
    match (lhs, rhs) {
84
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a * b)),
85
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)),
86
0
        (Value::String(s), Value::Int(n)) | (Value::Int(n), Value::String(s)) => {
87
0
            if *n < 0 {
88
0
                bail!("Cannot repeat string negative times");
89
0
            }
90
0
            Ok(Value::String(s.repeat(*n as usize)))
91
        }
92
0
        _ => bail!("Cannot multiply {:?} and {:?}", lhs, rhs),
93
    }
94
0
}
95
96
0
fn evaluate_divide(lhs: &Value, rhs: &Value) -> Result<Value> {
97
0
    match (lhs, rhs) {
98
0
        (Value::Int(a), Value::Int(b)) => {
99
0
            if *b == 0 {
100
0
                bail!("Division by zero");
101
0
            }
102
0
            Ok(Value::Int(a / b))
103
        }
104
0
        (Value::Float(a), Value::Float(b)) => {
105
0
            if b.abs() < f64::EPSILON {
106
0
                bail!("Division by zero");
107
0
            }
108
0
            Ok(Value::Float(a / b))
109
        }
110
0
        _ => bail!("Cannot divide {:?} and {:?}", lhs, rhs),
111
    }
112
0
}
113
114
0
fn evaluate_modulo(lhs: &Value, rhs: &Value) -> Result<Value> {
115
0
    match (lhs, rhs) {
116
0
        (Value::Int(a), Value::Int(b)) => {
117
0
            if *b == 0 {
118
0
                bail!("Modulo by zero");
119
0
            }
120
0
            Ok(Value::Int(a % b))
121
        }
122
0
        _ => bail!("Cannot modulo {:?} and {:?}", lhs, rhs),
123
    }
124
0
}
125
126
0
fn evaluate_power(lhs: &Value, rhs: &Value) -> Result<Value> {
127
0
    match (lhs, rhs) {
128
0
        (Value::Int(a), Value::Int(b)) => {
129
0
            if *b < 0 {
130
0
                bail!("Negative exponent not supported for integers");
131
0
            }
132
0
            Ok(Value::Int(a.pow(*b as u32)))
133
        }
134
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a.powf(*b))),
135
0
        _ => bail!("Cannot exponentiate {:?} and {:?}", lhs, rhs),
136
    }
137
0
}
138
139
0
fn evaluate_less(lhs: &Value, rhs: &Value) -> Result<Value> {
140
0
    match (lhs, rhs) {
141
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a < b)),
142
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a < b)),
143
0
        (Value::String(a), Value::String(b)) => Ok(Value::Bool(a < b)),
144
0
        _ => bail!("Cannot compare {:?} and {:?}", lhs, rhs),
145
    }
146
0
}
147
148
0
fn evaluate_less_equal(lhs: &Value, rhs: &Value) -> Result<Value> {
149
0
    match (lhs, rhs) {
150
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a <= b)),
151
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a <= b)),
152
0
        (Value::String(a), Value::String(b)) => Ok(Value::Bool(a <= b)),
153
0
        _ => bail!("Cannot compare {:?} and {:?}", lhs, rhs),
154
    }
155
0
}
156
157
0
fn evaluate_greater(lhs: &Value, rhs: &Value) -> Result<Value> {
158
0
    match (lhs, rhs) {
159
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a > b)),
160
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a > b)),
161
0
        (Value::String(a), Value::String(b)) => Ok(Value::Bool(a > b)),
162
0
        _ => bail!("Cannot compare {:?} and {:?}", lhs, rhs),
163
    }
164
0
}
165
166
0
fn evaluate_greater_equal(lhs: &Value, rhs: &Value) -> Result<Value> {
167
0
    match (lhs, rhs) {
168
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a >= b)),
169
0
        (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a >= b)),
170
0
        (Value::String(a), Value::String(b)) => Ok(Value::Bool(a >= b)),
171
0
        _ => bail!("Cannot compare {:?} and {:?}", lhs, rhs),
172
    }
173
0
}
174
175
0
fn evaluate_and(lhs: &Value, rhs: &Value) -> Result<Value> {
176
0
    match (lhs, rhs) {
177
0
        (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(*a && *b)),
178
0
        _ => bail!("Cannot AND {:?} and {:?}", lhs, rhs),
179
    }
180
0
}
181
182
0
fn evaluate_or(lhs: &Value, rhs: &Value) -> Result<Value> {
183
0
    match (lhs, rhs) {
184
0
        (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(*a || *b)),
185
0
        _ => bail!("Cannot OR {:?} and {:?}", lhs, rhs),
186
    }
187
0
}
188
189
0
fn evaluate_bitwise_and(lhs: &Value, rhs: &Value) -> Result<Value> {
190
0
    match (lhs, rhs) {
191
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a & b)),
192
0
        _ => bail!("Cannot bitwise AND {:?} and {:?}", lhs, rhs),
193
    }
194
0
}
195
196
0
fn evaluate_bitwise_or(lhs: &Value, rhs: &Value) -> Result<Value> {
197
0
    match (lhs, rhs) {
198
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a | b)),
199
0
        _ => bail!("Cannot bitwise OR {:?} and {:?}", lhs, rhs),
200
    }
201
0
}
202
203
0
fn evaluate_bitwise_xor(lhs: &Value, rhs: &Value) -> Result<Value> {
204
0
    match (lhs, rhs) {
205
0
        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a ^ b)),
206
0
        _ => bail!("Cannot bitwise XOR {:?} and {:?}", lhs, rhs),
207
    }
208
0
}
209
210
0
fn evaluate_left_shift(lhs: &Value, rhs: &Value) -> Result<Value> {
211
0
    match (lhs, rhs) {
212
0
        (Value::Int(a), Value::Int(b)) => {
213
0
            if *b < 0 || *b >= 64 {
214
0
                bail!("Invalid shift amount: {}", b);
215
0
            }
216
0
            Ok(Value::Int(a << b))
217
        }
218
0
        _ => bail!("Cannot left shift {:?} by {:?}", lhs, rhs),
219
    }
220
0
}
221
222
/// Check if two values are equal
223
0
fn values_equal(v1: &Value, v2: &Value) -> bool {
224
0
    match (v1, v2) {
225
0
        (Value::Unit, Value::Unit) => true,
226
0
        (Value::Int(a), Value::Int(b)) => a == b,
227
0
        (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON,
228
0
        (Value::String(a), Value::String(b)) => a == b,
229
0
        (Value::Bool(a), Value::Bool(b)) => a == b,
230
0
        (Value::Char(a), Value::Char(b)) => a == b,
231
0
        (Value::List(a), Value::List(b)) => {
232
0
            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
233
        }
234
0
        (Value::Tuple(a), Value::Tuple(b)) => {
235
0
            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
236
        }
237
0
        _ => false,
238
    }
239
0
}