rtlola_parser/ast/
conversion.rs

1#![allow(dead_code)]
2use std::num::ParseIntError;
3use std::str::FromStr;
4
5use num::rational::Rational64 as Rational;
6use num::traits::{CheckedMul, Inv};
7use num::{FromPrimitive, One, Signed};
8use uom::si::frequency::hertz;
9use uom::si::rational64::{Frequency as UOM_Frequency, Time as UOM_Time};
10use uom::si::time::second;
11
12use super::{Expression, ExpressionKind, LitKind, Offset, TimeUnit};
13use crate::ast::{LambdaExpr, Literal};
14use crate::parse::RtLolaParser;
15
16pub(crate) type RationalType = i64;
17
18impl Expression {
19    /// Parses an expression into an offset used in [ExpressionKind::Offset]
20    pub(crate) fn parse_offset(&self) -> Result<Offset, String> {
21        if let Some(val) = self.parse_literal::<i16>() {
22            Ok(Offset::Discrete(val))
23        } else {
24            // has to be a real-time expression
25            let (val, unit) = match &self.kind {
26                ExpressionKind::Lit(l) => match &l.kind {
27                    LitKind::Numeric(val, Some(unit)) => (val, unit),
28                    _ => return Err(format!("expected numeric value with unit, found `{l}`")),
29                },
30                _ => return Err(format!("expected numeric value with unit, found `{self}`")),
31            };
32            Ok(Offset::RealTime(
33                RtLolaParser::parse_rational(val)?,
34                TimeUnit::from_str(unit)?,
35            ))
36        }
37    }
38
39    /// Parses an expression into a duration for a discrete window [ExpressionKind::DiscreteWindowAggregation]
40    pub fn parse_discrete_duration(&self) -> Result<u64, String> {
41        match &self.kind {
42            ExpressionKind::Lit(l) => match &l.kind {
43                LitKind::Numeric(val, None) => {
44                    val.parse().map_err(|err: ParseIntError| err.to_string())
45                }
46                _ => Err(format!("expected numeric value without unit, found `{l}`")),
47            },
48            _ => Err(format!(
49                "expected numeric value without unit, found `{self}`"
50            )),
51        }
52    }
53
54    /// Parses an expression into a duration with a given unit of time
55    pub(crate) fn parse_duration(&self) -> Result<UOM_Time, String> {
56        let (val, unit) = match &self.kind {
57            ExpressionKind::Lit(l) => match &l.kind {
58                LitKind::Numeric(val, Some(unit)) => (RtLolaParser::parse_rational(val)?, unit),
59                _ => return Err(format!("expected numeric value with unit, found `{l}`")),
60            },
61            _ => return Err(format!("expected numeric value with unit, found `{self}`")),
62        };
63
64        match unit.as_str() {
65            "ns" | "μs" | "us" | "ms" | "s" | "min" | "h" | "d" | "w" | "a" => {
66                use uom::si::time::*;
67                let factor = match unit.as_str() {
68                    "ns" => UOM_Time::new::<nanosecond>(Rational::one()),
69                    "μs" | "us" => UOM_Time::new::<microsecond>(Rational::one()),
70                    "ms" => UOM_Time::new::<millisecond>(Rational::one()),
71                    "s" => UOM_Time::new::<second>(Rational::one()),
72                    "min" => UOM_Time::new::<minute>(Rational::one()),
73                    "h" => UOM_Time::new::<hour>(Rational::one()),
74                    "d" => UOM_Time::new::<day>(Rational::one()),
75                    "w" => UOM_Time::new::<day>(Rational::from_u64(7).unwrap()),
76                    "a" => UOM_Time::new::<day>(Rational::from_u64(365).unwrap()),
77                    u => unreachable!("'{}' should not have been catched by outer match", u),
78                };
79                let factor = factor.get::<second>();
80                let duration = match val.checked_mul(&factor) {
81                    Some(d) => d,
82                    _ => {
83                        return Err(format!(
84                            "parsing duration failed: rational {val}*{factor} does not fit into Rational64"
85                        ))
86                    }
87                };
88                Ok(UOM_Time::new::<second>(duration))
89            }
90            u => Err(format!("expected duration unit, found `{u}`")),
91        }
92    }
93
94    /// Parses an expression into a frequency.
95    /// Expression must be a positive numeric value with Hz unit.
96    pub fn parse_frequency(&self) -> Result<UOM_Frequency, String> {
97        let (val, unit) = match &self.kind {
98            ExpressionKind::Lit(l) => match &l.kind {
99                LitKind::Numeric(val, Some(unit)) => (RtLolaParser::parse_rational(val)?, unit),
100                _ => return Err(format!("expected numeric value with unit, found `{l}`")),
101            },
102            _ => return Err(format!("expected numeric value with unit, found `{self}`")),
103        };
104
105        if !val.is_positive() {
106            return Err("frequencies have to be positive".to_string());
107        }
108
109        assert!(val.is_positive());
110
111        match unit.as_str() {
112            "μHz" | "uHz" | "mHz" | "Hz" | "kHz" | "MHz" | "GHz" => {
113                use uom::si::frequency::*;
114                let factor = match unit.as_str() {
115                    "μHz" | "uHz" => UOM_Frequency::new::<microhertz>(Rational::one()),
116                    "mHz" => UOM_Frequency::new::<millihertz>(Rational::one()),
117                    "Hz" => UOM_Frequency::new::<hertz>(Rational::one()),
118                    "kHz" => UOM_Frequency::new::<kilohertz>(Rational::one()),
119                    "MHz" => UOM_Frequency::new::<megahertz>(Rational::one()),
120                    "GHz" => UOM_Frequency::new::<gigahertz>(Rational::one()),
121                    u => unreachable!("'{}' should not have been catched by outer match", u),
122                };
123                let factor = factor.get::<hertz>();
124                let freq = match val.checked_mul(&factor) {
125                    Some(f) => f,
126                    _ => {
127                        return Err(format!(
128                            "parsing frequency failed: rational {val}*{factor} does not fit into Rational64",
129                        ))
130                    }
131                };
132                Ok(UOM_Frequency::new::<hertz>(freq))
133            }
134            u => Err(format!("expected frequency unit, found `{u}`")),
135        }
136    }
137
138    /// Parses a frequency annotation.
139    /// Expression must either be a frequency or a duration representing the period
140    pub fn parse_freqspec(&self) -> Result<UOM_Frequency, String> {
141        if let Ok(freq) = self.parse_frequency() {
142            Ok(freq)
143        } else if let Ok(period) = self.parse_duration() {
144            let seconds = period.get::<second>();
145            if seconds.is_positive() {
146                Ok(UOM_Frequency::new::<hertz>(seconds.inv()))
147            } else {
148                Err(format!(
149                    "duration of periodic stream specification must be positive, found `{period:#?}`"
150                ))
151            }
152        } else {
153            Err(format!("expected frequency or duration, found `{self}`"))
154        }
155    }
156
157    /// Attempts to extract the numeric, constant, unit-less value out of an `Expression::Lit`.
158    pub(crate) fn parse_literal<T>(&self) -> Option<T>
159    where
160        T: FromStr,
161    {
162        match &self.kind {
163            ExpressionKind::Lit(l) => l.parse_numeric(),
164            _ => None,
165        }
166    }
167}
168
169impl Literal {
170    /// Parses a literal of kind numeric into its number representation i.e. u32
171    pub(crate) fn parse_numeric<T>(&self) -> Option<T>
172    where
173        T: FromStr,
174    {
175        match &self.kind {
176            LitKind::Numeric(val, unit) => {
177                if unit.is_some() {
178                    return None;
179                }
180                val.parse::<T>().ok()
181            }
182            _ => None,
183        }
184    }
185}
186
187impl Offset {
188    /// Transforms a real-time offset into a [UOM_Time]
189    pub fn to_uom_time(&self) -> Option<UOM_Time> {
190        match self {
191            Offset::Discrete(_) => None,
192            Offset::RealTime(val, unit) => {
193                let seconds = val * unit.to_uom_time().get::<second>();
194                Some(UOM_Time::new::<second>(seconds))
195            }
196        }
197    }
198}
199
200impl FromStr for TimeUnit {
201    type Err = String;
202
203    fn from_str(unit: &str) -> Result<Self, Self::Err> {
204        match unit {
205            "ns" => Ok(TimeUnit::Nanosecond),
206            "μs" | "us" => Ok(TimeUnit::Microsecond),
207            "ms" => Ok(TimeUnit::Millisecond),
208            "s" => Ok(TimeUnit::Second),
209            "min" => Ok(TimeUnit::Minute),
210            "h" => Ok(TimeUnit::Hour),
211            "d" => Ok(TimeUnit::Day),
212            "w" => Ok(TimeUnit::Week),
213            "a" => Ok(TimeUnit::Year),
214            _ => Err(format!("unknown time unit `{unit}`")),
215        }
216    }
217}
218
219impl TimeUnit {
220    /// Transforms a TimeUnit into a UOM_Time i.e. the number of seconds the timeunit spans.
221    pub(crate) fn to_uom_time(self) -> UOM_Time {
222        let f = match self {
223            TimeUnit::Nanosecond => Rational::new(
224                RationalType::from_u64(1).unwrap(),
225                RationalType::from_u64(10_u64.pow(9)).unwrap(),
226            ),
227            TimeUnit::Microsecond => Rational::new(
228                RationalType::from_u64(1).unwrap(),
229                RationalType::from_u64(10_u64.pow(6)).unwrap(),
230            ),
231            TimeUnit::Millisecond => Rational::new(
232                RationalType::from_u64(1).unwrap(),
233                RationalType::from_u64(10_u64.pow(3)).unwrap(),
234            ),
235            TimeUnit::Second => Rational::from_u64(1).unwrap(),
236            TimeUnit::Minute => Rational::from_u64(60).unwrap(),
237            TimeUnit::Hour => Rational::from_u64(60 * 60).unwrap(),
238            TimeUnit::Day => Rational::from_u64(60 * 60 * 24).unwrap(),
239            TimeUnit::Week => Rational::from_u64(60 * 60 * 24 * 7).unwrap(),
240            TimeUnit::Year => Rational::from_u64(60 * 60 * 24 * 365).unwrap(),
241        };
242        UOM_Time::new::<second>(f)
243    }
244}
245
246impl Expression {
247    /// Tries to resolve a tuple index access
248    fn get_expr_from_tuple(&self, idx: usize) -> Option<&Expression> {
249        use ExpressionKind::*;
250        match &self.kind {
251            Tuple(entries) => Some(&entries[idx]),
252            _ => None,
253        }
254    }
255
256    /// A recursive iterator over an `Expression` tree
257    /// Inspired by https://amos.me/blog/2019/recursive-iterators-rust/
258    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Expression> + 'a> {
259        use ExpressionKind::*;
260        match &self.kind {
261            Lit(_) | Ident(_) | MissingExpression => Box::new(std::iter::once(self)),
262            Unary(_, inner)
263            | Field(inner, _)
264            | StreamAccess(inner, _)
265            | Offset(inner, _)
266            | ParenthesizedExpression(inner) => Box::new(std::iter::once(self).chain(inner.iter())),
267            Binary(_, left, right)
268            | Default(left, right)
269            | DiscreteWindowAggregation {
270                expr: left,
271                duration: right,
272                ..
273            }
274            | SlidingWindowAggregation {
275                expr: left,
276                duration: right,
277                ..
278            } => Box::new(std::iter::once(self).chain(left.iter()).chain(right.iter())),
279            InstanceAggregation { expr, .. } => Box::new(std::iter::once(self).chain(expr.iter())),
280            Ite(cond, normal, alternative) => Box::new(
281                std::iter::once(self)
282                    .chain(cond.iter())
283                    .chain(normal.iter())
284                    .chain(alternative.iter()),
285            ),
286            Tuple(entries) | Function(_, _, entries) => {
287                Box::new(std::iter::once(self).chain(entries.iter().flat_map(|entry| entry.iter())))
288            }
289            Method(base, _, _, arguments) => Box::new(
290                std::iter::once(self)
291                    .chain(base.iter())
292                    .chain(arguments.iter().flat_map(|entry| entry.iter())),
293            ),
294            Lambda(LambdaExpr {
295                parameters: _,
296                expr,
297            }) => Box::new(std::iter::once(self).chain(expr.iter())),
298        }
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use std::time::Duration;
305
306    use num::ToPrimitive;
307
308    use super::*;
309    use crate::ast::{Literal, NodeId, Span};
310
311    #[test]
312    fn test_parse_rational() {
313        macro_rules! check_on {
314            ($f:expr) => {
315                let f_string = format!("{}", $f);
316                let f = f_string.parse::<f64>().unwrap();
317                let was = super::RtLolaParser::parse_rational(f_string.as_str())
318                    .unwrap_or_else(|e| panic!("parsing failed: {}", e));
319                assert_eq!(was, Rational::from_f64(f).unwrap());
320            };
321        }
322        check_on!(0);
323        check_on!(42);
324        check_on!(-1);
325        check_on!(0.1);
326        check_on!(42.12);
327        check_on!(-1.123);
328        check_on!(0.1e-0);
329        check_on!(42.12e+1);
330        check_on!(-1.123e-2);
331    }
332
333    fn time_spec_int(val: &str, unit: &str) -> Duration {
334        let expr = Expression::new(
335            NodeId::new(32),
336            ExpressionKind::Lit(Literal::new_numeric(
337                NodeId::new(24),
338                val,
339                Some(unit.to_string()),
340                Span::Unknown,
341            )),
342            Span::Unknown,
343        );
344        let freq = expr.parse_freqspec().unwrap();
345        let period = UOM_Time::new::<second>(freq.get::<hertz>().inv());
346        Duration::from_nanos(
347            period
348                .get::<uom::si::time::nanosecond>()
349                .to_integer()
350                .to_u64()
351                .unwrap(),
352        )
353    }
354
355    #[test]
356    fn test_time_spec_to_duration_conversion() {
357        assert_eq!(time_spec_int("1", "s"), Duration::new(1, 0));
358        assert_eq!(time_spec_int("2", "min"), Duration::new(2 * 60, 0));
359        assert_eq!(time_spec_int("33", "h"), Duration::new(33 * 60 * 60, 0));
360        assert_eq!(time_spec_int("12354", "ns"), Duration::from_nanos(12354));
361        assert_eq!(
362            time_spec_int("90351", "us"),
363            Duration::from_nanos(90351 * 1_000)
364        );
365        assert_eq!(
366            time_spec_int("248", "ms"),
367            Duration::from_nanos(248 * 1_000_000)
368        );
369        assert_eq!(
370            time_spec_int("29489232", "ms"),
371            Duration::from_nanos(29_489_232 * 1_000_000)
372        );
373    }
374
375    #[test]
376    fn test_frequency_to_duration_conversion() {
377        assert_eq!(time_spec_int("1", "Hz"), Duration::new(1, 0));
378        assert_eq!(time_spec_int("10", "Hz"), Duration::new(0, 100_000_000));
379        assert_eq!(time_spec_int("400", "uHz"), Duration::new(2_500, 0));
380        assert_eq!(time_spec_int("20", "mHz"), Duration::new(50, 0));
381    }
382}