From 66da16f4e309281d4aad48954c53551b374bf625 Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 21 Dec 2021 00:14:14 +0100 Subject: [PATCH] Evaluate REMINDs --- src/eval/command.rs | 75 ++++++++++++++++++++++++++++-------- src/eval/command/birthday.rs | 15 +++++--- src/eval/command/date.rs | 8 ++-- src/eval/command/formula.rs | 4 +- src/eval/entry.rs | 19 ++++++++- src/eval/error.rs | 23 +++++++++++ 6 files changed, 115 insertions(+), 29 deletions(-) diff --git a/src/eval/command.rs b/src/eval/command.rs index cec8e2d..8bdac96 100644 --- a/src/eval/command.rs +++ b/src/eval/command.rs @@ -2,11 +2,14 @@ use std::collections::HashMap; use chrono::NaiveDate; -use crate::files::commands::{BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task}; -use crate::files::primitives::Span; +use crate::files::commands::{ + self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task, +}; +use crate::files::primitives::{Span, Spanned}; use crate::files::SourcedCommand; use super::date::Dates; +use super::delta::Delta; use super::{DateRange, Entry, EntryKind, Error, Result}; mod birthday; @@ -19,6 +22,7 @@ pub struct CommandState<'a> { from: Option, until: Option, + remind: Option>, dated: HashMap, undated: Vec, @@ -44,6 +48,7 @@ impl<'a> CommandState<'a> { command, from: None, until: None, + remind: None, dated: HashMap::new(), undated: Vec::new(), } @@ -73,6 +78,13 @@ impl<'a> CommandState<'a> { } } + fn range_with_remind(&self) -> DateRange { + match &self.remind { + None => self.range, + Some(delta) => self.range.expand_by(&delta.value), + } + } + /// Last root date mentioned in any `DONE`. fn last_done_root(&self) -> Option { match self.command.command { @@ -113,11 +125,31 @@ impl<'a> CommandState<'a> { } } + fn entry_with_remind(&self, kind: EntryKind, dates: Option) -> Result { + let remind = if let (Some(dates), Some(delta)) = (dates, &self.remind) { + let index = self.command.source.file(); + let start = dates.sorted().root(); + let remind = delta.value.apply_date(index, dates.sorted().root())?; + if remind >= start { + return Err(Error::RemindDidNotMoveBackwards { + index, + span: delta.span, + from: start, + to: remind, + }); + } + Some(remind) + } else { + None + }; + + Ok(Entry::new(self.command.source, kind, dates, remind)) + } + /// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not /// overwrite existing entries if a root date is specified. - fn add(&mut self, kind: EntryKind, dates: Option) { - let entry = Entry::new(self.command.source, kind, dates); - if let Some(dates) = dates { + fn add(&mut self, entry: Entry) { + if let Some(dates) = entry.dates { let root = dates.root(); if let Some(from) = self.from { if root < from { @@ -137,9 +169,8 @@ impl<'a> CommandState<'a> { /// Add an entry, ignoring [`Self::from`] and [`Self::until`]. Always /// overwrites existing entries if a root date is specified. - fn add_forced(&mut self, kind: EntryKind, dates: Option) { - let entry = Entry::new(self.command.source, kind, dates); - if let Some(dates) = dates { + fn add_forced(&mut self, entry: Entry) { + if let Some(dates) = entry.dates { self.dated.insert(dates.root(), entry); } else { self.undated.push(entry); @@ -160,11 +191,11 @@ impl<'a> CommandState<'a> { self.eval_statement(statement)?; } } else if task.done.is_empty() { - self.add(self.kind(), None); + self.add(self.entry_with_remind(self.kind(), None)?); } for done in &task.done { - self.eval_done(done); + self.eval_done(done)?; } Ok(()) @@ -176,7 +207,7 @@ impl<'a> CommandState<'a> { self.eval_statement(statement)?; } } else { - self.add(self.kind(), None); + self.add(self.entry_with_remind(self.kind(), None)?); } Ok(()) @@ -185,11 +216,12 @@ impl<'a> CommandState<'a> { fn eval_statement(&mut self, statement: &Statement) -> Result<()> { match statement { Statement::Date(spec) => self.eval_date(spec)?, - Statement::BDate(spec) => self.eval_bdate(spec), + Statement::BDate(spec) => self.eval_bdate(spec)?, Statement::From(date) => self.from = *date, Statement::Until(date) => self.until = *date, Statement::Except(date) => self.eval_except(*date), Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?, + Statement::Remind(delta) => self.eval_remind(delta), } Ok(()) } @@ -202,8 +234,8 @@ impl<'a> CommandState<'a> { } } - fn eval_bdate(&mut self, spec: &BirthdaySpec) { - self.eval_birthday_spec(spec); + fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<()> { + self.eval_birthday_spec(spec) } fn eval_except(&mut self, date: NaiveDate) { @@ -226,10 +258,19 @@ impl<'a> CommandState<'a> { } } - fn eval_done(&mut self, done: &Done) { - self.add_forced( + fn eval_remind(&mut self, delta: &Option>) { + if let Some(delta) = delta { + self.remind = Some(Spanned::new(delta.span, (&delta.value).into())); + } else { + self.remind = None; + } + } + + fn eval_done(&mut self, done: &Done) -> Result<()> { + self.add_forced(self.entry_with_remind( EntryKind::TaskDone(done.done_at), done.date.map(|date| date.into()), - ); + )?); + Ok(()) } } diff --git a/src/eval/command/birthday.rs b/src/eval/command/birthday.rs index 829160e..b10d83f 100644 --- a/src/eval/command/birthday.rs +++ b/src/eval/command/birthday.rs @@ -4,13 +4,14 @@ use crate::files::commands::BirthdaySpec; use super::super::command::CommandState; use super::super::date::Dates; +use super::super::error::Result; use super::super::EntryKind; impl<'a> CommandState<'a> { - pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) { - let range = match self.limit_from_until(self.range) { + pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<()> { + let range = match self.limit_from_until(self.range_with_remind()) { Some(range) => range, - None => return, + None => return Ok(()), }; for year in range.years() { @@ -26,15 +27,19 @@ impl<'a> CommandState<'a> { let kind = EntryKind::Birthday(age); if let Some(date) = spec.date.with_year(year) { - self.add(EntryKind::Birthday(age), Some(Dates::new(date, date))); + self.add( + self.entry_with_remind(EntryKind::Birthday(age), Some(Dates::new(date, date)))?, + ); } else { assert_eq!(spec.date.month(), 2); assert_eq!(spec.date.day(), 29); let first = NaiveDate::from_ymd(year, 2, 28); let second = NaiveDate::from_ymd(year, 3, 1); - self.add(kind, Some(Dates::new(first, second))); + self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?); } } + + Ok(()) } } diff --git a/src/eval/command/date.rs b/src/eval/command/date.rs index 9fe1292..8ef5ff6 100644 --- a/src/eval/command/date.rs +++ b/src/eval/command/date.rs @@ -84,7 +84,7 @@ impl DateSpec { .map(|date| date.succ()) .unwrap_or(self.start); let range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta) .with_from(range_from)?; @@ -93,7 +93,7 @@ impl DateSpec { Command::Note(_) => { let start = self.start; let range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta); (start, false, range) @@ -142,13 +142,13 @@ impl<'a> CommandState<'a> { } while start <= range.until() { let dates = spec.dates(index, start)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.kind(), Some(dates))?); start = DateSpec::step(index, start, repeat)?; } } } else { let dates = spec.dates(index, spec.start)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.kind(), Some(dates))?); } Ok(()) } diff --git a/src/eval/command/formula.rs b/src/eval/command/formula.rs index 43f7d21..5ecc6a2 100644 --- a/src/eval/command/formula.rs +++ b/src/eval/command/formula.rs @@ -324,7 +324,7 @@ impl From<&commands::WeekdaySpec> for FormulaSpec { impl FormulaSpec { fn range(&self, s: &CommandState<'_>) -> Option { let mut range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta); @@ -367,7 +367,7 @@ impl<'a> CommandState<'a> { for day in range.days() { if spec.eval(index, day)? { let dates = spec.dates(index, day)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.kind(), Some(dates))?); } } } diff --git a/src/eval/entry.rs b/src/eval/entry.rs index cdfb0f8..d3daf26 100644 --- a/src/eval/entry.rs +++ b/src/eval/entry.rs @@ -19,14 +19,31 @@ pub struct Entry { pub source: Source, pub kind: EntryKind, pub dates: Option, + /// Remind the user of an entry before it occurs. This date should always be + /// before the entry's start date, or `None` if there is no start date. + pub remind: Option, } impl Entry { - pub fn new(source: Source, kind: EntryKind, dates: Option) -> Self { + pub fn new( + source: Source, + kind: EntryKind, + dates: Option, + remind: Option, + ) -> Self { + if let Some(dates) = dates { + if let Some(remind) = remind { + assert!(remind < dates.sorted().root()); + } + } else { + assert!(remind.is_none()); + } + Self { source, kind, dates, + remind, } } diff --git a/src/eval/error.rs b/src/eval/error.rs index 16fdf5c..b485143 100644 --- a/src/eval/error.rs +++ b/src/eval/error.rs @@ -34,6 +34,16 @@ pub enum Error { from: NaiveDate, to: NaiveDate, }, + /// A `REMIND`'s delta did not move backwards in time from the entry's start + /// date. Instead, it either remained at the start date (`to == from`) or + /// moved forwards in time (`from < to`). + #[error("remind delta did not move backwards")] + RemindDidNotMoveBackwards { + index: usize, + span: Span, + from: NaiveDate, + to: NaiveDate, + }, /// A `MOVE a TO b` statement was executed, but there was no entry at the /// date `a`. #[error("tried to move nonexisting entry")] @@ -135,6 +145,19 @@ impl Error { ); Self::print_at(sources, index, span, msg); } + Error::RemindDidNotMoveBackwards { + index, + span, + from, + to, + } => { + let msg = format!( + "Remind delta did not move backwards\ + \nMoved from {} to {}", + from, to + ); + Self::print_at(sources, index, span, msg); + } Error::MoveWithoutSource { index, span } => { let msg = "Tried to move nonexisting entry".to_string(); Self::print_at(sources, index, span, msg);