1use async_trait::async_trait;
20use endbasic_core::{
21 ArgSepSyntax, CallError, CallResult, Callable, CallableMetadata, CallableMetadataBuilder,
22 ExprType, RequiredValueSyntax, Scope, SingularArgSyntax,
23};
24use std::borrow::Cow;
25use std::rc::Rc;
26use std::thread;
27use std::time::{Duration, Instant};
28
29use crate::MachineBuilder;
30
31pub(crate) const CATEGORY: &str = "Date and time";
33
34#[async_trait(?Send)]
36pub trait DateTime {
37 fn monotonic(&self) -> Duration;
39
40 async fn sleep(&self, d: Duration) -> Result<(), String>;
42}
43
44pub struct SystemDateTime {
46 epoch: Instant,
47}
48
49impl Default for SystemDateTime {
50 fn default() -> Self {
51 Self { epoch: Instant::now() }
52 }
53}
54
55#[async_trait(?Send)]
56impl DateTime for SystemDateTime {
57 fn monotonic(&self) -> Duration {
58 self.epoch.elapsed()
59 }
60
61 async fn sleep(&self, d: Duration) -> Result<(), String> {
62 thread::sleep(d);
63 Ok(())
64 }
65}
66
67pub struct MonotonicFunction {
69 metadata: Rc<CallableMetadata>,
70 datetime: Rc<dyn DateTime>,
71}
72
73impl MonotonicFunction {
74 pub fn new(datetime: Rc<dyn DateTime>) -> Rc<Self> {
76 Rc::from(Self {
77 metadata: CallableMetadataBuilder::new("MONOTONIC")
78 .with_return_type(ExprType::Double)
79 .with_syntax(&[(&[], None)])
80 .with_category(CATEGORY)
81 .with_description(
82 "Returns a monotonic timestamp.
83The returned value is the number of seconds since an unspecified reference point. The timestamp \
84is only useful to compute elapsed time by subtraction and is not related to the current date or \
85wall-clock time.",
86 )
87 .build(),
88 datetime,
89 })
90 }
91}
92
93#[async_trait(?Send)]
94impl Callable for MonotonicFunction {
95 fn metadata(&self) -> Rc<CallableMetadata> {
96 self.metadata.clone()
97 }
98
99 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
100 debug_assert_eq!(0, scope.nargs());
101 scope.return_double(self.datetime.monotonic().as_secs_f64())
102 }
103}
104
105pub struct SleepCommand {
107 metadata: Rc<CallableMetadata>,
108 datetime: Rc<dyn DateTime>,
109}
110
111impl SleepCommand {
112 pub fn new(datetime: Rc<dyn DateTime>) -> Rc<Self> {
114 Rc::from(Self {
115 metadata: CallableMetadataBuilder::new("SLEEP")
116 .with_async(true)
117 .with_syntax(&[(
118 &[SingularArgSyntax::RequiredValue(
119 RequiredValueSyntax {
120 name: Cow::Borrowed("seconds"),
121 vtype: ExprType::Double,
122 },
123 ArgSepSyntax::End,
124 )],
125 None,
126 )])
127 .with_category(CATEGORY)
128 .with_description(
129 "Suspends program execution.
130Pauses program execution for the given number of seconds, which can be specified either as an \
131integer or as a floating point number for finer precision.",
132 )
133 .build(),
134 datetime,
135 })
136 }
137}
138
139#[async_trait(?Send)]
140impl Callable for SleepCommand {
141 fn metadata(&self) -> Rc<CallableMetadata> {
142 self.metadata.clone()
143 }
144
145 async fn async_exec(&self, scope: Scope<'_>) -> CallResult<()> {
146 debug_assert_eq!(1, scope.nargs());
147 let n = scope.get_double(0);
148
149 if n < 0.0 {
150 return Err(CallError::Syntax(
151 scope.get_pos(0),
152 "Sleep time must be positive".to_owned(),
153 ));
154 }
155
156 self.datetime
157 .sleep(Duration::from_secs_f64(n))
158 .await
159 .map_err(|e| CallError::Syntax(scope.get_pos(0), e))
160 }
161}
162
163pub fn add_all(machine: &mut MachineBuilder, datetime: Rc<dyn DateTime>) {
167 machine.add_callable(MonotonicFunction::new(datetime.clone()));
168 machine.add_callable(SleepCommand::new(datetime));
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::MachineBuilder;
175 use crate::testutils::*;
176 use futures_lite::future::block_on;
177 use std::time::Instant;
178
179 #[test]
180 fn test_monotonic_ok() {
181 let mut tester = Tester::default();
182 tester.get_datetime().add_monotonic(Duration::from_millis(12_345));
183 tester.run("result = MONOTONIC").expect_var("result", 12.345).check();
184 }
185
186 #[test]
187 fn test_monotonic_errors() {
188 check_expr_compilation_error("1:10: MONOTONIC expected no arguments", "MONOTONIC()");
189 }
190
191 #[test]
192 fn test_sleep_ok_int() {
193 let mut tester = Tester::default();
194 tester.run("SLEEP 123").check();
195 assert_eq!(vec![Duration::from_secs(123)], tester.get_datetime().sleeps());
196 }
197
198 #[test]
199 fn test_sleep_ok_float() {
200 let mut tester = Tester::default();
201 tester.run("SLEEP 123.1").check();
202 let sleeps = tester.get_datetime().sleeps();
203 assert_eq!(1, sleeps.len());
204 let ms = sleeps[0].as_millis();
205 assert!(ms > 123095 && ms < 123105, "Bad {}", ms);
206 }
207
208 #[test]
209 fn test_sleep_real() {
210 let before = Instant::now();
211 let mut machine = MachineBuilder::default().build();
212 machine.compile(&mut "SLEEP 0.010".as_bytes()).unwrap();
213 match block_on(machine.exec()) {
214 Ok(None) => (),
215 r => panic!("Expected Ok(None) but got {:?}", r),
216 }
217 assert!(before.elapsed() >= Duration::from_millis(10));
218 }
219
220 #[test]
221 fn test_sleep_errors() {
222 check_stmt_compilation_err("1:1: SLEEP expected seconds#", "SLEEP");
223 check_stmt_compilation_err("1:1: SLEEP expected seconds#", "SLEEP 2, 3");
224 check_stmt_compilation_err("1:1: SLEEP expected seconds#", "SLEEP 2; 3");
225 check_stmt_compilation_err("1:7: STRING is not a number", "SLEEP \"foo\"");
226 check_stmt_err("1:7: Sleep time must be positive", "SLEEP -1");
227 check_stmt_err("1:7: Sleep time must be positive", "SLEEP -0.001");
228 }
229}