1use super::super::pair_span;
11use crate::ast::{
12 DateTimeExpr, Duration, DurationUnit, Expr, NamedTime, RelativeTime, TimeDirection,
13 TimeReference, TimeUnit, Timeframe,
14};
15use crate::error::{Result, ShapeError};
16use crate::parser::Rule;
17use crate::parser::string_literals::parse_string_literal;
18use pest::iterators::Pair;
19
20pub fn parse_time_ref(pair: Pair<Rule>) -> Result<TimeReference> {
22 let inner = pair.into_inner().next().unwrap();
23
24 match inner.as_rule() {
25 Rule::quoted_time => Ok(TimeReference::Absolute(parse_string_literal(
26 inner.as_str(),
27 )?)),
28 Rule::named_time => {
29 let named = match inner.as_str() {
30 "today" => NamedTime::Today,
31 "yesterday" => NamedTime::Yesterday,
32 "now" => NamedTime::Now,
33 _ => {
34 return Err(ShapeError::ParseError {
35 message: format!("Unknown named time: {}", inner.as_str()),
36 location: None,
37 });
38 }
39 };
40 Ok(TimeReference::Named(named))
41 }
42 Rule::relative_time => {
43 let s = inner.as_str();
45 Ok(TimeReference::Relative(parse_relative_time(s)?))
46 }
47 _ => Err(ShapeError::ParseError {
48 message: format!("Unexpected time reference: {:?}", inner.as_rule()),
49 location: None,
50 }),
51 }
52}
53
54pub fn parse_relative_time(s: &str) -> Result<RelativeTime> {
56 let parts: Vec<&str> = s.split_whitespace().collect();
59 if parts.len() < 3 {
60 return Err(ShapeError::ParseError {
61 message: format!("Invalid relative time format: {}", s),
62 location: None,
63 });
64 }
65
66 let amount: i32 = parts[0].parse().map_err(|e| ShapeError::ParseError {
67 message: format!("Invalid integer in relative time: {}", e),
68 location: None,
69 })?;
70 let unit = match parts[1] {
71 "minute" | "minutes" => TimeUnit::Minutes,
72 "hour" | "hours" => TimeUnit::Hours,
73 "day" | "days" => TimeUnit::Days,
74 "week" | "weeks" => TimeUnit::Weeks,
75 "month" | "months" => TimeUnit::Months,
76 _ => {
77 return Err(ShapeError::ParseError {
78 message: format!("Unknown time unit: {}", parts[1]),
79 location: None,
80 });
81 }
82 };
83
84 let direction = match parts[2] {
85 "ago" => TimeDirection::Ago,
86 "future" | "ahead" => TimeDirection::Future,
87 _ => {
88 return Err(ShapeError::ParseError {
89 message: format!("Unknown time direction: {}", parts[2]),
90 location: None,
91 });
92 }
93 };
94
95 Ok(RelativeTime {
96 amount,
97 unit,
98 direction,
99 })
100}
101
102pub fn parse_temporal_nav(pair: Pair<Rule>) -> Result<Expr> {
105 let span = pair_span(&pair);
106 let inner = pair.into_inner().next().unwrap();
107
108 match inner.as_rule() {
109 Rule::back_nav | Rule::forward_nav => {
110 let is_back = inner.as_rule() == Rule::back_nav;
111 let nav_amount = inner.into_inner().next().unwrap();
112 let mut amount_inner = nav_amount.into_inner();
113
114 let num_pair = amount_inner.next().unwrap();
116 let value: f64 = num_pair
117 .as_str()
118 .parse()
119 .map_err(|e| ShapeError::ParseError {
120 message: format!("Invalid navigation amount: {}", e),
121 location: None,
122 })?;
123
124 let unit = if let Some(unit_pair) = amount_inner.next() {
126 match unit_pair.as_str() {
127 "sample" | "samples" | "record" | "records" => DurationUnit::Samples,
128 "minute" | "minutes" => DurationUnit::Minutes,
129 "hour" | "hours" => DurationUnit::Hours,
130 "day" | "days" => DurationUnit::Days,
131 "week" | "weeks" => DurationUnit::Weeks,
132 "month" | "months" => DurationUnit::Months,
133 _ => DurationUnit::Samples,
134 }
135 } else {
136 DurationUnit::Samples
137 };
138
139 let final_value = if is_back { -value } else { value };
141
142 Ok(Expr::Duration(
143 Duration {
144 value: final_value,
145 unit,
146 },
147 span,
148 ))
149 }
150 _ => Err(ShapeError::ParseError {
151 message: format!(
152 "Expected back_nav or forward_nav, got {:?}",
153 inner.as_rule()
154 ),
155 location: None,
156 }),
157 }
158}
159
160pub fn parse_timeframe_expr(pair: Pair<Rule>) -> Result<Expr> {
162 let span = pair_span(&pair);
163 let mut inner = pair.into_inner();
164
165 let timeframe_str = inner
167 .next()
168 .ok_or_else(|| ShapeError::ParseError {
169 message: "Expected timeframe in on() expression".to_string(),
170 location: None,
171 })?
172 .as_str();
173
174 let timeframe = Timeframe::parse(timeframe_str).ok_or_else(|| ShapeError::ParseError {
175 message: format!("Invalid timeframe: {}", timeframe_str),
176 location: None,
177 })?;
178
179 let expr_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
181 message: "Expected expression in on() block".to_string(),
182 location: None,
183 })?;
184
185 let expr = crate::parser::expressions::parse_expression(expr_pair)?;
186
187 Ok(Expr::TimeframeContext {
188 timeframe,
189 expr: Box::new(expr),
190 span,
191 })
192}
193
194pub fn parse_datetime_expr(pair: Pair<Rule>) -> Result<DateTimeExpr> {
196 match pair.as_rule() {
197 Rule::datetime_expr => {
198 let inner = pair.into_inner().next().unwrap();
200 parse_datetime_expr(inner)
201 }
202 Rule::datetime_primary => {
203 let mut inner = pair.into_inner();
204 let expr_pair = inner.next().unwrap();
205
206 match expr_pair.as_rule() {
207 Rule::datetime_literal => {
208 let mut lit_inner = expr_pair.into_inner();
209 let string_pair = lit_inner.next().unwrap();
210 Ok(DateTimeExpr::Literal(parse_string_literal(
211 string_pair.as_str(),
212 )?))
213 }
214 Rule::named_time => {
215 let named = match expr_pair.as_str() {
216 "today" => NamedTime::Today,
217 "yesterday" => NamedTime::Yesterday,
218 "now" => NamedTime::Now,
219 _ => {
220 return Err(ShapeError::ParseError {
221 message: format!("Unknown named time: {}", expr_pair.as_str()),
222 location: None,
223 });
224 }
225 };
226 Ok(DateTimeExpr::Named(named))
227 }
228 _ => Err(ShapeError::ParseError {
229 message: format!("Unexpected datetime primary: {:?}", expr_pair.as_rule()),
230 location: None,
231 }),
232 }
233 }
234 Rule::datetime_arithmetic => {
235 let mut inner = pair.into_inner();
236 let base_pair = inner.next().unwrap();
237 let mut result = parse_datetime_expr(base_pair)?;
238
239 while let Some(op_pair) = inner.next() {
240 let op = op_pair.as_str();
241 if op != "+" && op != "-" {
242 return Err(ShapeError::ParseError {
243 message: format!("Invalid datetime arithmetic operator: {}", op),
244 location: None,
245 });
246 }
247
248 let duration_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
249 message: "Datetime arithmetic missing duration".to_string(),
250 location: None,
251 })?;
252 let duration_expr = parse_duration(duration_pair)?;
253 let duration = match duration_expr {
254 Expr::Duration(duration, _) => duration,
255 _ => {
256 return Err(ShapeError::ParseError {
257 message: "Datetime arithmetic expects a duration".to_string(),
258 location: None,
259 });
260 }
261 };
262
263 result = DateTimeExpr::Arithmetic {
264 base: Box::new(result),
265 operator: op.to_string(),
266 duration,
267 };
268 }
269
270 Ok(result)
271 }
272 _ => Err(ShapeError::ParseError {
273 message: format!("Unexpected datetime expression: {:?}", pair.as_rule()),
274 location: None,
275 }),
276 }
277}
278
279pub fn parse_datetime_range(pair: Pair<Rule>) -> Result<(Expr, Option<Expr>)> {
281 let mut inner = pair.into_inner();
283 let first_pair = inner.next().unwrap();
284 let first_span = pair_span(&first_pair);
285 let first_datetime = parse_datetime_expr(first_pair)?;
286
287 if let Some(second_pair) = inner.next() {
289 let second_span = pair_span(&second_pair);
290 let second_datetime = parse_datetime_expr(second_pair)?;
291 Ok((
292 Expr::DateTime(first_datetime, first_span),
293 Some(Expr::DateTime(second_datetime, second_span)),
294 ))
295 } else {
296 Ok((Expr::DateTime(first_datetime, first_span), None))
297 }
298}
299
300pub fn parse_duration(pair: Pair<Rule>) -> Result<Expr> {
302 let span = pair_span(&pair);
303 let duration_str = pair.as_str();
305
306 let mut components = Vec::new();
308 let mut current_number = String::new();
309 let mut chars = duration_str.chars().peekable();
310
311 while let Some(ch) = chars.next() {
312 if ch.is_numeric() || ch == '.' || (ch == '-' && current_number.is_empty()) {
313 current_number.push(ch);
314 } else {
315 if !current_number.is_empty() {
317 let value: f64 = current_number.parse().map_err(|e| ShapeError::ParseError {
318 message: format!("Invalid duration value: {}", e),
319 location: None,
320 })?;
321
322 let mut unit_str = String::new();
324 unit_str.push(ch);
325
326 while let Some(&next_ch) = chars.peek() {
328 if next_ch.is_alphabetic() {
329 unit_str.push(chars.next().unwrap());
330 } else {
331 break;
332 }
333 }
334
335 let unit = match unit_str.as_str() {
336 "s" | "seconds" => DurationUnit::Seconds,
337 "m" | "minutes" => DurationUnit::Minutes,
338 "h" | "hours" => DurationUnit::Hours,
339 "d" | "days" => DurationUnit::Days,
340 "w" | "weeks" => DurationUnit::Weeks,
341 "M" | "months" => DurationUnit::Months,
342 "y" | "years" => DurationUnit::Years,
343 "samples" => DurationUnit::Samples,
344 _ => {
345 return Err(ShapeError::ParseError {
346 message: format!("Unknown duration unit: {}", unit_str),
347 location: None,
348 });
349 }
350 };
351
352 components.push((value, unit));
353 current_number.clear();
354 }
355 }
356 }
357
358 if components.len() == 1 {
360 let (value, unit) = components.into_iter().next().unwrap();
361 return Ok(Expr::Duration(Duration { value, unit }, span));
362 }
363
364 let mut total_seconds = 0.0;
366 for (value, unit) in components {
367 let seconds = match unit {
368 DurationUnit::Seconds => value,
369 DurationUnit::Minutes => value * 60.0,
370 DurationUnit::Hours => value * 3600.0,
371 DurationUnit::Days => value * 86400.0,
372 DurationUnit::Weeks => value * 604800.0,
373 DurationUnit::Months => value * 2592000.0, DurationUnit::Years => value * 31536000.0, DurationUnit::Samples => {
376 return Err(ShapeError::ParseError {
377 message: "Cannot use 'samples' in compound duration".to_string(),
378 location: None,
379 });
380 }
381 };
382 total_seconds += seconds;
383 }
384
385 let (value, unit) = if total_seconds < 60.0 {
387 (total_seconds, DurationUnit::Seconds)
388 } else if total_seconds < 3600.0 {
389 (total_seconds / 60.0, DurationUnit::Minutes)
390 } else if total_seconds < 86400.0 {
391 (total_seconds / 3600.0, DurationUnit::Hours)
392 } else if total_seconds < 604800.0 {
393 (total_seconds / 86400.0, DurationUnit::Days)
394 } else if total_seconds < 2592000.0 {
395 (total_seconds / 604800.0, DurationUnit::Weeks)
396 } else if total_seconds < 31536000.0 {
397 (total_seconds / 2592000.0, DurationUnit::Months)
398 } else {
399 (total_seconds / 31536000.0, DurationUnit::Years)
400 };
401
402 Ok(Expr::Duration(Duration { value, unit }, span))
403}
404
405#[cfg(test)]
406mod tests {
407 use crate::ast::{DateTimeExpr, DurationUnit, Expr};
408
409 fn parse_expr(code: &str) -> Expr {
410 let program = crate::parser::parse_program(code).expect("parse failed");
411 match &program.items[0] {
413 crate::ast::Item::Expression(expr, _) => expr.clone(),
414 crate::ast::Item::Statement(crate::ast::Statement::Expression(expr, _), _) => {
415 expr.clone()
416 }
417 other => panic!("expected expression statement, got {:?}", other),
418 }
419 }
420
421 #[test]
422 fn test_parse_datetime_literal_iso8601() {
423 let expr = parse_expr(r#"@"2024-06-15T14:30:00""#);
424 match expr {
425 Expr::DateTime(DateTimeExpr::Literal(s), _) => {
426 assert_eq!(s, "2024-06-15T14:30:00");
427 }
428 other => panic!("expected DateTime literal, got {:?}", other),
429 }
430 }
431
432 #[test]
433 fn test_parse_datetime_literal_date_only() {
434 let expr = parse_expr(r#"@"2024-01-15""#);
435 match expr {
436 Expr::DateTime(DateTimeExpr::Literal(s), _) => {
437 assert_eq!(s, "2024-01-15");
438 }
439 other => panic!("expected DateTime literal, got {:?}", other),
440 }
441 }
442
443 #[test]
444 fn test_parse_datetime_named_now() {
445 let expr = parse_expr("@now");
446 match expr {
447 Expr::DateTime(DateTimeExpr::Named(crate::ast::NamedTime::Now), _) => {}
448 other => panic!("expected DateTime Named(Now), got {:?}", other),
449 }
450 }
451
452 #[test]
453 fn test_parse_duration_days() {
454 let expr = parse_expr("3d");
455 match expr {
456 Expr::Duration(dur, _) => {
457 assert_eq!(dur.value, 3.0);
458 assert_eq!(dur.unit, DurationUnit::Days);
459 }
460 other => panic!("expected Duration, got {:?}", other),
461 }
462 }
463
464 #[test]
465 fn test_parse_duration_hours() {
466 let expr = parse_expr("2h");
467 match expr {
468 Expr::Duration(dur, _) => {
469 assert_eq!(dur.value, 2.0);
470 assert_eq!(dur.unit, DurationUnit::Hours);
471 }
472 other => panic!("expected Duration, got {:?}", other),
473 }
474 }
475
476 #[test]
477 fn test_parse_duration_minutes() {
478 let expr = parse_expr("30m");
479 match expr {
480 Expr::Duration(dur, _) => {
481 assert_eq!(dur.value, 30.0);
482 assert_eq!(dur.unit, DurationUnit::Minutes);
483 }
484 other => panic!("expected Duration, got {:?}", other),
485 }
486 }
487
488 #[test]
489 fn test_parse_duration_seconds() {
490 let expr = parse_expr("10s");
491 match expr {
492 Expr::Duration(dur, _) => {
493 assert_eq!(dur.value, 10.0);
494 assert_eq!(dur.unit, DurationUnit::Seconds);
495 }
496 other => panic!("expected Duration, got {:?}", other),
497 }
498 }
499}