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