Implement proper errors for expression evaluation

... the hard way
This commit is contained in:
Joscha 2021-12-06 22:18:57 +01:00
parent efa9b7de03
commit 5ee70bd769
4 changed files with 186 additions and 75 deletions

View file

@ -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();