Skip to main content

elo_rust/codegen/
temporal.rs

1//! Temporal value code generation for dates, times, and durations
2
3use proc_macro2::TokenStream;
4use quote::quote;
5
6/// Generates code for temporal operations
7#[derive(Debug)]
8pub struct TemporalGenerator;
9
10impl TemporalGenerator {
11    /// Create a new temporal generator
12    pub fn new() -> Self {
13        Self
14    }
15
16    /// Generate code for a date literal (ISO8601)
17    pub fn date(&self, date_str: &str) -> TokenStream {
18        quote! {
19            {
20                use chrono::NaiveDate;
21                NaiveDate::parse_from_str(#date_str, "%Y-%m-%d")
22                    .expect("Invalid date format")
23            }
24        }
25    }
26
27    /// Generate code for a datetime literal (RFC3339)
28    pub fn datetime(&self, datetime_str: &str) -> TokenStream {
29        quote! {
30            {
31                use chrono::DateTime;
32                DateTime::parse_from_rfc3339(#datetime_str)
33                    .map(|dt| dt.with_timezone(&chrono::Utc))
34                    .expect("Invalid datetime format")
35            }
36        }
37    }
38
39    /// Generate code for a duration literal (ISO8601)
40    pub fn duration(&self, duration_str: &str) -> TokenStream {
41        quote! {
42            {
43                use chrono::Duration;
44                use elo_rust::runtime::TemporalValue;
45                TemporalValue::parse_duration(#duration_str)
46                    .map(|tv| match tv {
47                        TemporalValue::Duration(d) => d,
48                        _ => Duration::zero(),
49                    })
50                    .unwrap_or_else(|_| Duration::zero())
51            }
52        }
53    }
54
55    /// Generate code for a temporal keyword
56    pub fn keyword(&self, keyword: &str) -> TokenStream {
57        match keyword {
58            "NOW" => quote! {
59                {
60                    use chrono::Utc;
61                    Utc::now()
62                }
63            },
64            "TODAY" => quote! {
65                {
66                    use chrono::Local;
67                    Local::now().naive_local().date()
68                }
69            },
70            "TOMORROW" => quote! {
71                {
72                    use chrono::{Local, Duration};
73                    (Local::now().naive_local().date() + Duration::days(1))
74                }
75            },
76            "YESTERDAY" => quote! {
77                {
78                    use chrono::{Local, Duration};
79                    (Local::now().naive_local().date() - Duration::days(1))
80                }
81            },
82            "START_OF_DAY" => quote! {
83                {
84                    use chrono::Local;
85                    let today = Local::now().naive_local().date();
86                    today.and_hms_opt(0, 0, 0).unwrap()
87                }
88            },
89            "END_OF_DAY" => quote! {
90                {
91                    use chrono::Local;
92                    let today = Local::now().naive_local().date();
93                    today.and_hms_opt(23, 59, 59).unwrap()
94                }
95            },
96            "START_OF_WEEK" => quote! {
97                {
98                    use chrono::{Local, Datelike};
99                    let today = Local::now().naive_local().date();
100                    let days_since_monday = today.weekday().number_from_monday() - 1;
101                    today - chrono::Duration::days(days_since_monday as i64)
102                }
103            },
104            "END_OF_WEEK" => quote! {
105                {
106                    use chrono::{Local, Datelike};
107                    let today = Local::now().naive_local().date();
108                    let days_until_sunday = 7 - today.weekday().number_from_monday();
109                    today + chrono::Duration::days(days_until_sunday as i64)
110                }
111            },
112            "START_OF_MONTH" => quote! {
113                {
114                    use chrono::Local;
115                    let today = Local::now().naive_local().date();
116                    today.with_day(1).unwrap()
117                }
118            },
119            "END_OF_MONTH" => quote! {
120                {
121                    use chrono::{Local, NaiveDate};
122                    let today = Local::now().naive_local().date();
123                    let last_day = if today.month() == 12 {
124                        NaiveDate::from_ymd_opt(today.year() + 1, 1, 1)
125                            .unwrap()
126                            - chrono::Duration::days(1)
127                    } else {
128                        NaiveDate::from_ymd_opt(today.year(), today.month() + 1, 1)
129                            .unwrap()
130                            - chrono::Duration::days(1)
131                    };
132                    last_day
133                }
134            },
135            "START_OF_QUARTER" => quote! {
136                {
137                    use chrono::Local;
138                    let today = Local::now().naive_local().date();
139                    let quarter = (today.month() - 1) / 3;
140                    let month = quarter * 3 + 1;
141                    today.with_month(month).unwrap().with_day(1).unwrap()
142                }
143            },
144            "END_OF_QUARTER" => quote! {
145                {
146                    use chrono::{Local, NaiveDate};
147                    let today = Local::now().naive_local().date();
148                    let quarter = (today.month() - 1) / 3;
149                    let next_quarter_month = (quarter + 1) * 3 + 1;
150                    let year = if next_quarter_month > 12 {
151                        today.year() + 1
152                    } else {
153                        today.year()
154                    };
155                    let month = if next_quarter_month > 12 {
156                        next_quarter_month - 12
157                    } else {
158                        next_quarter_month
159                    };
160                    NaiveDate::from_ymd_opt(year, month, 1)
161                        .unwrap()
162                        - chrono::Duration::days(1)
163                }
164            },
165            "START_OF_YEAR" => quote! {
166                {
167                    use chrono::Local;
168                    let today = Local::now().naive_local().date();
169                    today.with_month(1).unwrap().with_day(1).unwrap()
170                }
171            },
172            "END_OF_YEAR" => quote! {
173                {
174                    use chrono::Local;
175                    let today = Local::now().naive_local().date();
176                    today.with_month(12).unwrap().with_day(31).unwrap()
177                }
178            },
179            "BEGINNING_OF_TIME" => quote! {
180                {
181                    use chrono::NaiveDate;
182                    NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()
183                }
184            },
185            "END_OF_TIME" => quote! {
186                {
187                    use chrono::NaiveDate;
188                    NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()
189                }
190            },
191            _ => quote!(),
192        }
193    }
194
195    /// Generate code for temporal comparison (dates)
196    pub fn date_compare(&self, left: TokenStream, op: &str, right: TokenStream) -> TokenStream {
197        match op {
198            "<" => quote!(#left < #right),
199            "<=" => quote!(#left <= #right),
200            ">" => quote!(#left > #right),
201            ">=" => quote!(#left >= #right),
202            "==" => quote!(#left == #right),
203            "!=" => quote!(#left != #right),
204            _ => quote!(),
205        }
206    }
207
208    /// Generate code for temporal arithmetic (dates + durations)
209    pub fn temporal_add(&self, left: TokenStream, right: TokenStream) -> TokenStream {
210        quote! {
211            (#left + #right)
212        }
213    }
214
215    /// Generate code for temporal subtraction
216    pub fn temporal_subtract(&self, left: TokenStream, right: TokenStream) -> TokenStream {
217        quote! {
218            (#left - #right)
219        }
220    }
221}
222
223impl Default for TemporalGenerator {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_temporal_generator_creation() {
235        let _gen = TemporalGenerator::new();
236    }
237
238    #[test]
239    fn test_date_literal_generation() {
240        let gen = TemporalGenerator::new();
241        let token = gen.date("2024-01-15");
242        let token_str = token.to_string();
243        assert!(token_str.contains("parse_from_str"));
244        assert!(token_str.contains("2024-01-15"));
245    }
246
247    #[test]
248    fn test_datetime_literal_generation() {
249        let gen = TemporalGenerator::new();
250        let token = gen.datetime("2024-01-15T10:30:00Z");
251        let token_str = token.to_string();
252        assert!(token_str.contains("parse_from_rfc3339"));
253    }
254
255    #[test]
256    fn test_now_keyword() {
257        let gen = TemporalGenerator::new();
258        let token = gen.keyword("NOW");
259        let token_str = token.to_string();
260        assert!(token_str.contains("Utc"));
261    }
262
263    #[test]
264    fn test_today_keyword() {
265        let gen = TemporalGenerator::new();
266        let token = gen.keyword("TODAY");
267        let token_str = token.to_string();
268        assert!(token_str.contains("Local"));
269    }
270
271    #[test]
272    fn test_tomorrow_keyword() {
273        let gen = TemporalGenerator::new();
274        let token = gen.keyword("TOMORROW");
275        let token_str = token.to_string();
276        assert!(token_str.contains("days"));
277    }
278
279    #[test]
280    fn test_start_of_day_keyword() {
281        let gen = TemporalGenerator::new();
282        let token = gen.keyword("START_OF_DAY");
283        let token_str = token.to_string();
284        assert!(token_str.contains("0"));
285    }
286
287    #[test]
288    fn test_date_comparison() {
289        let gen = TemporalGenerator::new();
290        let left = quote!(date1);
291        let right = quote!(date2);
292        let token = gen.date_compare(left, "<", right);
293        let token_str = token.to_string();
294        assert!(token_str.contains("<"));
295    }
296}