use nom::{ bytes::complete::{take_till, take_while1, take_while_m_n}, character::complete::{space0, space1}, combinator::{map, opt}, sequence::tuple, IResult, }; use super::{ combinator::{ colon_token, l_angle_token, l_bracket_token, l_parens_token, minus2_token, minus_token, node, percent2_token, r_angle_token, r_bracket_token, r_parens_token, GreenElement, NodeBuilder, }, input::Input, SyntaxKind::*, }; #[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn timestamp_diary_node(input: Input) -> IResult { let mut parser = map( tuple(( l_angle_token, percent2_token, l_parens_token, take_till(|c| c == ')' || c == '>' || c == '\n'), r_parens_token, r_angle_token, )), |(l_angle, percent2, l_paren, value, r_paren, r_angle)| { node( TIMESTAMP_DIARY, [ l_angle, percent2, l_paren, value.text_token(), r_paren, r_angle, ], ) }, ); crate::lossless_parser!(parser, input) } fn date(i: Input) -> IResult { map( tuple(( take_while_m_n(4, 4, |c: char| c.is_ascii_digit()), minus_token, take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), minus_token, take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), )), |(year, minus, month, minus_, day)| { [ year.token(TIMESTAMP_YEAR), minus, month.token(TIMESTAMP_MONTH), minus_, day.token(TIMESTAMP_DAY), ] }, )(i) } fn dayname(i: Input) -> IResult { map( take_while1(|c: char| { !c.is_ascii_whitespace() && !c.is_ascii_digit() && c != '+' && c != '-' && c != ']' && c != '>' }), |i: Input| i.token(TIMESTAMP_DAYNAME), )(i) } fn time(i: Input) -> IResult { map( tuple(( take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), colon_token, take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), )), |(hour, colon, minute)| { [ hour.token(TIMESTAMP_HOUR), colon, minute.token(TIMESTAMP_MINUTE), ] }, )(i) } fn timestamp_node_base( input: Input, l_parser: impl Fn(Input) -> IResult, r_parser: impl Fn(Input) -> IResult, ) -> IResult, ()> { let (input, l_angle) = l_parser(input)?; let (input, start_date) = date(input)?; let (input, start_dayname) = opt(tuple((space1, dayname)))(input)?; let (input, start_time) = opt(tuple((space1, time)))(input)?; let mut b = NodeBuilder::new(); b.push(l_angle); b.children.extend(start_date); if let Some((ws, dayname)) = start_dayname { b.push(ws.ws_token()); b.push(dayname); } if input.as_str().starts_with('-') { let (ws, start_time) = match start_time { Some(start_time) => start_time, None => return Err(nom::Err::Error(())), }; let (input, minus) = minus_token(input)?; let (input, end_time) = time(input)?; let (input, space) = space0(input)?; // TODO: delay-or-repeater let (input, r_angle) = r_parser(input)?; b.ws(ws); b.children.extend(start_time); b.push(minus); b.children.extend(end_time); b.ws(space); b.push(r_angle); return Ok((input, b.children)); } let (input, space) = space0(input)?; let (input, r_angle) = r_parser(input)?; if let Some((ws, start_time)) = start_time { b.ws(ws); b.children.extend(start_time); } b.ws(space); b.push(r_angle); if input.as_str().starts_with("--") { let (input, minus2) = minus2_token(input)?; let (input, l_angle) = l_parser(input)?; let (input, end_date) = date(input)?; let (input, end_dayname) = opt(tuple((space1, dayname)))(input)?; let (input, end_time) = opt(tuple((space1, time)))(input)?; let (input, space_) = space0(input)?; // TODO: delay-or-repeater let (input, r_angle) = r_parser(input)?; b.children.extend([minus2, l_angle]); b.children.extend(end_date); if let Some((ws, dayname)) = end_dayname { b.push(ws.ws_token()); b.push(dayname); } if let Some((ws, end_time)) = end_time { b.ws(ws); b.children.extend(end_time); } b.ws(space_); b.push(r_angle); Ok((input, b.children)) } else { Ok((input, b.children)) } } #[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn timestamp_active_node(input: Input) -> IResult { fn parser(input: Input) -> IResult { let (input, children) = timestamp_node_base(input, l_angle_token, r_angle_token)?; Ok((input, node(TIMESTAMP_ACTIVE, children))) } crate::lossless_parser!(parser, input) } #[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn timestamp_inactive_node(input: Input) -> IResult { fn parser(input: Input) -> IResult { let (input, children) = timestamp_node_base(input, l_bracket_token, r_bracket_token)?; Ok((input, node(TIMESTAMP_INACTIVE, children))) } crate::lossless_parser!(parser, input) } #[test] fn parse() { use crate::{ast::Timestamp, tests::to_ast}; let to_timestamp = to_ast::(timestamp_inactive_node); to_timestamp("[2003-09-16]"); to_timestamp("[2003-09-16 09:09]"); to_timestamp("[2003-09-16 Tue]"); to_timestamp("[2003-09-16 Tue 09:09]"); to_timestamp("[2003-09-16]--[2003-09-16]"); to_timestamp("[2003-09-16 09:09]--[2003-09-16 09:09]"); to_timestamp("[2003-09-16]--[2003-09-16 09:09]"); to_timestamp("[2003-09-16 Tue]--[2003-09-16 Tue]"); to_timestamp("[2003-09-16 Tue 09:09]--[2003-09-16 Tue 09:09]"); to_timestamp("[2003-09-16 Tue 09:09-09:09]"); to_timestamp("[2003-09-16 09:09-09:09]"); let ts = to_timestamp("[2003-09-16 Tue]"); assert!(!ts.is_range()); insta::assert_debug_snapshot!( ts.syntax, @r###" TIMESTAMP_INACTIVE@0..16 L_BRACKET@0..1 "[" TIMESTAMP_YEAR@1..5 "2003" MINUS@5..6 "-" TIMESTAMP_MONTH@6..8 "09" MINUS@8..9 "-" TIMESTAMP_DAY@9..11 "16" WHITESPACE@11..12 " " TIMESTAMP_DAYNAME@12..15 "Tue" R_BRACKET@15..16 "]" "### ); let ts = to_timestamp("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"); assert!(ts.is_range()); insta::assert_debug_snapshot!( ts.syntax, @r###" TIMESTAMP_INACTIVE@0..46 L_BRACKET@0..1 "[" TIMESTAMP_YEAR@1..5 "2003" MINUS@5..6 "-" TIMESTAMP_MONTH@6..8 "09" MINUS@8..9 "-" TIMESTAMP_DAY@9..11 "16" WHITESPACE@11..12 " " TIMESTAMP_DAYNAME@12..15 "Tue" WHITESPACE@15..16 " " TIMESTAMP_HOUR@16..18 "09" COLON@18..19 ":" TIMESTAMP_MINUTE@19..21 "39" R_BRACKET@21..22 "]" MINUS2@22..24 "--" L_BRACKET@24..25 "[" TIMESTAMP_YEAR@25..29 "2003" MINUS@29..30 "-" TIMESTAMP_MONTH@30..32 "09" MINUS@32..33 "-" TIMESTAMP_DAY@33..35 "16" WHITESPACE@35..36 " " TIMESTAMP_DAYNAME@36..39 "Tue" WHITESPACE@39..40 " " TIMESTAMP_HOUR@40..42 "10" COLON@42..43 ":" TIMESTAMP_MINUTE@43..45 "39" R_BRACKET@45..46 "]" "### ); let ts = to_timestamp("[2003-09-16 Tue 09:39-10:39]"); assert!(ts.is_range()); insta::assert_debug_snapshot!( ts.syntax, @r###" TIMESTAMP_INACTIVE@0..28 L_BRACKET@0..1 "[" TIMESTAMP_YEAR@1..5 "2003" MINUS@5..6 "-" TIMESTAMP_MONTH@6..8 "09" MINUS@8..9 "-" TIMESTAMP_DAY@9..11 "16" WHITESPACE@11..12 " " TIMESTAMP_DAYNAME@12..15 "Tue" WHITESPACE@15..16 " " TIMESTAMP_HOUR@16..18 "09" COLON@18..19 ":" TIMESTAMP_MINUTE@19..21 "39" MINUS@21..22 "-" TIMESTAMP_HOUR@22..24 "10" COLON@24..25 ":" TIMESTAMP_MINUTE@25..27 "39" R_BRACKET@27..28 "]" "### ); }