reactor_rt/util/mod.rs
1/*
2 * Copyright (c) 2021, TU Dresden.
3 *
4 * Redistribution and use in source and binary forms, with or without modification,
5 * are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
17 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
21 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
22 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25use std::convert::TryFrom;
26use std::time::Duration;
27
28pub(crate) mod vecmap;
29
30#[macro_export]
31#[doc(hidden)]
32macro_rules! join_to {
33 ($f:expr, $iter:expr) => {
34 join_to!($f, $iter, ", ")
35 };
36 ($f:expr, $iter:expr, $sep:literal) => {
37 join_to!($f, $iter, $sep, "", "")
38 };
39 ($f:expr, $iter:expr, $sep:literal, $prefix:literal, $suffix:literal) => {
40 join_to!($f, $iter, $sep, $prefix, $suffix, |x| format!("{}", x))
41 };
42 ($f:expr, $iter:expr, $sep:literal, $prefix:literal, $suffix:literal, $display:expr) => {{
43 $crate::util::do_write($f, $iter, $sep, $prefix, $suffix, $display)
44 }};
45}
46
47pub(crate) fn do_write<X>(
48 f: &mut impl std::fmt::Write,
49 iter: impl Iterator<Item = X>,
50 sep: &'static str,
51 prefix: &'static str,
52 suffix: &'static str,
53 formatter: impl Fn(X) -> String,
54) -> std::fmt::Result {
55 let mut iter = iter;
56 write!(f, "{}", prefix)?;
57 if let Some(first) = iter.next() {
58 write!(f, "{}", formatter(first))?;
59 }
60 for item in iter {
61 write!(f, "{}", sep)?;
62 write!(f, "{}", formatter(item))?;
63 }
64 write!(f, "{}", suffix)
65}
66
67/// Creates a [Duration] value using the same syntax as in LF.
68///
69/// ```
70/// use std::time::Duration;
71/// use reactor_rt::delay;
72///
73/// assert_eq!(delay!(10 ns), Duration::from_nanos(10));
74/// assert_eq!(delay!(10 ms), delay!(10 msec));
75/// assert_eq!(delay!(10 msec), Duration::from_millis(10));
76/// assert_eq!(delay!(10 sec), Duration::from_secs(10));
77/// assert_eq!(delay!(2 min), delay!(120 s));
78/// assert_eq!(delay!(0), Duration::from_secs(0));
79///
80/// let x = 2;
81/// assert_eq!(delay!(x min), delay!(120 s));
82/// assert_eq!(delay!((1+2) min), delay!(180 s));
83///
84/// // more verbose aliases
85/// assert_eq!(delay!(2 min), delay!(2 minutes));
86/// assert_eq!(delay!(2 h), delay!(2 hours));
87/// assert_eq!(delay!(1 week), delay!(7 days));
88///
89/// ```
90#[macro_export]
91macro_rules! delay {
92 (0) => { $crate::Duration::from_nanos(0) };
93 ($amount:tt ns) => { $crate::Duration::from_nanos($amount) };
94 ($amount:tt nsec) => { delay!($amount ns) };
95 ($amount:tt nsecs) => { delay!($amount ns) };
96 ($amount:tt us) => { $crate::Duration::from_micros($amount) };
97 ($amount:tt usec) => { delay!($amount us) };
98 ($amount:tt usecs) => { delay!($amount us) };
99 ($amount:tt ms) => { $crate::Duration::from_millis($amount) };
100 ($amount:tt msec) => { delay!($amount ms) };
101 ($amount:tt msecs) => { delay!($amount ms) };
102 ($amount:tt s) => { $crate::Duration::from_secs($amount) };
103 ($amount:tt sec) => { delay!($amount s) };
104 ($amount:tt secs) => { delay!($amount s) };
105 ($amount:tt second) => { delay!($amount s) };
106 ($amount:tt seconds) => { delay!($amount s) };
107 ($amount:tt min) => { $crate::Duration::from_secs(60 * $amount) };
108 ($amount:tt mins) => { delay!($amount min) };
109 ($amount:tt minute) => { delay!($amount min) };
110 ($amount:tt minutes) => { delay!($amount min) };
111 ($amount:tt h) => { delay!((3600 * $amount) s) };
112 ($amount:tt hour) => { delay!($amount h) };
113 ($amount:tt hours) => { delay!($amount h) };
114 ($amount:tt d) => { delay!((24*$amount) h) };
115 ($amount:tt day) => { delay!($amount d) };
116 ($amount:tt days) => { delay!($amount d) };
117 ($amount:tt week) => { delay!((7*$amount) d) };
118 ($amount:tt weeks) => { delay!($amount week) };
119 ($amount:tt $i:ident) => { compile_error!(concat!("Unknown time unit `", stringify!($i), "`")) };
120}
121
122/// Shorthand for using [After](crate::Offset::After) together with [delay].
123///
124/// ```
125/// use std::time::Duration;
126/// use reactor_rt::{after, Offset::After};
127///
128/// assert_eq!(after!(10 ns), After(Duration::from_nanos(10)));
129/// assert_eq!(after!(2 min), After(Duration::from_secs(120)));
130/// ```
131#[macro_export]
132macro_rules! after {
133 ($amount:tt $unit:tt) => { $crate::Offset::After($crate::delay!($amount $unit)) }
134}
135
136/// Convenient macro to [create a tag](crate::EventTag).
137/// This is just a shorthand for using the constructor together
138/// with the syntax of [delay].
139///
140/// ```no_run
141/// use reactor_rt::{tag, delay};
142///
143/// tag!(T0 + 20 ms);
144/// tag!(T0 + 60 ms);
145/// tag!(T0); // the origin tag
146/// // with a microstep:
147/// tag!(T0, 1);
148/// tag!(T0 + 3 sec, 1);
149/// ```
150#[macro_export]
151macro_rules! tag {
152 (T0) => {$crate::EventTag::ORIGIN};
153 (T0, $microstep:expr) => {tag!(T0 + 0 sec, $microstep)};
154 (T0 + $amount:tt $unit:ident) => {tag!(T0 + $amount $unit, 0)};
155 (T0 + $amount:tt $unit:ident, $microstep:expr) => {
156 $crate::EventTag::offset($crate::delay!($amount $unit), $microstep)
157 };
158}
159
160/// Convenient macro to assert equality of the current tag.
161/// This is just shorthand for using `assert_eq!` with the
162/// syntax of [tag].
163///
164/// ```no_run
165/// # use reactor_rt::{assert_tag_is, delay, ReactionCtx};
166/// # let ctx : ReactionCtx = unimplemented!();
167/// # struct Foo { i: u32 }
168/// # let foo = Foo { i: 0 };
169///
170/// assert_tag_is!(ctx, T0 + 20 ms);
171/// assert_tag_is!(ctx, T0 + 60 ms);
172/// assert_tag_is!(ctx, T0);
173/// // with a microstep, add parentheses
174/// assert_tag_is!(ctx, (T0, 1));
175/// assert_tag_is!(ctx, (T0 + 3 sec, 1));
176/// assert_tag_is!(ctx, (T0 + 3 sec, foo.i));
177/// ```
178#[macro_export]
179macro_rules! assert_tag_is {
180 ($ctx:tt, T0) => {assert_tag_is!($ctx, (T0 + 0 sec, 0))};
181 ($ctx:tt, (T0, $microstep:expr)) => {assert_tag_is!($ctx, (T0 + 0 sec, $microstep))};
182 ($ctx:tt, T0 + $amount:tt $unit:ident) => {assert_tag_is!($ctx, (T0 + $amount $unit, 0))};
183 ($ctx:tt, (T0 + $amount:tt $unit:ident, $microstep:expr)) => {
184 assert_eq!(
185 $crate::tag!(T0 + $amount $unit, $microstep),
186 $ctx.get_tag()
187 )
188 };
189}
190
191/// A unit of time, used in LF.
192#[derive(Debug)]
193pub enum TimeUnit {
194 NANO,
195 MICRO,
196 MILLI,
197 SEC,
198 MIN,
199 HOUR,
200 DAY,
201}
202
203impl TryFrom<&str> for TimeUnit {
204 type Error = ();
205
206 /// This recognizes the same strings as LF
207 fn try_from(value: &str) -> Result<Self, Self::Error> {
208 let u = match value {
209 "day" | "days" => Self::DAY,
210 "h" | "hour" | "hours" => Self::HOUR,
211 "min" | "minute" | "minutes" => Self::MIN,
212 "s" | "sec" | "secs" => Self::SEC,
213 "ms" | "msec" | "msecs" => Self::MILLI,
214 "us" | "usec" | "usecs" => Self::MICRO,
215 "ns" | "nsec" | "nsecs" => Self::NANO,
216 _ => return Err(()),
217 };
218 Ok(u)
219 }
220}
221
222impl TimeUnit {
223 pub fn to_duration(&self, magnitude: u64) -> Duration {
224 match *self {
225 TimeUnit::NANO => Duration::from_nanos(magnitude),
226 TimeUnit::MICRO => Duration::from_micros(magnitude),
227 TimeUnit::MILLI => Duration::from_millis(magnitude),
228 TimeUnit::SEC => Duration::from_secs(magnitude),
229 TimeUnit::MIN => Duration::from_secs(60 * magnitude),
230 TimeUnit::HOUR => Duration::from_secs(60 * 60 * magnitude),
231 TimeUnit::DAY => Duration::from_secs(60 * 60 * 24 * magnitude),
232 }
233 }
234}
235
236/// Parse a duration from a string. This is used for CLI
237/// parameter parsing in programs generated by LFC, specifically,
238/// to parse main parameters with `time` type, and scheduler
239/// options with time type.
240///
241/// ### Tests
242///
243/// ```
244/// use reactor_rt::try_parse_duration;
245/// use std::time::Duration;
246///
247/// assert_eq!(try_parse_duration("3 ms"), Ok(Duration::from_millis(3)));
248/// assert_eq!(try_parse_duration("3ms"), Ok(Duration::from_millis(3)));
249/// assert_eq!(try_parse_duration("5us"), Ok(Duration::from_micros(5)));
250/// assert_eq!(try_parse_duration("30ns"), Ok(Duration::from_nanos(30)));
251/// assert_eq!(try_parse_duration("30nsec"), Ok(Duration::from_nanos(30)));
252/// assert_eq!(try_parse_duration("30secs"), Ok(Duration::from_secs(30)));
253/// // unit is not required for zero
254/// assert_eq!(try_parse_duration("0"), Ok(Duration::from_secs(0)));
255///
256/// assert_eq!(try_parse_duration(""), Err("cannot parse empty string".into()));
257/// assert_eq!(try_parse_duration("30"), Err("time unit required".into()));
258/// assert_eq!(try_parse_duration("30000000000000000000000ns"), Err("number too large to fit in target type".into()));
259///
260/// ```
261///
262pub fn try_parse_duration(t: &str) -> Result<Duration, String> {
263 // note: we parse this manually to avoid depending on regex
264 let mut chars = t.char_indices().skip_while(|(_, c)| c.is_numeric());
265
266 if let Some((num_end, _)) = &chars.next() {
267 let magnitude: u64 = t[0..*num_end].parse::<u64>().map_err(|e| format!("{}", e))?;
268
269 let unit = t[*num_end..].trim();
270
271 let duration = match TimeUnit::try_from(unit) {
272 Ok(unit) => unit.to_duration(magnitude),
273 Err(_) => return Err(format!("unknown time unit '{}'", unit)),
274 };
275 Ok(duration)
276 } else if t != "0" {
277 // no unit
278 if !t.is_empty() {
279 Err("time unit required".into())
280 } else {
281 Err("cannot parse empty string".into())
282 }
283 } else {
284 Ok(Duration::from_secs(0))
285 }
286}