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}