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, 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    Ok(Value::Float(*n as f64))
109}
110
111fn abs(args: &[Value]) -> Result<Value, RuntimeError> {
112    let [val] = one_arg("Float.abs", args)?;
113    let Value::Float(f) = val else {
114        return Err(RuntimeError::Error(
115            "Float.abs: argument must be a Float".to_string(),
116        ));
117    };
118    Ok(Value::Float(f.abs()))
119}
120
121fn floor(args: &[Value]) -> Result<Value, RuntimeError> {
122    let [val] = one_arg("Float.floor", args)?;
123    let Value::Float(f) = val else {
124        return Err(RuntimeError::Error(
125            "Float.floor: argument must be a Float".to_string(),
126        ));
127    };
128    Ok(Value::Int(f.floor() as i64))
129}
130
131fn ceil(args: &[Value]) -> Result<Value, RuntimeError> {
132    let [val] = one_arg("Float.ceil", args)?;
133    let Value::Float(f) = val else {
134        return Err(RuntimeError::Error(
135            "Float.ceil: argument must be a Float".to_string(),
136        ));
137    };
138    Ok(Value::Int(f.ceil() as i64))
139}
140
141fn round(args: &[Value]) -> Result<Value, RuntimeError> {
142    let [val] = one_arg("Float.round", args)?;
143    let Value::Float(f) = val else {
144        return Err(RuntimeError::Error(
145            "Float.round: argument must be a Float".to_string(),
146        ));
147    };
148    Ok(Value::Int(f.round() as i64))
149}
150
151fn min(args: &[Value]) -> Result<Value, RuntimeError> {
152    let [a, b] = two_args("Float.min", args)?;
153    let (Value::Float(x), Value::Float(y)) = (a, b) else {
154        return Err(RuntimeError::Error(
155            "Float.min: both arguments must be Float".to_string(),
156        ));
157    };
158    Ok(Value::Float(f64::min(*x, *y)))
159}
160
161fn max(args: &[Value]) -> Result<Value, RuntimeError> {
162    let [a, b] = two_args("Float.max", args)?;
163    let (Value::Float(x), Value::Float(y)) = (a, b) else {
164        return Err(RuntimeError::Error(
165            "Float.max: both arguments must be Float".to_string(),
166        ));
167    };
168    Ok(Value::Float(f64::max(*x, *y)))
169}
170
171fn sin(args: &[Value]) -> Result<Value, RuntimeError> {
172    let [val] = one_arg("Float.sin", args)?;
173    let Value::Float(f) = val else {
174        return Err(RuntimeError::Error(
175            "Float.sin: argument must be a Float".to_string(),
176        ));
177    };
178    Ok(Value::Float(f.sin()))
179}
180
181fn cos(args: &[Value]) -> Result<Value, RuntimeError> {
182    let [val] = one_arg("Float.cos", args)?;
183    let Value::Float(f) = val else {
184        return Err(RuntimeError::Error(
185            "Float.cos: argument must be a Float".to_string(),
186        ));
187    };
188    Ok(Value::Float(f.cos()))
189}
190
191fn sqrt(args: &[Value]) -> Result<Value, RuntimeError> {
192    let [val] = one_arg("Float.sqrt", args)?;
193    let Value::Float(f) = val else {
194        return Err(RuntimeError::Error(
195            "Float.sqrt: argument must be a Float".to_string(),
196        ));
197    };
198    Ok(Value::Float(f.sqrt()))
199}
200
201fn pow(args: &[Value]) -> Result<Value, RuntimeError> {
202    let [a, b] = two_args("Float.pow", args)?;
203    let (Value::Float(base), Value::Float(exp)) = (a, b) else {
204        return Err(RuntimeError::Error(
205            "Float.pow: both arguments must be Float".to_string(),
206        ));
207    };
208    Ok(Value::Float(base.powf(*exp)))
209}
210
211fn atan2(args: &[Value]) -> Result<Value, RuntimeError> {
212    let [a, b] = two_args("Float.atan2", args)?;
213    let (Value::Float(y), Value::Float(x)) = (a, b) else {
214        return Err(RuntimeError::Error(
215            "Float.atan2: both arguments must be Float".to_string(),
216        ));
217    };
218    Ok(Value::Float(y.atan2(*x)))
219}
220
221fn pi(args: &[Value]) -> Result<Value, RuntimeError> {
222    if !args.is_empty() {
223        return Err(RuntimeError::Error(format!(
224            "Float.pi() takes 0 arguments, got {}",
225            args.len()
226        )));
227    }
228    Ok(Value::Float(std::f64::consts::PI))
229}
230
231// ─── Helpers ────────────────────────────────────────────────────────────────
232
233fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
234    if args.len() != 1 {
235        return Err(RuntimeError::Error(format!(
236            "{}() takes 1 argument, got {}",
237            name,
238            args.len()
239        )));
240    }
241    Ok([&args[0]])
242}
243
244fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
245    if args.len() != 2 {
246        return Err(RuntimeError::Error(format!(
247            "{}() takes 2 arguments, got {}",
248            name,
249            args.len()
250        )));
251    }
252    Ok([&args[0], &args[1]])
253}
254
255// ─── NanValue-native API ─────────────────────────────────────────────────────
256
257pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
258    let methods = &[
259        "fromString",
260        "fromInt",
261        "abs",
262        "floor",
263        "ceil",
264        "round",
265        "min",
266        "max",
267        "sin",
268        "cos",
269        "sqrt",
270        "pow",
271        "atan2",
272        "pi",
273    ];
274    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
275    for method in methods {
276        let idx = arena.push_builtin(&format!("Float.{}", method));
277        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
278    }
279    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
280        name: Rc::from("Float"),
281        members,
282    });
283    global.insert("Float".to_string(), NanValue::new_namespace(ns_idx));
284}
285
286pub fn call_nv(
287    name: &str,
288    args: &[NanValue],
289    arena: &mut Arena,
290) -> Option<Result<NanValue, RuntimeError>> {
291    match name {
292        "Float.fromString" => Some(from_string_nv(args, arena)),
293        "Float.fromInt" => Some(from_int_nv(args, arena)),
294        "Float.abs" => Some(abs_nv(args, arena)),
295        "Float.floor" => Some(floor_nv(args, arena)),
296        "Float.ceil" => Some(ceil_nv(args, arena)),
297        "Float.round" => Some(round_nv(args, arena)),
298        "Float.min" => Some(min_nv(args, arena)),
299        "Float.max" => Some(max_nv(args, arena)),
300        "Float.sin" => Some(sin_nv(args, arena)),
301        "Float.cos" => Some(cos_nv(args, arena)),
302        "Float.sqrt" => Some(sqrt_nv(args, arena)),
303        "Float.pow" => Some(pow_nv(args, arena)),
304        "Float.atan2" => Some(atan2_nv(args, arena)),
305        "Float.pi" => Some(pi_nv(args)),
306        _ => None,
307    }
308}
309
310fn nv_check1(name: &str, args: &[NanValue]) -> Result<NanValue, RuntimeError> {
311    if args.len() != 1 {
312        return Err(RuntimeError::Error(format!(
313            "{}() takes 1 argument, got {}",
314            name,
315            args.len()
316        )));
317    }
318    Ok(args[0])
319}
320
321fn nv_check2(name: &str, args: &[NanValue]) -> Result<(NanValue, NanValue), RuntimeError> {
322    if args.len() != 2 {
323        return Err(RuntimeError::Error(format!(
324            "{}() takes 2 arguments, got {}",
325            name,
326            args.len()
327        )));
328    }
329    Ok((args[0], args[1]))
330}
331
332fn from_string_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
333    let v = nv_check1("Float.fromString", args)?;
334    if !v.is_string() {
335        return Err(RuntimeError::Error(
336            "Float.fromString: argument must be a String".to_string(),
337        ));
338    }
339    let s = arena.get_string_value(v);
340    match s.parse::<f64>() {
341        Ok(f) => {
342            let inner = NanValue::new_float(f);
343            Ok(NanValue::new_ok_value(inner, arena))
344        }
345        Err(_) => {
346            let msg = format!("Cannot parse '{}' as Float", s);
347            let inner = NanValue::new_string_value(&msg, arena);
348            Ok(NanValue::new_err_value(inner, arena))
349        }
350    }
351}
352
353fn from_int_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
354    let v = nv_check1("Float.fromInt", args)?;
355    if !v.is_int() {
356        return Err(RuntimeError::Error(
357            "Float.fromInt: argument must be an Int".to_string(),
358        ));
359    }
360    Ok(NanValue::new_float(v.as_int(arena) as f64))
361}
362
363fn abs_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
364    let v = nv_check1("Float.abs", args)?;
365    if !v.is_float() {
366        return Err(RuntimeError::Error(
367            "Float.abs: argument must be a Float".to_string(),
368        ));
369    }
370    Ok(NanValue::new_float(v.as_float().abs()))
371}
372
373fn floor_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
374    let v = nv_check1("Float.floor", args)?;
375    if !v.is_float() {
376        return Err(RuntimeError::Error(
377            "Float.floor: argument must be a Float".to_string(),
378        ));
379    }
380    Ok(NanValue::new_int(v.as_float().floor() as i64, arena))
381}
382
383fn ceil_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
384    let v = nv_check1("Float.ceil", args)?;
385    if !v.is_float() {
386        return Err(RuntimeError::Error(
387            "Float.ceil: argument must be a Float".to_string(),
388        ));
389    }
390    Ok(NanValue::new_int(v.as_float().ceil() as i64, arena))
391}
392
393fn round_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
394    let v = nv_check1("Float.round", args)?;
395    if !v.is_float() {
396        return Err(RuntimeError::Error(
397            "Float.round: argument must be a Float".to_string(),
398        ));
399    }
400    Ok(NanValue::new_int(v.as_float().round() as i64, arena))
401}
402
403fn min_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
404    let (a, b) = nv_check2("Float.min", args)?;
405    if !a.is_float() || !b.is_float() {
406        return Err(RuntimeError::Error(
407            "Float.min: both arguments must be Float".to_string(),
408        ));
409    }
410    Ok(NanValue::new_float(f64::min(a.as_float(), b.as_float())))
411}
412
413fn max_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
414    let (a, b) = nv_check2("Float.max", args)?;
415    if !a.is_float() || !b.is_float() {
416        return Err(RuntimeError::Error(
417            "Float.max: both arguments must be Float".to_string(),
418        ));
419    }
420    Ok(NanValue::new_float(f64::max(a.as_float(), b.as_float())))
421}
422
423fn sin_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
424    let v = nv_check1("Float.sin", args)?;
425    if !v.is_float() {
426        return Err(RuntimeError::Error(
427            "Float.sin: argument must be a Float".to_string(),
428        ));
429    }
430    Ok(NanValue::new_float(v.as_float().sin()))
431}
432
433fn cos_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
434    let v = nv_check1("Float.cos", args)?;
435    if !v.is_float() {
436        return Err(RuntimeError::Error(
437            "Float.cos: argument must be a Float".to_string(),
438        ));
439    }
440    Ok(NanValue::new_float(v.as_float().cos()))
441}
442
443fn sqrt_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
444    let v = nv_check1("Float.sqrt", args)?;
445    if !v.is_float() {
446        return Err(RuntimeError::Error(
447            "Float.sqrt: argument must be a Float".to_string(),
448        ));
449    }
450    Ok(NanValue::new_float(v.as_float().sqrt()))
451}
452
453fn pow_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
454    let (a, b) = nv_check2("Float.pow", args)?;
455    if !a.is_float() || !b.is_float() {
456        return Err(RuntimeError::Error(
457            "Float.pow: both arguments must be Float".to_string(),
458        ));
459    }
460    Ok(NanValue::new_float(a.as_float().powf(b.as_float())))
461}
462
463fn atan2_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
464    let (a, b) = nv_check2("Float.atan2", args)?;
465    if !a.is_float() || !b.is_float() {
466        return Err(RuntimeError::Error(
467            "Float.atan2: both arguments must be Float".to_string(),
468        ));
469    }
470    Ok(NanValue::new_float(a.as_float().atan2(b.as_float())))
471}
472
473fn pi_nv(args: &[NanValue]) -> Result<NanValue, RuntimeError> {
474    if !args.is_empty() {
475        return Err(RuntimeError::Error(format!(
476            "Float.pi() takes 0 arguments, got {}",
477            args.len()
478        )));
479    }
480    Ok(NanValue::new_float(std::f64::consts::PI))
481}