From 8c83c0b0b95d4031285e0b8313f1e67db716d444 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 19 Nov 2021 19:14:14 +0100 Subject: [PATCH] Parse deltas --- src/commands.rs | 9 +-- src/parse.rs | 145 +++++++++++++++++++++++++++++++++++++-- src/parse/todayfile.pest | 22 ++++-- 3 files changed, 159 insertions(+), 17 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 42c581b..b331f02 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,14 +34,7 @@ pub enum DeltaStep { } #[derive(Debug)] -pub struct Delta { - pub years: i32, - pub months: i32, - pub weeks: i32, - pub days: i32, - pub hours: i32, - pub minutes: i32, -} +pub struct Delta(pub Vec); #[derive(Debug)] pub struct DateEndSpec { diff --git a/src/parse.rs b/src/parse.rs index 8233f56..24f5b25 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -7,7 +7,8 @@ use pest::iterators::Pair; use pest::{Parser, Span}; use crate::commands::{ - Birthday, BirthdaySpec, Command, DateSpec, Done, FormulaSpec, Note, Spec, Task, WeekdaySpec, + Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, FormulaSpec, Note, Spec, + Task, Weekday, WeekdaySpec, }; #[derive(pest_derive::Parser)] @@ -16,13 +17,17 @@ struct TodayfileParser; type Result = result::Result>; -fn fail, T>(span: Span, message: S) -> Result { - Err(Error::new_from_span( +fn error>(span: Span, message: S) -> Error { + Error::new_from_span( ErrorVariant::CustomError { message: message.into(), }, span, - )) + ) +} + +fn fail, T>(span: Span, message: S) -> Result { + Err(error(span, message)) } fn parse_title(p: Pair) -> Result { @@ -65,6 +70,138 @@ fn parse_time(p: Pair) -> Result { } } +#[derive(Clone, Copy)] +pub enum Sign { + Positive, + Negative, +} +pub struct Amount { + sign: Option, + value: i32, +} + +impl Amount { + pub fn with_prev_sign(mut self, prev: Option) -> Self { + if self.sign.is_none() { + self.sign = prev; + } + self + } + + pub fn value(&self) -> Option { + match self.sign { + None => None, + Some(Sign::Positive) => Some(self.value), + Some(Sign::Negative) => Some(-self.value), + } + } +} + +fn parse_amount(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::amount); + + let mut sign = None; + let mut value = 0; + for p in p.into_inner() { + match p.as_rule() { + Rule::amount_sign => { + sign = Some(match p.as_str() { + "+" => Sign::Positive, + "-" => Sign::Negative, + _ => unreachable!(), + }) + } + Rule::amount_value => value = p.as_str().parse().unwrap(), + _ => unreachable!(), + } + } + + Ok(Amount { sign, value }) +} + +fn parse_weekday(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::weekday); + Ok(match p.as_str() { + "mon" => Weekday::Monday, + "tue" => Weekday::Tuesday, + "wed" => Weekday::Wednesday, + "thu" => Weekday::Thursday, + "fri" => Weekday::Friday, + "sat" => Weekday::Saturday, + "sun" => Weekday::Sunday, + _ => unreachable!(), + }) +} + +fn parse_delta_weekdays(p: Pair, sign: &mut Option) -> Result { + assert_eq!(p.as_rule(), Rule::delta_weekdays); + let span = p.as_span(); + let mut p = p.into_inner(); + + let amount = parse_amount(p.next().unwrap())?; + let weekday = parse_weekday(p.next().unwrap())?; + + assert_eq!(p.next(), None); + + let value = amount + .value() + .ok_or_else(|| error(span, "ambiguous sign"))?; + *sign = amount.sign; + + Ok(DeltaStep::Weekday(value, weekday)) +} + +fn parse_delta_step( + p: Pair, + sign: &mut Option, + f: impl FnOnce(i32) -> DeltaStep, +) -> Result { + assert!(matches!( + p.as_rule(), + Rule::delta_years + | Rule::delta_months + | Rule::delta_months_reverse + | Rule::delta_days + | Rule::delta_weeks + | Rule::delta_hours + | Rule::delta_minutes + )); + + let span = p.as_span(); + let amount = parse_amount(p.into_inner().next().unwrap())?.with_prev_sign(*sign); + let value = amount + .value() + .ok_or_else(|| error(span, "ambiguous sign"))?; + + *sign = amount.sign; + Ok(f(value)) +} + +fn parse_delta(p: Pair) -> Result { + assert_eq!(p.as_rule(), Rule::delta); + + let mut sign = None; + let mut steps = vec![]; + + for p in p.into_inner() { + match p.as_rule() { + Rule::delta_weekdays => steps.push(parse_delta_weekdays(p, &mut sign)?), + Rule::delta_minutes => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?), + Rule::delta_years => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Year)?), + Rule::delta_months => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?), + Rule::delta_months_reverse => { + steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?) + } + Rule::delta_days => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?), + Rule::delta_weeks => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?), + Rule::delta_hours => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?), + _ => unreachable!(), + } + } + + Ok(Delta(steps)) +} + fn parse_date_fixed(p: Pair) -> Result { assert_eq!(p.as_rule(), Rule::date_fixed); dbg!(p); diff --git a/src/parse/todayfile.pest b/src/parse/todayfile.pest index b5dc5d9..3075971 100644 --- a/src/parse/todayfile.pest +++ b/src/parse/todayfile.pest @@ -18,19 +18,31 @@ time = ${ hour ~ ":" ~ minute } weekday = { "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun" } amount_sign = { ("+" | "-")? } -amount_value = { ASCII_DIGIT* } +amount_value = { ASCII_DIGIT{0,9} } // Fits in an i32 amount = { amount_sign ~ amount_value } delta_weekdays = { amount ~ weekday } delta_minutes = { amount ~ "min" } -delta_years = { amount ~ ("y" | "Y") } -delta_months = { amount ~ ("m" | "M") } +delta_years = { amount ~ "y" } +delta_months = { amount ~ "m" } +delta_months_reverse = { amount ~ "M" } delta_days = { amount ~ "d" } delta_weeks = { amount ~ "w" } delta_hours = { amount ~ "h" } -delta = { (delta_weekdays | delta_minutes | delta_years | delta_months | delta_days | delta_weeks | delta_hours)+ } +delta = { + ( + delta_weekdays + | delta_minutes + | delta_years + | delta_months + | delta_months_reverse + | delta_days + | delta_weeks + | delta_hours + )+ +} paren_expr = { "(" ~ expr ~ ")" } -number = @{ ASCII_DIGIT+ } +number = @{ ASCII_DIGIT{1,9} } // Fits in an i32 boolean = { "true" | "false" } variable = { "j"