Skip to main content

bock_core/primitives/
float.rs

1//! Float primitive type methods and trait implementations.
2
3use bock_interp::{BockString, BuiltinRegistry, OrdF64, RuntimeError, TypeTag, Value};
4
5/// Register all Float methods and trait implementations.
6pub fn register(registry: &mut BuiltinRegistry) {
7    // ── Arithmetic trait methods ─────────────────────────────────────────
8    registry.register(TypeTag::Float, "add", float_add);
9    registry.register(TypeTag::Float, "sub", float_sub);
10    registry.register(TypeTag::Float, "mul", float_mul);
11    registry.register(TypeTag::Float, "div", float_div);
12    registry.register(TypeTag::Float, "rem", float_rem);
13    registry.register(TypeTag::Float, "pow", float_pow);
14    registry.register(TypeTag::Float, "negate", float_negate);
15
16    // ── Comparable trait ─────────────────────────────────────────────────
17    registry.register(TypeTag::Float, "compare", float_compare);
18
19    // ── Equatable trait ──────────────────────────────────────────────────
20    registry.register(TypeTag::Float, "equals", float_equals);
21
22    // ── Hashable trait ───────────────────────────────────────────────────
23    registry.register(TypeTag::Float, "hash_code", float_hash_code);
24
25    // ── Displayable trait ────────────────────────────────────────────────
26    registry.register(TypeTag::Float, "display", float_display);
27
28    // ── Type-specific methods ────────────────────────────────────────────
29    registry.register(TypeTag::Float, "abs", float_abs);
30    registry.register(TypeTag::Float, "floor", float_floor);
31    registry.register(TypeTag::Float, "ceil", float_ceil);
32    registry.register(TypeTag::Float, "round", float_round);
33    registry.register(TypeTag::Float, "to_int", float_to_int);
34    registry.register(TypeTag::Float, "sqrt", float_sqrt);
35    registry.register(TypeTag::Float, "is_nan", float_is_nan);
36    registry.register(TypeTag::Float, "is_infinite", float_is_infinite);
37    registry.register(TypeTag::Float, "min", float_min);
38    registry.register(TypeTag::Float, "max", float_max);
39    registry.register(TypeTag::Float, "clamp", float_clamp);
40}
41
42// ─── Helpers ──────────────────────────────────────────────────────────────────
43
44fn expect_float(args: &[Value], pos: usize, method: &str) -> Result<f64, RuntimeError> {
45    match args.get(pos) {
46        Some(Value::Float(v)) => Ok(v.0),
47        Some(other) => Err(RuntimeError::TypeError(format!(
48            "Float.{method} expects Float, got {other}"
49        ))),
50        None => Err(RuntimeError::ArityMismatch {
51            expected: pos + 1,
52            got: args.len(),
53        }),
54    }
55}
56
57// ─── Arithmetic ───────────────────────────────────────────────────────────────
58
59fn float_add(args: &[Value]) -> Result<Value, RuntimeError> {
60    let a = expect_float(args, 0, "add")?;
61    let b = expect_float(args, 1, "add")?;
62    Ok(Value::Float(OrdF64(a + b)))
63}
64
65fn float_sub(args: &[Value]) -> Result<Value, RuntimeError> {
66    let a = expect_float(args, 0, "sub")?;
67    let b = expect_float(args, 1, "sub")?;
68    Ok(Value::Float(OrdF64(a - b)))
69}
70
71fn float_mul(args: &[Value]) -> Result<Value, RuntimeError> {
72    let a = expect_float(args, 0, "mul")?;
73    let b = expect_float(args, 1, "mul")?;
74    Ok(Value::Float(OrdF64(a * b)))
75}
76
77fn float_div(args: &[Value]) -> Result<Value, RuntimeError> {
78    let a = expect_float(args, 0, "div")?;
79    let b = expect_float(args, 1, "div")?;
80    Ok(Value::Float(OrdF64(a / b)))
81}
82
83fn float_rem(args: &[Value]) -> Result<Value, RuntimeError> {
84    let a = expect_float(args, 0, "rem")?;
85    let b = expect_float(args, 1, "rem")?;
86    Ok(Value::Float(OrdF64(a % b)))
87}
88
89fn float_pow(args: &[Value]) -> Result<Value, RuntimeError> {
90    let a = expect_float(args, 0, "pow")?;
91    let b = expect_float(args, 1, "pow")?;
92    Ok(Value::Float(OrdF64(a.powf(b))))
93}
94
95fn float_negate(args: &[Value]) -> Result<Value, RuntimeError> {
96    let a = expect_float(args, 0, "negate")?;
97    Ok(Value::Float(OrdF64(-a)))
98}
99
100// ─── Comparable ───────────────────────────────────────────────────────────────
101
102fn float_compare(args: &[Value]) -> Result<Value, RuntimeError> {
103    let a = expect_float(args, 0, "compare")?;
104    let b = expect_float(args, 1, "compare")?;
105    Ok(Value::Int(a.total_cmp(&b) as i64))
106}
107
108// ─── Equatable ────────────────────────────────────────────────────────────────
109
110fn float_equals(args: &[Value]) -> Result<Value, RuntimeError> {
111    let a = expect_float(args, 0, "equals")?;
112    let b = expect_float(args, 1, "equals")?;
113    Ok(Value::Bool(OrdF64(a) == OrdF64(b)))
114}
115
116// ─── Hashable ─────────────────────────────────────────────────────────────────
117
118fn float_hash_code(args: &[Value]) -> Result<Value, RuntimeError> {
119    use std::hash::{Hash, Hasher};
120    let a = expect_float(args, 0, "hash_code")?;
121    let mut hasher = std::collections::hash_map::DefaultHasher::new();
122    OrdF64(a).hash(&mut hasher);
123    Ok(Value::Int(hasher.finish() as i64))
124}
125
126// ─── Displayable ──────────────────────────────────────────────────────────────
127
128fn float_display(args: &[Value]) -> Result<Value, RuntimeError> {
129    let a = expect_float(args, 0, "display")?;
130    Ok(Value::String(BockString::new(format!("{a}"))))
131}
132
133// ─── Type-specific methods ────────────────────────────────────────────────────
134
135fn float_abs(args: &[Value]) -> Result<Value, RuntimeError> {
136    let a = expect_float(args, 0, "abs")?;
137    Ok(Value::Float(OrdF64(a.abs())))
138}
139
140fn float_floor(args: &[Value]) -> Result<Value, RuntimeError> {
141    let a = expect_float(args, 0, "floor")?;
142    Ok(Value::Float(OrdF64(a.floor())))
143}
144
145fn float_ceil(args: &[Value]) -> Result<Value, RuntimeError> {
146    let a = expect_float(args, 0, "ceil")?;
147    Ok(Value::Float(OrdF64(a.ceil())))
148}
149
150fn float_round(args: &[Value]) -> Result<Value, RuntimeError> {
151    let a = expect_float(args, 0, "round")?;
152    Ok(Value::Float(OrdF64(a.round())))
153}
154
155fn float_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
156    let a = expect_float(args, 0, "to_int")?;
157    if a.is_nan() || a.is_infinite() {
158        return Err(RuntimeError::TypeError(
159            "cannot convert NaN or Infinity to Int".to_string(),
160        ));
161    }
162    Ok(Value::Int(a as i64))
163}
164
165fn float_sqrt(args: &[Value]) -> Result<Value, RuntimeError> {
166    let a = expect_float(args, 0, "sqrt")?;
167    Ok(Value::Float(OrdF64(a.sqrt())))
168}
169
170fn float_is_nan(args: &[Value]) -> Result<Value, RuntimeError> {
171    let a = expect_float(args, 0, "is_nan")?;
172    Ok(Value::Bool(a.is_nan()))
173}
174
175fn float_is_infinite(args: &[Value]) -> Result<Value, RuntimeError> {
176    let a = expect_float(args, 0, "is_infinite")?;
177    Ok(Value::Bool(a.is_infinite()))
178}
179
180fn float_min(args: &[Value]) -> Result<Value, RuntimeError> {
181    let a = expect_float(args, 0, "min")?;
182    let b = expect_float(args, 1, "min")?;
183    Ok(Value::Float(OrdF64(a.min(b))))
184}
185
186fn float_max(args: &[Value]) -> Result<Value, RuntimeError> {
187    let a = expect_float(args, 0, "max")?;
188    let b = expect_float(args, 1, "max")?;
189    Ok(Value::Float(OrdF64(a.max(b))))
190}
191
192fn float_clamp(args: &[Value]) -> Result<Value, RuntimeError> {
193    let a = expect_float(args, 0, "clamp")?;
194    let lo = expect_float(args, 1, "clamp")?;
195    let hi = expect_float(args, 2, "clamp")?;
196    Ok(Value::Float(OrdF64(a.clamp(lo, hi))))
197}
198
199// ─── Tests ────────────────────────────────────────────────────────────────────
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    fn reg() -> BuiltinRegistry {
206        let mut r = BuiltinRegistry::new();
207        register(&mut r);
208        r
209    }
210
211    fn f(v: f64) -> Value {
212        Value::Float(OrdF64(v))
213    }
214
215    #[test]
216    fn add_ok() {
217        let r = reg();
218        let result = r.call(TypeTag::Float, "add", &[f(1.5), f(2.5)]);
219        assert_eq!(result.unwrap().unwrap(), f(4.0));
220    }
221
222    #[test]
223    fn sub_ok() {
224        let r = reg();
225        let result = r.call(TypeTag::Float, "sub", &[f(5.0), f(2.0)]);
226        assert_eq!(result.unwrap().unwrap(), f(3.0));
227    }
228
229    #[test]
230    fn mul_ok() {
231        let r = reg();
232        let result = r.call(TypeTag::Float, "mul", &[f(3.0), f(4.0)]);
233        assert_eq!(result.unwrap().unwrap(), f(12.0));
234    }
235
236    #[test]
237    fn div_ok() {
238        let r = reg();
239        let result = r.call(TypeTag::Float, "div", &[f(10.0), f(4.0)]);
240        assert_eq!(result.unwrap().unwrap(), f(2.5));
241    }
242
243    #[test]
244    fn pow_ok() {
245        let r = reg();
246        let result = r.call(TypeTag::Float, "pow", &[f(2.0), f(3.0)]);
247        assert_eq!(result.unwrap().unwrap(), f(8.0));
248    }
249
250    #[test]
251    fn negate_ok() {
252        let r = reg();
253        let result = r.call(TypeTag::Float, "negate", &[f(3.14)]);
254        assert_eq!(result.unwrap().unwrap(), f(-3.14));
255    }
256
257    #[test]
258    fn compare_less() {
259        let r = reg();
260        let result = r.call(TypeTag::Float, "compare", &[f(1.0), f(2.0)]);
261        assert_eq!(result.unwrap().unwrap(), Value::Int(-1));
262    }
263
264    #[test]
265    fn equals_true() {
266        let r = reg();
267        let result = r.call(TypeTag::Float, "equals", &[f(1.5), f(1.5)]);
268        assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
269    }
270
271    #[test]
272    fn display_float() {
273        let r = reg();
274        let result = r.call(TypeTag::Float, "display", &[f(3.14)]);
275        assert_eq!(
276            result.unwrap().unwrap(),
277            Value::String(BockString::new("3.14"))
278        );
279    }
280
281    #[test]
282    fn abs_negative() {
283        let r = reg();
284        let result = r.call(TypeTag::Float, "abs", &[f(-42.5)]);
285        assert_eq!(result.unwrap().unwrap(), f(42.5));
286    }
287
288    #[test]
289    fn floor_ok() {
290        let r = reg();
291        let result = r.call(TypeTag::Float, "floor", &[f(3.7)]);
292        assert_eq!(result.unwrap().unwrap(), f(3.0));
293    }
294
295    #[test]
296    fn ceil_ok() {
297        let r = reg();
298        let result = r.call(TypeTag::Float, "ceil", &[f(3.2)]);
299        assert_eq!(result.unwrap().unwrap(), f(4.0));
300    }
301
302    #[test]
303    fn round_ok() {
304        let r = reg();
305        let result = r.call(TypeTag::Float, "round", &[f(3.5)]);
306        assert_eq!(result.unwrap().unwrap(), f(4.0));
307    }
308
309    #[test]
310    fn to_int_ok() {
311        let r = reg();
312        let result = r.call(TypeTag::Float, "to_int", &[f(42.9)]);
313        assert_eq!(result.unwrap().unwrap(), Value::Int(42));
314    }
315
316    #[test]
317    fn to_int_nan_error() {
318        let r = reg();
319        let result = r.call(TypeTag::Float, "to_int", &[f(f64::NAN)]);
320        assert!(result.unwrap().is_err());
321    }
322
323    #[test]
324    fn sqrt_ok() {
325        let r = reg();
326        let result = r.call(TypeTag::Float, "sqrt", &[f(9.0)]);
327        assert_eq!(result.unwrap().unwrap(), f(3.0));
328    }
329
330    #[test]
331    fn is_nan_true() {
332        let r = reg();
333        let result = r.call(TypeTag::Float, "is_nan", &[f(f64::NAN)]);
334        assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
335    }
336
337    #[test]
338    fn is_infinite_true() {
339        let r = reg();
340        let result = r.call(TypeTag::Float, "is_infinite", &[f(f64::INFINITY)]);
341        assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
342    }
343
344    #[test]
345    fn min_ok() {
346        let r = reg();
347        let result = r.call(TypeTag::Float, "min", &[f(3.0), f(7.0)]);
348        assert_eq!(result.unwrap().unwrap(), f(3.0));
349    }
350
351    #[test]
352    fn max_ok() {
353        let r = reg();
354        let result = r.call(TypeTag::Float, "max", &[f(3.0), f(7.0)]);
355        assert_eq!(result.unwrap().unwrap(), f(7.0));
356    }
357
358    #[test]
359    fn clamp_ok() {
360        let r = reg();
361        let result = r.call(TypeTag::Float, "clamp", &[f(15.0), f(0.0), f(10.0)]);
362        assert_eq!(result.unwrap().unwrap(), f(10.0));
363    }
364
365    #[test]
366    fn hash_code_deterministic() {
367        let r = reg();
368        let h1 = r
369            .call(TypeTag::Float, "hash_code", &[f(3.14)])
370            .unwrap()
371            .unwrap();
372        let h2 = r
373            .call(TypeTag::Float, "hash_code", &[f(3.14)])
374            .unwrap()
375            .unwrap();
376        assert_eq!(h1, h2);
377    }
378}