timeblok/ir/
filter.rs

1use std::fmt::Debug;
2
3use dyn_clone::DynClone;
4
5use crate::environment::Environment;
6use crate::ir::NumVal::{Number, Unsure};
7use crate::ir::*;
8use crate::resolver::{resolve_date, resolve_range};
9
10pub trait Filter<T>: Debug + DynClone {
11    fn check(&self, value: &T, env: Option<&Environment>) -> bool;
12    fn filter(&self, values: Vec<T>, env: Option<&Environment>) -> Vec<T> {
13        // PERFORMANCE: Change this to parallel execution, use references and lifetimes to improve memory efficiency
14        let mut res = vec![];
15        for value in values {
16            if self.check(&value, env) {
17                res.push(value);
18            }
19        }
20        res
21    }
22}
23
24dyn_clone::clone_trait_object!(<T> Filter<T>);
25
26#[derive(Debug, Clone)]
27pub enum Op {
28    OR,
29    And,
30}
31
32pub type BDF<T> = Box<dyn Filter<T>>;
33
34#[derive(Debug, Clone)]
35pub struct BinFilt<T: Debug> {
36    pub lhs: BDF<T>,
37    pub rhs: BDF<T>,
38    pub op: Op,
39}
40
41impl<T: Debug + Clone> Filter<T> for BinFilt<T> {
42    fn check(&self, value: &T, env: Option<&Environment>) -> bool {
43        match self.op {
44            Op::OR => self.lhs.check(value, env) || self.rhs.check(value, env),
45            Op::And => self.lhs.check(value, env) && self.rhs.check(value, env),
46        }
47    }
48}
49
50#[derive(Debug, Clone)]
51pub struct ExcludeFilt<T> {
52    pub target: BDF<T>,
53}
54
55impl<T: Debug + Clone> ExcludeFilt<T> {
56    pub fn new(target: BDF<T>) -> Self {
57        ExcludeFilt { target }
58    }
59}
60
61impl<T: Debug + Clone> Filter<T> for ExcludeFilt<T> {
62    fn check(&self, value: &T, env: Option<&Environment>) -> bool {
63        !self.target.check(value, env)
64    }
65}
66
67impl Filter<NumVal> for NumRange {
68    fn check(&self, value: &NumVal, _env: Option<&Environment>) -> bool {
69        match *value {
70            Number(target) => match (&self.start, &self.end) {
71                (Unsure, Unsure) => true,
72                (Unsure, Number(nd)) => target <= *nd,
73                (Number(st), Unsure) => target >= *st,
74                (Number(st), Number(nd)) => target >= *st && target <= *nd,
75            },
76            _ => true,
77        }
78    }
79}
80
81impl Filter<NumVal> for NumVal {
82    fn check(&self, value: &NumVal, _env: Option<&Environment>) -> bool {
83        if matches!(self, Unsure) {
84            true
85        } else {
86            self == value
87        }
88    }
89}
90
91impl Filter<ExactDate> for ExactRange {
92    fn check(&self, value: &ExactDate, _env: Option<&Environment>) -> bool {
93        match self {
94            ExactRange::TimeRange(tr) => {
95                // unwrap or return false
96                let start = tr.start.date.to_chrono().unwrap();
97                let end = tr.end.date.to_chrono().unwrap();
98                let target = value.to_chrono().unwrap();
99                target >= start && target <= end
100            }
101            ExactRange::AllDay(date) => date == value,
102        }
103    }
104}
105
106impl Filter<Date> for Range {
107    fn check(&self, value: &Date, env: Option<&Environment>) -> bool {
108        // TODO: Change Resolving to Environment based
109        let exact_range = resolve_range(self, env.unwrap()).unwrap();
110        let exact_date = resolve_date(value, env.unwrap()).unwrap();
111        exact_range.check(&exact_date, env)
112    }
113}
114
115impl Filter<NumVal> for FlexField {
116    fn check(&self, value: &NumVal, env: Option<&Environment>) -> bool {
117        match self {
118            FlexField::NumRange(nr) => nr.check(value, env),
119            FlexField::NumVal(nv) => nv.check(value, env),
120        }
121    }
122}
123
124impl Filter<ExactDate> for FlexDate {
125    fn check(&self, value: &ExactDate, env: Option<&Environment>) -> bool {
126        self.year.check(&Number(value.year as i64), env)
127            && self.month.check(&Number(value.month as i64), env)
128            && self.day.check(&Number(value.day as i64), env)
129    }
130}
131
132impl Filter<Date> for FlexDate {
133    fn check(&self, value: &Date, env: Option<&Environment>) -> bool {
134        let exact_date = resolve_date(value, env.unwrap()).unwrap();
135        self.year.check(&Number(exact_date.year as i64), env)
136            && self.month.check(&Number(exact_date.month as i64), env)
137            && self.day.check(&Number(exact_date.day as i64), env)
138    }
139}
140
141// Add a unit test for filters
142// Thank you copilot
143mod tests {
144    use super::*;
145    use crate::ir::Range;
146    use crate::ir::TimeRange;
147    #[test]
148    fn test_flex_date() {
149        let fd = FlexDate {
150            year: Box::new(Number(2023)),
151            month: Box::new(NumRange {
152                start: Number(6),
153                end: Number(10),
154            }),
155            day: Box::new(NumRange {
156                start: Number(8),
157                end: Number(15),
158            }),
159        };
160        assert!(fd.check(
161            &ExactDate {
162                year: 2023,
163                month: 6,
164                day: 8
165            },
166            None
167        ));
168        assert!(!fd.check(
169            &ExactDate {
170                year: 2023,
171                month: 6,
172                day: 7
173            },
174            None
175        ));
176        assert!(!fd.check(
177            &ExactDate {
178                year: 2023,
179                month: 5,
180                day: 8
181            },
182            None
183        ));
184        assert!(!fd.check(
185            &ExactDate {
186                year: 2022,
187                month: 6,
188                day: 8
189            },
190            None
191        ));
192    }
193
194    #[test]
195    fn test_time_range() {
196        let trange = TimeRange {
197            start: DateTime::from_ymd(2020, 1, 3),
198            end: DateTime::from_ymd(2020, 2, 1),
199        };
200        eprintln!("{:?}", trange);
201        let range = Range::Time(trange);
202        let env = Environment::from_exact(ExactDateTime::from_ymd_hms(2020, 1, 1, 1, 1, 1));
203        assert!(range.check(&Date::from_ymd(2020, 1, 3), Some(&env)));
204        assert!(!range.check(&Date::from_ymd(2020, 1, 2), Some(&env)));
205        assert!(range.check(&Date::from_ymd(2020, 2, 1), Some(&env)));
206    }
207    #[test]
208    fn test_tree() {
209        // Testing combined filters
210        let orfilt = BinFilt {
211            lhs: Box::new(NumRange {
212                start: Number(1),
213                end: Number(5),
214            }),
215            rhs: Box::new(NumRange {
216                start: Number(10),
217                end: Number(15),
218            }),
219            op: Op::OR,
220        };
221        assert!(orfilt.check(&Number(1), None));
222        assert!(!orfilt.check(&Number(8), None));
223        assert!(orfilt.check(&Number(13), None));
224        let andfilt = BinFilt {
225            lhs: Box::new(NumRange {
226                start: Number(1),
227                end: Number(8),
228            }),
229            rhs: Box::new(NumRange {
230                start: Number(3),
231                end: Unsure,
232            }),
233            op: Op::And,
234        };
235        assert!(!andfilt.check(&Number(1), None));
236        assert!(andfilt.check(&Number(8), None));
237        assert!(!andfilt.check(&Number(13), None));
238        let combfilt = BinFilt {
239            lhs: Box::new(ExcludeFilt {
240                target: Box::new(orfilt),
241            }),
242            rhs: Box::new(andfilt),
243            op: Op::OR,
244        };
245        assert!(combfilt.check(&Number(3), None));
246        assert!(combfilt.check(&Number(9), None));
247        assert!(!combfilt.check(&Number(2), None));
248    }
249}