Skip to main content

rusteron_code_gen/
parser.rs

1use crate::generator::{parse_custom_methods, CBinding, CWrapper, Method};
2use crate::{Arg, ArgProcessing, CHandler};
3use itertools::Itertools;
4use quote::ToTokens;
5use std::collections::{BTreeMap, BTreeSet};
6use std::fs;
7use std::path::PathBuf;
8use syn::{Attribute, Item, ItemForeignMod, ItemStruct, ItemType, Lit, Meta, MetaNameValue};
9
10pub fn parse_bindings(out: &PathBuf) -> CBinding {
11    let file_content = fs::read_to_string(out.clone()).expect("Unable to read file");
12    let syntax_tree = syn::parse_file(&file_content).expect("Unable to parse file");
13    let mut wrappers = BTreeMap::new();
14    let mut methods = Vec::new();
15    let mut handlers = Vec::new();
16
17    let items = syntax_tree.items;
18
19    for item in &items {
20        if let Item::Type(ty) = item {
21            process_type(&mut wrappers, &mut handlers, ty);
22        }
23    }
24
25    let handler_names = handlers
26        .iter()
27        .filter(|h| {
28            !["aeron_udp_channel", "aeron_udp_transport"]
29                .iter()
30                .any(|&filter| h.type_name.starts_with(filter))
31        })
32        .map(|handler| handler.type_name.clone())
33        .collect();
34
35    for item in &items {
36        if let Item::Struct(s) = item {
37            process_struct(&mut wrappers, s, &handler_names);
38        }
39    }
40
41    for item in &items {
42        if let Item::ForeignMod(fm) = item {
43            process_c_method(&mut wrappers, &mut methods, fm, &handler_names);
44        }
45    }
46
47    let mut bindings = CBinding {
48        wrappers: wrappers
49            .into_iter()
50            .filter(|(_, wrapper)| {
51                // these are from media driver and do not follow convention
52                ![
53                    "aeron_thread",
54                    "aeron_command",
55                    "aeron_executor",
56                    "aeron_name_resolver",
57                    "aeron_udp_channel_transport", // this one I have issues with handlers
58                    "aeron_udp_transport",         // this one I have issues with handlers
59                ]
60                .iter()
61                .any(|&filter| wrapper.type_name.starts_with(filter))
62            })
63            .collect(),
64        methods,
65        handlers: handlers
66            .into_iter()
67            .filter(|h| {
68                !["aeron_udp_channel", "aeron_udp_transport"]
69                    .iter()
70                    .any(|&filter| h.type_name.starts_with(filter))
71            })
72            .collect(),
73    };
74
75    let mismatched_types = bindings
76        .wrappers
77        .iter()
78        .filter(|(key, w)| key.as_str() != w.type_name)
79        .map(|(a, b)| (a.clone(), b.clone()))
80        .collect_vec();
81    assert_eq!(Vec::<(String, CWrapper)>::new(), mismatched_types);
82
83    let custom = parse_custom_methods(crate::CUSTOM_AERON_CODE);
84    for wrapper in bindings.wrappers.values_mut() {
85        if let Some(methods) = custom.get(&wrapper.class_name) {
86            wrapper.skipped_methods = methods.clone();
87        }
88    }
89
90    bindings
91}
92
93fn process_c_method(
94    wrappers: &mut BTreeMap<String, CWrapper>,
95    methods: &mut Vec<Method>,
96    fm: &ItemForeignMod,
97    handler_names: &BTreeSet<String>,
98) {
99    // Extract functions inside extern "C" blocks
100    if fm.abi.name.is_some() && fm.abi.name.as_ref().unwrap().value() == "C" {
101        for foreign_item in &fm.items {
102            if let syn::ForeignItem::Fn(f) = foreign_item {
103                let docs = get_doc_comments(&f.attrs);
104                let fn_name = f.sig.ident.to_string();
105
106                // Get function arguments and return type as Rust code
107                let args = extract_function_arguments(&f.sig.inputs);
108                let ret = extract_return_type(&f.sig.output);
109
110                let option = if let Some(arg) = args
111                    .iter()
112                    .skip_while(|a| a.is_mut_pointer() && a.is_primitive())
113                    .next()
114                {
115                    let ty = &arg.c_type;
116                    let ty = ty.split(' ').last().map(|t| t.to_string()).unwrap();
117                    if wrappers.contains_key(&ty) {
118                        Some(ty)
119                    } else {
120                        find_closest_wrapper_from_method_name(wrappers, &fn_name)
121                    }
122                } else {
123                    find_closest_wrapper_from_method_name(wrappers, &fn_name)
124                };
125
126                match option {
127                    Some(key) => {
128                        let wrapper = wrappers.get_mut(&key).unwrap();
129                        wrapper.methods.push(Method {
130                            fn_name: fn_name.clone(),
131                            struct_method_name: fn_name
132                                .replace(&wrapper.type_name[..wrapper.type_name.len() - 1], "")
133                                .to_string(),
134                            return_type: Arg {
135                                name: "".to_string(),
136                                c_type: ret.clone(),
137                                processing: ArgProcessing::Default,
138                            },
139                            arguments: process_types(args.clone(), Some(handler_names)),
140                            docs: docs.clone(),
141                        });
142                    }
143                    None => methods.push(Method {
144                        fn_name: fn_name.clone(),
145                        struct_method_name: "".to_string(),
146                        return_type: Arg {
147                            name: "".to_string(),
148                            c_type: ret.clone(),
149                            processing: ArgProcessing::Default,
150                        },
151                        arguments: process_types(args.clone(), Some(handler_names)),
152                        docs: docs.clone(),
153                    }),
154                }
155            }
156        }
157    }
158}
159
160fn find_closest_wrapper_from_method_name(
161    wrappers: &mut BTreeMap<String, CWrapper>,
162    fn_name: &String,
163) -> Option<String> {
164    let type_names = get_possible_wrappers(&fn_name);
165
166    let mut value = None;
167    for ty in type_names {
168        if wrappers.contains_key(&ty) {
169            value = Some(ty);
170            break;
171        }
172    }
173
174    value
175}
176
177pub fn get_possible_wrappers(fn_name: &str) -> Vec<String> {
178    // Try all possible wrapper names by splitting at underscores
179    fn_name
180        .char_indices()
181        .filter(|(_, c)| *c == '_')
182        .map(|(i, _)| format!("{}_t", &fn_name[..i]))
183        .rev()
184        .collect_vec()
185}
186
187fn process_type(
188    wrappers: &mut BTreeMap<String, CWrapper>,
189    handlers: &mut Vec<CHandler>,
190    ty: &ItemType,
191) {
192    // Handle type definitions and get docs
193    let docs = get_doc_comments(&ty.attrs);
194
195    let type_name = ty.ident.to_string();
196    let class_name = snake_to_pascal_case(&type_name);
197
198    if is_struct_typedef(&ty.ty) {
199        wrappers
200            .entry(type_name.clone())
201            .or_insert(CWrapper {
202                class_name,
203                without_name: type_name[..type_name.len() - 2].to_string(),
204                type_name,
205                ..Default::default()
206            })
207            .docs
208            .extend(docs);
209    } else {
210        // Parse the function pointer type -> it is typically used for handlers/callbacks
211        if let syn::Type::Path(type_path) = &*ty.ty {
212            if let Some(segment) = type_path.path.segments.last() {
213                if segment.ident.to_string() == "Option" {
214                    if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
215                        if let Some(syn::GenericArgument::Type(syn::Type::BareFn(bare_fn))) =
216                            args.args.first()
217                        {
218                            let args: Vec<Arg> = bare_fn
219                                .inputs
220                                .iter()
221                                .map(|arg| {
222                                    let arg_name = match &arg.name {
223                                        Some((ident, _)) => ident.to_string(),
224                                        None => "".to_string(),
225                                    };
226                                    let arg_type = arg.ty.to_token_stream().to_string();
227                                    (arg_name, arg_type)
228                                })
229                                .map(|(field_name, field_type)| Arg {
230                                    name: field_name,
231                                    c_type: field_type,
232                                    processing: ArgProcessing::Default,
233                                })
234                                .collect();
235                            let string = bare_fn.output.to_token_stream().to_string();
236                            let mut return_type = string.trim();
237
238                            if return_type.starts_with("-> ") {
239                                return_type = &return_type[3..];
240                            }
241
242                            if return_type.is_empty() {
243                                return_type = "()";
244                            }
245
246                            if is_handler_typedef(&args) {
247                                let value = CHandler {
248                                    type_name: ty.ident.to_string(),
249                                    args: process_types(args, None),
250                                    return_type: Arg {
251                                        name: "".to_string(),
252                                        c_type: return_type.to_string(),
253                                        processing: ArgProcessing::Default,
254                                    },
255                                    docs: docs.clone(),
256                                    fn_mut_signature: Default::default(),
257                                    closure_type_name: Default::default(),
258                                };
259                                handlers.push(value);
260                            }
261                        }
262                    }
263                }
264            }
265        }
266    }
267}
268
269fn is_handler_typedef(args: &[Arg]) -> bool {
270    args.iter().filter(|arg| arg.is_c_void()).count() == 1
271        || args
272            .first()
273            .map(|arg| arg.is_c_void() && is_client_data_arg(&arg.name))
274            .unwrap_or(false)
275}
276
277fn is_client_data_arg(name: &str) -> bool {
278    name == "clientd"
279        || name == "state"
280        || name == "task_clientd"
281        || name.ends_with("_clientd")
282        || name.ends_with("_state")
283}
284
285fn is_struct_typedef(ty: &syn::Type) -> bool {
286    if let syn::Type::Path(type_path) = ty {
287        if let Some(segment) = type_path.path.segments.last() {
288            return segment.ident.to_string().ends_with("_stct");
289        }
290    }
291
292    false
293}
294
295fn process_struct(
296    wrappers: &mut BTreeMap<String, CWrapper>,
297    s: &ItemStruct,
298    handler_names: &BTreeSet<String>,
299) {
300    // Print the struct name and its doc comments
301    let docs = get_doc_comments(&s.attrs);
302    let type_name = s.ident.to_string().replace("_stct", "_t");
303    let class_name = snake_to_pascal_case(&type_name);
304
305    let fields: Vec<Arg> = s
306        .fields
307        .iter()
308        .map(|f| {
309            let field_name = f.ident.as_ref().unwrap().to_string();
310            let field_type = f.ty.to_token_stream().to_string();
311            (field_name, field_type)
312        })
313        .map(|(field_name, field_type)| Arg {
314            name: field_name,
315            c_type: field_type,
316            processing: ArgProcessing::Default,
317        })
318        .collect();
319
320    let w = wrappers.entry(type_name.to_string()).or_insert(CWrapper {
321        class_name,
322        without_name: type_name[..type_name.len() - 2].to_string(),
323        type_name,
324        ..Default::default()
325    });
326    w.docs.extend(docs);
327    w.fields = process_types(fields, Some(handler_names));
328}
329
330fn process_types(
331    mut name_and_type: Vec<Arg>,
332    handler_names: Option<&BTreeSet<String>>,
333) -> Vec<Arg> {
334    // now mark arguments which can be reduced
335    for i in 1..name_and_type.len() {
336        let param1 = &name_and_type[i - 1];
337        let param2 = &name_and_type[i];
338
339        let is_int = param2.c_type == "usize" || param2.c_type == "i32";
340        let length_field = param2.name == "length"
341            || param2.name == "len"
342            || (param2.name.ends_with("_length") && param2.name.starts_with(&param1.name));
343        if param2.is_c_void()
344            && !param1.is_mut_pointer()
345            && param1.c_type.ends_with("_t")
346            && handler_names
347                .map(|handler_names| handler_names.contains(&param1.c_type))
348                .unwrap_or(false)
349        {
350            // closures
351            //         handler: aeron_on_available_counter_t,
352            //         clientd: *mut ::std::os::raw::c_void,
353            let processing = ArgProcessing::Handler(vec![param1.clone(), param2.clone()]);
354            name_and_type[i - 1].processing = processing.clone();
355            name_and_type[i].processing = processing.clone();
356        } else if param1.is_c_string_any() && !param1.is_mut_pointer() && is_int && length_field {
357            //     pub stripped_channel: *mut ::std::os::raw::c_char,
358            //     pub stripped_channel_length: usize,
359            let processing = ArgProcessing::StringWithLength(vec![param1.clone(), param2.clone()]);
360            name_and_type[i - 1].processing = processing.clone();
361            name_and_type[i].processing = processing.clone();
362        } else if param1.is_byte_array()
363            // && !param1.is_mut_pointer()
364            && is_int
365            && length_field
366        {
367            //         key_buffer: *const u8,
368            //         key_buffer_length: usize,
369            let processing =
370                ArgProcessing::ByteArrayWithLength(vec![param1.clone(), param2.clone()]);
371            name_and_type[i - 1].processing = processing.clone();
372            name_and_type[i].processing = processing.clone();
373        }
374
375        //
376    }
377
378    name_and_type
379}
380
381// Helper function to extract doc comments
382fn get_doc_comments(attrs: &[Attribute]) -> BTreeSet<String> {
383    attrs
384        .iter()
385        .filter_map(|attr| {
386            // Parse the attribute meta to check if it is a `Meta::NameValue`
387            if let Meta::NameValue(MetaNameValue {
388                path,
389                value: syn::Expr::Lit(expr_lit),
390                ..
391            }) = &attr.meta
392            {
393                // Check if the path is "doc"
394                if path.is_ident("doc") {
395                    // Check if the literal is a string and return its value
396                    if let Lit::Str(lit_str) = &expr_lit.lit {
397                        return Some(lit_str.value().trim().to_string());
398                    }
399                }
400            }
401            None
402        })
403        .collect()
404}
405
406pub fn snake_to_pascal_case(mut snake: &str) -> String {
407    if snake.ends_with("_t") {
408        snake = &snake[..snake.len() - 2];
409    }
410    snake
411        .split('_')
412        .filter(|x| *x != "on") // Split the string by underscores
413        .map(|word| {
414            let mut chars = word.chars();
415            // Capitalize the first letter and collect the rest of the letters
416            match chars.next() {
417                Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
418                None => String::new(),
419            }
420        })
421        .collect()
422}
423
424// Helper function to extract function arguments as Rust code
425fn extract_function_arguments(
426    inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
427) -> Vec<Arg> {
428    inputs
429        .iter()
430        .map(|arg| match arg {
431            syn::FnArg::Receiver(_) => "self".to_string(), // Handle self receiver
432            syn::FnArg::Typed(pat_type) => pat_type.to_token_stream().to_string(), // Convert the pattern and type to Rust code
433        })
434        .map(|arg| {
435            arg.splitn(2, ':')
436                .map(|s| s.trim().to_string())
437                .collect_tuple()
438                .unwrap()
439        })
440        .map(|(name, ty)| Arg {
441            name,
442            c_type: ty,
443            processing: ArgProcessing::Default,
444        })
445        .collect_vec()
446}
447
448// Helper function to extract return type as Rust code
449fn extract_return_type(output: &syn::ReturnType) -> String {
450    match output {
451        syn::ReturnType::Default => "()".to_string(), // No return type, equivalent to ()
452        syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(), // Convert the type to Rust code
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use crate::parser::parse_bindings;
459    use std::path::PathBuf;
460
461    fn running_under_valgrind() -> bool {
462        std::env::var_os("RUSTERON_VALGRIND").is_some()
463    }
464
465    #[test]
466    fn media_driver() {
467        if running_under_valgrind() {
468            return;
469        }
470
471        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
472            .join("bindings")
473            .join("media-driver.rs");
474        let bindings = parse_bindings(&path);
475        assert_eq!(
476            "AeronImageFragmentAssembler",
477            bindings
478                .wrappers
479                .get("aeron_image_fragment_assembler_t")
480                .unwrap()
481                .class_name
482        );
483    }
484    #[test]
485    fn client() {
486        if running_under_valgrind() {
487            return;
488        }
489
490        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
491            .join("bindings")
492            .join("client.rs");
493        let bindings = parse_bindings(&path);
494        assert_eq!(
495            "AeronImageFragmentAssembler",
496            bindings
497                .wrappers
498                .get("aeron_image_fragment_assembler_t")
499                .unwrap()
500                .class_name
501        );
502        assert!(bindings.handlers.len() > 1);
503    }
504}