shape_runtime/
stdlib_time.rs1use crate::module_exports::{ModuleExports, ModuleParam};
14use crate::typed_module_exports::{
15 ConcreteReturn, ConcreteType, TypedReturn, register_typed_async_function,
16 register_typed_function,
17};
18use shape_value::KindedSlot;
19
20fn slot_as_f64(slot: &KindedSlot) -> Option<f64> {
28 Some(slot.slot().as_f64())
29}
30
31pub fn create_time_module() -> ModuleExports {
33 let mut module = ModuleExports::new("std::core::time");
34 module.description = "Precision timing utilities".to_string();
35
36 register_typed_function(
38 &mut module,
39 "now",
40 "Return the current monotonic instant for measuring elapsed time",
41 vec![],
42 ConcreteType::Instant,
43 |_args, _ctx| {
44 Ok(TypedReturn::Concrete(ConcreteReturn::Instant(
45 std::time::Instant::now(),
46 )))
47 },
48 );
49
50 register_typed_async_function(
52 &mut module,
53 "sleep",
54 "Sleep for the specified number of milliseconds (async)",
55 vec![ModuleParam {
56 name: "ms".to_string(),
57 type_name: "number".to_string(),
58 required: true,
59 description: "Duration in milliseconds".to_string(),
60 ..Default::default()
61 }],
62 ConcreteType::Unit,
63 |args: Vec<KindedSlot>| async move {
64 let ms = args.first().and_then(slot_as_f64).ok_or_else(|| {
65 "time.sleep() requires a number argument (milliseconds)".to_string()
66 })?;
67 if ms < 0.0 {
68 return Err("time.sleep() duration must be non-negative".to_string());
69 }
70 tokio::time::sleep(std::time::Duration::from_millis(ms as u64)).await;
71 Ok(TypedReturn::Concrete(ConcreteReturn::Unit))
72 },
73 );
74
75 register_typed_function(
77 &mut module,
78 "sleep_sync",
79 "Sleep for the specified number of milliseconds (blocking)",
80 vec![ModuleParam {
81 name: "ms".to_string(),
82 type_name: "number".to_string(),
83 required: true,
84 description: "Duration in milliseconds".to_string(),
85 ..Default::default()
86 }],
87 ConcreteType::Unit,
88 |args, _ctx| {
89 let ms = args.first().and_then(slot_as_f64).ok_or_else(|| {
90 "time.sleep_sync() requires a number argument (milliseconds)".to_string()
91 })?;
92 if ms < 0.0 {
93 return Err("time.sleep_sync() duration must be non-negative".to_string());
94 }
95 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
96 Ok(TypedReturn::Concrete(ConcreteReturn::Unit))
97 },
98 );
99
100 register_typed_function(
102 &mut module,
103 "benchmark",
104 "Benchmark a function over N iterations, returning timing statistics",
105 vec![
106 ModuleParam {
107 name: "fn".to_string(),
108 type_name: "function".to_string(),
109 required: true,
110 description: "Function to benchmark".to_string(),
111 ..Default::default()
112 },
113 ModuleParam {
114 name: "iterations".to_string(),
115 type_name: "int".to_string(),
116 required: false,
117 description: "Number of iterations (default: 1000)".to_string(),
118 default_snippet: Some("1000".to_string()),
119 ..Default::default()
120 },
121 ],
122 ConcreteType::TypedObject,
123 |args, _ctx| {
124 let _func = args
125 .first()
126 .ok_or_else(|| "time.benchmark() requires a function argument".to_string())?;
127
128 let iterations = args.get(1).and_then(slot_as_f64).unwrap_or(1000.0) as u64;
129
130 if iterations == 0 {
131 return Err("time.benchmark() iterations must be > 0".to_string());
132 }
133
134 let start = std::time::Instant::now();
138 let elapsed = start.elapsed();
139 let elapsed_ms = elapsed.as_secs_f64() * 1000.0;
140
141 Ok(TypedReturn::TypedObject(vec![
142 ("elapsed_ms".to_string(), ConcreteReturn::F64(elapsed_ms)),
143 (
144 "iterations".to_string(),
145 ConcreteReturn::F64(iterations as f64),
146 ),
147 (
148 "avg_ms".to_string(),
149 ConcreteReturn::F64(elapsed_ms / iterations as f64),
150 ),
151 ]))
152 },
153 );
154
155 register_typed_function(
157 &mut module,
158 "stopwatch",
159 "Start a stopwatch (returns an Instant). Call .elapsed() to read.",
160 vec![],
161 ConcreteType::Instant,
162 |_args, _ctx| {
163 Ok(TypedReturn::Concrete(ConcreteReturn::Instant(
164 std::time::Instant::now(),
165 )))
166 },
167 );
168
169 register_typed_function(
171 &mut module,
172 "millis",
173 "Return current wall-clock time as milliseconds since Unix epoch",
174 vec![],
175 ConcreteType::Number,
176 |_args, _ctx| {
177 let now = std::time::SystemTime::now();
178 let since_epoch = now
179 .duration_since(std::time::UNIX_EPOCH)
180 .map_err(|e| format!("SystemTime error: {}", e))?;
181 Ok(TypedReturn::Concrete(ConcreteReturn::F64(
182 since_epoch.as_millis() as f64,
183 )))
184 },
185 );
186
187 module
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_time_module_creation() {
196 let module = create_time_module();
197 assert_eq!(module.name, "std::core::time");
198 assert!(module.has_export("now"));
199 assert!(module.has_export("sleep"));
200 assert!(module.has_export("sleep_sync"));
201 assert!(module.has_export("benchmark"));
202 assert!(module.has_export("stopwatch"));
203 assert!(module.has_export("millis"));
204 }
205
206 #[test]
207 fn test_time_sleep_is_async() {
208 let module = create_time_module();
209 assert!(module.is_async("sleep"));
210 assert!(!module.is_async("sleep_sync"));
211 }
212
213 #[test]
214 fn test_time_schemas() {
215 let module = create_time_module();
216 let now_schema = module.get_schema("now").unwrap();
217 assert_eq!(now_schema.return_type.as_deref(), Some("Instant"));
218
219 let sleep_schema = module.get_schema("sleep").unwrap();
220 assert_eq!(sleep_schema.params.len(), 1);
221 assert_eq!(sleep_schema.params[0].name, "ms");
222
223 let bench_schema = module.get_schema("benchmark").unwrap();
224 assert_eq!(bench_schema.params.len(), 2);
225 assert!(!bench_schema.params[1].required);
226 }
227
228 #[test]
229 fn test_time_typed_registry_populated() {
230 let module = create_time_module();
231 let typed = module.typed_exports();
232 assert!(typed.get("now").is_some());
233 assert!(typed.get("sleep_sync").is_some());
234 assert!(typed.get("benchmark").is_some());
235 assert!(typed.get("stopwatch").is_some());
236 assert!(typed.get("millis").is_some());
237 assert!(typed.get_async("sleep").is_some());
239 assert!(typed.get("sleep").is_none());
240 }
241
242 }