1use alloc::string::{String, ToString};
2use alloc::vec::Vec;
3use core::cmp::Ord;
4use core::convert::TryInto;
5use core::fmt::Debug;
6use core::hash::Hash;
7use core::ops::RangeInclusive;
8
9use chrono::Duration;
10
11use pest::iterators::Pair;
12
13use crate::error::{err_empty, Error, Result};
14use crate::extended_time::ExtendedTime;
15use crate::rules::day::{self as ds, WeekNum, Year};
16use crate::rules::time as ts;
17use crate::util::{is_capitalized, is_lowercase, PairsIterExtension, Sign};
18use crate::{rules as rl, Warning};
19
20#[derive(pest_derive::Parser)]
21#[grammar = "grammar.pest"]
22struct Grammar;
23
24pub fn parse(data: &str) -> Result<rl::OpeningHoursExpression> {
26 Parser::default().parse(data)
27}
28
29impl alloc::str::FromStr for rl::OpeningHoursExpression {
30 type Err = Error;
31
32 fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
33 parse(s)
34 }
35}
36
37pub struct Parser<F: FnMut(Warning) = fn(Warning)> {
43 warning_handler: F,
44}
45
46impl Default for Parser<fn(Warning)> {
47 fn default() -> Self {
48 Self { warning_handler: |_| {} }
49 }
50}
51
52impl<F: FnMut(Warning)> Parser<F> {
53 pub fn parse(&mut self, data: &str) -> Result<rl::OpeningHoursExpression> {
55 use pest::Parser;
56
57 Grammar::parse(Rule::input_opening_hours, data)
58 .map_err(Error::from)?
59 .next()
60 .ok_or(Error::GrammarLogic {
61 rule: Rule::input_opening_hours,
62 invariant: "cannot be missing",
63 })
64 .and_then(|p| self.build_opening_hours(p))
65 .map(|rules| rl::OpeningHoursExpression { rules })
66 .inspect_err(|err| {
67 debug_assert!(
68 !err.is_implementation_error(),
69 "parser implementation error: {err:?}",
70 )
71 })
72 }
73
74 pub fn with_warning_handler<G: FnMut(Warning)>(self, warning_handler: G) -> Parser<G> {
76 Parser { warning_handler }
77 }
78
79 fn warn(&mut self, warning: Warning) {
84 (self.warning_handler)(warning)
85 }
86
87 fn build_opening_hours(&mut self, pair: Pair<Rule>) -> Result<Vec<rl::RuleSequence>> {
88 debug_assert_eq!(pair.as_rule(), Rule::opening_hours);
89 let mut pairs = pair.into_inner();
90 let mut rules = Vec::new();
91
92 while let Some(pair) = pairs.next() {
93 rules.push(match pair.as_rule() {
94 Rule::rule_sequence => self.build_rule_sequence(pair, rl::RuleOperator::Normal),
95 Rule::any_rule_separator => {
96 let separator = self.build_any_rule_separator(pair)?;
97
98 self.build_rule_sequence(
99 pairs.next().ok_or(Error::GrammarLogic {
100 rule: Rule::opening_hours,
101 invariant: "a separator is always followed by a rule",
102 })?,
103 separator,
104 )
105 }
106 unexpected => {
107 return Err(Error::GrammarUnexpectedToken {
108 rule: Rule::opening_hours,
109 unexpected,
110 })
111 }
112 }?)
113 }
114
115 Ok(rules)
116 }
117
118 fn build_rule_sequence(
119 &mut self,
120 pair: Pair<Rule>,
121 operator: rl::RuleOperator,
122 ) -> Result<rl::RuleSequence> {
123 debug_assert_eq!(pair.as_rule(), Rule::rule_sequence);
124 let mut pairs = pair.into_inner();
125 let root_pair = pairs.next().ok_or(err_empty(Rule::rule_sequence))?;
126 let (day_selector, time_selector, extra_comment) =
127 self.build_selector_sequence(root_pair)?;
128
129 let (kind, comment) = pairs
130 .next()
131 .map(|p| self.build_rules_modifier(p))
132 .transpose()?
133 .unwrap_or((rl::RuleKind::Open, None));
134
135 let comment = comment
136 .into_iter()
137 .chain(extra_comment)
138 .next()
139 .unwrap_or_default()
140 .into();
141
142 Ok(rl::RuleSequence {
143 day_selector,
144 time_selector,
145 kind,
146 operator,
147 comment,
148 })
149 }
150
151 fn build_any_rule_separator(&mut self, pair: Pair<Rule>) -> Result<rl::RuleOperator> {
152 debug_assert_eq!(pair.as_rule(), Rule::any_rule_separator);
153
154 let root_pair = pair
155 .into_inner()
156 .next()
157 .ok_or(err_empty(Rule::any_rule_separator))?;
158
159 match root_pair.as_rule() {
160 Rule::normal_rule_separator => Ok(rl::RuleOperator::Normal),
161 Rule::additional_rule_separator => Ok(rl::RuleOperator::Additional),
162 Rule::fallback_rule_separator => Ok(rl::RuleOperator::Fallback),
163 unexpected => {
164 Err(Error::GrammarUnexpectedToken { rule: Rule::any_rule_separator, unexpected })
165 }
166 }
167 }
168
169 fn build_rules_modifier(&mut self, pair: Pair<Rule>) -> Result<(rl::RuleKind, Option<String>)> {
174 debug_assert_eq!(pair.as_rule(), Rule::rules_modifier);
175 let mut pairs = pair.into_inner();
176
177 let kind = pairs
178 .next_if_rule(Rule::rules_modifier_enum)
179 .map(|p| self.build_rules_modifier_enum(p))
180 .transpose()?
181 .unwrap_or(rl::RuleKind::Open);
182
183 let comment = pairs.next().map(|p| self.build_comment(p)).transpose()?;
184 Ok((kind, comment))
185 }
186
187 fn build_rules_modifier_enum(&mut self, pair: Pair<Rule>) -> Result<rl::RuleKind> {
188 debug_assert_eq!(pair.as_rule(), Rule::rules_modifier_enum);
189
190 if !is_lowercase(pair.as_str()) {
191 self.warn(Warning::ShouldBeLowercase(pair.clone()));
192 }
193
194 let pair = (pair.into_inner())
195 .next()
196 .ok_or(err_empty(Rule::rules_modifier_enum))?;
197
198 match pair.as_rule() {
199 Rule::rules_modifier_enum_closed => Ok(rl::RuleKind::Closed),
200 Rule::rules_modifier_enum_open => Ok(rl::RuleKind::Open),
201 Rule::rules_modifier_enum_unknown => Ok(rl::RuleKind::Unknown),
202 unexpected => {
203 Err(Error::GrammarUnexpectedToken { rule: Rule::rules_modifier_enum, unexpected })
204 }
205 }
206 }
207
208 fn build_selector_sequence(
213 &mut self,
214 pair: Pair<Rule>,
215 ) -> Result<(ds::DaySelector, ts::TimeSelector, Option<String>)> {
216 debug_assert_eq!(pair.as_rule(), Rule::selector_sequence);
217 let mut pairs = pair.into_inner();
218
219 if pairs.next_if_rule(Rule::always_open).is_some() {
220 return Ok(Default::default());
221 }
222
223 let (year, monthday, week, comment) = pairs
224 .next_if_rule(Rule::wide_range_selectors)
225 .map(|p| self.build_wide_range_selectors(p))
226 .transpose()?
227 .unwrap_or_default();
228
229 let (weekday, time) = pairs
230 .next()
231 .map(|p| self.build_small_range_selectors(p))
232 .transpose()?
233 .unwrap_or_default();
234
235 Ok((
236 ds::DaySelector { year, monthday, week, weekday },
237 ts::TimeSelector::new(time),
238 comment,
239 ))
240 }
241
242 #[allow(clippy::type_complexity)]
243 fn build_wide_range_selectors(
244 &mut self,
245 pair: Pair<Rule>,
246 ) -> Result<(
247 Vec<ds::YearRange>,
248 Vec<ds::MonthdayRange>,
249 Vec<ds::WeekRange>,
250 Option<String>,
251 )> {
252 debug_assert_eq!(pair.as_rule(), Rule::wide_range_selectors);
253
254 let mut year_selector = Vec::new();
255 let mut monthday_selector = Vec::new();
256 let mut week_selector = Vec::new();
257 let mut comment = None;
258
259 for pair in pair.into_inner() {
260 match pair.as_rule() {
261 Rule::year_selector => year_selector = self.build_year_selector(pair)?,
262 Rule::monthday_selector => {
263 monthday_selector = self.build_monthday_selector(pair)?
264 }
265 Rule::week_selector => week_selector = self.build_week_selector(pair)?,
266 Rule::comment => comment = Some(self.build_comment(pair)?),
267 unexpected => {
268 return Err(Error::GrammarUnexpectedToken {
269 rule: Rule::wide_range_selectors,
270 unexpected,
271 })
272 }
273 }
274 }
275
276 Ok((year_selector, monthday_selector, week_selector, comment))
277 }
278
279 fn build_small_range_selectors(
280 &mut self,
281 pair: Pair<Rule>,
282 ) -> Result<(Vec<ds::WeekDayRange>, Vec<ts::TimeSpan>)> {
283 debug_assert_eq!(pair.as_rule(), Rule::small_range_selectors);
284
285 let mut weekday_selector = Vec::new();
286 let mut time_selector = Vec::new();
287
288 for pair in pair.into_inner() {
289 match pair.as_rule() {
290 Rule::weekday_selector => weekday_selector = self.build_weekday_selector(pair)?,
291 Rule::time_selector => time_selector = self.build_time_selector(pair)?,
292 unexpected => {
293 return Err(Error::GrammarUnexpectedToken {
294 rule: Rule::wide_range_selectors,
295 unexpected,
296 })
297 }
298 }
299 }
300
301 Ok((weekday_selector, time_selector))
302 }
303
304 fn build_time_selector(&mut self, pair: Pair<Rule>) -> Result<Vec<ts::TimeSpan>> {
309 debug_assert_eq!(pair.as_rule(), Rule::time_selector);
310 pair.into_inner().map(|p| self.build_timespan(p)).collect()
311 }
312
313 fn build_timespan(&mut self, pair: Pair<Rule>) -> Result<ts::TimeSpan> {
314 debug_assert_eq!(pair.as_rule(), Rule::timespan);
315 let mut pairs = pair.into_inner();
316 let mut repeats = None;
317 let start = self.build_time(pairs.next().ok_or(err_empty(Rule::timespan))?)?;
318
319 let (mut open_end, end) = match pairs.next() {
320 None => {
321 return Err(Error::Unsupported("point in time"));
322 }
323 Some(pair) if pair.as_rule() == Rule::timespan_plus => {
324 (true, ts::Time::Fixed(ExtendedTime::MIDNIGHT_24))
327 }
328 Some(pair) => (false, self.build_extended_time(pair)?),
329 };
330
331 if let Some(pair_repetition) = pairs.next() {
332 match pair_repetition.as_rule() {
333 Rule::timespan_plus => open_end = true,
334 Rule::minute => repeats = Some(self.build_minute(pair_repetition)?),
335 Rule::hour_minutes => {
336 repeats = Some(self.build_hour_minutes_as_duration(pair_repetition)?)
337 }
338 unexpected => {
339 return Err(Error::GrammarUnexpectedToken { rule: Rule::timespan, unexpected })
340 }
341 }
342 }
343
344 debug_assert!(pairs.next().is_none());
345 Ok(ts::TimeSpan { range: start..end, repeats, open_end })
346 }
347
348 fn build_time(&mut self, pair: Pair<Rule>) -> Result<ts::Time> {
349 debug_assert_eq!(pair.as_rule(), Rule::time);
350 let root_pair = pair.into_inner().next().ok_or(err_empty(Rule::time))?;
351
352 Ok(match root_pair.as_rule() {
353 Rule::hour_minutes => ts::Time::Fixed(self.build_hour_minutes(root_pair)?),
354 Rule::variable_time => ts::Time::Variable(self.build_variable_time(root_pair)?),
355 unexpected => {
356 return Err(Error::GrammarUnexpectedToken { rule: Rule::time, unexpected })
357 }
358 })
359 }
360
361 fn build_extended_time(&mut self, pair: Pair<Rule>) -> Result<ts::Time> {
362 debug_assert_eq!(pair.as_rule(), Rule::extended_time);
363
364 let root_pair = pair
365 .into_inner()
366 .next()
367 .ok_or(err_empty(Rule::extended_time))?;
368
369 match root_pair.as_rule() {
370 Rule::extended_hour_minutes => self
371 .build_extended_hour_minutes(root_pair)
372 .map(ts::Time::Fixed),
373 Rule::variable_time => self.build_variable_time(root_pair).map(ts::Time::Variable),
374 unexpected => {
375 Err(Error::GrammarUnexpectedToken { rule: Rule::extended_time, unexpected })
376 }
377 }
378 }
379
380 fn build_variable_time(&mut self, pair: Pair<Rule>) -> Result<ts::VariableTime> {
381 debug_assert_eq!(pair.as_rule(), Rule::variable_time);
382 let mut pairs = pair.into_inner();
383 let event = self.build_event(pairs.next().ok_or(err_empty(Rule::variable_time))?)?;
384
385 let offset = {
386 if let Some(sign_pair) = pairs.next() {
387 let sign = self.build_plus_or_minus(sign_pair)?;
388
389 let hour_minutes_pair = pairs.next().ok_or(Error::GrammarLogic {
390 rule: Rule::variable_time,
391 invariant: "a sign is always followed by hours and minutes",
392 })?;
393
394 let mins: i16 = self
395 .build_hour_minutes(hour_minutes_pair)?
396 .mins_from_midnight()
397 .try_into()
398 .map_err(|_| Error::GrammarLogic {
399 rule: Rule::variable_time,
400 invariant: "daily number of minutes fits in an i16",
401 })?;
402
403 match sign {
404 Sign::Pos => mins,
405 Sign::Neg => -mins,
406 }
407 } else {
408 0
409 }
410 };
411
412 Ok(ts::VariableTime { event, offset })
413 }
414
415 fn build_event(&mut self, pair: Pair<Rule>) -> Result<ts::TimeEvent> {
416 debug_assert_eq!(pair.as_rule(), Rule::event);
417
418 if !is_lowercase(pair.as_str()) {
419 self.warn(Warning::ShouldBeLowercase(pair.clone()))
420 }
421
422 let pair = (pair.clone().into_inner())
423 .next()
424 .ok_or(err_empty(Rule::event))?;
425
426 match pair.as_rule() {
427 Rule::dawn => Ok(ts::TimeEvent::Dawn),
428 Rule::sunrise => Ok(ts::TimeEvent::Sunrise),
429 Rule::sunset => Ok(ts::TimeEvent::Sunset),
430 Rule::dusk => Ok(ts::TimeEvent::Dusk),
431 unexpected => Err(Error::GrammarUnexpectedToken { rule: Rule::event, unexpected }),
432 }
433 }
434
435 fn build_weekday_selector(&mut self, pair: Pair<Rule>) -> Result<Vec<ds::WeekDayRange>> {
440 debug_assert_eq!(pair.as_rule(), Rule::weekday_selector);
441 let mut ranges = Vec::new();
442
443 for pair in pair.into_inner() {
444 match pair.as_rule() {
445 Rule::weekday_sequence => {
446 for pair in pair.into_inner() {
447 ranges.push(self.build_weekday_range(pair)?)
448 }
449 }
450 Rule::holiday_sequence => {
451 for pair in pair.into_inner() {
452 ranges.push(self.build_holiday(pair)?)
453 }
454 }
455 unexpected => {
456 return Err(Error::GrammarUnexpectedToken {
457 rule: Rule::weekday_selector,
458 unexpected,
459 })
460 }
461 }
462 }
463
464 Ok(ranges)
465 }
466
467 fn build_weekday_range(&mut self, pair: Pair<Rule>) -> Result<ds::WeekDayRange> {
468 debug_assert_eq!(pair.as_rule(), Rule::weekday_range);
469 let mut pairs = pair.into_inner();
470 let start = self.build_wday(pairs.next().ok_or(err_empty(Rule::weekday_range))?)?;
471
472 let end = pairs
473 .next_if_rule(Rule::wday)
474 .map(|p| self.build_wday(p))
475 .transpose()?
476 .unwrap_or(start);
477
478 let mut nth_from_start = [false; 5];
479 let mut nth_from_end = [false; 5];
480
481 while let Some(pair_nth_entry) = pairs.next_if_rule(Rule::nth_entry) {
482 let (sign, indices) = self.build_nth_entry(pair_nth_entry)?;
483
484 let nth_array = match sign {
485 Sign::Neg => &mut nth_from_end,
486 Sign::Pos => &mut nth_from_start,
487 };
488
489 for i in indices {
490 nth_array[usize::from(i - 1)] = true;
491 }
492 }
493
494 if !nth_from_start.contains(&true) && !nth_from_end.contains(&true) {
495 nth_from_start = [true; 5];
496 nth_from_end = [true; 5];
497 }
498
499 let offset = {
500 if let Some(pair) = pairs.next() {
501 self.build_day_offset(pair)?
502 } else {
503 0
504 }
505 };
506
507 Ok(ds::WeekDayRange::Fixed {
508 range: start..=end,
509 offset,
510 nth_from_start,
511 nth_from_end,
512 })
513 }
514
515 fn build_holiday(&mut self, pair: Pair<Rule>) -> Result<ds::WeekDayRange> {
516 debug_assert_eq!(pair.as_rule(), Rule::holiday);
517 let mut pairs = pair.into_inner();
518
519 let kind = match pairs.next().ok_or(err_empty(Rule::holiday))?.as_rule() {
520 Rule::public_holiday => ds::HolidayKind::Public,
521 Rule::school_holiday => ds::HolidayKind::School,
522 unexpected => {
523 return Err(Error::GrammarUnexpectedToken { rule: Rule::holiday, unexpected })
524 }
525 };
526
527 let offset = pairs
528 .next()
529 .map(|p| self.build_day_offset(p))
530 .unwrap_or(Ok(0))?;
531
532 Ok(ds::WeekDayRange::Holiday { kind, offset })
533 }
534
535 fn build_nth_entry(&mut self, pair: Pair<Rule>) -> Result<(Sign, RangeInclusive<u8>)> {
536 debug_assert_eq!(pair.as_rule(), Rule::nth_entry);
537 let mut pairs = pair.into_inner();
538
539 let sign = {
540 if pairs.next_if_rule(Rule::nth_minus).is_some() {
541 Sign::Neg
542 } else {
543 Sign::Pos
544 }
545 };
546
547 let start = self.build_nth(pairs.next().ok_or(Error::GrammarLogic {
548 rule: Rule::nth_entry,
549 invariant: "a sign is always followed by a number",
550 })?)?;
551
552 let end = pairs
553 .next()
554 .map(|p| self.build_nth(p))
555 .transpose()?
556 .unwrap_or(start);
557
558 Ok((sign, start..=end))
559 }
560
561 fn build_nth(&mut self, pair: Pair<Rule>) -> Result<u8> {
562 debug_assert_eq!(pair.as_rule(), Rule::nth);
563
564 pair.as_str().parse().map_err(|_| Error::GrammarLogic {
565 rule: Rule::nth,
566 invariant: "must be valid number for 1 to 5",
567 })
568 }
569
570 fn build_day_offset(&mut self, pair: Pair<Rule>) -> Result<i16> {
571 debug_assert_eq!(pair.as_rule(), Rule::day_offset);
572 let mut pairs = pair.into_inner();
573 let sign = self.build_plus_or_minus(pairs.next().ok_or(err_empty(Rule::day_offset))?)?;
574
575 let val_abs = self.build_positive_number(pairs.next().ok_or(Error::GrammarLogic {
576 rule: Rule::day_offset,
577 invariant: "a sign is always followed by a number",
578 })?)?;
579
580 Ok(match sign {
581 Sign::Pos => val_abs,
582 Sign::Neg => -val_abs,
583 })
584 }
585
586 fn build_week_selector(&mut self, pair: Pair<Rule>) -> Result<Vec<ds::WeekRange>> {
591 debug_assert_eq!(pair.as_rule(), Rule::week_selector);
592 pair.into_inner().map(|p| self.build_week(p)).collect()
593 }
594
595 fn build_week(&mut self, pair: Pair<Rule>) -> Result<ds::WeekRange> {
596 debug_assert_eq!(pair.as_rule(), Rule::week);
597 let mut rules = pair.into_inner();
598 let start = self.build_weeknum(rules.next().ok_or(err_empty(Rule::week))?)?;
599
600 let end = rules
601 .next()
602 .map(|p| self.build_weeknum(p))
603 .transpose()?
604 .unwrap_or(start);
605
606 let step = rules
607 .next()
608 .map(|p| self.build_positive_number(p))
609 .transpose()?
610 .unwrap_or(1);
611
612 let step = step
613 .try_into()
614 .map_err(|_| Error::Overflow { value: step, expected_bounds: 0i16..=255i16 })?;
615
616 ds::WeekRange::new(start..=end, step).ok_or(Error::InvertedWeekRange { start, end, step })
617 }
618
619 fn build_monthday_selector(&mut self, pair: Pair<Rule>) -> Result<Vec<ds::MonthdayRange>> {
624 debug_assert_eq!(pair.as_rule(), Rule::monthday_selector);
625
626 pair.into_inner()
627 .map(|p| self.build_monthday_range(p))
628 .collect()
629 }
630
631 fn build_monthday_range(&mut self, pair: Pair<Rule>) -> Result<ds::MonthdayRange> {
632 debug_assert_eq!(pair.as_rule(), Rule::monthday_range);
633 let mut pairs = pair.into_inner();
634 let mut first_pair = pairs.next().ok_or(err_empty(Rule::monthday_range))?;
635
636 let year = {
637 if first_pair.as_rule() == Rule::year {
638 let year = self.build_year(first_pair)?;
639
640 first_pair = pairs.next().ok_or(Error::GrammarLogic {
641 rule: Rule::monthday_range,
642 invariant: "cannot contain just a year",
643 })?;
644
645 Some(year)
646 } else {
647 None
648 }
649 };
650
651 match first_pair.as_rule() {
652 Rule::month => {
653 let start = self.build_month(first_pair)?;
654
655 let end = (pairs.next())
656 .map(|p| self.build_month(p))
657 .transpose()?
658 .unwrap_or(start);
659
660 Ok(ds::MonthdayRange::Month { year, range: start..=end })
661 }
662 Rule::date_from => {
663 let start = self.build_date_from(first_pair)?;
664
665 let start_offset = pairs
666 .next_if_rule(Rule::date_offset)
667 .map(|p| self.build_date_offset(p))
668 .transpose()?
669 .unwrap_or_default();
670
671 let Some(pair_end) = pairs.next() else {
672 return Ok(ds::MonthdayRange::Date {
673 start: (start, start_offset),
674 end: (start, start_offset),
675 });
676 };
677
678 let end = match pair_end.as_rule() {
679 Rule::date_to => self.build_date_to(pair_end, start)?,
680 Rule::monthday_range_plus => {
681 if start.year().is_some() {
682 ds::Date::ymd(31, ds::Month::December, Year(9999))
683 } else {
684 ds::Date::md(31, ds::Month::December)
685 }
686 }
687 unexpected => {
688 return Err(Error::GrammarUnexpectedToken {
689 rule: Rule::monthday_range,
690 unexpected,
691 })
692 }
693 };
694
695 let end_offset = pairs
696 .next()
697 .map(|p| self.build_date_offset(p))
698 .unwrap_or_else(|| Ok(Default::default()))?;
699
700 Ok(ds::MonthdayRange::Date {
701 start: (start, start_offset),
702 end: (end, end_offset),
703 })
704 }
705 unexpected => {
706 Err(Error::GrammarUnexpectedToken { rule: Rule::monthday_range, unexpected })
707 }
708 }
709 }
710
711 fn build_date_offset(&mut self, pair: Pair<Rule>) -> Result<ds::DateOffset> {
712 debug_assert_eq!(pair.as_rule(), Rule::date_offset);
713 let mut pairs = pair.into_inner();
714
715 let wday_offset = {
716 if let Some(pair_sign) = pairs.next_if_rule(Rule::plus_or_minus) {
717 let sign = self.build_plus_or_minus(pair_sign)?;
718
719 let wday = self.build_wday(pairs.next().ok_or(Error::GrammarLogic {
720 rule: Rule::date_offset,
721 invariant: "a sign is always followed by a wday",
722 })?)?;
723
724 match sign {
725 Sign::Pos => ds::WeekDayOffset::Next(wday),
726 Sign::Neg => ds::WeekDayOffset::Prev(wday),
727 }
728 } else {
729 ds::WeekDayOffset::None
730 }
731 };
732
733 let day_offset = pairs
734 .next()
735 .map(|p| self.build_day_offset(p))
736 .unwrap_or(Ok(0))?;
737 Ok(ds::DateOffset { wday_offset, day_offset })
738 }
739
740 fn build_date_from(&mut self, pair: Pair<Rule>) -> Result<ds::Date> {
741 debug_assert_eq!(pair.as_rule(), Rule::date_from);
742 let mut pairs = pair.into_inner();
743 let year = pairs
744 .next_if_rule(Rule::year)
745 .map(|p| self.build_year(p))
746 .transpose()?;
747
748 let pair_month_or_variable = pairs.next().ok_or(Error::GrammarLogic {
749 rule: Rule::date_from,
750 invariant: "must have a month component",
751 })?;
752
753 if pair_month_or_variable.as_rule() == Rule::variable_date {
754 if !is_lowercase(pair_month_or_variable.as_str()) {
755 self.warn(Warning::ShouldBeLowercase(pair_month_or_variable));
756 }
757
758 return Ok(ds::Date::Easter { year });
759 }
760
761 let month = self.build_month(pair_month_or_variable)?;
762
763 let pair_day = pairs.next().ok_or(Error::GrammarLogic {
764 rule: Rule::date_from,
765 invariant: "must have a daynum or wday component",
766 })?;
767
768 match pair_day.as_rule() {
769 Rule::daynum => Ok(ds::Date::Fixed { year, month, day: self.build_daynum(pair_day)? }),
770 Rule::wday => {
771 let weekday = self.build_wday(pair_day)?;
772
773 let nth_sign = {
774 if pairs.next_if_rule(Rule::nth_minus).is_some() {
775 -1
776 } else {
777 1
778 }
779 };
780
781 let nth: i8 = (pairs.next())
782 .map(|p| self.build_nth(p))
783 .transpose()?
784 .ok_or(Error::GrammarLogic {
785 rule: Rule::date_from,
786 invariant: "a sign is always followed by a number",
787 })?
788 .try_into()
789 .map_err(|_| Error::GrammarLogic {
790 rule: Rule::date_from,
791 invariant: "must be a valid number between 1 and 5",
792 })?;
793
794 Ok(ds::Date::Weekday { year, month, wday: weekday, nth: nth_sign * nth })
795 }
796 unexpected => Err(Error::GrammarUnexpectedToken { rule: Rule::date_from, unexpected }),
797 }
798 }
799
800 fn build_date_to(&mut self, pair: Pair<Rule>, from: ds::Date) -> Result<ds::Date> {
801 debug_assert_eq!(pair.as_rule(), Rule::date_to);
802 let pair = pair.into_inner().next().ok_or(err_empty(Rule::date_to))?;
803
804 match pair.as_rule() {
805 Rule::date_from => self.build_date_from(pair),
806 Rule::daynum => {
807 let daynum = self.build_daynum(pair)?;
808
809 match from {
810 ds::Date::Easter { .. } => {
811 Err(Error::Unsupported("Easter followed by a day number"))
815 }
816 ds::Date::Weekday { year, month, .. } => {
817 Ok(ds::Date::Fixed { year, month, day: daynum })
818 }
819 ds::Date::Fixed { mut year, mut month, day } => {
820 if day > daynum {
821 month = month.next();
822
823 if month == ds::Month::January {
824 if let Some(x) = year.as_mut() {
825 **x += 1
826 }
827 }
828 }
829
830 Ok(ds::Date::Fixed { year, month, day: daynum })
831 }
832 }
833 }
834 unexpected => Err(Error::GrammarUnexpectedToken { rule: Rule::date_to, unexpected }),
835 }
836 }
837
838 fn build_year_selector(&mut self, pair: Pair<Rule>) -> Result<Vec<ds::YearRange>> {
843 debug_assert_eq!(pair.as_rule(), Rule::year_selector);
844 pair.into_inner()
845 .map(|p| self.build_year_range(p))
846 .collect()
847 }
848
849 fn build_year_range(&mut self, pair: Pair<Rule>) -> Result<ds::YearRange> {
850 debug_assert_eq!(pair.as_rule(), Rule::year_range);
851 let mut rules = pair.into_inner();
852 let start = self.build_year(rules.next().ok_or(err_empty(Rule::year_range))?)?;
853
854 let end = rules
855 .next()
856 .map(|pair| match pair.as_rule() {
857 Rule::year => self.build_year(pair),
858 Rule::year_range_plus => Ok(Year(9999)),
859 unexpected => {
860 Err(Error::GrammarUnexpectedToken { rule: Rule::year_range, unexpected })
861 }
862 })
863 .transpose()?
864 .unwrap_or(start);
865
866 let step = rules
867 .next()
868 .map(|p| self.build_positive_number(p))
869 .transpose()?
870 .unwrap_or(1)
871 .unsigned_abs();
872
873 ds::YearRange::new(start..=end, step).ok_or(Error::InvertedYearRange { start, end, step })
874 }
875
876 fn build_plus_or_minus(&mut self, pair: Pair<Rule>) -> Result<Sign> {
881 debug_assert_eq!(pair.as_rule(), Rule::plus_or_minus);
882
883 let pair = pair
884 .into_inner()
885 .next()
886 .ok_or(err_empty(Rule::plus_or_minus))?;
887
888 match pair.as_rule() {
889 Rule::plus => Ok(Sign::Pos),
890 Rule::minus => Ok(Sign::Neg),
891 unexpected => {
892 Err(Error::GrammarUnexpectedToken { rule: Rule::plus_or_minus, unexpected })
893 }
894 }
895 }
896
897 fn build_minute(&mut self, pair: Pair<Rule>) -> Result<Duration> {
898 debug_assert_eq!(pair.as_rule(), Rule::minute);
899
900 pair.as_str()
901 .parse()
902 .map_err(|_| Error::GrammarLogic {
903 rule: Rule::minute,
904 invariant: "must be a valid number",
905 })
906 .map(Duration::minutes)
907 }
908
909 fn build_hour_minutes(&mut self, pair: Pair<Rule>) -> Result<ExtendedTime> {
910 debug_assert_eq!(pair.as_rule(), Rule::hour_minutes);
911 let mut pairs = pair.into_inner();
912
913 let Some(hour_rule) = pairs.next() else {
914 return Ok(ExtendedTime::MIDNIGHT_24);
915 };
916
917 let hour = hour_rule
918 .as_str()
919 .parse()
920 .map_err(|_| Error::GrammarLogic {
921 rule: Rule::hour,
922 invariant: "must be a valid number",
923 })?;
924
925 let minutes = pairs
926 .next()
927 .ok_or(Error::GrammarLogic {
928 rule: Rule::hour_minutes,
929 invariant: "hour must be followed by minutes",
930 })?
931 .as_str()
932 .parse()
933 .map_err(|_| Error::GrammarLogic {
934 rule: Rule::minute,
935 invariant: "must be a valid number",
936 })?;
937
938 ExtendedTime::new(hour, minutes).ok_or(Error::InvalidExtendedTime { hour, minutes })
939 }
940
941 fn build_extended_hour_minutes(&mut self, pair: Pair<Rule>) -> Result<ExtendedTime> {
942 debug_assert_eq!(pair.as_rule(), Rule::extended_hour_minutes);
943 let mut pairs = pair.into_inner();
944
945 let hour = pairs
946 .next()
947 .ok_or(err_empty(Rule::extended_hour_minutes))?
948 .as_str()
949 .parse()
950 .map_err(|_| Error::GrammarLogic {
951 rule: Rule::extended_hour,
952 invariant: "must be a valid number",
953 })?;
954
955 let minutes = pairs
956 .next()
957 .ok_or(Error::GrammarLogic {
958 rule: Rule::extended_hour_minutes,
959 invariant: "hour must be followed by minutes",
960 })?
961 .as_str()
962 .parse()
963 .map_err(|_| Error::GrammarLogic {
964 rule: Rule::minute,
965 invariant: "must be a valid number",
966 })?;
967
968 ExtendedTime::new(hour, minutes).ok_or(Error::InvalidExtendedTime { hour, minutes })
969 }
970
971 fn build_hour_minutes_as_duration(&mut self, pair: Pair<Rule>) -> Result<Duration> {
972 debug_assert_eq!(pair.as_rule(), Rule::hour_minutes);
973 let mut pairs = pair.into_inner();
974
975 let hour = pairs
976 .next()
977 .ok_or(err_empty(Rule::hour_minutes))?
978 .as_str()
979 .parse()
980 .map_err(|_| Error::GrammarLogic {
981 rule: Rule::hour,
982 invariant: "must be a valid number",
983 })?;
984
985 let minutes = pairs
986 .next()
987 .ok_or(Error::GrammarLogic {
988 rule: Rule::hour_minutes,
989 invariant: "hour must be followed by minutes",
990 })?
991 .as_str()
992 .parse()
993 .map_err(|_| Error::GrammarLogic {
994 rule: Rule::minute,
995 invariant: "must be a valid number",
996 })?;
997
998 Ok(Duration::hours(hour) + Duration::minutes(minutes))
999 }
1000
1001 fn build_wday(&mut self, pair: Pair<Rule>) -> Result<ds::Weekday> {
1002 debug_assert_eq!(pair.as_rule(), Rule::wday);
1003
1004 if !is_capitalized(pair.as_str()) {
1005 self.warn(Warning::ShouldBeCapitalized(pair.clone()));
1006 }
1007
1008 let pair = pair.into_inner().next().ok_or(err_empty(Rule::wday))?;
1009
1010 match pair.as_rule() {
1011 Rule::sunday => Ok(ds::Weekday::Sun),
1012 Rule::monday => Ok(ds::Weekday::Mon),
1013 Rule::tuesday => Ok(ds::Weekday::Tue),
1014 Rule::wednesday => Ok(ds::Weekday::Wed),
1015 Rule::thursday => Ok(ds::Weekday::Thu),
1016 Rule::friday => Ok(ds::Weekday::Fri),
1017 Rule::saturday => Ok(ds::Weekday::Sat),
1018 unexpected => Err(Error::GrammarUnexpectedToken { rule: Rule::wday, unexpected }),
1019 }
1020 }
1021
1022 fn build_daynum(&mut self, pair: Pair<Rule>) -> Result<u8> {
1023 debug_assert_eq!(pair.as_rule(), Rule::daynum);
1024
1025 let daynum = pair.as_str().parse().map_err(|_| Error::GrammarLogic {
1026 rule: Rule::daynum,
1027 invariant: "must be a valid number",
1028 })?;
1029
1030 if daynum < 1 {
1031 return Err(Error::GrammarLogic {
1032 rule: Rule::daynum,
1033 invariant: "cannot be less than 1",
1034 });
1035 }
1036
1037 if daynum > 31 {
1038 return Err(Error::GrammarLogic {
1039 rule: Rule::daynum,
1040 invariant: "cannot be greater than 31",
1041 });
1042 }
1043
1044 Ok(daynum)
1045 }
1046
1047 fn build_weeknum(&mut self, pair: Pair<Rule>) -> Result<WeekNum> {
1048 debug_assert_eq!(pair.as_rule(), Rule::weeknum);
1049
1050 pair.as_str()
1051 .parse()
1052 .map_err(|_| Error::GrammarLogic {
1053 rule: Rule::weeknum,
1054 invariant: "must be a valid number",
1055 })
1056 .map(WeekNum)
1057 }
1058
1059 fn build_month(&mut self, pair: Pair<Rule>) -> Result<ds::Month> {
1060 debug_assert_eq!(pair.as_rule(), Rule::month);
1061
1062 if !is_capitalized(pair.as_str()) {
1063 self.warn(Warning::ShouldBeCapitalized(pair.clone()));
1064 }
1065
1066 let pair = pair.into_inner().next().ok_or(err_empty(Rule::month))?;
1067
1068 match pair.as_rule() {
1069 Rule::january => Ok(ds::Month::January),
1070 Rule::february => Ok(ds::Month::February),
1071 Rule::march => Ok(ds::Month::March),
1072 Rule::april => Ok(ds::Month::April),
1073 Rule::may => Ok(ds::Month::May),
1074 Rule::june => Ok(ds::Month::June),
1075 Rule::july => Ok(ds::Month::July),
1076 Rule::august => Ok(ds::Month::August),
1077 Rule::september => Ok(ds::Month::September),
1078 Rule::october => Ok(ds::Month::October),
1079 Rule::november => Ok(ds::Month::November),
1080 Rule::december => Ok(ds::Month::December),
1081 unexpected => Err(Error::GrammarUnexpectedToken { rule: Rule::month, unexpected }),
1082 }
1083 }
1084
1085 fn build_year(&mut self, pair: Pair<Rule>) -> Result<Year> {
1086 debug_assert_eq!(pair.as_rule(), Rule::year);
1087
1088 pair.as_str()
1089 .parse()
1090 .map_err(|_| Error::GrammarLogic {
1091 rule: Rule::year,
1092 invariant: "must be a valid number",
1093 })
1094 .map(Year)
1095 }
1096
1097 fn build_positive_number(&mut self, pair: Pair<Rule>) -> Result<i16> {
1098 debug_assert_eq!(pair.as_rule(), Rule::positive_number);
1099
1100 let val = pair.as_str().parse().map_err(|_| Error::GrammarLogic {
1101 rule: Rule::positive_number,
1102 invariant: "must be a valid 16 bits number",
1103 })?;
1104
1105 debug_assert!(val >= 0);
1106 Ok(val)
1107 }
1108
1109 fn build_comment(&mut self, pair: Pair<Rule>) -> Result<String> {
1110 debug_assert_eq!(pair.as_rule(), Rule::comment);
1111
1112 pair.into_inner()
1113 .next()
1114 .ok_or(err_empty(Rule::comment))
1115 .map(|p| self.build_comment_inner(p))
1116 }
1117
1118 fn build_comment_inner(&mut self, pair: Pair<Rule>) -> String {
1119 debug_assert_eq!(pair.as_rule(), Rule::comment_inner);
1120 pair.as_str().to_string()
1121 }
1122}