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