Skip to main content

aver/services/
time.rs

1/// Time service — wall clock and sleeping.
2///
3/// Methods:
4///   Time.now()      — current UTC timestamp as RFC3339-like string
5///   Time.unixMs()   — unix epoch milliseconds as Int
6///   Time.sleep(ms)  — sleep current thread for `ms` milliseconds
7///
8/// Effects are granular:
9/// - Time.now
10/// - Time.unixMs
11/// - Time.sleep
12use std::collections::HashMap;
13use std::sync::Arc as Rc;
14
15use crate::nan_value::{Arena, NanIntExt, NanValue};
16use crate::value::{RuntimeError, Value};
17
18pub fn register(global: &mut HashMap<String, Value>) {
19    let mut members = HashMap::new();
20    for method in &["now", "unixMs", "sleep"] {
21        members.insert(
22            method.to_string(),
23            Value::Builtin(format!("Time.{}", method)),
24        );
25    }
26    global.insert(
27        "Time".to_string(),
28        Value::Namespace {
29            name: "Time".to_string(),
30            members,
31        },
32    );
33}
34
35pub const DECLARED_EFFECTS: &[&str] = &["Time.now", "Time.unixMs", "Time.sleep"];
36
37pub fn effects(name: &str) -> &'static [&'static str] {
38    match name {
39        "Time.now" => &["Time.now"],
40        "Time.unixMs" => &["Time.unixMs"],
41        "Time.sleep" => &["Time.sleep"],
42        _ => &[],
43    }
44}
45
46pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
47    match name {
48        "Time.now" => Some(now(args)),
49        "Time.unixMs" => Some(unix_ms(args)),
50        "Time.sleep" => Some(sleep(args)),
51        _ => None,
52    }
53}
54
55fn now(args: &[Value]) -> Result<Value, RuntimeError> {
56    if !args.is_empty() {
57        return Err(RuntimeError::Error(format!(
58            "Time.now() takes 0 arguments, got {}",
59            args.len()
60        )));
61    }
62    Ok(Value::Str(aver_rt::time_now()))
63}
64
65fn unix_ms(args: &[Value]) -> Result<Value, RuntimeError> {
66    if !args.is_empty() {
67        return Err(RuntimeError::Error(format!(
68            "Time.unixMs() takes 0 arguments, got {}",
69            args.len()
70        )));
71    }
72    Ok(Value::int(aver_rt::time_unix_ms()))
73}
74
75fn sleep(args: &[Value]) -> Result<Value, RuntimeError> {
76    let [ms] = args else {
77        return Err(RuntimeError::Error(format!(
78            "Time.sleep() takes 1 argument (ms), got {}",
79            args.len()
80        )));
81    };
82    let Value::Int(ms) = ms else {
83        return Err(RuntimeError::Error(
84            "Time.sleep: ms must be an Int".to_string(),
85        ));
86    };
87    let Some(ms) = ms.to_i64() else {
88        return Err(RuntimeError::Error(
89            "Time.sleep: ms must fit a 64-bit integer".to_string(),
90        ));
91    };
92    if ms < 0 {
93        return Err(RuntimeError::Error(
94            "Time.sleep: ms must be non-negative".to_string(),
95        ));
96    }
97    aver_rt::time_sleep(ms);
98    Ok(Value::Unit)
99}
100
101// ─── NanValue-native API ─────────────────────────────────────────────────────
102
103pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
104    let methods = &["now", "unixMs", "sleep"];
105    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
106    for method in methods {
107        let idx = arena.push_builtin(&format!("Time.{}", method));
108        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
109    }
110    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
111        name: Rc::from("Time"),
112        members,
113    });
114    global.insert("Time".to_string(), NanValue::new_namespace(ns_idx));
115}
116
117pub fn call_nv(
118    name: &str,
119    args: &[NanValue],
120    arena: &mut Arena,
121) -> Option<Result<NanValue, RuntimeError>> {
122    match name {
123        "Time.now" => Some(now_nv(args, arena)),
124        "Time.unixMs" => Some(unix_ms_nv(args, arena)),
125        "Time.sleep" => Some(sleep_nv(args, arena)),
126        _ => None,
127    }
128}
129
130fn now_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
131    if !args.is_empty() {
132        return Err(RuntimeError::Error(format!(
133            "Time.now() takes 0 arguments, got {}",
134            args.len()
135        )));
136    }
137    let s = aver_rt::time_now();
138    Ok(NanValue::new_string_value(&s, arena))
139}
140
141fn unix_ms_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
142    if !args.is_empty() {
143        return Err(RuntimeError::Error(format!(
144            "Time.unixMs() takes 0 arguments, got {}",
145            args.len()
146        )));
147    }
148    Ok(NanValue::new_int(aver_rt::time_unix_ms(), arena))
149}
150
151fn sleep_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
152    if args.len() != 1 {
153        return Err(RuntimeError::Error(format!(
154            "Time.sleep() takes 1 argument (ms), got {}",
155            args.len()
156        )));
157    }
158    if !args[0].is_int() {
159        return Err(RuntimeError::Error(
160            "Time.sleep: ms must be an Int".to_string(),
161        ));
162    }
163    let Some(ms) = args[0].as_aver_int(arena).to_i64() else {
164        return Err(RuntimeError::Error(
165            "Time.sleep: ms must fit a 64-bit integer".to_string(),
166        ));
167    };
168    if ms < 0 {
169        return Err(RuntimeError::Error(
170            "Time.sleep: ms must be non-negative".to_string(),
171        ));
172    }
173    aver_rt::time_sleep(ms);
174    Ok(NanValue::UNIT)
175}