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