Prepare eval error handling for cli arg errors

This commit is contained in:
Joscha 2021-12-18 23:31:27 +01:00
parent 279bf4a4d1
commit ff627b98df
8 changed files with 107 additions and 93 deletions

View file

@ -139,7 +139,7 @@ pub fn run() {
let range = find_range(&opt, now); let range = find_range(&opt, now);
if let Err(e) = run_command(&opt, &mut files, range, now) { if let Err(e) = run_command(&opt, &mut files, range, now) {
e.print(&files); e.print(&files.sources());
process::exit(1); process::exit(1);
} }

View file

@ -1,7 +1,6 @@
use std::result; use std::result;
use crate::eval; use crate::eval::{self, SourceInfo};
use crate::files::Files;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -14,9 +13,9 @@ pub enum Error {
} }
impl Error { impl Error {
pub fn print(&self, files: &Files) { pub fn print<'a>(&self, sources: &[SourceInfo<'a>]) {
match self { match self {
Error::Eval(e) => e.print(files), Error::Eval(e) => e.print(sources),
Error::NoSuchEntry(n) => eprintln!("No entry with number {}", n), Error::NoSuchEntry(n) => eprintln!("No entry with number {}", n),
Error::NotATask(ns) => { Error::NotATask(ns) => {
if ns.is_empty() { if ns.is_empty() {

View file

@ -216,7 +216,7 @@ impl<'a> CommandState<'a> {
Ok(()) Ok(())
} else { } else {
Err(Error::MoveWithoutSource { Err(Error::MoveWithoutSource {
file: self.command.source.file(), index: self.command.source.file(),
span, span,
}) })
} }

View file

@ -103,13 +103,13 @@ impl DateSpec {
Some((start, skip, range)) Some((start, skip, range))
} }
fn step(file: usize, from: NaiveDate, repeat: &Spanned<Delta>) -> Result<NaiveDate> { fn step(index: usize, from: NaiveDate, repeat: &Spanned<Delta>) -> Result<NaiveDate> {
let to = repeat.value.apply_date(file, from)?; let to = repeat.value.apply_date(index, from)?;
if to > from { if to > from {
Ok(to) Ok(to)
} else { } else {
Err(Error::RepeatDidNotMoveForwards { Err(Error::RepeatDidNotMoveForwards {
file, index,
span: repeat.span, span: repeat.span,
from, from,
to, to,
@ -117,13 +117,13 @@ impl DateSpec {
} }
} }
fn dates(&self, file: usize, start: NaiveDate) -> Result<Dates> { fn dates(&self, index: usize, start: NaiveDate) -> Result<Dates> {
let root = self.start_delta.apply_date(file, 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(file, root, root_time)?; let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?;
Dates::new_with_time(root, root_time, other, other_time) Dates::new_with_time(root, root_time, other, other_time)
} else { } else {
let other = self.end_delta.apply_date(file, root)?; let other = self.end_delta.apply_date(index, root)?;
Dates::new(root, other) Dates::new(root, other)
}) })
} }
@ -131,23 +131,23 @@ 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<()> {
let file = self.command.source.file(); let index = self.command.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 {
start = DateSpec::step(file, start, repeat)?; start = DateSpec::step(index, start, repeat)?;
} }
while start < range.from() { while start < range.from() {
start = DateSpec::step(file, start, repeat)?; start = DateSpec::step(index, start, repeat)?;
} }
while start <= range.until() { while start <= range.until() {
let dates = spec.dates(file, start)?; let dates = spec.dates(index, start)?;
self.add(self.kind(), Some(dates)); self.add(self.kind(), Some(dates));
start = DateSpec::step(file, start, repeat)?; start = DateSpec::step(index, start, repeat)?;
} }
} }
} else { } else {
let dates = spec.dates(file, spec.start)?; let dates = spec.dates(index, spec.start)?;
self.add(self.kind(), Some(dates)); self.add(self.kind(), Some(dates));
} }
Ok(()) Ok(())

View file

@ -46,7 +46,7 @@ pub enum Var {
} }
impl Var { impl Var {
fn eval(self, file: usize, date: NaiveDate) -> Result<i64> { fn eval(self, index: usize, date: NaiveDate) -> Result<i64> {
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(),
@ -81,7 +81,7 @@ impl Var {
} }
Var::Easter(span) => { Var::Easter(span) => {
let e = computus::gregorian(date.year()).map_err(|e| Error::Easter { let e = computus::gregorian(date.year()).map_err(|e| Error::Easter {
file, index,
span, span,
date, date,
msg: e, msg: e,
@ -199,46 +199,46 @@ impl From<Weekday> for Expr {
} }
impl Expr { impl Expr {
fn eval(&self, file: usize, date: NaiveDate) -> Result<i64> { fn eval(&self, index: usize, date: NaiveDate) -> Result<i64> {
Ok(match self { Ok(match self {
Expr::Lit(l) => *l, Expr::Lit(l) => *l,
Expr::Var(v) => v.eval(file, date)?, Expr::Var(v) => v.eval(index, date)?,
Expr::Neg(e) => -e.eval(file, date)?, Expr::Neg(e) => -e.eval(index, date)?,
Expr::Add(a, b) => a.eval(file, date)? + b.eval(file, date)?, Expr::Add(a, b) => a.eval(index, date)? + b.eval(index, date)?,
Expr::Sub(a, b) => a.eval(file, date)? - b.eval(file, date)?, Expr::Sub(a, b) => a.eval(index, date)? - b.eval(index, date)?,
Expr::Mul(a, b) => a.eval(file, date)? * b.eval(file, date)?, Expr::Mul(a, b) => a.eval(index, date)? * b.eval(index, date)?,
Expr::Div(a, b, span) => { Expr::Div(a, b, span) => {
let b = b.eval(file, date)?; let b = b.eval(index, date)?;
if b == 0 { if b == 0 {
return Err(Error::DivByZero { return Err(Error::DivByZero {
file, index,
span: *span, span: *span,
date, date,
}); });
} }
a.eval(file, date)?.div_euclid(b) a.eval(index, date)?.div_euclid(b)
} }
Expr::Mod(a, b, span) => { Expr::Mod(a, b, span) => {
let b = b.eval(file, date)?; let b = b.eval(index, date)?;
if b == 0 { if b == 0 {
return Err(Error::ModByZero { return Err(Error::ModByZero {
file, index,
span: *span, span: *span,
date, date,
}); });
} }
a.eval(file, date)?.rem_euclid(b) a.eval(index, date)?.rem_euclid(b)
} }
Expr::Eq(a, b) => b2i(a.eval(file, date)? == b.eval(file, date)?), Expr::Eq(a, b) => b2i(a.eval(index, date)? == b.eval(index, date)?),
Expr::Neq(a, b) => b2i(a.eval(file, date)? != b.eval(file, date)?), Expr::Neq(a, b) => b2i(a.eval(index, date)? != b.eval(index, date)?),
Expr::Lt(a, b) => b2i(a.eval(file, date)? < b.eval(file, date)?), Expr::Lt(a, b) => b2i(a.eval(index, date)? < b.eval(index, date)?),
Expr::Lte(a, b) => b2i(a.eval(file, date)? <= b.eval(file, date)?), Expr::Lte(a, b) => b2i(a.eval(index, date)? <= b.eval(index, date)?),
Expr::Gt(a, b) => b2i(a.eval(file, date)? > b.eval(file, date)?), Expr::Gt(a, b) => b2i(a.eval(index, date)? > b.eval(index, date)?),
Expr::Gte(a, b) => b2i(a.eval(file, date)? >= b.eval(file, date)?), Expr::Gte(a, b) => b2i(a.eval(index, date)? >= b.eval(index, date)?),
Expr::Not(e) => b2i(!i2b(e.eval(file, date)?)), Expr::Not(e) => b2i(!i2b(e.eval(index, date)?)),
Expr::And(a, b) => b2i(i2b(a.eval(file, date)?) && i2b(b.eval(file, date)?)), Expr::And(a, b) => b2i(i2b(a.eval(index, date)?) && i2b(b.eval(index, date)?)),
Expr::Or(a, b) => b2i(i2b(a.eval(file, date)?) || i2b(b.eval(file, date)?)), Expr::Or(a, b) => b2i(i2b(a.eval(index, date)?) || i2b(b.eval(index, date)?)),
Expr::Xor(a, b) => b2i(i2b(a.eval(file, date)?) ^ i2b(b.eval(file, date)?)), Expr::Xor(a, b) => b2i(i2b(a.eval(index, date)?) ^ i2b(b.eval(index, date)?)),
}) })
} }
} }
@ -341,29 +341,29 @@ impl FormulaSpec {
s.limit_from_until(range) s.limit_from_until(range)
} }
fn dates(&self, file: usize, start: NaiveDate) -> Result<Dates> { fn dates(&self, index: usize, start: NaiveDate) -> Result<Dates> {
let root = self.start_delta.apply_date(file, 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(file, root, root_time)?; let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?;
Dates::new_with_time(root, root_time, other, other_time) Dates::new_with_time(root, root_time, other, other_time)
} else { } else {
let other = self.end_delta.apply_date(file, root)?; let other = self.end_delta.apply_date(index, root)?;
Dates::new(root, other) Dates::new(root, other)
}) })
} }
fn eval(&self, file: usize, date: NaiveDate) -> Result<bool> { fn eval(&self, index: usize, date: NaiveDate) -> Result<bool> {
Ok(i2b(self.start.eval(file, 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<()> {
if let Some(range) = spec.range(self) { if let Some(range) = spec.range(self) {
let file = self.command.source.file(); let index = self.command.source.file();
for day in range.days() { for day in range.days() {
if spec.eval(file, day)? { if spec.eval(index, day)? {
let dates = spec.dates(file, day)?; let dates = spec.dates(index, day)?;
self.add(self.kind(), Some(dates)); self.add(self.kind(), Some(dates));
} }
} }

View file

@ -143,7 +143,7 @@ impl From<&commands::Delta> for Delta {
} }
struct DeltaEval { struct DeltaEval {
file: usize, index: usize,
start: NaiveDate, start: NaiveDate,
start_time: Option<Time>, start_time: Option<Time>,
curr: NaiveDate, curr: NaiveDate,
@ -151,9 +151,9 @@ struct DeltaEval {
} }
impl DeltaEval { impl DeltaEval {
fn new(file: usize, start: NaiveDate, start_time: Option<Time>) -> Self { fn new(index: usize, start: NaiveDate, start_time: Option<Time>) -> Self {
Self { Self {
file, index,
start, start,
start_time, start_time,
curr: start, curr: start,
@ -163,7 +163,7 @@ impl DeltaEval {
fn err_step(&self, span: Span) -> Error { fn err_step(&self, span: Span) -> Error {
Error::DeltaInvalidStep { Error::DeltaInvalidStep {
file: self.file, index: self.index,
span, span,
start: self.start, start: self.start,
start_time: self.start_time, start_time: self.start_time,
@ -174,7 +174,7 @@ impl DeltaEval {
fn err_time(&self, span: Span) -> Error { fn err_time(&self, span: Span) -> Error {
Error::DeltaNoTime { Error::DeltaNoTime {
file: self.file, index: self.index,
span, span,
start: self.start, start: self.start,
prev: self.curr, prev: self.curr,
@ -315,27 +315,27 @@ impl Delta {
fn apply( fn apply(
&self, &self,
file: usize, index: usize,
start: (NaiveDate, Option<Time>), start: (NaiveDate, Option<Time>),
) -> Result<(NaiveDate, Option<Time>)> { ) -> Result<(NaiveDate, Option<Time>)> {
let mut eval = DeltaEval::new(file, 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)?;
} }
Ok((eval.curr, eval.curr_time)) Ok((eval.curr, eval.curr_time))
} }
pub fn apply_date(&self, file: usize, date: NaiveDate) -> Result<NaiveDate> { pub fn apply_date(&self, index: usize, date: NaiveDate) -> Result<NaiveDate> {
Ok(self.apply(file, (date, None))?.0) Ok(self.apply(index, (date, None))?.0)
} }
pub fn apply_date_time( pub fn apply_date_time(
&self, &self,
file: usize, index: usize,
date: NaiveDate, date: NaiveDate,
time: Time, time: Time,
) -> Result<(NaiveDate, Time)> { ) -> Result<(NaiveDate, Time)> {
let (date, time) = self.apply(file, (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")))
} }
} }

View file

@ -3,14 +3,13 @@ use std::result;
use chrono::NaiveDate; use chrono::NaiveDate;
use crate::files::primitives::{Span, Time}; use crate::files::primitives::{Span, Time};
use crate::files::Files;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
/// 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 {
file: usize, index: usize,
span: Span, span: Span,
start: NaiveDate, start: NaiveDate,
start_time: Option<Time>, start_time: Option<Time>,
@ -20,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 {
file: usize, index: usize,
span: Span, span: Span,
start: NaiveDate, start: NaiveDate,
prev: NaiveDate, prev: NaiveDate,
@ -30,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 {
file: usize, index: usize,
span: Span, span: Span,
from: NaiveDate, from: NaiveDate,
to: NaiveDate, to: NaiveDate,
@ -38,38 +37,48 @@ 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 { file: usize, span: Span }, MoveWithoutSource { index: usize, 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 {
file: usize, index: usize,
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 {
file: usize, index: usize,
span: Span, span: Span,
date: NaiveDate, date: NaiveDate,
}, },
/// Easter calculation failed. /// Easter calculation failed.
#[error("easter calculation failed")] #[error("easter calculation failed")]
Easter { Easter {
file: usize, index: usize,
span: Span, span: Span,
date: NaiveDate, date: NaiveDate,
msg: &'static str, msg: &'static str,
}, },
} }
pub struct SourceInfo<'a> {
pub name: Option<String>,
pub content: &'a str,
}
impl Error { impl Error {
fn print_at(files: &Files, file: &usize, span: &Span, message: String) { fn print_at<'a>(sources: &[SourceInfo<'a>], index: &usize, span: &Span, message: String) {
use pest::error as pe; use pest::error as pe;
let (name, content) = files.file(*file).expect("file index is valid");
let span = pest::Span::new(content, span.start, span.end).expect("span is valid"); 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 variant = pe::ErrorVariant::<()>::CustomError { message };
let error = pe::Error::new_from_span(variant, span).with_path(&name.to_string_lossy()); let mut error = pe::Error::new_from_span(variant, span);
if let Some(name) = &source.name {
error = error.with_path(name);
}
eprintln!("{}", error); eprintln!("{}", error);
} }
@ -80,10 +89,10 @@ impl Error {
} }
} }
pub fn print(&self, files: &Files) { pub fn print<'a>(&self, sources: &[SourceInfo<'a>]) {
match self { match self {
Error::DeltaInvalidStep { Error::DeltaInvalidStep {
file, index,
span, span,
start, start,
start_time, start_time,
@ -97,10 +106,10 @@ impl Error {
Self::fmt_date_time(*start, *start_time), Self::fmt_date_time(*start, *start_time),
Self::fmt_date_time(*prev, *prev_time), Self::fmt_date_time(*prev, *prev_time),
); );
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
Error::DeltaNoTime { Error::DeltaNoTime {
file, index,
span, span,
start, start,
prev, prev,
@ -111,10 +120,10 @@ impl Error {
\nPrevious step: {}", \nPrevious step: {}",
start, prev start, prev
); );
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
Error::RepeatDidNotMoveForwards { Error::RepeatDidNotMoveForwards {
file, index,
span, span,
from, from,
to, to,
@ -124,30 +133,30 @@ impl Error {
\nMoved from {} to {}", \nMoved from {} to {}",
from, to from, to
); );
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
Error::MoveWithoutSource { file, span } => { Error::MoveWithoutSource { index, span } => {
let msg = "Tried to move nonexisting entry".to_string(); let msg = "Tried to move nonexisting entry".to_string();
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
Error::DivByZero { file, span, date } => { Error::DivByZero { index, span, date } => {
let msg = format!( let msg = format!(
"Tried to divide by zero\ "Tried to divide by zero\
\nAt date: {}", \nAt date: {}",
date date
); );
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
Error::ModByZero { file, span, date } => { Error::ModByZero { index, span, date } => {
let msg = format!( let msg = format!(
"Tried to modulo by zero\ "Tried to modulo by zero\
\nAt date: {}", \nAt date: {}",
date date
); );
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
Error::Easter { Error::Easter {
file, index,
span, span,
date, date,
msg, msg,
@ -158,7 +167,7 @@ impl Error {
\nReason: {}", \nReason: {}",
date, msg date, msg
); );
Self::print_at(files, file, span, msg); Self::print_at(sources, index, span, msg);
} }
} }
} }

View file

@ -5,6 +5,8 @@ use std::path::{Path, PathBuf};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use tzfile::Tz; use tzfile::Tz;
use crate::eval::SourceInfo;
use self::commands::{Command, Done, File}; use self::commands::{Command, Done, File};
pub use self::error::{Error, Result}; pub use self::error::{Error, Result};
@ -164,10 +166,14 @@ impl Files {
&self.files[source.file].file.commands[source.command] &self.files[source.file].file.commands[source.command]
} }
pub fn file(&self, file: usize) -> Option<(&Path, &str)> { pub fn sources(&self) -> Vec<SourceInfo<'_>> {
self.files self.files
.get(file) .iter()
.map(|f| (&f.name as &Path, &f.file.contents as &str)) .map(|f| SourceInfo {
name: Some(f.name.to_string_lossy().to_string()),
content: &f.file.contents,
})
.collect()
} }
/// Add a [`Done`] statement to the task identified by `source`. /// Add a [`Done`] statement to the task identified by `source`.