Implement proper errors for expression evaluation
... the hard way
This commit is contained in:
parent
efa9b7de03
commit
5ee70bd769
4 changed files with 186 additions and 75 deletions
|
|
@ -1,7 +1,7 @@
|
|||
use chrono::{Datelike, NaiveDate};
|
||||
|
||||
use crate::files::commands::{self, Command, Expr, Var};
|
||||
use crate::files::primitives::{Spanned, Time, Weekday};
|
||||
use crate::files::commands::{self, Command};
|
||||
use crate::files::primitives::{Span, Spanned, Time, Weekday};
|
||||
|
||||
use super::super::command::CommandState;
|
||||
use super::super::date::Dates;
|
||||
|
|
@ -20,18 +20,34 @@ fn i2b(i: i64) -> bool {
|
|||
i != 0
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Var {
|
||||
JulianDay,
|
||||
Year,
|
||||
YearLength,
|
||||
YearDay,
|
||||
YearDayReverse,
|
||||
YearWeek,
|
||||
YearWeekReverse,
|
||||
Month,
|
||||
MonthLength,
|
||||
MonthWeek,
|
||||
MonthWeekReverse,
|
||||
Day,
|
||||
DayReverse,
|
||||
IsoYear,
|
||||
IsoYearLength,
|
||||
IsoWeek,
|
||||
Weekday,
|
||||
Easter(Span),
|
||||
IsWeekday,
|
||||
IsWeekend,
|
||||
IsLeapYear,
|
||||
}
|
||||
|
||||
impl Var {
|
||||
fn eval(self, date: NaiveDate) -> Result<i64> {
|
||||
Ok(match self {
|
||||
Var::True => 1,
|
||||
Var::False => 0,
|
||||
Var::Monday => 1,
|
||||
Var::Tuesday => 2,
|
||||
Var::Wednesday => 3,
|
||||
Var::Thursday => 4,
|
||||
Var::Friday => 5,
|
||||
Var::Saturday => 6,
|
||||
Var::Sunday => 7,
|
||||
Var::JulianDay => date.num_days_from_ce().into(),
|
||||
Var::Year => date.year().into(),
|
||||
Var::YearLength => util::year_length(date.year()).into(),
|
||||
|
|
@ -63,9 +79,9 @@ impl Var {
|
|||
let wd: Weekday = date.weekday().into();
|
||||
wd.num().into()
|
||||
}
|
||||
Var::Easter => {
|
||||
Var::Easter(span) => {
|
||||
let e = computus::gregorian(date.year()).map_err(|e| Error::Easter {
|
||||
span: todo!(),
|
||||
span,
|
||||
date,
|
||||
msg: e,
|
||||
})?;
|
||||
|
|
@ -84,33 +100,123 @@ impl Var {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Expr {
|
||||
Lit(i64),
|
||||
Var(Var),
|
||||
Neg(Box<Expr>),
|
||||
Add(Box<Expr>, Box<Expr>),
|
||||
Sub(Box<Expr>, Box<Expr>),
|
||||
Mul(Box<Expr>, Box<Expr>),
|
||||
Div(Box<Expr>, Box<Expr>, Span),
|
||||
Mod(Box<Expr>, Box<Expr>, Span),
|
||||
Eq(Box<Expr>, Box<Expr>),
|
||||
Neq(Box<Expr>, Box<Expr>),
|
||||
Lt(Box<Expr>, Box<Expr>),
|
||||
Lte(Box<Expr>, Box<Expr>),
|
||||
Gt(Box<Expr>, Box<Expr>),
|
||||
Gte(Box<Expr>, Box<Expr>),
|
||||
Not(Box<Expr>),
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
Xor(Box<Expr>, Box<Expr>),
|
||||
}
|
||||
|
||||
impl From<&Spanned<commands::Expr>> for Expr {
|
||||
fn from(expr: &Spanned<commands::Expr>) -> Self {
|
||||
fn conv(expr: &Spanned<commands::Expr>) -> Box<Expr> {
|
||||
Box::new(expr.into())
|
||||
}
|
||||
|
||||
match &expr.value {
|
||||
commands::Expr::Lit(l) => Self::Lit(*l),
|
||||
commands::Expr::Var(v) => match v {
|
||||
commands::Var::True => Self::Lit(1),
|
||||
commands::Var::False => Self::Lit(0),
|
||||
commands::Var::Monday => Self::Lit(1),
|
||||
commands::Var::Tuesday => Self::Lit(2),
|
||||
commands::Var::Wednesday => Self::Lit(3),
|
||||
commands::Var::Thursday => Self::Lit(4),
|
||||
commands::Var::Friday => Self::Lit(5),
|
||||
commands::Var::Saturday => Self::Lit(6),
|
||||
commands::Var::Sunday => Self::Lit(7),
|
||||
commands::Var::JulianDay => Self::Var(Var::JulianDay),
|
||||
commands::Var::Year => Self::Var(Var::Year),
|
||||
commands::Var::YearLength => Self::Var(Var::YearLength),
|
||||
commands::Var::YearDay => Self::Var(Var::YearDay),
|
||||
commands::Var::YearDayReverse => Self::Var(Var::YearDayReverse),
|
||||
commands::Var::YearWeek => Self::Var(Var::YearWeek),
|
||||
commands::Var::YearWeekReverse => Self::Var(Var::YearWeekReverse),
|
||||
commands::Var::Month => Self::Var(Var::Month),
|
||||
commands::Var::MonthLength => Self::Var(Var::MonthLength),
|
||||
commands::Var::MonthWeek => Self::Var(Var::MonthWeek),
|
||||
commands::Var::MonthWeekReverse => Self::Var(Var::MonthWeekReverse),
|
||||
commands::Var::Day => Self::Var(Var::Day),
|
||||
commands::Var::DayReverse => Self::Var(Var::DayReverse),
|
||||
commands::Var::IsoYear => Self::Var(Var::IsoYear),
|
||||
commands::Var::IsoYearLength => Self::Var(Var::IsoYearLength),
|
||||
commands::Var::IsoWeek => Self::Var(Var::IsoWeek),
|
||||
commands::Var::Weekday => Self::Var(Var::Weekday),
|
||||
commands::Var::Easter => Self::Var(Var::Easter(expr.span)),
|
||||
commands::Var::IsWeekday => Self::Var(Var::IsWeekday),
|
||||
commands::Var::IsWeekend => Self::Var(Var::IsWeekend),
|
||||
commands::Var::IsLeapYear => Self::Var(Var::IsLeapYear),
|
||||
},
|
||||
commands::Expr::Paren(i) => i.as_ref().into(),
|
||||
commands::Expr::Neg(i) => Self::Neg(conv(i)),
|
||||
commands::Expr::Add(a, b) => Self::Add(conv(a), conv(b)),
|
||||
commands::Expr::Sub(a, b) => Self::Sub(conv(a), conv(b)),
|
||||
commands::Expr::Mul(a, b) => Self::Mul(conv(a), conv(b)),
|
||||
commands::Expr::Div(a, b) => Self::Div(conv(a), conv(b), expr.span),
|
||||
commands::Expr::Mod(a, b) => Self::Mod(conv(a), conv(b), expr.span),
|
||||
commands::Expr::Eq(a, b) => Self::Eq(conv(a), conv(b)),
|
||||
commands::Expr::Neq(a, b) => Self::Neq(conv(a), conv(b)),
|
||||
commands::Expr::Lt(a, b) => Self::Lt(conv(a), conv(b)),
|
||||
commands::Expr::Lte(a, b) => Self::Lte(conv(a), conv(b)),
|
||||
commands::Expr::Gt(a, b) => Self::Gt(conv(a), conv(b)),
|
||||
commands::Expr::Gte(a, b) => Self::Gte(conv(a), conv(b)),
|
||||
commands::Expr::Not(i) => Self::Not(conv(i)),
|
||||
commands::Expr::And(a, b) => Self::And(conv(a), conv(b)),
|
||||
commands::Expr::Or(a, b) => Self::Or(conv(a), conv(b)),
|
||||
commands::Expr::Xor(a, b) => Self::Xor(conv(a), conv(b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Weekday> for Expr {
|
||||
fn from(wd: Weekday) -> Self {
|
||||
match wd {
|
||||
Weekday::Monday => Self::Lit(1),
|
||||
Weekday::Tuesday => Self::Lit(2),
|
||||
Weekday::Wednesday => Self::Lit(3),
|
||||
Weekday::Thursday => Self::Lit(4),
|
||||
Weekday::Friday => Self::Lit(5),
|
||||
Weekday::Saturday => Self::Lit(6),
|
||||
Weekday::Sunday => Self::Lit(7),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
fn eval(&self, date: NaiveDate) -> Result<i64> {
|
||||
Ok(match self {
|
||||
Expr::Lit(l) => *l,
|
||||
Expr::Var(v) => v.eval(date)?,
|
||||
Expr::Paren(e) => e.eval(date)?,
|
||||
Expr::Neg(e) => -e.eval(date)?,
|
||||
Expr::Add(a, b) => a.eval(date)? + b.eval(date)?,
|
||||
Expr::Sub(a, b) => a.eval(date)? - b.eval(date)?,
|
||||
Expr::Mul(a, b) => a.eval(date)? * b.eval(date)?,
|
||||
Expr::Div(a, b) => {
|
||||
Expr::Div(a, b, span) => {
|
||||
let b = b.eval(date)?;
|
||||
if b == 0 {
|
||||
return Err(Error::DivByZero {
|
||||
span: todo!(),
|
||||
date,
|
||||
});
|
||||
return Err(Error::DivByZero { span: *span, date });
|
||||
}
|
||||
a.eval(date)?.div_euclid(b)
|
||||
}
|
||||
Expr::Mod(a, b) => {
|
||||
Expr::Mod(a, b, span) => {
|
||||
let b = b.eval(date)?;
|
||||
if b == 0 {
|
||||
return Err(Error::ModByZero {
|
||||
span: todo!(),
|
||||
date,
|
||||
});
|
||||
return Err(Error::ModByZero { span: *span, date });
|
||||
}
|
||||
a.eval(date)?.rem_euclid(b)
|
||||
}
|
||||
|
|
@ -137,7 +243,10 @@ pub struct FormulaSpec {
|
|||
|
||||
impl From<&commands::FormulaSpec> for FormulaSpec {
|
||||
fn from(spec: &commands::FormulaSpec) -> Self {
|
||||
let start: Expr = spec.start.as_ref().cloned().unwrap_or(Expr::Lit(1));
|
||||
let start: Expr = match &spec.start {
|
||||
Some(expr) => expr.into(),
|
||||
None => Expr::Lit(1), // Always true
|
||||
};
|
||||
|
||||
let start_delta: Delta = spec
|
||||
.start_delta
|
||||
|
|
@ -169,7 +278,7 @@ impl From<&commands::WeekdaySpec> for FormulaSpec {
|
|||
fn from(spec: &commands::WeekdaySpec) -> Self {
|
||||
let start = Expr::Eq(
|
||||
Box::new(Expr::Var(Var::Weekday)),
|
||||
Box::new(Expr::Var(spec.start.into())),
|
||||
Box::new(spec.start.into()),
|
||||
);
|
||||
|
||||
let mut end_delta = Delta::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue