Skip to main content

aver/types/
float.rs

1/// Float namespace — numeric helpers for floating-point values.
2///
3/// Methods:
4///   Float.fromString(s)  → Result<Float, String>  — parse string to float
5///   Float.fromInt(n)     → Float                  — widen int to float
6///   Float.abs(f)         → Float                  — absolute value
7///   Float.floor(f)       → Int                    — floor to int
8///   Float.ceil(f)        → Int                    — ceil to int
9///   Float.round(f)       → Int                    — round to int
10///   Float.min(a, b)      → Float                  — minimum of two floats
11///   Float.max(a, b)      → Float                  — maximum of two floats
12///   Float.sin(f)         → Float                  — sine (radians)
13///   Float.cos(f)         → Float                  — cosine (radians)
14///   Float.sqrt(f)        → Float                  — square root
15///   Float.pow(base, exp) → Float                  — exponentiation
16///   Float.atan2(y, x)    → Float                  — two-argument arctangent
17///   Float.pi()           → Float                  — π constant
18///
19/// No effects required.
20use std::collections::HashMap;
21use std::sync::Arc as Rc;
22
23use crate::nan_value::{Arena, NanIntExt, NanValue};
24use crate::value::{RuntimeError, Value};
25
26pub fn register(global: &mut HashMap<String, Value>) {
27    let mut members = HashMap::new();
28    for method in &[
29        "fromString",
30        "fromInt",
31        "abs",
32        "floor",
33        "ceil",
34        "round",
35        "min",
36        "max",
37        "sin",
38        "cos",
39        "sqrt",
40        "pow",
41        "atan2",
42        "pi",
43    ] {
44        members.insert(
45            method.to_string(),
46            Value::Builtin(format!("Float.{}", method)),
47        );
48    }
49    global.insert(
50        "Float".to_string(),
51        Value::Namespace {
52            name: "Float".to_string(),
53            members,
54        },
55    );
56}
57
58pub fn effects(_name: &str) -> &'static [&'static str] {
59    &[]
60}
61
62/// Returns `Some(result)` when `name` is owned by this namespace, `None` otherwise.
63pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
64    match name {
65        "Float.fromString" => Some(from_string(args)),
66        "Float.fromInt" => Some(from_int(args)),
67        "Float.abs" => Some(abs(args)),
68        "Float.floor" => Some(floor(args)),
69        "Float.ceil" => Some(ceil(args)),
70        "Float.round" => Some(round(args)),
71        "Float.min" => Some(min(args)),
72        "Float.max" => Some(max(args)),
73        "Float.sin" => Some(sin(args)),
74        "Float.cos" => Some(cos(args)),
75        "Float.sqrt" => Some(sqrt(args)),
76        "Float.pow" => Some(pow(args)),
77        "Float.atan2" => Some(atan2(args)),
78        "Float.pi" => Some(pi(args)),
79        _ => None,
80    }
81}
82
83// ─── Implementations ────────────────────────────────────────────────────────
84
85fn from_string(args: &[Value]) -> Result<Value, RuntimeError> {
86    let [val] = one_arg("Float.fromString", args)?;
87    let Value::Str(s) = val else {
88        return Err(RuntimeError::Error(
89            "Float.fromString: argument must be a String".to_string(),
90        ));
91    };
92    match s.parse::<f64>() {
93        Ok(f) => Ok(Value::Ok(Box::new(Value::Float(f)))),
94        Err(_) => Ok(Value::Err(Box::new(Value::Str(format!(
95            "Cannot parse '{}' as Float",
96            s
97        ))))),
98    }
99}
100
101fn from_int(args: &[Value]) -> Result<Value, RuntimeError> {
102    let [val] = one_arg("Float.fromInt", args)?;
103    let Value::Int(n) = val else {
104        return Err(RuntimeError::Error(
105            "Float.fromInt: argument must be an Int".to_string(),
106        ));
107    };
108    // Lossy by nature; huge magnitudes saturate to ±∞ (matching the Lean
109    // prelude's `Float.ofInt`), never `NaN`.
110    Ok(Value::Float(n.to_f64()))
111}
112
113fn abs(args: &[Value]) -> Result<Value, RuntimeError> {
114    let [val] = one_arg("Float.abs", args)?;
115    let Value::Float(f) = val else {
116        return Err(RuntimeError::Error(
117            "Float.abs: argument must be a Float".to_string(),
118        ));
119    };
120    Ok(Value::Float(f.abs()))
121}
122
123fn floor(args: &[Value]) -> Result<Value, RuntimeError> {
124    let [val] = one_arg("Float.floor", args)?;
125    let Value::Float(f) = val else {
126        return Err(RuntimeError::Error(
127            "Float.floor: argument must be a Float".to_string(),
128        ));
129    };
130    Ok(Value::Int(crate::types::int::float_to_aver_int(f.floor())))
131}
132
133fn ceil(args: &[Value]) -> Result<Value, RuntimeError> {
134    let [val] = one_arg("Float.ceil", args)?;
135    let Value::Float(f) = val else {
136        return Err(RuntimeError::Error(
137            "Float.ceil: argument must be a Float".to_string(),
138        ));
139    };
140    Ok(Value::Int(crate::types::int::float_to_aver_int(f.ceil())))
141}
142
143fn round(args: &[Value]) -> Result<Value, RuntimeError> {
144    let [val] = one_arg("Float.round", args)?;
145    let Value::Float(f) = val else {
146        return Err(RuntimeError::Error(
147            "Float.round: argument must be a Float".to_string(),
148        ));
149    };
150    Ok(Value::Int(crate::types::int::float_to_aver_int(f.round())))
151}
152
153fn min(args: &[Value]) -> Result<Value, RuntimeError> {
154    let [a, b] = two_args("Float.min", args)?;
155    let (Value::Float(x), Value::Float(y)) = (a, b) else {
156        return Err(RuntimeError::Error(
157            "Float.min: both arguments must be Float".to_string(),
158        ));
159    };
160    Ok(Value::Float(f64::min(*x, *y)))
161}
162
163fn max(args: &[Value]) -> Result<Value, RuntimeError> {
164    let [a, b] = two_args("Float.max", args)?;
165    let (Value::Float(x), Value::Float(y)) = (a, b) else {
166        return Err(RuntimeError::Error(
167            "Float.max: both arguments must be Float".to_string(),
168        ));
169    };
170    Ok(Value::Float(f64::max(*x, *y)))
171}
172
173fn sin(args: &[Value]) -> Result<Value, RuntimeError> {
174    let [val] = one_arg("Float.sin", args)?;
175    let Value::Float(f) = val else {
176        return Err(RuntimeError::Error(
177            "Float.sin: argument must be a Float".to_string(),
178        ));
179    };
180    Ok(Value::Float(f.sin()))
181}
182
183fn cos(args: &[Value]) -> Result<Value, RuntimeError> {
184    let [val] = one_arg("Float.cos", args)?;
185    let Value::Float(f) = val else {
186        return Err(RuntimeError::Error(
187            "Float.cos: argument must be a Float".to_string(),
188        ));
189    };
190    Ok(Value::Float(f.cos()))
191}
192
193fn sqrt(args: &[Value]) -> Result<Value, RuntimeError> {
194    let [val] = one_arg("Float.sqrt", args)?;
195    let Value::Float(f) = val else {
196        return Err(RuntimeError::Error(
197            "Float.sqrt: argument must be a Float".to_string(),
198        ));
199    };
200    Ok(Value::Float(f.sqrt()))
201}
202
203fn pow(args: &[Value]) -> Result<Value, RuntimeError> {
204    let [a, b] = two_args("Float.pow", args)?;
205    let (Value::Float(base), Value::Float(exp)) = (a, b) else {
206        return Err(RuntimeError::Error(
207            "Float.pow: both arguments must be Float".to_string(),
208        ));
209    };
210    Ok(Value::Float(base.powf(*exp)))
211}
212
213fn atan2(args: &[Value]) -> Result<Value, RuntimeError> {
214    let [a, b] = two_args("Float.atan2", args)?;
215    let (Value::Float(y), Value::Float(x)) = (a, b) else {
216        return Err(RuntimeError::Error(
217            "Float.atan2: both arguments must be Float".to_string(),
218        ));
219    };
220    Ok(Value::Float(y.atan2(*x)))
221}
222
223fn pi(args: &[Value]) -> Result<Value, RuntimeError> {
224    if !args.is_empty() {
225        return Err(RuntimeError::Error(format!(
226            "Float.pi() takes 0 arguments, got {}",
227            args.len()
228        )));
229    }
230    Ok(Value::Float(std::f64::consts::PI))
231}
232
233// ─── Helpers ────────────────────────────────────────────────────────────────
234
235fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
236    if args.len() != 1 {
237        return Err(RuntimeError::Error(format!(
238            "{}() takes 1 argument, got {}",
239            name,
240            args.len()
241        )));
242    }
243    Ok([&args[0]])
244}
245
246fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
247    if args.len() != 2 {
248        return Err(RuntimeError::Error(format!(
249            "{}() takes 2 arguments, got {}",
250            name,
251            args.len()
252        )));
253    }
254    Ok([&args[0], &args[1]])
255}
256
257// ─── NanValue-native API ─────────────────────────────────────────────────────
258
259pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
260    let methods = &[
261        "fromString",
262        "fromInt",
263        "abs",
264        "floor",
265        "ceil",
266        "round",
267        "min",
268        "max",
269        "sin",
270        "cos",
271        "sqrt",
272        "pow",
273        "atan2",
274        "pi",
275    ];
276    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
277    for method in methods {
278        let idx = arena.push_builtin(&format!("Float.{}", method));
279        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
280    }
281    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
282        name: Rc::from("Float"),
283        members,
284    });
285    global.insert("Float".to_string(), NanValue::new_namespace(ns_idx));
286}
287
288pub fn call_nv(
289    name: &str,
290    args: &[NanValue],
291    arena: &mut Arena,
292) -> Option<Result<NanValue, RuntimeError>> {
293    match name {
294        "Float.fromString" => Some(from_string_nv(args, arena)),
295        "Float.fromInt" => Some(from_int_nv(args, arena)),
296        "Float.abs" => Some(abs_nv(args, arena)),
297        "Float.floor" => Some(floor_nv(args, arena)),
298        "Float.ceil" => Some(ceil_nv(args, arena)),
299        "Float.round" => Some(round_nv(args, arena)),
300        "Float.min" => Some(min_nv(args, arena)),
301        "Float.max" => Some(max_nv(args, arena)),
302        "Float.sin" => Some(sin_nv(args, arena)),
303        "Float.cos" => Some(cos_nv(args, arena)),
304        "Float.sqrt" => Some(sqrt_nv(args, arena)),
305        "Float.pow" => Some(pow_nv(args, arena)),
306        "Float.atan2" => Some(atan2_nv(args, arena)),
307        "Float.pi" => Some(pi_nv(args)),
308        _ => None,
309    }
310}
311
312fn nv_check1(name: &str, args: &[NanValue]) -> Result<NanValue, RuntimeError> {
313    if args.len() != 1 {
314        return Err(RuntimeError::Error(format!(
315            "{}() takes 1 argument, got {}",
316            name,
317            args.len()
318        )));
319    }
320    Ok(args[0])
321}
322
323fn nv_check2(name: &str, args: &[NanValue]) -> Result<(NanValue, NanValue), RuntimeError> {
324    if args.len() != 2 {
325        return Err(RuntimeError::Error(format!(
326            "{}() takes 2 arguments, got {}",
327            name,
328            args.len()
329        )));
330    }
331    Ok((args[0], args[1]))
332}
333
334fn from_string_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
335    let v = nv_check1("Float.fromString", args)?;
336    if !v.is_string() {
337        return Err(RuntimeError::Error(
338            "Float.fromString: argument must be a String".to_string(),
339        ));
340    }
341    let s = arena.get_string_value(v);
342    match s.parse::<f64>() {
343        Ok(f) => {
344            let inner = NanValue::new_float(f);
345            Ok(NanValue::new_ok_value(inner, arena))
346        }
347        Err(_) => {
348            let msg = format!("Cannot parse '{}' as Float", s);
349            let inner = NanValue::new_string_value(&msg, arena);
350            Ok(NanValue::new_err_value(inner, arena))
351        }
352    }
353}
354
355fn from_int_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
356    let v = nv_check1("Float.fromInt", args)?;
357    if !v.is_int() {
358        return Err(RuntimeError::Error(
359            "Float.fromInt: argument must be an Int".to_string(),
360        ));
361    }
362    Ok(NanValue::new_float(v.as_aver_int(arena).to_f64()))
363}
364
365fn abs_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
366    let v = nv_check1("Float.abs", args)?;
367    if !v.is_float() {
368        return Err(RuntimeError::Error(
369            "Float.abs: argument must be a Float".to_string(),
370        ));
371    }
372    Ok(NanValue::new_float(v.as_float().abs()))
373}
374
375fn floor_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
376    let v = nv_check1("Float.floor", args)?;
377    if !v.is_float() {
378        return Err(RuntimeError::Error(
379            "Float.floor: argument must be a Float".to_string(),
380        ));
381    }
382    let r = crate::types::int::float_to_aver_int(v.as_float().floor());
383    Ok(NanValue::from_aver_int(r, arena))
384}
385
386fn ceil_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
387    let v = nv_check1("Float.ceil", args)?;
388    if !v.is_float() {
389        return Err(RuntimeError::Error(
390            "Float.ceil: argument must be a Float".to_string(),
391        ));
392    }
393    let r = crate::types::int::float_to_aver_int(v.as_float().ceil());
394    Ok(NanValue::from_aver_int(r, arena))
395}
396
397fn round_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
398    let v = nv_check1("Float.round", args)?;
399    if !v.is_float() {
400        return Err(RuntimeError::Error(
401            "Float.round: argument must be a Float".to_string(),
402        ));
403    }
404    let r = crate::types::int::float_to_aver_int(v.as_float().round());
405    Ok(NanValue::from_aver_int(r, arena))
406}
407
408fn min_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
409    let (a, b) = nv_check2("Float.min", args)?;
410    if !a.is_float() || !b.is_float() {
411        return Err(RuntimeError::Error(
412            "Float.min: both arguments must be Float".to_string(),
413        ));
414    }
415    Ok(NanValue::new_float(f64::min(a.as_float(), b.as_float())))
416}
417
418fn max_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
419    let (a, b) = nv_check2("Float.max", args)?;
420    if !a.is_float() || !b.is_float() {
421        return Err(RuntimeError::Error(
422            "Float.max: both arguments must be Float".to_string(),
423        ));
424    }
425    Ok(NanValue::new_float(f64::max(a.as_float(), b.as_float())))
426}
427
428fn sin_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
429    let v = nv_check1("Float.sin", args)?;
430    if !v.is_float() {
431        return Err(RuntimeError::Error(
432            "Float.sin: argument must be a Float".to_string(),
433        ));
434    }
435    Ok(NanValue::new_float(v.as_float().sin()))
436}
437
438fn cos_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
439    let v = nv_check1("Float.cos", args)?;
440    if !v.is_float() {
441        return Err(RuntimeError::Error(
442            "Float.cos: argument must be a Float".to_string(),
443        ));
444    }
445    Ok(NanValue::new_float(v.as_float().cos()))
446}
447
448fn sqrt_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
449    let v = nv_check1("Float.sqrt", args)?;
450    if !v.is_float() {
451        return Err(RuntimeError::Error(
452            "Float.sqrt: argument must be a Float".to_string(),
453        ));
454    }
455    Ok(NanValue::new_float(v.as_float().sqrt()))
456}
457
458fn pow_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
459    let (a, b) = nv_check2("Float.pow", args)?;
460    if !a.is_float() || !b.is_float() {
461        return Err(RuntimeError::Error(
462            "Float.pow: both arguments must be Float".to_string(),
463        ));
464    }
465    Ok(NanValue::new_float(a.as_float().powf(b.as_float())))
466}
467
468fn atan2_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
469    let (a, b) = nv_check2("Float.atan2", args)?;
470    if !a.is_float() || !b.is_float() {
471        return Err(RuntimeError::Error(
472            "Float.atan2: both arguments must be Float".to_string(),
473        ));
474    }
475    Ok(NanValue::new_float(a.as_float().atan2(b.as_float())))
476}
477
478fn pi_nv(args: &[NanValue]) -> Result<NanValue, RuntimeError> {
479    if !args.is_empty() {
480        return Err(RuntimeError::Error(format!(
481            "Float.pi() takes 0 arguments, got {}",
482            args.len()
483        )));
484    }
485    Ok(NanValue::new_float(std::f64::consts::PI))
486}