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    pub nag_interval: Option<Interval>,
143}
144
145#[derive(Debug)]
146pub struct Cron {
147    pub expr: String,
148}
149
150#[derive(Debug, Default)]
151pub struct Description(pub String);
152
153trait Parse {
154    fn parse(pair: Pair<'_, Rule>) -> Option<Self>
155    where
156        Self: Sized;
157}
158
159impl Parse for HoleyDate {
160    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
161        let mut holey_date = Self::default();
162        for rec in pair.into_inner() {
163            match rec.as_rule() {
164                Rule::year => {
165                    holey_date.year = Some(rec.as_str().parse().ok()?);
166                }
167                Rule::month => {
168                    holey_date.month = Some(rec.as_str().parse().ok()?);
169                }
170                Rule::day => {
171                    holey_date.day = Some(rec.as_str().parse().ok()?);
172                }
173                _ => unreachable!(),
174            }
175        }
176        Some(holey_date)
177    }
178}
179
180impl Parse for Interval {
181    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
182        let mut interval = Self::default();
183        for rec in pair.into_inner() {
184            match rec.as_rule() {
185                Rule::interval_years => {
186                    interval.years = rec.as_str().parse().ok()?;
187                }
188                Rule::interval_months => {
189                    interval.months = rec.as_str().parse().ok()?;
190                }
191                Rule::interval_weeks => {
192                    interval.weeks = rec.as_str().parse().ok()?;
193                }
194                Rule::interval_days => {
195                    interval.days = rec.as_str().parse().ok()?;
196                }
197                Rule::interval_hours => {
198                    interval.hours = rec.as_str().parse().ok()?;
199                }
200                Rule::interval_minutes => {
201                    interval.minutes = rec.as_str().parse().ok()?;
202                }
203                Rule::interval_seconds => {
204                    interval.seconds = rec.as_str().parse().ok()?;
205                }
206                _ => unreachable!(),
207            }
208        }
209        Some(interval)
210    }
211}
212
213impl Weekday {
214    fn next(&self) -> Self {
215        match *self {
216            Self::Monday => Self::Tuesday,
217            Self::Tuesday => Self::Wednesday,
218            Self::Wednesday => Self::Thursday,
219            Self::Thursday => Self::Friday,
220            Self::Friday => Self::Saturday,
221            Self::Saturday => Self::Sunday,
222            Self::Sunday => Self::Monday,
223        }
224    }
225}
226
227impl Parse for Weekday {
228    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
229        pair.into_inner()
230            .next()
231            .map(|weekday| match weekday.as_rule() {
232                Rule::monday => Self::Monday,
233                Rule::tuesday => Self::Tuesday,
234                Rule::wednesday => Self::Wednesday,
235                Rule::thursday => Self::Thursday,
236                Rule::friday => Self::Friday,
237                Rule::saturday => Self::Saturday,
238                Rule::sunday => Self::Sunday,
239                _ => unreachable!(),
240            })
241    }
242}
243
244impl Weekdays {
245    fn push(&mut self, weekday: Weekday) {
246        *self |= match weekday {
247            Weekday::Monday => Self::Monday,
248            Weekday::Tuesday => Self::Tuesday,
249            Weekday::Wednesday => Self::Wednesday,
250            Weekday::Thursday => Self::Thursday,
251            Weekday::Friday => Self::Friday,
252            Weekday::Saturday => Self::Saturday,
253            Weekday::Sunday => Self::Sunday,
254        };
255    }
256}
257impl Parse for Weekdays {
258    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
259        let mut weekdays = Self::none();
260        let mut weekday_range = pair.into_inner();
261        let mut weekday_from = weekday_range.next().and_then(Weekday::parse)?;
262        let weekday_to = weekday_range
263            .next()
264            .and_then(Weekday::parse)
265            .unwrap_or(weekday_from);
266        while weekday_from != weekday_to {
267            weekdays.push(weekday_from);
268            weekday_from = weekday_from.next();
269        }
270        weekdays.push(weekday_from);
271        Some(weekdays)
272    }
273}
274
275impl Parse for DateRange {
276    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
277        let mut date_range = Self::default();
278        for rec in pair.into_inner() {
279            match rec.as_rule() {
280                Rule::date_from => {
281                    date_range.from = HoleyDate::parse(rec)?;
282                }
283                Rule::date_until => {
284                    date_range.until = Some(HoleyDate::parse(rec)?);
285                }
286                Rule::date_interval => {
287                    date_range.date_divisor =
288                        DateDivisor::Interval(DateInterval::parse(rec)?);
289                }
290                Rule::weekdays_range => {
291                    let weekdays = match date_range.date_divisor {
292                        DateDivisor::Weekdays(ref mut w) => w,
293                        _ => {
294                            date_range.date_divisor =
295                                DateDivisor::Weekdays(Weekdays::none());
296                            match date_range.date_divisor {
297                                DateDivisor::Weekdays(ref mut w) => w,
298                                _ => unreachable!(),
299                            }
300                        }
301                    };
302                    *weekdays |= Weekdays::parse(rec)?;
303                }
304                _ => unreachable!(),
305            }
306        }
307        Some(date_range)
308    }
309}
310
311impl Parse for Time {
312    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
313        let mut time = Self::default();
314        for time_component in pair.into_inner() {
315            match time_component.as_rule() {
316                Rule::hour => {
317                    time.hour = time_component.as_str().parse().ok()?;
318                }
319                Rule::minute => {
320                    time.minute = time_component.as_str().parse().ok()?;
321                }
322                Rule::second => {
323                    time.second = time_component.as_str().parse().ok()?;
324                }
325                _ => unreachable!(),
326            }
327        }
328        Some(time)
329    }
330}
331
332impl Parse for TimeInterval {
333    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
334        let mut time_interval = Self::default();
335        for rec in pair.into_inner() {
336            match rec.as_rule() {
337                Rule::interval_hours => {
338                    time_interval.hours = rec.as_str().parse().ok()?;
339                }
340                Rule::interval_minutes => {
341                    time_interval.minutes = rec.as_str().parse().ok()?;
342                }
343                Rule::interval_seconds => {
344                    time_interval.seconds = rec.as_str().parse().ok()?;
345                }
346                _ => unreachable!(),
347            }
348        }
349        Some(time_interval)
350    }
351}
352
353impl Parse for DateInterval {
354    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
355        let mut date_interval = Self::default();
356        for rec in pair.into_inner() {
357            match rec.as_rule() {
358                Rule::interval_years => {
359                    date_interval.years = rec.as_str().parse().ok()?;
360                }
361                Rule::interval_months => {
362                    date_interval.months = rec.as_str().parse().ok()?;
363                }
364                Rule::interval_weeks => {
365                    date_interval.weeks = rec.as_str().parse().ok()?;
366                }
367                Rule::interval_days => {
368                    date_interval.days = rec.as_str().parse().ok()?;
369                }
370                _ => unreachable!(),
371            }
372        }
373        Some(date_interval)
374    }
375}
376
377impl Parse for TimeRange {
378    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
379        let mut time_range = Self::default();
380        for rec in pair.into_inner() {
381            match rec.as_rule() {
382                Rule::time_from => {
383                    time_range.from = Some(Time::parse(rec)?);
384                }
385                Rule::time_until => {
386                    time_range.until = Some(Time::parse(rec)?);
387                }
388                Rule::time_interval => {
389                    time_range.interval = TimeInterval::parse(rec)?;
390                }
391                _ => unreachable!(),
392            }
393        }
394        Some(time_range)
395    }
396}
397
398impl Default for Recurrence {
399    fn default() -> Self {
400        // make sure there's at least one date range
401        // the inserted holey range will correspond to the current date point
402        Self {
403            dates_patterns: nonempty![DatePattern::Point(HoleyDate::default())],
404            time_patterns: vec![],
405        }
406    }
407}
408
409impl Parse for Recurrence {
410    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
411        let mut recurrence = Self::default();
412        for rec in pair.into_inner() {
413            match rec.as_rule() {
414                Rule::dates_point => {
415                    recurrence
416                        .dates_patterns
417                        .push(DatePattern::Point(HoleyDate::parse(rec)?));
418                }
419                Rule::dates_range => {
420                    recurrence
421                        .dates_patterns
422                        .push(DatePattern::Range(DateRange::parse(rec)?));
423                }
424                Rule::time_point => {
425                    recurrence
426                        .time_patterns
427                        .push(TimePattern::Point(Time::parse(rec)?));
428                }
429                Rule::time_range => {
430                    recurrence
431                        .time_patterns
432                        .push(TimePattern::Range(TimeRange::parse(rec)?));
433                }
434                _ => unreachable!(),
435            }
436        }
437        if recurrence.dates_patterns.len() > 1 {
438            recurrence.dates_patterns =
439                NonEmpty::from_vec(recurrence.dates_patterns.tail).unwrap();
440        }
441        Some(recurrence)
442    }
443}
444
445impl Parse for Countdown {
446    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
447        let mut countdown = Self::default();
448        for rec in pair.into_inner() {
449            match rec.as_rule() {
450                Rule::interval => {
451                    countdown.durations.push(Interval::parse(rec)?);
452                }
453                _ => unreachable!(),
454            }
455        }
456        Some(countdown)
457    }
458}
459
460impl Parse for Cron {
461    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
462        for rec in pair.into_inner() {
463            if rec.as_rule() == Rule::cron_expr {
464                return Some(Self {
465                    expr: rec.as_str().to_string(),
466                });
467            }
468        }
469        None
470    }
471}
472
473impl Parse for Description {
474    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
475        Some(Self(pair.as_str().to_string()))
476    }
477}
478
479impl Parse for Reminder {
480    fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
481        let mut reminder = Self::default();
482        for rec in pair.into_inner() {
483            match rec.as_rule() {
484                Rule::description => {
485                    reminder.description = Some(Description::parse(rec)?);
486                }
487                Rule::recurrence => {
488                    reminder.pattern = Some(ReminderPattern::Recurrence(
489                        Recurrence::parse(rec)?,
490                    ));
491                }
492                Rule::countdown => {
493                    reminder.pattern = Some(ReminderPattern::Countdown(
494                        Countdown::parse(rec)?,
495                    ));
496                }
497                Rule::cron => {
498                    reminder.pattern =
499                        Some(ReminderPattern::Cron(Cron::parse(rec)?));
500                }
501                Rule::nag_suffix => {
502                    let nag_interval = rec.into_inner().find_map(|inner| {
503                        if inner.as_rule() == Rule::interval {
504                            Interval::parse(inner)
505                        } else {
506                            None
507                        }
508                    })?;
509                    reminder.nag_interval = Some(nag_interval);
510                }
511                Rule::EOI => {}
512                _ => unreachable!(),
513            }
514        }
515        Some(reminder)
516    }
517}
518
519pub fn parse_reminder(s: &str) -> Option<Reminder> {
520    Reminder::parse(
521        ReminderParser::parse(Rule::reminder, s)
522            .map_err(|err| {
523                log::debug!("{err}");
524            })
525            .ok()?
526            .next()?,
527    )
528}