elo_rust/codegen/
temporal.rs1use proc_macro2::TokenStream;
4use quote::quote;
5
6#[derive(Debug)]
8pub struct TemporalGenerator;
9
10impl TemporalGenerator {
11 pub fn new() -> Self {
13 Self
14 }
15
16 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 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 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 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 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 pub fn temporal_add(&self, left: TokenStream, right: TokenStream) -> TokenStream {
210 quote! {
211 (#left + #right)
212 }
213 }
214
215 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}