From cb7c87b8b23a6b0673c72e6eec7b1415975964e8 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 6 Dec 2021 12:47:19 +0000 Subject: [PATCH] Implement formula evaluation for DATE statement --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/eval/command/formula.rs | 176 ++++++++++++++++++++++++++++++++++-- src/eval/error.rs | 12 +++ src/eval/range.rs | 5 + src/eval/util.rs | 28 ++++-- src/files/primitives.rs | 38 ++++---- 7 files changed, 237 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f46d9ef..4c891b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,12 @@ dependencies = [ "vec_map", ] +[[package]] +name = "computus" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9eaa21df489b1e50af464db43c1fc69f60492060ac7baae3996fe8a5f476790" + [[package]] name = "digest" version = "0.8.1" @@ -370,6 +376,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "computus", "pest", "pest_derive", "structopt", diff --git a/Cargo.toml b/Cargo.toml index d0f72cb..6138e05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] anyhow = "1.0.45" chrono = "0.4.19" +computus = "1.0.0" pest = "2.1.3" pest_derive = "2.1.0" structopt = "0.3.25" diff --git a/src/eval/command/formula.rs b/src/eval/command/formula.rs index 141f555..509b1fa 100644 --- a/src/eval/command/formula.rs +++ b/src/eval/command/formula.rs @@ -1,12 +1,134 @@ -use crate::files::commands::{self, Expr, Var}; -use crate::files::primitives::{Spanned, Time}; +use chrono::{Datelike, NaiveDate}; + +use crate::files::commands::{self, Command, Expr, Var}; +use crate::files::primitives::{Spanned, Time, Weekday}; use super::super::command::CommandState; +use super::super::date::Dates; use super::super::delta::{Delta, DeltaStep}; -use super::super::Result; +use super::super::{util, DateRange, Error, Result}; + +fn b2i(b: bool) -> i64 { + if b { + 1 + } else { + 0 + } +} + +fn i2b(i: i64) -> bool { + i != 0 +} + +impl Var { + fn eval(self, date: NaiveDate) -> Result { + 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(), + 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.month0().div_euclid(7) + 1).into(), + Var::MonthWeekReverse => { + #[allow(non_snake_case)] + let mD = util::month_length(date.year(), date.month()) - date.month0(); + (mD.div_euclid(7) + 1).into() + } + Var::Day => date.day().into(), + Var::DayReverse => { + let ml = util::month_length(date.year(), date.month()); + (ml - date.month0()).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 => { + let e = computus::gregorian(date.year()).map_err(|e| Error::Easter { + span: todo!(), + 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())), + }) + } +} + +impl Expr { + fn eval(&self, date: NaiveDate) -> Result { + 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) => { + let b = b.eval(date)?; + if b == 0 { + return Err(Error::DivByZero { + span: todo!(), + date, + }); + } + a.eval(date)?.div_euclid(b) + } + Expr::Mod(a, b) => { + let b = b.eval(date)?; + if b == 0 { + return Err(Error::ModByZero { + span: todo!(), + date, + }); + } + a.eval(date)?.rem_euclid(b) + } + Expr::Eq(a, b) => b2i(a.eval(date)? == b.eval(date)?), + Expr::Neq(a, b) => b2i(a.eval(date)? != b.eval(date)?), + Expr::Lt(a, b) => b2i(a.eval(date)? < b.eval(date)?), + Expr::Lte(a, b) => b2i(a.eval(date)? <= b.eval(date)?), + Expr::Gt(a, b) => b2i(a.eval(date)? > b.eval(date)?), + Expr::Gte(a, b) => b2i(a.eval(date)? >= b.eval(date)?), + Expr::Not(e) => b2i(!i2b(e.eval(date)?)), + Expr::And(a, b) => b2i(i2b(a.eval(date)?) && i2b(b.eval(date)?)), + Expr::Or(a, b) => b2i(i2b(a.eval(date)?) || i2b(b.eval(date)?)), + Expr::Xor(a, b) => b2i(i2b(a.eval(date)?) ^ i2b(b.eval(date)?)), + }) + } +} pub struct FormulaSpec { - // TODO Implement more efficient exprs and expr evaluation pub start: Expr, pub start_delta: Delta, pub start_time: Option