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 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 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 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 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 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 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 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 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 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 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 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 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}