Skip to main content

elo_rust/codegen/
functions.rs

1//! Standard library function call code generation
2
3use proc_macro2::TokenStream;
4use quote::quote;
5
6/// Maximum timeout for regex operations (in milliseconds)
7/// Reason: Reserved for future use with timeout guards on regex matching
8#[allow(dead_code)]
9const REGEX_TIMEOUT_MS: u64 = 1000;
10
11/// Generates code for function calls
12#[derive(Debug)]
13pub struct FunctionGenerator;
14
15impl FunctionGenerator {
16    /// Create a new function generator
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// Generate code for a function call
22    pub fn call(&self, name: &str, args: Vec<TokenStream>) -> TokenStream {
23        match name {
24            // String functions
25            "matches" | "contains" | "length" | "uppercase" | "lowercase" | "trim"
26            | "starts_with" | "ends_with" => self.string_function(name, args),
27            // DateTime functions
28            "today" | "now" | "age" | "days_since" | "date" => self.datetime_function(name, args),
29            // Array functions
30            "any" | "all" => self.array_function(name, args),
31            _ => quote!(),
32        }
33    }
34
35    /// Generate code for a string function
36    pub fn string_function(&self, name: &str, args: Vec<TokenStream>) -> TokenStream {
37        match name {
38            "matches" => {
39                if args.len() < 2 {
40                    return quote!();
41                }
42                let subject = &args[0];
43                let pattern = &args[1];
44                quote! {
45                    {
46                        use regex::Regex;
47                        // Validate regex pattern and compile with timeout guard
48                        match Regex::new(#pattern) {
49                            Ok(re) => {
50                                // Rust's regex crate provides built-in ReDoS protection
51                                // by using a different matching algorithm for certain patterns
52                                match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
53                                    re.is_match(#subject)
54                                })) {
55                                    Ok(result) => result,
56                                    Err(_) => {
57                                        eprintln!(
58                                            "⚠️  Regex matching failed: pattern may cause performance issues"
59                                        );
60                                        false
61                                    }
62                                }
63                            }
64                            Err(_) => {
65                                eprintln!("⚠️  Invalid regex pattern: {}", #pattern);
66                                false
67                            }
68                        }
69                    }
70                }
71            }
72            "contains" => {
73                if args.len() < 2 {
74                    return quote!();
75                }
76                let subject = &args[0];
77                let substring = &args[1];
78                quote!(#subject.contains(#substring))
79            }
80            "length" => {
81                if args.is_empty() {
82                    return quote!();
83                }
84                let subject = &args[0];
85                quote!(#subject.len())
86            }
87            "uppercase" => {
88                if args.is_empty() {
89                    return quote!();
90                }
91                let subject = &args[0];
92                quote!(#subject.to_uppercase())
93            }
94            "lowercase" => {
95                if args.is_empty() {
96                    return quote!();
97                }
98                let subject = &args[0];
99                quote!(#subject.to_lowercase())
100            }
101            "trim" => {
102                if args.is_empty() {
103                    return quote!();
104                }
105                let subject = &args[0];
106                quote!(#subject.trim())
107            }
108            "starts_with" => {
109                if args.len() < 2 {
110                    return quote!();
111                }
112                let subject = &args[0];
113                let prefix = &args[1];
114                quote!(#subject.starts_with(#prefix))
115            }
116            "ends_with" => {
117                if args.len() < 2 {
118                    return quote!();
119                }
120                let subject = &args[0];
121                let suffix = &args[1];
122                quote!(#subject.ends_with(#suffix))
123            }
124            _ => quote!(),
125        }
126    }
127
128    /// Generate code for a date/time function
129    pub fn datetime_function(&self, name: &str, args: Vec<TokenStream>) -> TokenStream {
130        match name {
131            "today" => {
132                quote! {
133                    {
134                        use chrono::Local;
135                        Local::now().date_naive()
136                    }
137                }
138            }
139            "now" => {
140                quote! {
141                    {
142                        use chrono::Utc;
143                        Utc::now()
144                    }
145                }
146            }
147            "age" => {
148                if args.is_empty() {
149                    return quote!();
150                }
151                let birth_date = &args[0];
152                quote! {
153                    {
154                        use chrono::Local;
155                        let today = Local::now().date_naive();
156                        let mut age = today.year() - #birth_date.year();
157                        if (today.month(), today.day()) < (#birth_date.month(), #birth_date.day()) {
158                            age -= 1;
159                        }
160                        age as u32
161                    }
162                }
163            }
164            "days_since" => {
165                if args.is_empty() {
166                    return quote!();
167                }
168                let date = &args[0];
169                quote! {
170                    {
171                        use chrono::Local;
172                        (Local::now().date_naive() - #date).num_days()
173                    }
174                }
175            }
176            "date" => {
177                if args.is_empty() {
178                    return quote!();
179                }
180                let date_str = &args[0];
181                quote! {
182                    {
183                        use chrono::NaiveDate;
184                        NaiveDate::parse_from_str(#date_str, "%Y-%m-%d")
185                            .expect("Invalid date format")
186                    }
187                }
188            }
189            _ => quote!(),
190        }
191    }
192
193    /// Generate code for a collection function
194    pub fn array_function(&self, name: &str, args: Vec<TokenStream>) -> TokenStream {
195        match name {
196            "contains" => {
197                if args.len() < 2 {
198                    return quote!();
199                }
200                let array = &args[0];
201                let value = &args[1];
202                quote!(#array.contains(&#value))
203            }
204            "any" => {
205                if args.len() < 2 {
206                    return quote!();
207                }
208                let array = &args[0];
209                let predicate = &args[1];
210                quote!(#array.iter().any(|item| #predicate))
211            }
212            "all" => {
213                if args.len() < 2 {
214                    return quote!();
215                }
216                let array = &args[0];
217                let predicate = &args[1];
218                quote!(#array.iter().all(|item| #predicate))
219            }
220            "length" => {
221                if args.is_empty() {
222                    return quote!();
223                }
224                let array = &args[0];
225                quote!(#array.len())
226            }
227            "is_empty" => {
228                if args.is_empty() {
229                    return quote!();
230                }
231                let array = &args[0];
232                quote!(#array.is_empty())
233            }
234            // Type checking functions
235            "is_null" => {
236                if args.is_empty() {
237                    return quote!();
238                }
239                let value = &args[0];
240                quote!(#value.is_none())
241            }
242            "is_some" => {
243                if args.is_empty() {
244                    return quote!();
245                }
246                let value = &args[0];
247                quote!(#value.is_some())
248            }
249            _ => quote!(),
250        }
251    }
252}
253
254impl Default for FunctionGenerator {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_function_generator_creation() {
266        let _gen = FunctionGenerator::new();
267    }
268}