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///
14/// No effects required.
15use std::collections::HashMap;
16use std::rc::Rc;
17
18use crate::nan_value::{Arena, NanValue};
19use crate::value::{RuntimeError, Value};
20
21pub fn register(global: &mut HashMap<String, Value>) {
22    let mut members = HashMap::new();
23    for method in &[
24        "fromString",
25        "fromInt",
26        "toString",
27        "abs",
28        "floor",
29        "ceil",
30        "round",
31        "min",
32        "max",
33    ] {
34        members.insert(
35            method.to_string(),
36            Value::Builtin(format!("Float.{}", method)),
37        );
38    }
39    global.insert(
40        "Float".to_string(),
41        Value::Namespace {
42            name: "Float".to_string(),
43            members,
44        },
45    );
46}
47
48pub fn effects(_name: &str) -> &'static [&'static str] {
49    &[]
50}
51
52/// Returns `Some(result)` when `name` is owned by this namespace, `None` otherwise.
53pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
54    match name {
55        "Float.fromString" => Some(from_string(args)),
56        "Float.fromInt" => Some(from_int(args)),
57        "Float.toString" => Some(to_string(args)),
58        "Float.abs" => Some(abs(args)),
59        "Float.floor" => Some(floor(args)),
60        "Float.ceil" => Some(ceil(args)),
61        "Float.round" => Some(round(args)),
62        "Float.min" => Some(min(args)),
63        "Float.max" => Some(max(args)),
64        _ => None,
65    }
66}
67
68// ─── Implementations ────────────────────────────────────────────────────────
69
70fn from_string(args: &[Value]) -> Result<Value, RuntimeError> {
71    let [val] = one_arg("Float.fromString", args)?;
72    let Value::Str(s) = val else {
73        return Err(RuntimeError::Error(
74            "Float.fromString: argument must be a String".to_string(),
75        ));
76    };
77    match s.parse::<f64>() {
78        Ok(f) => Ok(Value::Ok(Box::new(Value::Float(f)))),
79        Err(_) => Ok(Value::Err(Box::new(Value::Str(format!(
80            "Cannot parse '{}' as Float",
81            s
82        ))))),
83    }
84}
85
86fn from_int(args: &[Value]) -> Result<Value, RuntimeError> {
87    let [val] = one_arg("Float.fromInt", args)?;
88    let Value::Int(n) = val else {
89        return Err(RuntimeError::Error(
90            "Float.fromInt: argument must be an Int".to_string(),
91        ));
92    };
93    Ok(Value::Float(*n as f64))
94}
95
96fn to_string(args: &[Value]) -> Result<Value, RuntimeError> {
97    let [val] = one_arg("Float.toString", args)?;
98    let Value::Float(f) = val else {
99        return Err(RuntimeError::Error(
100            "Float.toString: argument must be a Float".to_string(),
101        ));
102    };
103    Ok(Value::Str(format!("{}", f)))
104}
105
106fn abs(args: &[Value]) -> Result<Value, RuntimeError> {
107    let [val] = one_arg("Float.abs", args)?;
108    let Value::Float(f) = val else {
109        return Err(RuntimeError::Error(
110            "Float.abs: argument must be a Float".to_string(),
111        ));
112    };
113    Ok(Value::Float(f.abs()))
114}
115
116fn floor(args: &[Value]) -> Result<Value, RuntimeError> {
117    let [val] = one_arg("Float.floor", args)?;
118    let Value::Float(f) = val else {
119        return Err(RuntimeError::Error(
120            "Float.floor: argument must be a Float".to_string(),
121        ));
122    };
123    Ok(Value::Int(f.floor() as i64))
124}
125
126fn ceil(args: &[Value]) -> Result<Value, RuntimeError> {
127    let [val] = one_arg("Float.ceil", args)?;
128    let Value::Float(f) = val else {
129        return Err(RuntimeError::Error(
130            "Float.ceil: argument must be a Float".to_string(),
131        ));
132    };
133    Ok(Value::Int(f.ceil() as i64))
134}
135
136fn round(args: &[Value]) -> Result<Value, RuntimeError> {
137    let [val] = one_arg("Float.round", args)?;
138    let Value::Float(f) = val else {
139        return Err(RuntimeError::Error(
140            "Float.round: argument must be a Float".to_string(),
141        ));
142    };
143    Ok(Value::Int(f.round() as i64))
144}
145
146fn min(args: &[Value]) -> Result<Value, RuntimeError> {
147    let [a, b] = two_args("Float.min", args)?;
148    let (Value::Float(x), Value::Float(y)) = (a, b) else {
149        return Err(RuntimeError::Error(
150            "Float.min: both arguments must be Float".to_string(),
151        ));
152    };
153    Ok(Value::Float(f64::min(*x, *y)))
154}
155
156fn max(args: &[Value]) -> Result<Value, RuntimeError> {
157    let [a, b] = two_args("Float.max", args)?;
158    let (Value::Float(x), Value::Float(y)) = (a, b) else {
159        return Err(RuntimeError::Error(
160            "Float.max: both arguments must be Float".to_string(),
161        ));
162    };
163    Ok(Value::Float(f64::max(*x, *y)))
164}
165
166// ─── Helpers ────────────────────────────────────────────────────────────────
167
168fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
169    if args.len() != 1 {
170        return Err(RuntimeError::Error(format!(
171            "{}() takes 1 argument, got {}",
172            name,
173            args.len()
174        )));
175    }
176    Ok([&args[0]])
177}
178
179fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
180    if args.len() != 2 {
181        return Err(RuntimeError::Error(format!(
182            "{}() takes 2 arguments, got {}",
183            name,
184            args.len()
185        )));
186    }
187    Ok([&args[0], &args[1]])
188}
189
190// ─── NanValue-native API ─────────────────────────────────────────────────────
191
192pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
193    let methods = &[
194        "fromString",
195        "fromInt",
196        "toString",
197        "abs",
198        "floor",
199        "ceil",
200        "round",
201        "min",
202        "max",
203    ];
204    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
205    for method in methods {
206        let idx = arena.push_builtin(&format!("Float.{}", method));
207        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
208    }
209    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
210        name: Rc::from("Float"),
211        members,
212    });
213    global.insert("Float".to_string(), NanValue::new_namespace(ns_idx));
214}
215
216pub fn call_nv(
217    name: &str,
218    args: &[NanValue],
219    arena: &mut Arena,
220) -> Option<Result<NanValue, RuntimeError>> {
221    match name {
222        "Float.fromString" => Some(from_string_nv(args, arena)),
223        "Float.fromInt" => Some(from_int_nv(args, arena)),
224        "Float.toString" => Some(to_string_nv(args, arena)),
225        "Float.abs" => Some(abs_nv(args, arena)),
226        "Float.floor" => Some(floor_nv(args, arena)),
227        "Float.ceil" => Some(ceil_nv(args, arena)),
228        "Float.round" => Some(round_nv(args, arena)),
229        "Float.min" => Some(min_nv(args, arena)),
230        "Float.max" => Some(max_nv(args, arena)),
231        _ => None,
232    }
233}
234
235fn nv_check1(name: &str, args: &[NanValue]) -> Result<NanValue, 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 nv_check2(name: &str, args: &[NanValue]) -> Result<(NanValue, NanValue), 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
257fn from_string_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
258    let v = nv_check1("Float.fromString", args)?;
259    if !v.is_string() {
260        return Err(RuntimeError::Error(
261            "Float.fromString: argument must be a String".to_string(),
262        ));
263    }
264    let s = arena.get_string_value(v);
265    match s.parse::<f64>() {
266        Ok(f) => {
267            let inner = NanValue::new_float(f);
268            Ok(NanValue::new_ok_value(inner, arena))
269        }
270        Err(_) => {
271            let msg = format!("Cannot parse '{}' as Float", s);
272            let inner = NanValue::new_string_value(&msg, arena);
273            Ok(NanValue::new_err_value(inner, arena))
274        }
275    }
276}
277
278fn from_int_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
279    let v = nv_check1("Float.fromInt", args)?;
280    if !v.is_int() {
281        return Err(RuntimeError::Error(
282            "Float.fromInt: argument must be an Int".to_string(),
283        ));
284    }
285    Ok(NanValue::new_float(v.as_int(arena) as f64))
286}
287
288fn to_string_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
289    let v = nv_check1("Float.toString", args)?;
290    if !v.is_float() {
291        return Err(RuntimeError::Error(
292            "Float.toString: argument must be a Float".to_string(),
293        ));
294    }
295    let s = format!("{}", v.as_float());
296    Ok(NanValue::new_string_value(&s, arena))
297}
298
299fn abs_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
300    let v = nv_check1("Float.abs", args)?;
301    if !v.is_float() {
302        return Err(RuntimeError::Error(
303            "Float.abs: argument must be a Float".to_string(),
304        ));
305    }
306    Ok(NanValue::new_float(v.as_float().abs()))
307}
308
309fn floor_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
310    let v = nv_check1("Float.floor", args)?;
311    if !v.is_float() {
312        return Err(RuntimeError::Error(
313            "Float.floor: argument must be a Float".to_string(),
314        ));
315    }
316    Ok(NanValue::new_int(v.as_float().floor() as i64, arena))
317}
318
319fn ceil_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
320    let v = nv_check1("Float.ceil", args)?;
321    if !v.is_float() {
322        return Err(RuntimeError::Error(
323            "Float.ceil: argument must be a Float".to_string(),
324        ));
325    }
326    Ok(NanValue::new_int(v.as_float().ceil() as i64, arena))
327}
328
329fn round_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
330    let v = nv_check1("Float.round", args)?;
331    if !v.is_float() {
332        return Err(RuntimeError::Error(
333            "Float.round: argument must be a Float".to_string(),
334        ));
335    }
336    Ok(NanValue::new_int(v.as_float().round() as i64, arena))
337}
338
339fn min_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
340    let (a, b) = nv_check2("Float.min", args)?;
341    if !a.is_float() || !b.is_float() {
342        return Err(RuntimeError::Error(
343            "Float.min: both arguments must be Float".to_string(),
344        ));
345    }
346    Ok(NanValue::new_float(f64::min(a.as_float(), b.as_float())))
347}
348
349fn max_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
350    let (a, b) = nv_check2("Float.max", args)?;
351    if !a.is_float() || !b.is_float() {
352        return Err(RuntimeError::Error(
353            "Float.max: both arguments must be Float".to_string(),
354        ));
355    }
356    Ok(NanValue::new_float(f64::max(a.as_float(), b.as_float())))
357}