From 7d53854a98ecc6c4a11b3f5c88a51f588e1939cb Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 16 Sep 2023 22:01:54 -0400 Subject: [PATCH] Add date parsing --- sgf/src/date.rs | 2 + sgf/src/parser.rs | 131 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/sgf/src/date.rs b/sgf/src/date.rs index 8de0e1b..a347fe1 100644 --- a/sgf/src/date.rs +++ b/sgf/src/date.rs @@ -104,6 +104,7 @@ pub fn parse_date_field(s: &str) -> Result, Error> { Ok(dates) } +/* #[cfg(test)] mod test { use super::*; @@ -155,3 +156,4 @@ mod test { ); } } +*/ diff --git a/sgf/src/parser.rs b/sgf/src/parser.rs index 3fc59f9..73fdb54 100644 --- a/sgf/src/parser.rs +++ b/sgf/src/parser.rs @@ -1,4 +1,5 @@ use crate::{Color, Date, Error, GameResult}; +use chrono::{Datelike, NaiveDate}; use nom::{ branch::alt, bytes::complete::{escaped_transform, tag, take_until1}, @@ -815,6 +816,81 @@ fn parse_text<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, String, E> { } } +enum DateSegment { + One(i32), + Two(i32, i32), + Three(i32, i32, i32), +} + +fn parse_date_field<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, Vec, E> { + |input| { + let (input, first_date) = parse_date().parse(input)?; + let (input, mut more_dates) = many0(parse_date_segment())(input)?; + + let mut date_segments = vec![first_date]; + date_segments.append(&mut more_dates); + + let mut dates = vec![]; + let mut most_recent = None; + + for date_segment in date_segments { + let new_date = match date_segment { + DateSegment::One(v) => match most_recent { + Some(Date::Year(_)) => Date::Year(v), + Some(Date::YearMonth(y, _)) => Date::YearMonth(y, v as u32), + Some(Date::Date(d)) => Date::Date(d.clone().with_day(v as u32).unwrap()), + None => Date::Year(v), + }, + DateSegment::Two(y, m) => Date::YearMonth(y, m as u32), + DateSegment::Three(y, m, d) => { + Date::Date(chrono::NaiveDate::from_ymd_opt(y, m as u32, d as u32).unwrap()) + } + }; + dates.push(new_date.clone()); + most_recent = Some(new_date); + } + + Ok((input, dates)) + } +} + +fn parse_date_segment<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, DateSegment, E> { + |input| { + let (input, _) = tag(",")(input)?; + let (input, element) = parse_date().parse(input)?; + + Ok((input, element)) + } +} + +fn parse_date<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, DateSegment, E> { + alt((parse_three(), parse_two(), parse_one())) +} + +fn parse_one<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, DateSegment, E> { + parse_number().map(|year| DateSegment::One(year)) +} + +fn parse_two<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, DateSegment, E> { + |input| { + let (input, year) = parse_number().parse(input)?; + let (input, _) = tag("-")(input)?; + let (input, month) = parse_number().parse(input)?; + Ok((input, DateSegment::Two(year, month))) + } +} + +fn parse_three<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, DateSegment, E> { + |input| { + let (input, year) = parse_number().parse(input)?; + let (input, _) = tag("-")(input)?; + let (input, month) = parse_number().parse(input)?; + let (input, _) = tag("-")(input)?; + let (input, day) = parse_number().parse(input)?; + Ok((input, DateSegment::Three(year, month, day))) + } +} + #[cfg(test)] mod test { use super::*; @@ -1059,3 +1135,58 @@ k<. Hard line breaks are all other linebreaks.", parse_tree::>(&data).unwrap(); } } + +#[cfg(test)] +mod date_test { + use super::*; + use cool_asserts::assert_matches; + + #[test] + fn it_parses_a_year() { + assert_matches!(parse_date_field::>().parse("1996"), Ok((_, date)) => { + assert_eq!(date, vec![Date::Year(1996)]); + }); + } + + #[test] + fn it_parses_a_month() { + assert_matches!( + parse_date_field::>().parse("1996-12"), + Ok((_, date)) => assert_eq!(date, vec![Date::YearMonth(1996, 12)]) + ); + } + + #[test] + fn it_parses_a_date() { + assert_matches!( + parse_date_field::>().parse("1996-12-27"), + Ok((_, date)) => assert_eq!(date, vec![Date::Date( + NaiveDate::from_ymd_opt(1996, 12, 27).unwrap() + )]) + ); + } + + #[test] + fn it_parses_date_continuation() { + assert_matches!( + parse_date_field::>().parse("1996-12-27,28"), + Ok((_, date)) => assert_eq!(date, vec![ + Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()), + Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap()) + ]) + ); + } + + #[test] + fn it_parses_date_crossing_year_boundary() { + assert_matches!( + parse_date_field::>().parse("1996-12-27,28,1997-01-03,04"), + Ok((_, date)) => assert_eq!(date, vec![ + Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()), + Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap()), + Date::Date(NaiveDate::from_ymd_opt(1997, 1, 3).unwrap()), + Date::Date(NaiveDate::from_ymd_opt(1997, 1, 4).unwrap()), + ]) + ); + } +}