use chrono::{Datelike, Duration, NaiveDate}; use crate::files::commands; use crate::files::primitives::{Span, Spanned, Time, Weekday}; use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; use super::super::delta::{Delta, DeltaStep}; use super::super::{util, DateRange, Error}; use super::EvalCommand; fn b2i(b: bool) -> i64 { if b { 1 } else { 0 } } 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, IsIsoLeapYear, } impl Var { fn eval(self, index: S, date: NaiveDate) -> Result> { Ok(match self { Var::JulianDay => date.num_days_from_ce().into(), Var::Year => date.year().into(), Var::YearLength => util::year_length(date.year()).into(), Var::YearDay => date.ordinal().into(), Var::YearDayReverse => (util::year_length(date.year()) - date.ordinal0()).into(), Var::YearWeek => (date.ordinal0().div_euclid(7) + 1).into(), Var::YearWeekReverse => { #[allow(non_snake_case)] let yD = util::year_length(date.year()) - date.ordinal(); (yD.div_euclid(7) + 1).into() } Var::Month => date.month().into(), Var::MonthLength => util::month_length(date.year(), date.month()).into(), Var::MonthWeek => (date.day0().div_euclid(7) + 1).into(), Var::MonthWeekReverse => { #[allow(non_snake_case)] let mD = util::month_length(date.year(), date.month()) - date.day(); (mD.div_euclid(7) + 1).into() } Var::Day => date.day().into(), Var::DayReverse => { let ml = util::month_length(date.year(), date.month()); (ml - date.day0()).into() } Var::IsoYear => date.iso_week().year().into(), Var::IsoYearLength => util::iso_year_length(date.iso_week().year()).into(), Var::IsoWeek => date.iso_week().week().into(), Var::Weekday => { let wd: Weekday = date.weekday().into(); wd.num().into() } Var::Easter(span) => { let e = computus::gregorian(date.year()).map_err(|e| Error::Easter { index, span, date, msg: e, })?; NaiveDate::from_ymd(e.year, e.month, e.day).ordinal().into() } Var::IsWeekday => { let wd: Weekday = date.weekday().into(); b2i(!wd.is_weekend()) } Var::IsWeekend => { let wd: Weekday = date.weekday().into(); b2i(wd.is_weekend()) } Var::IsLeapYear => b2i(util::is_leap_year(date.year())), Var::IsIsoLeapYear => b2i(util::is_iso_leap_year(date.year())), }) } } #[derive(Debug)] pub enum Expr { Lit(i64), Var(Var), Neg(Box), Add(Box, Box), Sub(Box, Box), Mul(Box, Box), Div(Box, Box, Span), Mod(Box, Box, Span), Eq(Box, Box), Neq(Box, Box), Lt(Box, Box), Lte(Box, Box), Gt(Box, Box), Gte(Box, Box), Not(Box), And(Box, Box), Or(Box, Box), Xor(Box, Box), } impl From<&Spanned> for Expr { fn from(expr: &Spanned) -> Self { fn conv(expr: &Spanned) -> Box { 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::Var::IsIsoLeapYear => Self::Var(Var::IsIsoLeapYear), }, 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 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, index: S, date: NaiveDate) -> Result> { Ok(match self { Expr::Lit(l) => *l, Expr::Var(v) => v.eval(index, date)?, Expr::Neg(e) => -e.eval(index, date)?, Expr::Add(a, b) => a.eval(index, date)? + b.eval(index, date)?, Expr::Sub(a, b) => a.eval(index, date)? - b.eval(index, date)?, Expr::Mul(a, b) => a.eval(index, date)? * b.eval(index, date)?, Expr::Div(a, b, span) => { let b = b.eval(index, date)?; if b == 0 { return Err(Error::DivByZero { index, span: *span, date, }); } a.eval(index, date)?.div_euclid(b) } Expr::Mod(a, b, span) => { let b = b.eval(index, date)?; if b == 0 { return Err(Error::ModByZero { index, span: *span, date, }); } a.eval(index, date)?.rem_euclid(b) } Expr::Eq(a, b) => b2i(a.eval(index, date)? == b.eval(index, date)?), Expr::Neq(a, b) => b2i(a.eval(index, date)? != b.eval(index, date)?), Expr::Lt(a, b) => b2i(a.eval(index, date)? < b.eval(index, date)?), Expr::Lte(a, b) => b2i(a.eval(index, date)? <= b.eval(index, date)?), Expr::Gt(a, b) => b2i(a.eval(index, date)? > b.eval(index, date)?), Expr::Gte(a, b) => b2i(a.eval(index, date)? >= b.eval(index, date)?), Expr::Not(e) => b2i(!i2b(e.eval(index, date)?)), Expr::And(a, b) => b2i(i2b(a.eval(index, date)?) && i2b(b.eval(index, date)?)), Expr::Or(a, b) => b2i(i2b(a.eval(index, date)?) || i2b(b.eval(index, date)?)), Expr::Xor(a, b) => b2i(i2b(a.eval(index, date)?) ^ i2b(b.eval(index, date)?)), }) } } pub struct FormulaSpec { pub start: Expr, pub start_delta: Delta, pub start_time: Option