Skip to main content

aver/services/
random.rs

1/// Random service — effectful random number generation.
2///
3/// Methods:
4///   Random.int(min, max)  — random Int in [min, max] inclusive; ! [Random]
5///   Random.float()        — random Float in [0.0, 1.0); ! [Random]
6///
7/// Backed by `aver_rt::random` (OS entropy via `rand` crate).
8use std::collections::HashMap;
9use std::rc::Rc;
10
11use crate::nan_value::{Arena, NanValue};
12use crate::value::{RuntimeError, Value};
13
14pub fn register(global: &mut HashMap<String, Value>) {
15    let mut members = HashMap::new();
16    for method in &["int", "float"] {
17        members.insert(
18            method.to_string(),
19            Value::Builtin(format!("Random.{}", method)),
20        );
21    }
22    global.insert(
23        "Random".to_string(),
24        Value::Namespace {
25            name: "Random".to_string(),
26            members,
27        },
28    );
29}
30
31pub fn effects(name: &str) -> &'static [&'static str] {
32    match name {
33        "Random.int" => &["Random.int"],
34        "Random.float" => &["Random.float"],
35        _ => &[],
36    }
37}
38
39pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
40    match name {
41        "Random.int" => Some(random_int(args)),
42        "Random.float" => Some(random_float(args)),
43        _ => None,
44    }
45}
46
47fn random_int(args: &[Value]) -> Result<Value, RuntimeError> {
48    if args.len() != 2 {
49        return Err(RuntimeError::Error(format!(
50            "Random.int takes 2 arguments (min, max), got {}",
51            args.len()
52        )));
53    }
54    let Value::Int(min) = &args[0] else {
55        return Err(RuntimeError::Error(
56            "Random.int: first argument must be an Int".to_string(),
57        ));
58    };
59    let Value::Int(max) = &args[1] else {
60        return Err(RuntimeError::Error(
61            "Random.int: second argument must be an Int".to_string(),
62        ));
63    };
64
65    match aver_rt::random::random_int(*min, *max) {
66        Ok(value) => Ok(Value::Int(value)),
67        Err(msg) => Err(RuntimeError::Error(msg)),
68    }
69}
70
71fn random_float(args: &[Value]) -> Result<Value, RuntimeError> {
72    if !args.is_empty() {
73        return Err(RuntimeError::Error(format!(
74            "Random.float takes 0 arguments, got {}",
75            args.len()
76        )));
77    }
78    Ok(Value::Float(aver_rt::random::random_float()))
79}
80
81// ─── NanValue-native API ─────────────────────────────────────────────────────
82
83pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
84    let methods = &["int", "float"];
85    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
86    for method in methods {
87        let idx = arena.push_builtin(&format!("Random.{}", method));
88        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
89    }
90    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
91        name: Rc::from("Random"),
92        members,
93    });
94    global.insert("Random".to_string(), NanValue::new_namespace(ns_idx));
95}
96
97pub fn call_nv(
98    name: &str,
99    args: &[NanValue],
100    arena: &mut Arena,
101) -> Option<Result<NanValue, RuntimeError>> {
102    match name {
103        "Random.int" => Some(random_int_nv(args, arena)),
104        "Random.float" => Some(random_float_nv(args, arena)),
105        _ => None,
106    }
107}
108
109fn random_int_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
110    if args.len() != 2 {
111        return Err(RuntimeError::Error(format!(
112            "Random.int takes 2 arguments (min, max), got {}",
113            args.len()
114        )));
115    }
116    if !args[0].is_int() {
117        return Err(RuntimeError::Error(
118            "Random.int: first argument must be an Int".to_string(),
119        ));
120    }
121    if !args[1].is_int() {
122        return Err(RuntimeError::Error(
123            "Random.int: second argument must be an Int".to_string(),
124        ));
125    }
126    let min = args[0].as_int(arena);
127    let max = args[1].as_int(arena);
128    match aver_rt::random::random_int(min, max) {
129        Ok(value) => Ok(NanValue::new_int(value, arena)),
130        Err(msg) => Err(RuntimeError::Error(msg)),
131    }
132}
133
134fn random_float_nv(args: &[NanValue], _arena: &mut Arena) -> Result<NanValue, RuntimeError> {
135    if !args.is_empty() {
136        return Err(RuntimeError::Error(format!(
137            "Random.float takes 0 arguments, got {}",
138            args.len()
139        )));
140    }
141    Ok(NanValue::new_float(aver_rt::random::random_float()))
142}