1use pest::iterators::{Pair, Pairs};
2
3use crate::Rule;
4use crate::ast::*;
5use crate::error::ParseError;
6
7type Result<T> = std::result::Result<T, ParseError>;
8
9pub fn build_file(pairs: Pairs<'_, Rule>) -> Result<File> {
11 let mut statements = Vec::new();
12 let file_pair = pairs.into_iter().next().unwrap();
14 for pair in file_pair.into_inner() {
15 match pair.as_rule() {
16 Rule::timeline_block => {
17 let span = pair_span(&pair);
18 statements.push(Spanned {
19 node: Statement::Timeline(build_timeline(pair)?),
20 span,
21 });
22 }
23 Rule::lane_decl => {
24 let span = pair_span(&pair);
25 statements.push(Spanned {
26 node: Statement::Lane(build_lane(pair)?),
27 span,
28 });
29 }
30 Rule::group_decl => {
31 let span = pair_span(&pair);
32 statements.push(Spanned {
33 node: Statement::Group(build_group(pair)?),
34 span,
35 });
36 }
37 Rule::span_decl => {
38 let span = pair_span(&pair);
39 statements.push(Spanned {
40 node: Statement::Span(build_span(pair)?),
41 span,
42 });
43 }
44 Rule::event_decl => {
45 let span = pair_span(&pair);
46 statements.push(Spanned {
47 node: Statement::Event(build_event(pair)?),
48 span,
49 });
50 }
51 Rule::event_range_decl => {
52 let span = pair_span(&pair);
53 statements.push(Spanned {
54 node: Statement::EventRange(build_event_range(pair)?),
55 span,
56 });
57 }
58 Rule::import_block => {
59 let span = pair_span(&pair);
60 statements.push(Spanned {
61 node: Statement::Import(build_import(pair)?),
62 span,
63 });
64 }
65 Rule::map_block => {
66 let span = pair_span(&pair);
67 statements.push(Spanned {
68 node: Statement::Map(build_map(pair)?),
69 span,
70 });
71 }
72 Rule::template_block => {
73 let span = pair_span(&pair);
74 statements.push(Spanned {
75 node: Statement::Template(build_template(pair)?),
76 span,
77 });
78 }
79 Rule::apply_block => {
80 let span = pair_span(&pair);
81 statements.push(Spanned {
82 node: Statement::Apply(build_apply(pair)?),
83 span,
84 });
85 }
86 Rule::EOI => {}
87 _ => {}
88 }
89 }
90 Ok(File { statements })
91}
92
93fn build_timeline(pair: Pair<'_, Rule>) -> Result<TimelineBlock> {
96 let mut inner = pair.into_inner();
97 let name = extract_string_literal(&inner.next().unwrap());
98
99 let mut block = TimelineBlock {
100 name,
101 title: None,
102 unit: None,
103 range: None,
104 calendar: None,
105 color_map: Vec::new(),
106 };
107
108 for prop in inner {
109 match prop.as_rule() {
110 Rule::title_prop => {
111 block.title = Some(extract_string_literal(&prop.into_inner().next().unwrap()));
112 }
113 Rule::unit_prop => {
114 block.unit = Some(prop.into_inner().next().unwrap().as_str().to_string());
115 }
116 Rule::range_prop => {
117 let mut nums = prop.into_inner();
118 let start = parse_time_value(nums.next().unwrap())?;
119 let end = parse_time_value(nums.next().unwrap())?;
120 block.range = Some(RangeExpr { start, end });
121 }
122 Rule::calendar_prop => {
123 block.calendar = Some(prop.into_inner().next().unwrap().as_str().to_string());
124 }
125 Rule::color_map_block => {
126 for entry in prop.into_inner() {
127 let mut ei = entry.into_inner();
128 let tag = ei.next().unwrap().as_str().to_string();
129 let color = extract_string_literal(&ei.next().unwrap());
130 block.color_map.push((tag, color));
131 }
132 }
133 _ => {}
134 }
135 }
136 Ok(block)
137}
138
139fn build_lane(pair: Pair<'_, Rule>) -> Result<LaneDecl> {
142 let mut inner = pair.into_inner();
143 let label = extract_string_literal(&inner.next().unwrap());
144
145 let mut decl = LaneDecl {
146 label,
147 alias: None,
148 kind: None,
149 order: None,
150 };
151
152 for item in inner {
153 match item.as_rule() {
154 Rule::ident => {
155 decl.alias = Some(item.as_str().to_string());
156 }
157 Rule::kind_prop => {
158 decl.kind = Some(item.into_inner().next().unwrap().as_str().to_string());
159 }
160 Rule::order_prop => {
161 decl.order = Some(parse_int(&item.into_inner().next().unwrap())?);
162 }
163 _ => {}
164 }
165 }
166 Ok(decl)
167}
168
169fn build_group(pair: Pair<'_, Rule>) -> Result<GroupDecl> {
172 let mut inner = pair.into_inner();
173 let label = extract_string_literal(&inner.next().unwrap());
174 let mut lanes = Vec::new();
175 for item in inner {
176 if item.as_rule() == Rule::lane_decl {
177 lanes.push(build_lane(item)?);
178 }
179 }
180 Ok(GroupDecl { label, lanes })
181}
182
183fn build_span(pair: Pair<'_, Rule>) -> Result<SpanDecl> {
186 let mut inner = pair.into_inner();
187 let lane_ref = inner.next().unwrap().as_str().to_string();
188 let start = parse_time_value(inner.next().unwrap())?;
189 let end = parse_time_value(inner.next().unwrap())?;
190 let label = extract_string_literal(&inner.next().unwrap());
191 let props = build_block_options(inner.next().unwrap())?;
192
193 Ok(SpanDecl {
194 lane_ref,
195 start,
196 end,
197 label,
198 props,
199 })
200}
201
202fn build_event(pair: Pair<'_, Rule>) -> Result<EventDecl> {
205 let mut inner = pair.into_inner();
206 let lane_ref = inner.next().unwrap().as_str().to_string();
207 let time = parse_time_value(inner.next().unwrap())?;
208 let label = extract_string_literal(&inner.next().unwrap());
209 let props = build_block_options(inner.next().unwrap())?;
210
211 Ok(EventDecl {
212 lane_ref,
213 time,
214 label,
215 props,
216 })
217}
218
219fn build_event_range(pair: Pair<'_, Rule>) -> Result<EventRangeDecl> {
222 let mut inner = pair.into_inner();
223 let lane_ref = inner.next().unwrap().as_str().to_string();
224 let start = parse_time_value(inner.next().unwrap())?;
225 let end = parse_time_value(inner.next().unwrap())?;
226 let label = extract_string_literal(&inner.next().unwrap());
227 let props = build_block_options(inner.next().unwrap())?;
228
229 Ok(EventRangeDecl {
230 lane_ref,
231 start,
232 end,
233 label,
234 props,
235 })
236}
237
238fn build_block_options(pair: Pair<'_, Rule>) -> Result<ItemProps> {
241 let mut props = ItemProps::default();
242 for item in pair.into_inner() {
243 match item.as_rule() {
244 Rule::tags_option => {
245 props.tags = build_string_list(item.into_inner().next().unwrap());
246 }
247 Rule::source_option => {
248 props.source = Some(build_source_ref(item.into_inner().next().unwrap()));
249 }
250 Rule::id_option => {
251 props.id = Some(extract_string_literal(&item.into_inner().next().unwrap()));
252 }
253 Rule::origin_option => {
254 props.origin = Some(item.into_inner().next().unwrap().as_str().to_string());
255 }
256 _ => {}
257 }
258 }
259 Ok(props)
260}
261
262fn build_import(pair: Pair<'_, Rule>) -> Result<ImportBlock> {
265 let mut inner = pair.into_inner();
266
267 let source_type = inner
268 .next()
269 .unwrap()
270 .into_inner()
271 .next()
272 .unwrap()
273 .as_str()
274 .to_string();
275
276 let mut alias = None;
277 let mut items = Vec::new();
278 let mut policy = None;
279
280 for item in inner {
281 match item.as_rule() {
282 Rule::ident => {
283 alias = Some(item.as_str().to_string());
284 }
285 Rule::entity_import => {
286 let mut ei = item.into_inner();
287 let qid = ei.next().unwrap().as_str().to_string();
288 let entity_alias = ei.next().map(|p| p.as_str().to_string());
289 items.push(ImportItem::Entity {
290 qid,
291 alias: entity_alias,
292 });
293 }
294 Rule::query_import => {
295 let mut qi = item.into_inner();
296 let query = extract_string_literal(&qi.next().unwrap());
297 let query_alias = qi.next().map(|p| p.as_str().to_string());
298 items.push(ImportItem::Query {
299 query,
300 alias: query_alias,
301 });
302 }
303 Rule::policy_import => {
304 let name = item.into_inner().next().unwrap().as_str();
305 policy = Some(match name {
306 "merge_by_source" => ReimportPolicy::MergeBySource,
307 "overwrite_imported" => ReimportPolicy::OverwriteImported,
308 "keep_manual" => ReimportPolicy::KeepManual,
309 other => return Err(ParseError::UnknownPolicy(other.to_string())),
310 });
311 }
312 Rule::field_priority_policy => {
313 let mut config = FieldPriorityConfig::default();
314 for decl in item.into_inner() {
315 let rule = decl.as_rule();
316 let strategy_str = decl.into_inner().next().unwrap().as_str();
317 let strategy = match strategy_str {
318 "manual" => FieldStrategy::Manual,
319 "wikidata" => FieldStrategy::Wikidata,
320 "merge" => FieldStrategy::Merge,
321 _ => unreachable!("grammar enforces valid values"),
322 };
323 match rule {
324 Rule::label_strategy => config.label = strategy,
325 Rule::time_strategy => config.time = strategy,
326 Rule::tags_strategy => config.tags = strategy,
327 _ => {}
328 }
329 }
330 policy = Some(ReimportPolicy::FieldPriority(config));
331 }
332 _ => {}
333 }
334 }
335
336 Ok(ImportBlock {
337 source_type,
338 alias,
339 items,
340 policy,
341 })
342}
343
344fn build_template(pair: Pair<'_, Rule>) -> Result<TemplateBlock> {
347 let mut inner = pair.into_inner();
348 let name = extract_string_literal(&inner.next().unwrap());
349
350 let mut alias = None;
352 let mut peeked = inner.next().unwrap();
353 if peeked.as_rule() == Rule::ident {
354 alias = Some(peeked.as_str().to_string());
355 peeked = inner.next().unwrap();
356 }
357
358 let target_type = parse_target_type(peeked.into_inner().next().unwrap())?;
360
361 let mut props = Vec::new();
362 for item in inner {
363 build_map_prop(item, &mut props)?;
364 }
365
366 Ok(TemplateBlock {
367 name,
368 alias,
369 target_type,
370 props,
371 })
372}
373
374fn build_apply(pair: Pair<'_, Rule>) -> Result<ApplyBlock> {
377 let mut inner = pair.into_inner();
378 let template_alias = inner.next().unwrap().as_str().to_string();
379 let import_alias = inner.next().unwrap().as_str().to_string();
380
381 let mut overrides = Vec::new();
382 for item in inner {
383 build_map_prop(item, &mut overrides)?;
384 }
385
386 Ok(ApplyBlock {
387 template_alias,
388 import_alias,
389 overrides,
390 })
391}
392
393fn build_map(pair: Pair<'_, Rule>) -> Result<MapBlock> {
396 let mut inner = pair.into_inner();
397 let source_ref = inner.next().unwrap().as_str().to_string();
398 let target_type = parse_target_type(inner.next().unwrap().into_inner().next().unwrap())?;
399
400 let mut props = Vec::new();
401 for item in inner {
402 build_map_prop(item, &mut props)?;
403 }
404
405 Ok(MapBlock {
406 source_ref,
407 target_type,
408 props,
409 })
410}
411
412fn build_map_prop(item: Pair<'_, Rule>, props: &mut Vec<MapProp>) -> Result<()> {
413 match item.as_rule() {
414 Rule::map_lane => {
415 props.push(MapProp::Lane(
416 item.into_inner().next().unwrap().as_str().to_string(),
417 ));
418 }
419 Rule::map_start => {
420 props.push(MapProp::Start(build_map_expr(
421 item.into_inner().next().unwrap(),
422 )?));
423 }
424 Rule::map_end => {
425 props.push(MapProp::End(build_map_expr(
426 item.into_inner().next().unwrap(),
427 )?));
428 }
429 Rule::map_time => {
430 props.push(MapProp::Time(build_map_expr(
431 item.into_inner().next().unwrap(),
432 )?));
433 }
434 Rule::map_label => {
435 props.push(MapProp::Label(build_label_expr(
436 item.into_inner().next().unwrap(),
437 )));
438 }
439 Rule::map_tags => {
440 props.push(MapProp::Tags(build_string_list(
441 item.into_inner().next().unwrap(),
442 )));
443 }
444 Rule::map_filter => {
445 props.push(MapProp::Filter(build_filter_expr(
446 item.into_inner().next().unwrap(),
447 )?));
448 }
449 Rule::map_expand => {
450 let claim_pair = item.into_inner().next().unwrap();
452 let property = claim_pair.into_inner().next().unwrap().as_str().to_string();
453 props.push(MapProp::Expand(ClaimCall { property }));
454 }
455 _ => {}
456 }
457 Ok(())
458}
459
460fn build_filter_expr(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
463 let inner = pair.into_inner().next().unwrap();
465 build_filter_or(inner)
466}
467
468fn build_filter_or(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
469 let mut iter = pair.into_inner();
470 let first = build_filter_and(iter.next().unwrap())?;
471 iter.try_fold(first, |acc, p| {
472 let rhs = build_filter_and(p)?;
473 Ok(FilterExpr::Or(Box::new(acc), Box::new(rhs)))
474 })
475}
476
477fn build_filter_and(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
478 let mut iter = pair.into_inner();
479 let first = build_filter_not(iter.next().unwrap())?;
480 iter.try_fold(first, |acc, p| {
481 let rhs = build_filter_not(p)?;
482 Ok(FilterExpr::And(Box::new(acc), Box::new(rhs)))
483 })
484}
485
486fn build_filter_not(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
487 let s = pair.as_str();
488 let inner = pair.into_inner().next().unwrap();
489 let atom = build_filter_atom(inner)?;
490 if s.trim_start().starts_with('!') {
491 Ok(FilterExpr::Not(Box::new(atom)))
492 } else {
493 Ok(atom)
494 }
495}
496
497fn build_filter_atom(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
498 let location = pair_location_str(&pair);
499 match pair.as_rule() {
500 Rule::filter_paren => build_filter_expr(pair.into_inner().next().unwrap()),
501 Rule::filter_compare => build_filter_compare(pair),
502 Rule::filter_string_op => build_filter_string_op(pair),
503 other => Err(ParseError::UnexpectedRule {
504 rule: format!("filter_atom: {other:?}"),
505 location,
506 }),
507 }
508}
509
510fn build_filter_string_op(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
511 let mut iter = pair.into_inner();
512 let label_ref_pair = iter.next().unwrap();
513 let lang = label_ref_pair
514 .as_str()
515 .strip_prefix("label@")
516 .unwrap_or(label_ref_pair.as_str())
517 .to_string();
518 let op_pair = iter.next().unwrap();
519 let location = pair_location_str(&op_pair);
520 let op = match op_pair.as_str() {
521 "contains" => StringMatchOp::Contains,
522 "startswith" => StringMatchOp::StartsWith,
523 other => {
524 return Err(ParseError::UnexpectedRule {
525 rule: format!("string_match_op: {other}"),
526 location,
527 });
528 }
529 };
530 let rhs_pair = iter.next().unwrap();
531 let rhs = extract_string_literal(&rhs_pair);
532 Ok(FilterExpr::StringMatch {
533 lhs: LabelRef { lang },
534 op,
535 rhs,
536 })
537}
538
539fn build_filter_compare(pair: Pair<'_, Rule>) -> Result<FilterExpr> {
540 let mut iter = pair.into_inner();
541 let lhs = build_filter_operand(iter.next().unwrap())?;
542 let op_pair = iter.next().unwrap();
543 let op_location = pair_location_str(&op_pair);
544 let op = parse_compare_op(op_pair.as_str(), op_location)?;
545 let rhs = build_filter_operand(iter.next().unwrap())?;
546 Ok(FilterExpr::Compare { lhs, op, rhs })
547}
548
549fn build_filter_operand(pair: Pair<'_, Rule>) -> Result<FilterOperand> {
550 let location = pair_location_str(&pair);
551 match pair.as_rule() {
552 Rule::null_literal => Ok(FilterOperand::Null),
553 Rule::claim_expr => Ok(FilterOperand::Claim(build_claim_expr(pair)?)),
554 Rule::integer => {
555 let raw = pair.as_str().to_string();
556 let n: i64 = raw.parse().map_err(|_| ParseError::InvalidInt {
557 value: raw,
558 location,
559 })?;
560 Ok(FilterOperand::Int(n))
561 }
562 other => Err(ParseError::UnexpectedRule {
563 rule: format!("filter_operand: {other:?}"),
564 location,
565 }),
566 }
567}
568
569fn parse_compare_op(s: &str, location: String) -> Result<CompareOp> {
570 match s {
571 "==" => Ok(CompareOp::Eq),
572 "!=" => Ok(CompareOp::NotEq),
573 "<" => Ok(CompareOp::Lt),
574 "<=" => Ok(CompareOp::Le),
575 ">" => Ok(CompareOp::Gt),
576 ">=" => Ok(CompareOp::Ge),
577 other => Err(ParseError::UnexpectedRule {
578 rule: format!("compare_op: {other}"),
579 location,
580 }),
581 }
582}
583
584fn build_map_expr(pair: Pair<'_, Rule>) -> Result<MapExpr> {
585 let mut fallbacks = Vec::new();
586 for child in pair.into_inner() {
587 let fb = match child.as_rule() {
588 Rule::claim_expr => MapFallback::Claim(build_claim_expr(child)?),
589 Rule::integer => {
590 let span = child.as_span();
591 let n: i64 = child.as_str().parse().map_err(|_| ParseError::InvalidInt {
592 value: child.as_str().to_string(),
593 location: format!("{}:{}", span.start(), span.end()),
594 })?;
595 MapFallback::Literal(n)
596 }
597 _ => {
598 let span = child.as_span();
599 return Err(ParseError::UnexpectedRule {
600 rule: format!("{:?}", child.as_rule()),
601 location: format!("{}:{}", span.start(), span.end()),
602 });
603 }
604 };
605 fallbacks.push(fb);
606 }
607 Ok(MapExpr { fallbacks })
608}
609
610fn build_claim_expr(pair: Pair<'_, Rule>) -> Result<ClaimExpr> {
611 let mut inner = pair.into_inner();
612 let claim_pair = inner.next().unwrap();
613 let property = claim_pair.into_inner().next().unwrap().as_str().to_string();
614
615 let mut qualifier = None;
616 let mut accessor = None;
617 let mut offset = None;
618 for child in inner {
619 match child.as_rule() {
620 Rule::claim_qualifier => {
621 qualifier = Some(child.into_inner().next().unwrap().as_str().to_string());
623 }
624 Rule::claim_accessor => {
625 accessor = Some(child.into_inner().next().unwrap().as_str().to_string());
627 }
628 Rule::claim_offset => {
629 offset = child.as_str().parse::<i32>().ok();
631 }
632 _ => {}
633 }
634 }
635
636 Ok(ClaimExpr {
637 claim: ClaimCall { property },
638 qualifier,
639 accessor,
640 offset,
641 })
642}
643
644fn build_label_expr(pair: Pair<'_, Rule>) -> LabelExpr {
645 let fallbacks = pair
646 .into_inner()
647 .map(|lr| {
648 let s = lr.as_str();
650 let lang = s.strip_prefix("label@").unwrap_or(s).to_string();
651 LabelRef { lang }
652 })
653 .collect();
654 LabelExpr { fallbacks }
655}
656
657fn parse_target_type(pair: Pair<'_, Rule>) -> Result<MapTargetType> {
658 match pair.as_str() {
659 "span" => Ok(MapTargetType::Span),
660 "event" => Ok(MapTargetType::Event),
661 "event_range" => Ok(MapTargetType::EventRange),
662 other => Err(ParseError::UnknownTargetType(other.to_string())),
663 }
664}
665
666fn extract_string_literal(pair: &Pair<'_, Rule>) -> String {
669 let inner = pair.clone().into_inner().next().unwrap();
670 let s = inner.as_str();
671 let mut out = String::with_capacity(s.len());
672 let mut chars = s.chars();
673 while let Some(c) = chars.next() {
674 if c == '\\' {
675 match chars.next() {
676 Some('"') => out.push('"'),
677 Some('\\') => out.push('\\'),
678 Some(c) => {
679 out.push('\\');
680 out.push(c);
681 }
682 None => out.push('\\'),
683 }
684 } else {
685 out.push(c);
686 }
687 }
688 out
689}
690
691fn build_source_ref(pair: Pair<'_, Rule>) -> SourceRef {
692 let s = pair.as_str();
693 let (prefix, qid) = s.split_once(':').unwrap();
694 SourceRef {
695 prefix: prefix.to_string(),
696 qid: qid.to_string(),
697 }
698}
699
700fn build_string_list(pair: Pair<'_, Rule>) -> Vec<String> {
701 pair.into_inner()
702 .map(|p| extract_string_literal(&p))
703 .collect()
704}
705
706fn parse_int(pair: &Pair<'_, Rule>) -> Result<i64> {
707 pair.as_str()
708 .parse::<i64>()
709 .map_err(|_| ParseError::InvalidInt {
710 value: pair.as_str().to_string(),
711 location: format!("{}:{}", pair.as_span().start(), pair.as_span().end()),
712 })
713}
714
715pub(crate) fn parse_time_value(pair: Pair<'_, Rule>) -> Result<TimeValue> {
721 let location = pair_location_str(&pair);
722 let inner = pair
723 .into_inner()
724 .next()
725 .ok_or_else(|| ParseError::UnexpectedRule {
726 rule: "time_value: empty inner".to_string(),
727 location: location.clone(),
728 })?;
729 match inner.as_rule() {
730 Rule::year_lit => {
731 let year = inner
732 .as_str()
733 .parse::<i64>()
734 .map_err(|_| ParseError::InvalidInt {
735 value: inner.as_str().to_string(),
736 location: location.clone(),
737 })?;
738 Ok(TimeValue::Year(year))
739 }
740 Rule::year_month_lit => {
741 let s = inner.as_str();
742 let (y, m) = s
743 .split_once('-')
744 .ok_or_else(|| ParseError::UnexpectedRule {
745 rule: format!("year_month_lit malformed: {s}"),
746 location: location.clone(),
747 })?;
748 let year = y.parse::<i64>().map_err(|_| ParseError::InvalidInt {
749 value: y.to_string(),
750 location: location.clone(),
751 })?;
752 let month: u32 = m.parse().map_err(|_| ParseError::InvalidInt {
753 value: m.to_string(),
754 location: location.clone(),
755 })?;
756 check_month(month, &location)?;
757 Ok(TimeValue::YearMonth(year, month as u8))
758 }
759 Rule::date_lit => {
760 let s = inner.as_str();
761 let mut parts = s.splitn(3, '-');
762 let y = parts.next().ok_or_else(|| ParseError::UnexpectedRule {
763 rule: format!("date_lit malformed: {s}"),
764 location: location.clone(),
765 })?;
766 let m = parts.next().ok_or_else(|| ParseError::UnexpectedRule {
767 rule: format!("date_lit malformed: {s}"),
768 location: location.clone(),
769 })?;
770 let d = parts.next().ok_or_else(|| ParseError::UnexpectedRule {
771 rule: format!("date_lit malformed: {s}"),
772 location: location.clone(),
773 })?;
774 let year = y.parse::<i64>().map_err(|_| ParseError::InvalidInt {
775 value: y.to_string(),
776 location: location.clone(),
777 })?;
778 let month: u32 = m.parse().map_err(|_| ParseError::InvalidInt {
779 value: m.to_string(),
780 location: location.clone(),
781 })?;
782 let day: u32 = d.parse().map_err(|_| ParseError::InvalidInt {
783 value: d.to_string(),
784 location: location.clone(),
785 })?;
786 check_month(month, &location)?;
787 check_day(day, &location)?;
788 Ok(TimeValue::Date(year, month as u8, day as u8))
789 }
790 other => Err(ParseError::UnexpectedRule {
791 rule: format!("time_value: {other:?}"),
792 location,
793 }),
794 }
795}
796
797fn check_month(month: u32, location: &str) -> Result<()> {
798 if (1..=12).contains(&month) {
799 Ok(())
800 } else {
801 Err(ParseError::InvalidMonth {
802 value: month,
803 location: location.to_string(),
804 })
805 }
806}
807
808fn check_day(day: u32, location: &str) -> Result<()> {
809 if (1..=31).contains(&day) {
810 Ok(())
811 } else {
812 Err(ParseError::InvalidDay {
813 value: day,
814 location: location.to_string(),
815 })
816 }
817}
818
819fn pair_span(pair: &Pair<'_, Rule>) -> Span {
820 let s = pair.as_span();
821 Span {
822 start: s.start(),
823 end: s.end(),
824 }
825}
826
827fn pair_location_str(pair: &Pair<'_, Rule>) -> String {
828 let s = pair.as_span();
829 format!("{}:{}", s.start(), s.end())
830}