Adapt eval code to changes

These changes include the new file representation and Files API as well
as the codespan error reporting.
This commit is contained in:
Joscha 2022-01-02 15:05:37 +01:00
parent 810ec67cf7
commit 8ae691bc3c
8 changed files with 238 additions and 215 deletions

View file

@ -1,14 +1,14 @@
use chrono::NaiveDate; use chrono::NaiveDate;
use crate::files::arguments::{Range, RangeDate}; use crate::files::arguments::{Range, RangeDate};
use crate::files::Files; use crate::files::{FileSource, Files};
use self::command::CommandState; use self::command::{CommandState, EvalCommand};
pub use self::date::Dates; pub use self::date::Dates;
use self::delta::Delta; use self::delta::Delta;
use self::entry::Entries; use self::entry::Entries;
pub use self::entry::{Entry, EntryKind, EntryMode}; pub use self::entry::{Entry, EntryKind, EntryMode};
pub use self::error::{Error, Result, SourceInfo}; pub use self::error::Error;
pub use self::range::DateRange; pub use self::range::DateRange;
mod command; mod command;
@ -20,19 +20,22 @@ mod range;
mod util; mod util;
impl Files { impl Files {
pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result<Vec<Entry>> { pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result<Vec<Entry>, Error<FileSource>> {
let mut entries = Entries::new(mode, range); let mut entries = Entries::new(mode, range);
for command in self.commands() { for command in self.commands() {
for entry in CommandState::new(command, range).eval()?.entries() { let source = command.source;
if let Some(command) = EvalCommand::new(command.command) {
for entry in CommandState::new(command, source, range).eval()?.entries() {
entries.add(entry); entries.add(entry);
} }
} }
}
Ok(entries.entries()) Ok(entries.entries())
} }
} }
impl Range { impl Range {
pub fn eval(&self, index: usize, today: NaiveDate) -> Result<DateRange> { pub fn eval<S: Copy>(&self, index: S, today: NaiveDate) -> Result<DateRange, Error<S>> {
let mut start = match self.start { let mut start = match self.start {
RangeDate::Date(d) => d, RangeDate::Date(d) => d,
RangeDate::Today => today, RangeDate::Today => today,

View file

@ -6,18 +6,69 @@ use crate::files::commands::{
self, BirthdaySpec, Command, Done, DoneDate, DoneKind, Note, Spec, Statement, Task, self, BirthdaySpec, Command, Done, DoneDate, DoneKind, Note, Spec, Statement, Task,
}; };
use crate::files::primitives::{Span, Spanned, Time}; use crate::files::primitives::{Span, Spanned, Time};
use crate::files::SourcedCommand; use crate::files::{FileSource, Source};
use super::date::Dates; use super::date::Dates;
use super::delta::Delta; use super::delta::Delta;
use super::{DateRange, Entry, EntryKind, Error, Result}; use super::{DateRange, Entry, EntryKind, Error};
mod birthday; mod birthday;
mod date; mod date;
mod formula; mod formula;
/// A command that can be evaluated.
pub enum EvalCommand<'a> {
Task(&'a Task),
Note(&'a Note),
}
impl<'a> EvalCommand<'a> {
pub fn new(command: &'a Command) -> Option<Self> {
match command {
Command::Task(task) => Some(Self::Task(task)),
Command::Note(note) => Some(Self::Note(note)),
_ => None,
}
}
fn statements(&self) -> &[Statement] {
match self {
Self::Task(task) => &task.statements,
Self::Note(note) => &note.statements,
}
}
fn kind(&self) -> EntryKind {
match self {
Self::Task(_) => EntryKind::Task,
Self::Note(_) => EntryKind::Note,
}
}
/// Last root date mentioned in any `DONE`.
fn last_done_root(&self) -> Option<NaiveDate> {
match self {
Self::Task(task) => task
.done
.iter()
.filter_map(|done| done.date.map(DoneDate::root))
.max(),
Self::Note(_) => None,
}
}
/// Last completion date mentioned in any `DONE`.
fn last_done_completion(&self) -> Option<NaiveDate> {
match self {
Self::Task(task) => task.done.iter().map(|done| done.done_at).max(),
Self::Note(_) => None,
}
}
}
pub struct CommandState<'a> { pub struct CommandState<'a> {
command: SourcedCommand<'a>, command: EvalCommand<'a>,
source: Source,
range: DateRange, range: DateRange,
from: Option<NaiveDate>, from: Option<NaiveDate>,
@ -29,7 +80,7 @@ pub struct CommandState<'a> {
} }
impl<'a> CommandState<'a> { impl<'a> CommandState<'a> {
pub fn new(command: SourcedCommand<'a>, mut range: DateRange) -> Self { pub fn new(command: EvalCommand<'a>, source: Source, mut range: DateRange) -> Self {
// If we don't calculate entries for the source of the move command, it // If we don't calculate entries for the source of the move command, it
// fails even though the user did nothing wrong. Also, move commands (or // fails even though the user did nothing wrong. Also, move commands (or
// chains thereof) may move an initially out-of-range entry into range. // chains thereof) may move an initially out-of-range entry into range.
@ -37,15 +88,16 @@ impl<'a> CommandState<'a> {
// To fix this, we just expand the range to contain all move command // To fix this, we just expand the range to contain all move command
// sources. This is a quick fix, but until it becomes a performance // sources. This is a quick fix, but until it becomes a performance
// issue (if ever), it's probably fine. // issue (if ever), it's probably fine.
for statement in command.command.statements() { for statement in command.statements() {
if let Statement::Move { from, .. } = statement { if let Statement::Move { from, .. } = statement {
range = range.containing(*from) range = range.containing(*from)
} }
} }
Self { Self {
range,
command, command,
source,
range,
from: None, from: None,
until: None, until: None,
remind: None, remind: None,
@ -54,10 +106,10 @@ impl<'a> CommandState<'a> {
} }
} }
pub fn eval(mut self) -> Result<Self> { pub fn eval(mut self) -> Result<Self, Error<FileSource>> {
match self.command.command { match self.command {
Command::Task(task) => self.eval_task(task)?, EvalCommand::Task(task) => self.eval_task(task)?,
Command::Note(note) => self.eval_note(note)?, EvalCommand::Note(note) => self.eval_note(note)?,
} }
Ok(self) Ok(self)
} }
@ -71,13 +123,6 @@ impl<'a> CommandState<'a> {
// Helper functions // Helper functions
fn kind(&self) -> EntryKind {
match self.command.command {
Command::Task(_) => EntryKind::Task,
Command::Note(_) => EntryKind::Note,
}
}
fn range_with_remind(&self) -> DateRange { fn range_with_remind(&self) -> DateRange {
match &self.remind { match &self.remind {
None => self.range, None => self.range,
@ -85,26 +130,6 @@ impl<'a> CommandState<'a> {
} }
} }
/// Last root date mentioned in any `DONE`.
fn last_done_root(&self) -> Option<NaiveDate> {
match self.command.command {
Command::Task(task) => task
.done
.iter()
.filter_map(|done| done.date.map(DoneDate::root))
.max(),
Command::Note(_) => None,
}
}
/// Last completion date mentioned in any `DONE`.
fn last_done_completion(&self) -> Option<NaiveDate> {
match self.command.command {
Command::Task(task) => task.done.iter().map(|done| done.done_at).max(),
Command::Note(_) => None,
}
}
fn limit_from_until(&self, range: DateRange) -> Option<DateRange> { fn limit_from_until(&self, range: DateRange) -> Option<DateRange> {
let range_from = range.from(); let range_from = range.from();
let from = self let from = self
@ -125,9 +150,13 @@ impl<'a> CommandState<'a> {
} }
} }
fn entry_with_remind(&self, kind: EntryKind, dates: Option<Dates>) -> Result<Entry> { fn entry_with_remind(
&self,
kind: EntryKind,
dates: Option<Dates>,
) -> Result<Entry, Error<FileSource>> {
let remind = if let (Some(dates), Some(delta)) = (dates, &self.remind) { let remind = if let (Some(dates), Some(delta)) = (dates, &self.remind) {
let index = self.command.source.file(); let index = self.source.file();
let start = dates.sorted().root(); let start = dates.sorted().root();
let remind = delta.value.apply_date(index, dates.sorted().root())?; let remind = delta.value.apply_date(index, dates.sorted().root())?;
if remind >= start { if remind >= start {
@ -143,7 +172,7 @@ impl<'a> CommandState<'a> {
None None
}; };
Ok(Entry::new(self.command.source, kind, dates, remind)) Ok(Entry::new(self.source, kind, dates, remind))
} }
/// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not /// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not
@ -185,13 +214,13 @@ impl<'a> CommandState<'a> {
.any(|s| matches!(s, Statement::Date(_) | Statement::BDate(_))) .any(|s| matches!(s, Statement::Date(_) | Statement::BDate(_)))
} }
fn eval_task(&mut self, task: &Task) -> Result<()> { fn eval_task(&mut self, task: &Task) -> Result<(), Error<FileSource>> {
if Self::has_date_stmt(&task.statements) { if Self::has_date_stmt(&task.statements) {
for statement in &task.statements { for statement in &task.statements {
self.eval_statement(statement)?; self.eval_statement(statement)?;
} }
} else if task.done.is_empty() { } else if task.done.is_empty() {
self.add(self.entry_with_remind(self.kind(), None)?); self.add(self.entry_with_remind(self.command.kind(), None)?);
} }
for done in &task.done { for done in &task.done {
@ -201,19 +230,19 @@ impl<'a> CommandState<'a> {
Ok(()) Ok(())
} }
fn eval_note(&mut self, note: &Note) -> Result<()> { fn eval_note(&mut self, note: &Note) -> Result<(), Error<FileSource>> {
if Self::has_date_stmt(&note.statements) { if Self::has_date_stmt(&note.statements) {
for statement in &note.statements { for statement in &note.statements {
self.eval_statement(statement)?; self.eval_statement(statement)?;
} }
} else { } else {
self.add(self.entry_with_remind(self.kind(), None)?); self.add(self.entry_with_remind(self.command.kind(), None)?);
} }
Ok(()) Ok(())
} }
fn eval_statement(&mut self, statement: &Statement) -> Result<()> { fn eval_statement(&mut self, statement: &Statement) -> Result<(), Error<FileSource>> {
match statement { match statement {
Statement::Date(spec) => self.eval_date(spec)?, Statement::Date(spec) => self.eval_date(spec)?,
Statement::BDate(spec) => self.eval_bdate(spec)?, Statement::BDate(spec) => self.eval_bdate(spec)?,
@ -231,7 +260,7 @@ impl<'a> CommandState<'a> {
Ok(()) Ok(())
} }
fn eval_date(&mut self, spec: &Spec) -> Result<()> { fn eval_date(&mut self, spec: &Spec) -> Result<(), Error<FileSource>> {
match spec { match spec {
Spec::Date(spec) => self.eval_date_spec(spec.into()), Spec::Date(spec) => self.eval_date_spec(spec.into()),
Spec::Weekday(spec) => self.eval_formula_spec(spec.into()), Spec::Weekday(spec) => self.eval_formula_spec(spec.into()),
@ -239,7 +268,7 @@ impl<'a> CommandState<'a> {
} }
} }
fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<()> { fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<(), Error<FileSource>> {
self.eval_birthday_spec(spec) self.eval_birthday_spec(spec)
} }
@ -254,7 +283,7 @@ impl<'a> CommandState<'a> {
from: NaiveDate, from: NaiveDate,
to: Option<NaiveDate>, to: Option<NaiveDate>,
to_time: Option<Spanned<Time>>, to_time: Option<Spanned<Time>>,
) -> Result<()> { ) -> Result<(), Error<FileSource>> {
if let Some(mut entry) = self.dated.remove(&from) { if let Some(mut entry) = self.dated.remove(&from) {
let mut dates = entry.dates.expect("comes from self.dated"); let mut dates = entry.dates.expect("comes from self.dated");
@ -268,7 +297,7 @@ impl<'a> CommandState<'a> {
delta = delta + Duration::minutes(root.minutes_to(to_time.value)); delta = delta + Duration::minutes(root.minutes_to(to_time.value));
} else { } else {
return Err(Error::TimedMoveWithoutTime { return Err(Error::TimedMoveWithoutTime {
index: self.command.source.file(), index: self.source.file(),
span: to_time.span, span: to_time.span,
}); });
} }
@ -281,7 +310,7 @@ impl<'a> CommandState<'a> {
Ok(()) Ok(())
} else { } else {
Err(Error::MoveWithoutSource { Err(Error::MoveWithoutSource {
index: self.command.source.file(), index: self.source.file(),
span, span,
}) })
} }
@ -295,7 +324,7 @@ impl<'a> CommandState<'a> {
} }
} }
fn eval_done(&mut self, done: &Done) -> Result<()> { fn eval_done(&mut self, done: &Done) -> Result<(), Error<FileSource>> {
let kind = match done.kind { let kind = match done.kind {
DoneKind::Done => EntryKind::TaskDone(done.done_at), DoneKind::Done => EntryKind::TaskDone(done.done_at),
DoneKind::Canceled => EntryKind::TaskCanceled(done.done_at), DoneKind::Canceled => EntryKind::TaskCanceled(done.done_at),

View file

@ -1,14 +1,15 @@
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use crate::files::commands::BirthdaySpec; use crate::files::commands::BirthdaySpec;
use crate::files::FileSource;
use super::super::command::CommandState; use super::super::command::CommandState;
use super::super::date::Dates; use super::super::date::Dates;
use super::super::error::Result; use super::super::error::Error;
use super::super::EntryKind; use super::super::EntryKind;
impl<'a> CommandState<'a> { impl<'a> CommandState<'a> {
pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<()> { pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error<FileSource>> {
let range = match self.limit_from_until(self.range_with_remind()) { let range = match self.limit_from_until(self.range_with_remind()) {
Some(range) => range, Some(range) => range,
None => return Ok(()), None => return Ok(()),

View file

@ -1,12 +1,14 @@
use chrono::NaiveDate; use chrono::NaiveDate;
use crate::files::commands::{self, Command}; use crate::files::commands;
use crate::files::primitives::{Spanned, Time}; use crate::files::primitives::{Spanned, Time};
use crate::files::FileSource;
use super::super::command::CommandState; use super::super::command::CommandState;
use super::super::date::Dates; use super::super::date::Dates;
use super::super::delta::{Delta, DeltaStep}; use super::super::delta::{Delta, DeltaStep};
use super::super::{DateRange, Error, Result}; use super::super::{DateRange, Error};
use super::EvalCommand;
pub struct DateSpec { pub struct DateSpec {
pub start: NaiveDate, pub start: NaiveDate,
@ -72,14 +74,16 @@ impl DateSpec {
/// `start` date itself should be skipped (and thus not result in an entry). /// `start` date itself should be skipped (and thus not result in an entry).
/// This may be necessary if [`Self::start_at_done`] is set. /// This may be necessary if [`Self::start_at_done`] is set.
fn start_and_range(&self, s: &CommandState<'_>) -> Option<(NaiveDate, bool, DateRange)> { fn start_and_range(&self, s: &CommandState<'_>) -> Option<(NaiveDate, bool, DateRange)> {
let (start, skip, range) = match s.command.command { let (start, skip, range) = match s.command {
Command::Task(_) => { EvalCommand::Task(_) => {
let (start, skip) = s let (start, skip) = s
.command
.last_done_completion() .last_done_completion()
.map(|start| (start, true)) .map(|start| (start, true))
.filter(|_| self.start_at_done) .filter(|_| self.start_at_done)
.unwrap_or((self.start, false)); .unwrap_or((self.start, false));
let range_from = s let range_from = s
.command
.last_done_root() .last_done_root()
.map(|date| date.succ()) .map(|date| date.succ())
.unwrap_or(self.start); .unwrap_or(self.start);
@ -90,7 +94,7 @@ impl DateSpec {
.with_from(range_from)?; .with_from(range_from)?;
(start, skip, range) (start, skip, range)
} }
Command::Note(_) => { EvalCommand::Note(_) => {
let start = self.start; let start = self.start;
let range = s let range = s
.range_with_remind() .range_with_remind()
@ -103,7 +107,11 @@ impl DateSpec {
Some((start, skip, range)) Some((start, skip, range))
} }
fn step(index: usize, from: NaiveDate, repeat: &Spanned<Delta>) -> Result<NaiveDate> { fn step(
index: FileSource,
from: NaiveDate,
repeat: &Spanned<Delta>,
) -> Result<NaiveDate, Error<FileSource>> {
let to = repeat.value.apply_date(index, from)?; let to = repeat.value.apply_date(index, from)?;
if to > from { if to > from {
Ok(to) Ok(to)
@ -117,7 +125,7 @@ impl DateSpec {
} }
} }
fn dates(&self, index: usize, start: NaiveDate) -> Result<Dates> { fn dates(&self, index: FileSource, start: NaiveDate) -> Result<Dates, Error<FileSource>> {
let root = self.start_delta.apply_date(index, start)?; let root = self.start_delta.apply_date(index, start)?;
Ok(if let Some(root_time) = self.start_time { Ok(if let Some(root_time) = self.start_time {
let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?; let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?;
@ -130,8 +138,8 @@ impl DateSpec {
} }
impl<'a> CommandState<'a> { impl<'a> CommandState<'a> {
pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<()> { pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error<FileSource>> {
let index = self.command.source.file(); let index = self.source.file();
if let Some(repeat) = &spec.repeat { if let Some(repeat) = &spec.repeat {
if let Some((mut start, skip, range)) = spec.start_and_range(self) { if let Some((mut start, skip, range)) = spec.start_and_range(self) {
if skip { if skip {
@ -142,13 +150,13 @@ impl<'a> CommandState<'a> {
} }
while start <= range.until() { while start <= range.until() {
let dates = spec.dates(index, start)?; let dates = spec.dates(index, start)?;
self.add(self.entry_with_remind(self.kind(), Some(dates))?); self.add(self.entry_with_remind(self.command.kind(), Some(dates))?);
start = DateSpec::step(index, start, repeat)?; start = DateSpec::step(index, start, repeat)?;
} }
} }
} else { } else {
let dates = spec.dates(index, spec.start)?; let dates = spec.dates(index, spec.start)?;
self.add(self.entry_with_remind(self.kind(), Some(dates))?); self.add(self.entry_with_remind(self.command.kind(), Some(dates))?);
} }
Ok(()) Ok(())
} }

View file

@ -1,12 +1,14 @@
use chrono::{Datelike, Duration, NaiveDate}; use chrono::{Datelike, Duration, NaiveDate};
use crate::files::commands::{self, Command}; use crate::files::commands;
use crate::files::primitives::{Span, Spanned, Time, Weekday}; use crate::files::primitives::{Span, Spanned, Time, Weekday};
use crate::files::FileSource;
use super::super::command::CommandState; use super::super::command::CommandState;
use super::super::date::Dates; use super::super::date::Dates;
use super::super::delta::{Delta, DeltaStep}; use super::super::delta::{Delta, DeltaStep};
use super::super::{util, DateRange, Error, Result}; use super::super::{util, DateRange, Error};
use super::EvalCommand;
fn b2i(b: bool) -> i64 { fn b2i(b: bool) -> i64 {
if b { if b {
@ -47,7 +49,7 @@ pub enum Var {
} }
impl Var { impl Var {
fn eval(self, index: usize, date: NaiveDate) -> Result<i64> { fn eval<S>(self, index: S, date: NaiveDate) -> Result<i64, Error<S>> {
Ok(match self { Ok(match self {
Var::JulianDay => date.num_days_from_ce().into(), Var::JulianDay => date.num_days_from_ce().into(),
Var::Year => date.year().into(), Var::Year => date.year().into(),
@ -202,7 +204,7 @@ impl From<Weekday> for Expr {
} }
impl Expr { impl Expr {
fn eval(&self, index: usize, date: NaiveDate) -> Result<i64> { fn eval<S: Copy>(&self, index: S, date: NaiveDate) -> Result<i64, Error<S>> {
Ok(match self { Ok(match self {
Expr::Lit(l) => *l, Expr::Lit(l) => *l,
Expr::Var(v) => v.eval(index, date)?, Expr::Var(v) => v.eval(index, date)?,
@ -328,12 +330,12 @@ impl FormulaSpec {
.expand_by(&self.end_delta) .expand_by(&self.end_delta)
.move_by(&self.start_delta); .move_by(&self.start_delta);
if let Command::Task(_) = s.command.command { if let EvalCommand::Task(_) = s.command {
if let Some(last_done_root) = s.last_done_root() { if let Some(last_done_root) = s.command.last_done_root() {
range = range.with_from(last_done_root.succ())?; range = range.with_from(last_done_root.succ())?;
} else if let Some(from) = s.from { } else if let Some(from) = s.from {
range = range.with_from(from)?; range = range.with_from(from)?;
} else if matches!(s.command.command, Command::Task(_)) { } else if matches!(s.command, EvalCommand::Task(_)) {
// We have no idea if we missed any tasks since the user hasn't // We have no idea if we missed any tasks since the user hasn't
// specified a `FROM`, so we just just look back one year. Any // specified a `FROM`, so we just just look back one year. Any
// task older than a year is probably not important anyways... // task older than a year is probably not important anyways...
@ -344,7 +346,7 @@ impl FormulaSpec {
s.limit_from_until(range) s.limit_from_until(range)
} }
fn dates(&self, index: usize, start: NaiveDate) -> Result<Dates> { fn dates(&self, index: FileSource, start: NaiveDate) -> Result<Dates, Error<FileSource>> {
let root = self.start_delta.apply_date(index, start)?; let root = self.start_delta.apply_date(index, start)?;
Ok(if let Some(root_time) = self.start_time { Ok(if let Some(root_time) = self.start_time {
let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?; let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?;
@ -355,19 +357,19 @@ impl FormulaSpec {
}) })
} }
fn eval(&self, index: usize, date: NaiveDate) -> Result<bool> { fn eval(&self, index: FileSource, date: NaiveDate) -> Result<bool, Error<FileSource>> {
Ok(i2b(self.start.eval(index, date)?)) Ok(i2b(self.start.eval(index, date)?))
} }
} }
impl<'a> CommandState<'a> { impl<'a> CommandState<'a> {
pub fn eval_formula_spec(&mut self, spec: FormulaSpec) -> Result<()> { pub fn eval_formula_spec(&mut self, spec: FormulaSpec) -> Result<(), Error<FileSource>> {
if let Some(range) = spec.range(self) { if let Some(range) = spec.range(self) {
let index = self.command.source.file(); let index = self.source.file();
for day in range.days() { for day in range.days() {
if spec.eval(index, day)? { if spec.eval(index, day)? {
let dates = spec.dates(index, day)?; let dates = spec.dates(index, day)?;
self.add(self.entry_with_remind(self.kind(), Some(dates))?); self.add(self.entry_with_remind(self.command.kind(), Some(dates))?);
} }
} }
} }
@ -386,7 +388,7 @@ mod tests {
use super::{Expr, Var}; use super::{Expr, Var};
fn expr(expr: &Expr, date: NaiveDate, target: i64) { fn expr(expr: &Expr, date: NaiveDate, target: i64) {
if let Ok(result) = expr.eval(0, date) { if let Ok(result) = expr.eval((), date) {
assert_eq!(result, target); assert_eq!(result, target);
} else { } else {
panic!("formula produced error for day {}", date); panic!("formula produced error for day {}", date);
@ -400,7 +402,7 @@ mod tests {
for delta in -1000..1000 { for delta in -1000..1000 {
let d1 = NaiveDate::from_ymd(2021, 12, 19); let d1 = NaiveDate::from_ymd(2021, 12, 19);
let d2 = d1 + Duration::days(delta); let d2 = d1 + Duration::days(delta);
assert_eq!(e.eval(0, d2).unwrap() - e.eval(0, d1).unwrap(), delta); assert_eq!(e.eval((), d2).unwrap() - e.eval((), d1).unwrap(), delta);
} }
} }

View file

@ -5,7 +5,7 @@ use chrono::{Datelike, Duration, NaiveDate};
use crate::files::commands; use crate::files::commands;
use crate::files::primitives::{Span, Spanned, Time, Weekday}; use crate::files::primitives::{Span, Spanned, Time, Weekday};
use super::{util, Error, Result}; use super::{util, Error};
/// Like [`commands::DeltaStep`] but includes a new constructor, /// Like [`commands::DeltaStep`] but includes a new constructor,
/// [`DeltaStep::Time`]. /// [`DeltaStep::Time`].
@ -142,16 +142,16 @@ impl From<&commands::Delta> for Delta {
} }
} }
struct DeltaEval { struct DeltaEval<I> {
index: usize, index: I,
start: NaiveDate, start: NaiveDate,
start_time: Option<Time>, start_time: Option<Time>,
curr: NaiveDate, curr: NaiveDate,
curr_time: Option<Time>, curr_time: Option<Time>,
} }
impl DeltaEval { impl<S: Copy> DeltaEval<S> {
fn new(index: usize, start: NaiveDate, start_time: Option<Time>) -> Self { fn new(index: S, start: NaiveDate, start_time: Option<Time>) -> Self {
Self { Self {
index, index,
start, start,
@ -161,7 +161,7 @@ impl DeltaEval {
} }
} }
fn err_step(&self, span: Span) -> Error { fn err_step(&self, span: Span) -> Error<S> {
Error::DeltaInvalidStep { Error::DeltaInvalidStep {
index: self.index, index: self.index,
span, span,
@ -172,7 +172,7 @@ impl DeltaEval {
} }
} }
fn err_time(&self, span: Span) -> Error { fn err_time(&self, span: Span) -> Error<S> {
Error::DeltaNoTime { Error::DeltaNoTime {
index: self.index, index: self.index,
span, span,
@ -181,7 +181,7 @@ impl DeltaEval {
} }
} }
fn apply(&mut self, step: &Spanned<DeltaStep>) -> Result<()> { fn apply(&mut self, step: &Spanned<DeltaStep>) -> Result<(), Error<S>> {
match step.value { match step.value {
DeltaStep::Year(n) => self.step_year(step.span, n)?, DeltaStep::Year(n) => self.step_year(step.span, n)?,
DeltaStep::Month(n) => self.step_month(step.span, n)?, DeltaStep::Month(n) => self.step_month(step.span, n)?,
@ -196,7 +196,7 @@ impl DeltaEval {
Ok(()) Ok(())
} }
fn step_year(&mut self, span: Span, amount: i32) -> Result<()> { fn step_year(&mut self, span: Span, amount: i32) -> Result<(), Error<S>> {
let year = self.curr.year() + amount; let year = self.curr.year() + amount;
match NaiveDate::from_ymd_opt(year, self.curr.month(), self.curr.day()) { match NaiveDate::from_ymd_opt(year, self.curr.month(), self.curr.day()) {
None => Err(self.err_step(span)), None => Err(self.err_step(span)),
@ -207,7 +207,7 @@ impl DeltaEval {
} }
} }
fn step_month(&mut self, span: Span, amount: i32) -> Result<()> { fn step_month(&mut self, span: Span, amount: i32) -> Result<(), Error<S>> {
let (year, month) = util::add_months(self.curr.year(), self.curr.month(), amount); let (year, month) = util::add_months(self.curr.year(), self.curr.month(), amount);
match NaiveDate::from_ymd_opt(year, month, self.curr.day()) { match NaiveDate::from_ymd_opt(year, month, self.curr.day()) {
None => Err(self.err_step(span)), None => Err(self.err_step(span)),
@ -218,7 +218,7 @@ impl DeltaEval {
} }
} }
fn step_month_reverse(&mut self, span: Span, amount: i32) -> Result<()> { fn step_month_reverse(&mut self, span: Span, amount: i32) -> Result<(), Error<S>> {
// Calculate offset from the last day of the month // Calculate offset from the last day of the month
let month_length = util::month_length(self.curr.year(), self.curr.month()) as i32; let month_length = util::month_length(self.curr.year(), self.curr.month()) as i32;
let end_offset = self.curr.day() as i32 - month_length; let end_offset = self.curr.day() as i32 - month_length;
@ -252,7 +252,7 @@ impl DeltaEval {
self.curr += delta; self.curr += delta;
} }
fn step_hour(&mut self, span: Span, amount: i32) -> Result<()> { fn step_hour(&mut self, span: Span, amount: i32) -> Result<(), Error<S>> {
let time = match self.curr_time { let time = match self.curr_time {
Some(time) => time, Some(time) => time,
None => return Err(self.err_time(span)), None => return Err(self.err_time(span)),
@ -264,7 +264,7 @@ impl DeltaEval {
Ok(()) Ok(())
} }
fn step_minute(&mut self, span: Span, amount: i32) -> Result<()> { fn step_minute(&mut self, span: Span, amount: i32) -> Result<(), Error<S>> {
let time = match self.curr_time { let time = match self.curr_time {
Some(time) => time, Some(time) => time,
None => return Err(self.err_time(span)), None => return Err(self.err_time(span)),
@ -290,7 +290,7 @@ impl DeltaEval {
} }
} }
fn step_time(&mut self, span: Span, time: Time) -> Result<()> { fn step_time(&mut self, span: Span, time: Time) -> Result<(), Error<S>> {
let curr_time = match self.curr_time { let curr_time = match self.curr_time {
Some(time) => time, Some(time) => time,
None => return Err(self.err_time(span)), None => return Err(self.err_time(span)),
@ -313,11 +313,11 @@ impl Delta {
self.steps.iter().map(|step| step.value.upper_bound()).sum() self.steps.iter().map(|step| step.value.upper_bound()).sum()
} }
fn apply( fn apply<S: Copy>(
&self, &self,
index: usize, index: S,
start: (NaiveDate, Option<Time>), start: (NaiveDate, Option<Time>),
) -> Result<(NaiveDate, Option<Time>)> { ) -> Result<(NaiveDate, Option<Time>), Error<S>> {
let mut eval = DeltaEval::new(index, start.0, start.1); let mut eval = DeltaEval::new(index, start.0, start.1);
for step in &self.steps { for step in &self.steps {
eval.apply(step)?; eval.apply(step)?;
@ -325,16 +325,16 @@ impl Delta {
Ok((eval.curr, eval.curr_time)) Ok((eval.curr, eval.curr_time))
} }
pub fn apply_date(&self, index: usize, date: NaiveDate) -> Result<NaiveDate> { pub fn apply_date<S: Copy>(&self, index: S, date: NaiveDate) -> Result<NaiveDate, Error<S>> {
Ok(self.apply(index, (date, None))?.0) Ok(self.apply(index, (date, None))?.0)
} }
pub fn apply_date_time( pub fn apply_date_time<S: Copy>(
&self, &self,
index: usize, index: S,
date: NaiveDate, date: NaiveDate,
time: Time, time: Time,
) -> Result<(NaiveDate, Time)> { ) -> Result<(NaiveDate, Time), Error<S>> {
let (date, time) = self.apply(index, (date, Some(time)))?; let (date, time) = self.apply(index, (date, Some(time)))?;
Ok((date, time.expect("time was not preserved"))) Ok((date, time.expect("time was not preserved")))
} }
@ -346,7 +346,7 @@ mod tests {
use crate::files::primitives::{Span, Spanned, Time}; use crate::files::primitives::{Span, Spanned, Time};
use super::super::Result; use super::super::Error;
use super::{Delta, DeltaStep as Step}; use super::{Delta, DeltaStep as Step};
const SPAN: Span = Span { start: 12, end: 34 }; const SPAN: Span = Span { start: 12, end: 34 };
@ -357,8 +357,8 @@ mod tests {
} }
} }
fn apply_d(step: Step, from: (i32, u32, u32)) -> Result<NaiveDate> { fn apply_d(step: Step, from: (i32, u32, u32)) -> Result<NaiveDate, Error<()>> {
delta(step).apply_date(0, NaiveDate::from_ymd(from.0, from.1, from.2)) delta(step).apply_date((), NaiveDate::from_ymd(from.0, from.1, from.2))
} }
fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) { fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) {
@ -368,9 +368,12 @@ mod tests {
); );
} }
fn apply_dt(step: Step, from: (i32, u32, u32, u32, u32)) -> Result<(NaiveDate, Time)> { fn apply_dt(
step: Step,
from: (i32, u32, u32, u32, u32),
) -> Result<(NaiveDate, Time), Error<()>> {
delta(step).apply_date_time( delta(step).apply_date_time(
0, (),
NaiveDate::from_ymd(from.0, from.1, from.2), NaiveDate::from_ymd(from.0, from.1, from.2),
Time::new(from.3, from.4), Time::new(from.3, from.4),
) )

View file

@ -1,15 +1,15 @@
use std::result;
use chrono::NaiveDate; use chrono::NaiveDate;
use codespan_reporting::diagnostic::{Diagnostic, Label};
use crate::files::primitives::{Span, Time}; use crate::files::primitives::{Span, Time};
use crate::files::{FileSource, Files};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error<S> {
/// A delta step resulted in an invalid date. /// A delta step resulted in an invalid date.
#[error("delta step resulted in invalid date")] #[error("delta step resulted in invalid date")]
DeltaInvalidStep { DeltaInvalidStep {
index: usize, index: S,
span: Span, span: Span,
start: NaiveDate, start: NaiveDate,
start_time: Option<Time>, start_time: Option<Time>,
@ -19,7 +19,7 @@ pub enum Error {
/// A time-based delta step was applied to a date without time. /// A time-based delta step was applied to a date without time.
#[error("time-based delta step applied to date without time")] #[error("time-based delta step applied to date without time")]
DeltaNoTime { DeltaNoTime {
index: usize, index: S,
span: Span, span: Span,
start: NaiveDate, start: NaiveDate,
prev: NaiveDate, prev: NaiveDate,
@ -29,7 +29,7 @@ pub enum Error {
/// in time (`to < from`). /// in time (`to < from`).
#[error("repeat delta did not move forwards")] #[error("repeat delta did not move forwards")]
RepeatDidNotMoveForwards { RepeatDidNotMoveForwards {
index: usize, index: S,
span: Span, span: Span,
from: NaiveDate, from: NaiveDate,
to: NaiveDate, to: NaiveDate,
@ -39,7 +39,7 @@ pub enum Error {
/// moved forwards in time (`from < to`). /// moved forwards in time (`from < to`).
#[error("remind delta did not move backwards")] #[error("remind delta did not move backwards")]
RemindDidNotMoveBackwards { RemindDidNotMoveBackwards {
index: usize, index: S,
span: Span, span: Span,
from: NaiveDate, from: NaiveDate,
to: NaiveDate, to: NaiveDate,
@ -47,55 +47,36 @@ pub enum Error {
/// A `MOVE a TO b` statement was executed, but there was no entry at the /// A `MOVE a TO b` statement was executed, but there was no entry at the
/// date `a`. /// date `a`.
#[error("tried to move nonexisting entry")] #[error("tried to move nonexisting entry")]
MoveWithoutSource { index: usize, span: Span }, MoveWithoutSource { index: S, span: Span },
/// A `MOVE a TO b` statement was executed where `b` contains a time but `a` /// A `MOVE a TO b` statement was executed where `b` contains a time but `a`
/// doesn't was executed. /// doesn't was executed.
#[error("tried to move un-timed entry to new time")] #[error("tried to move un-timed entry to new time")]
TimedMoveWithoutTime { index: usize, span: Span }, TimedMoveWithoutTime { index: S, span: Span },
/// A division by zero has occurred. /// A division by zero has occurred.
#[error("tried to divide by zero")] #[error("tried to divide by zero")]
DivByZero { DivByZero {
index: usize, index: S,
span: Span, span: Span,
date: NaiveDate, date: NaiveDate,
}, },
/// A modulo operation by zero has occurred. /// A modulo operation by zero has occurred.
#[error("tried to modulo by zero")] #[error("tried to modulo by zero")]
ModByZero { ModByZero {
index: usize, index: S,
span: Span, span: Span,
date: NaiveDate, date: NaiveDate,
}, },
/// Easter calculation failed. /// Easter calculation failed.
#[error("easter calculation failed")] #[error("easter calculation failed")]
Easter { Easter {
index: usize, index: S,
span: Span, span: Span,
date: NaiveDate, date: NaiveDate,
msg: &'static str, msg: &'static str,
}, },
} }
pub struct SourceInfo<'a> { impl Error<FileSource> {
pub name: Option<String>,
pub content: &'a str,
}
impl Error {
fn print_at<'a>(sources: &[SourceInfo<'a>], index: &usize, span: &Span, message: String) {
use pest::error as pe;
let source = sources.get(*index).expect("index is valid");
let span = pest::Span::new(source.content, span.start, span.end).expect("span is valid");
let variant = pe::ErrorVariant::<()>::CustomError { message };
let mut error = pe::Error::new_from_span(variant, span);
if let Some(name) = &source.name {
error = error.with_path(name);
}
eprintln!("{}", error);
}
fn fmt_date_time(date: NaiveDate, time: Option<Time>) -> String { fn fmt_date_time(date: NaiveDate, time: Option<Time>) -> String {
match time { match time {
None => format!("{}", date), None => format!("{}", date),
@ -103,8 +84,8 @@ impl Error {
} }
} }
pub fn print<'a>(&self, sources: &[SourceInfo<'a>]) { pub fn print(&self, files: &Files) {
match self { let diagnostic = match self {
Error::DeltaInvalidStep { Error::DeltaInvalidStep {
index, index,
span, span,
@ -113,95 +94,91 @@ impl Error {
prev, prev,
prev_time, prev_time,
} => { } => {
let msg = format!( let start_str = Self::fmt_date_time(*start, *start_time);
"Delta step resulted in invalid date\ let prev_str = Self::fmt_date_time(*prev, *prev_time);
\nInitial start: {}\ Diagnostic::error()
\nPrevious step: {}", .with_message("Delta step resulted in invalid date")
Self::fmt_date_time(*start, *start_time), .with_labels(vec![
Self::fmt_date_time(*prev, *prev_time), Label::primary(files.cs_id(*index), span).with_message("At this step")
); ])
Self::print_at(sources, index, span, msg); .with_notes(vec![
format!("Date before applying delta: {}", start_str),
format!("Date before applying this step: {}", prev_str),
])
} }
Error::DeltaNoTime { Error::DeltaNoTime {
index, index,
span, span,
start, start,
prev, prev,
} => { } => Diagnostic::error()
let msg = format!( .with_message("Time-based delta step applied to date without time")
"Time-based delta step applied to date without time\ .with_labels(vec![
\nInitial start: {}\ Label::primary(files.cs_id(*index), span).with_message("At this step")
\nPrevious step: {}", ])
start, prev .with_notes(vec![
); format!("Date before applying delta: {}", start),
Self::print_at(sources, index, span, msg); format!("Date before applying this step: {}", prev),
} ]),
Error::RepeatDidNotMoveForwards { Error::RepeatDidNotMoveForwards {
index, index,
span, span,
from, from,
to, to,
} => { } => Diagnostic::error()
let msg = format!( .with_message("Repeat delta did not move forwards")
"Repeat delta did not move forwards\ .with_labels(vec![
\nMoved from {} to {}", Label::primary(files.cs_id(*index), span).with_message("This delta")
from, to ])
); .with_notes(vec![format!("Moved from {} to {}", from, to)]),
Self::print_at(sources, index, span, msg);
}
Error::RemindDidNotMoveBackwards { Error::RemindDidNotMoveBackwards {
index, index,
span, span,
from, from,
to, to,
} => { } => Diagnostic::error()
let msg = format!( .with_message("Remind delta did not move backwards")
"Remind delta did not move backwards\ .with_labels(vec![
\nMoved from {} to {}", Label::primary(files.cs_id(*index), span).with_message("This delta")
from, to ])
); .with_notes(vec![format!("Moved from {} to {}", from, to)]),
Self::print_at(sources, index, span, msg); Error::MoveWithoutSource { index, span } => Diagnostic::error()
} .with_message("Tried to move nonexistent entry")
Error::MoveWithoutSource { index, span } => { .with_labels(vec![
let msg = "Tried to move nonexisting entry".to_string(); Label::primary(files.cs_id(*index), span).with_message("Here")
Self::print_at(sources, index, span, msg); ]),
} Error::TimedMoveWithoutTime { index, span } => Diagnostic::error()
Error::TimedMoveWithoutTime { index, span } => { .with_message("Tried to move un-timed entry to new time")
let msg = "Tried to move un-timed entry to new time".to_string(); .with_labels(vec![
Self::print_at(sources, index, span, msg); Label::primary(files.cs_id(*index), span).with_message("Here")
} ]),
Error::DivByZero { index, span, date } => { Error::DivByZero { index, span, date } => Diagnostic::error()
let msg = format!( .with_message("Tried to divide by zero")
"Tried to divide by zero\ .with_labels(vec![
\nAt date: {}", Label::primary(files.cs_id(*index), span).with_message("This expression")
date ])
); .with_notes(vec![format!("At date: {}", date)]),
Self::print_at(sources, index, span, msg); Error::ModByZero { index, span, date } => Diagnostic::error()
} .with_message("Tried to modulo by zero")
Error::ModByZero { index, span, date } => { .with_labels(vec![
let msg = format!( Label::primary(files.cs_id(*index), span).with_message("This expression")
"Tried to modulo by zero\ ])
\nAt date: {}", .with_notes(vec![format!("At date: {}", date)]),
date
);
Self::print_at(sources, index, span, msg);
}
Error::Easter { Error::Easter {
index, index,
span, span,
date, date,
msg, msg,
} => { } => Diagnostic::error()
let msg = format!( .with_message("Failed to calculate easter")
"Failed to calculate easter\ .with_labels(vec![
\nAt date: {}\ Label::primary(files.cs_id(*index), span).with_message("This expression")
\nReason: {}", ])
date, msg .with_notes(vec![
); format!("At date: {}", date),
Self::print_at(sources, index, span, msg); format!("Reason: {}", msg),
]),
};
files.eprint_diagnostic(&diagnostic);
} }
} }
}
}
pub type Result<T> = result::Result<T, Error>;

View file

@ -4,7 +4,7 @@
#![warn(clippy::use_self)] #![warn(clippy::use_self)]
mod cli; mod cli;
// mod eval; mod eval;
mod files; mod files;
fn main() { fn main() {