Skip to main content

remindee_parser/
grammar.rs

1use bitmask_enum::bitmask;
2use nonempty::{nonempty, NonEmpty};
3
4use pest::{iterators::Pair, Parser};
5use pest_derive::Parser;
6
7extern crate alloc;
8
9#[derive(Parser)]
10#[grammar = "grammars/reminder.pest"]
11struct ReminderParser;
12
13#[derive(Debug, Default)]
14pub struct HoleyDate {
15    pub year: Option<i32>,
16    pub month: Option<u32>,
17    pub day: Option<u32>,
18}
19
20#[derive(Debug, Default)]
21pub struct Interval {
22    pub years: i32,
23    pub months: u32,
24    pub weeks: u32,
25    pub days: u32,
26    pub hours: u32,
27    pub minutes: u32,
28    pub seconds: u32,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq)]
32enum Weekday {
33    Monday,
34    Tuesday,
35    Wednesday,
36    Thursday,
37    Friday,
38    Saturday,
39    Sunday,
40}
41
42#[bitmask(u8)]
43pub enum Weekdays {
44    Monday,
45    Tuesday,
46    Wednesday,
47    Thursday,
48    Friday,
49    Saturday,
50    Sunday,
51}
52
53#[derive(Debug)]
54pub enum DateDivisor {
55    Weekdays(Weekdays),
56    Interval(DateInterval),
57}
58
59#[derive(Debug)]
60pub struct DateRange {
61    pub from: HoleyDate,
62    pub until: Option<HoleyDate>,
63    pub date_divisor: DateDivisor,
64}
65
66impl Default for DateRange {
67    fn default() -> Self {
68        Self {
69            date_divisor: DateDivisor::Interval(DateInterval {
70                days: 1,
71                ..Default::default()
72            }),
73            from: Default::default(),
74            until: None,
75        }
76    }
77}
78
79#[derive(Debug)]
80pub enum DatePattern {
81    Point(HoleyDate),
82    Range(DateRange),
83}
84
85#[derive(Debug, Default)]
86pub struct Time {
87    pub hour: u32,
88    pub minute: u32,
89    pub second: u32,
90}
91
92#[derive(Debug, Default)]
93pub struct TimeInterval {
94    pub hours: u32,
95    pub minutes: u32,
96    pub seconds: u32,
97}
98
99#[derive(Debug, Default)]
100pub struct DateInterval {
101    pub years: i32,
102    pub months: u32,
103    pub weeks: u32,
104    pub days: u32,
105}
106
107#[derive(Debug, Default)]
108pub struct TimeRange {
109    pub from: Option<Time>,
110    pub until: Option<Time>,
111    pub interval: TimeInterval,
112}
113
114#[derive(Debug)]
115pub enum TimePattern {
116    Point(Time),
117    Range(TimeRange),
118}
119
120#[derive(Debug)]
121pub struct Recurrence {
122    pub dates_patterns: NonEmpty<DatePattern>,
123    pub time_patterns: Vec<TimePattern>,
124}
125
126#[derive(Debug, Default)]
127pub struct Countdown {
128    pub durations: Vec<Interval>,
129}
130
131#[derive(Debug)]
132pub enum ReminderPattern {
133    Recurrence(Recurrence),
134    Countdown(Countdown),
135    Cron(Cron),
136}
137
138#[derive(Debug, Default)]
139pub struct Reminder {
140    pub description: Option<Description>,
141    pub pattern: Option<ReminderPattern>,
142}
143
144#[derive(Debug)]
145pub struct Cron {
146    pub expr: String,
147}
148
149#[derive(Debug, Default)]
150pub struct Description(pub String);
151
152trait Parse {
153    fn parse(pair: Pair<'_, Rule>) -> Option<Self>
154    where
155        Self: Sized;
156}
157
158impl Parse for HoleyDate {
159    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
160        let mut holey_date = Self::default();
161        for rec in pair.into_inner() {
162            match rec.as_rule() {
163                Rule::year => {
164                    holey_date.year = Some(rec.as_str().parse().ok()?);
165                }
166                Rule::month => {
167                    holey_date.month = Some(rec.as_str().parse().ok()?);
168                }
169                Rule::day => {
170                    holey_date.day = Some(rec.as_str().parse().ok()?);
171                }
172                _ => unreachable!(),
173            }
174        }
175        Some(holey_date)
176    }
177}
178
179impl Parse for Interval {
180    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
181        let mut interval = Self::default();
182        for rec in pair.into_inner() {
183            match rec.as_rule() {
184                Rule::interval_years => {
185                    interval.years = rec.as_str().parse().ok()?;
186                }
187                Rule::interval_months => {
188                    interval.months = rec.as_str().parse().ok()?;
189                }
190                Rule::interval_weeks => {
191                    interval.weeks = rec.as_str().parse().ok()?;
192                }
193                Rule::interval_days => {
194                    interval.days = rec.as_str().parse().ok()?;
195                }
196                Rule::interval_hours => {
197                    interval.hours = rec.as_str().parse().ok()?;
198                }
199                Rule::interval_minutes => {
200                    interval.minutes = rec.as_str().parse().ok()?;
201                }
202                Rule::interval_seconds => {
203                    interval.seconds = rec.as_str().parse().ok()?;
204                }
205                _ => unreachable!(),
206            }
207        }
208        Some(interval)
209    }
210}
211
212impl Weekday {
213    fn next(&self) -> Self {
214        match *self {
215            Self::Monday => Self::Tuesday,
216            Self::Tuesday => Self::Wednesday,
217            Self::Wednesday => Self::Thursday,
218            Self::Thursday => Self::Friday,
219            Self::Friday => Self::Saturday,
220            Self::Saturday => Self::Sunday,
221            Self::Sunday => Self::Monday,
222        }
223    }
224}
225
226impl Parse for Weekday {
227    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
228        pair.into_inner()
229            .next()
230            .map(|weekday| match weekday.as_rule() {
231                Rule::monday => Self::Monday,
232                Rule::tuesday => Self::Tuesday,
233                Rule::wednesday => Self::Wednesday,
234                Rule::thursday => Self::Thursday,
235                Rule::friday => Self::Friday,
236                Rule::saturday => Self::Saturday,
237                Rule::sunday => Self::Sunday,
238                _ => unreachable!(),
239            })
240    }
241}
242
243impl Weekdays {
244    fn push(&mut self, weekday: Weekday) {
245        *self |= match weekday {
246            Weekday::Monday => Self::Monday,
247            Weekday::Tuesday => Self::Tuesday,
248            Weekday::Wednesday => Self::Wednesday,
249            Weekday::Thursday => Self::Thursday,
250            Weekday::Friday => Self::Friday,
251            Weekday::Saturday => Self::Saturday,
252            Weekday::Sunday => Self::Sunday,
253        };
254    }
255}
256impl Parse for Weekdays {
257    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
258        let mut weekdays = Self::none();
259        let mut weekday_range = pair.into_inner();
260        let mut weekday_from = weekday_range.next().and_then(Weekday::parse)?;
261        let weekday_to = weekday_range
262            .next()
263            .and_then(Weekday::parse)
264            .unwrap_or(weekday_from);
265        while weekday_from != weekday_to {
266            weekdays.push(weekday_from);
267            weekday_from = weekday_from.next();
268        }
269        weekdays.push(weekday_from);
270        Some(weekdays)
271    }
272}
273
274impl Parse for DateRange {
275    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
276        let mut date_range = Self::default();
277        for rec in pair.into_inner() {
278            match rec.as_rule() {
279                Rule::date_from => {
280                    date_range.from = HoleyDate::parse(rec)?;
281                }
282                Rule::date_until => {
283                    date_range.until = Some(HoleyDate::parse(rec)?);
284                }
285                Rule::date_interval => {
286                    date_range.date_divisor =
287                        DateDivisor::Interval(DateInterval::parse(rec)?);
288                }
289                Rule::weekdays_range => {
290                    let weekdays = match date_range.date_divisor {
291                        DateDivisor::Weekdays(ref mut w) => w,
292                        _ => {
293                            date_range.date_divisor =
294                                DateDivisor::Weekdays(Weekdays::none());
295                            match date_range.date_divisor {
296                                DateDivisor::Weekdays(ref mut w) => w,
297                                _ => unreachable!(),
298                            }
299                        }
300                    };
301                    *weekdays |= Weekdays::parse(rec)?;
302                }
303                _ => unreachable!(),
304            }
305        }
306        Some(date_range)
307    }
308}
309
310impl Parse for Time {
311    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
312        let mut time = Self::default();
313        for time_component in pair.into_inner() {
314            match time_component.as_rule() {
315                Rule::hour => {
316                    time.hour = time_component.as_str().parse().ok()?;
317                }
318                Rule::minute => {
319                    time.minute = time_component.as_str().parse().ok()?;
320                }
321                Rule::second => {
322                    time.second = time_component.as_str().parse().ok()?;
323                }
324                _ => unreachable!(),
325            }
326        }
327        Some(time)
328    }
329}
330
331impl Parse for TimeInterval {
332    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
333        let mut time_interval = Self::default();
334        for rec in pair.into_inner() {
335            match rec.as_rule() {
336                Rule::interval_hours => {
337                    time_interval.hours = rec.as_str().parse().ok()?;
338                }
339                Rule::interval_minutes => {
340                    time_interval.minutes = rec.as_str().parse().ok()?;
341                }
342                Rule::interval_seconds => {
343                    time_interval.seconds = rec.as_str().parse().ok()?;
344                }
345                _ => unreachable!(),
346            }
347        }
348        Some(time_interval)
349    }
350}
351
352impl Parse for DateInterval {
353    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
354        let mut date_interval = Self::default();
355        for rec in pair.into_inner() {
356            match rec.as_rule() {
357                Rule::interval_years => {
358                    date_interval.years = rec.as_str().parse().ok()?;
359                }
360                Rule::interval_months => {
361                    date_interval.months = rec.as_str().parse().ok()?;
362                }
363                Rule::interval_weeks => {
364                    date_interval.weeks = rec.as_str().parse().ok()?;
365                }
366                Rule::interval_days => {
367                    date_interval.days = rec.as_str().parse().ok()?;
368                }
369                _ => unreachable!(),
370            }
371        }
372        Some(date_interval)
373    }
374}
375
376impl Parse for TimeRange {
377    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
378        let mut time_range = Self::default();
379        for rec in pair.into_inner() {
380            match rec.as_rule() {
381                Rule::time_from => {
382                    time_range.from = Some(Time::parse(rec)?);
383                }
384                Rule::time_until => {
385                    time_range.until = Some(Time::parse(rec)?);
386                }
387                Rule::time_interval => {
388                    time_range.interval = TimeInterval::parse(rec)?;
389                }
390                _ => unreachable!(),
391            }
392        }
393        Some(time_range)
394    }
395}
396
397impl Default for Recurrence {
398    fn default() -> Self {
399        // make sure there's at least one date range
400        // the inserted holey range will correspond to the current date point
401        Self {
402            dates_patterns: nonempty![DatePattern::Point(HoleyDate::default())],
403            time_patterns: vec![],
404        }
405    }
406}
407
408impl Parse for Recurrence {
409    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
410        let mut recurrence = Self::default();
411        for rec in pair.into_inner() {
412            match rec.as_rule() {
413                Rule::dates_point => {
414                    recurrence
415                        .dates_patterns
416                        .push(DatePattern::Point(HoleyDate::parse(rec)?));
417                }
418                Rule::dates_range => {
419                    recurrence
420                        .dates_patterns
421                        .push(DatePattern::Range(DateRange::parse(rec)?));
422                }
423                Rule::time_point => {
424                    recurrence
425                        .time_patterns
426                        .push(TimePattern::Point(Time::parse(rec)?));
427                }
428                Rule::time_range => {
429                    recurrence
430                        .time_patterns
431                        .push(TimePattern::Range(TimeRange::parse(rec)?));
432                }
433                _ => unreachable!(),
434            }
435        }
436        if recurrence.dates_patterns.len() > 1 {
437            recurrence.dates_patterns =
438                NonEmpty::from_vec(recurrence.dates_patterns.tail).unwrap();
439        }
440        Some(recurrence)
441    }
442}
443
444impl Parse for Countdown {
445    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
446        let mut countdown = Self::default();
447        for rec in pair.into_inner() {
448            match rec.as_rule() {
449                Rule::interval => {
450                    countdown.durations.push(Interval::parse(rec)?);
451                }
452                _ => unreachable!(),
453            }
454        }
455        Some(countdown)
456    }
457}
458
459impl Parse for Cron {
460    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
461        for rec in pair.into_inner() {
462            if rec.as_rule() == Rule::cron_expr {
463                return Some(Self {
464                    expr: rec.as_str().to_string(),
465                });
466            }
467        }
468        None
469    }
470}
471
472impl Parse for Description {
473    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
474        Some(Self(pair.as_str().to_string()))
475    }
476}
477
478impl Parse for Reminder {
479    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
480        let mut reminder = Self::default();
481        for rec in pair.into_inner() {
482            match rec.as_rule() {
483                Rule::description => {
484                    reminder.description = Some(Description::parse(rec)?);
485                }
486                Rule::recurrence => {
487                    reminder.pattern = Some(ReminderPattern::Recurrence(
488                        Recurrence::parse(rec)?,
489                    ));
490                }
491                Rule::countdown => {
492                    reminder.pattern = Some(ReminderPattern::Countdown(
493                        Countdown::parse(rec)?,
494                    ));
495                }
496                Rule::cron => {
497                    reminder.pattern =
498                        Some(ReminderPattern::Cron(Cron::parse(rec)?));
499                }
500                Rule::EOI => {}
501                _ => unreachable!(),
502            }
503        }
504        Some(reminder)
505    }
506}
507
508pub fn parse_reminder(s: &str) -> Option<Reminder> {
509    Reminder::parse(
510        ReminderParser::parse(Rule::reminder, s)
511            .map_err(|err| {
512                log::debug!("{}", err);
513            })
514            .ok()?
515            .next()?,
516    )
517}