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 parse_bindings_with_custom(out, &[])
12}
13
14pub fn parse_bindings_with_custom(out: &PathBuf, extra_custom_code: &[&str]) -> CBinding {
17 let file_content = fs::read_to_string(out.clone()).expect("Unable to read file");
18 let syntax_tree = syn::parse_file(&file_content).expect("Unable to parse file");
19 let mut wrappers = BTreeMap::new();
20 let mut methods = Vec::new();
21 let mut handlers = Vec::new();
22
23 let items = syntax_tree.items;
24
25 for item in &items {
26 if let Item::Type(ty) = item {
27 process_type(&mut wrappers, &mut handlers, ty);
28 }
29 }
30
31 let handler_names = handlers
32 .iter()
33 .filter(|h| {
34 !["aeron_udp_channel", "aeron_udp_transport"]
35 .iter()
36 .any(|&filter| h.type_name.starts_with(filter))
37 })
38 .map(|handler| handler.type_name.clone())
39 .collect();
40
41 for item in &items {
42 if let Item::Struct(s) = item {
43 process_struct(&mut wrappers, s, &handler_names);
44 }
45 }
46
47 for item in &items {
48 if let Item::ForeignMod(fm) = item {
49 process_c_method(&mut wrappers, &mut methods, fm, &handler_names);
50 }
51 }
52
53 let mut bindings = CBinding {
54 wrappers: wrappers
55 .into_iter()
56 .filter(|(_, wrapper)| {
57 ![
59 "aeron_thread",
60 "aeron_command",
61 "aeron_executor",
62 "aeron_name_resolver",
63 "aeron_udp_channel_transport", "aeron_udp_transport", ]
66 .iter()
67 .any(|&filter| wrapper.type_name.starts_with(filter))
68 })
69 .collect(),
70 methods,
71 handlers: handlers
72 .into_iter()
73 .filter(|h| {
74 !["aeron_udp_channel", "aeron_udp_transport"]
75 .iter()
76 .any(|&filter| h.type_name.starts_with(filter))
77 })
78 .collect(),
79 };
80
81 let mismatched_types = bindings
82 .wrappers
83 .iter()
84 .filter(|(key, w)| key.as_str() != w.type_name)
85 .map(|(a, b)| (a.clone(), b.clone()))
86 .collect_vec();
87 assert_eq!(Vec::<(String, CWrapper)>::new(), mismatched_types);
88
89 let mut custom = parse_custom_methods(crate::CUSTOM_AERON_CODE);
90 for code in extra_custom_code {
91 for (class, methods) in parse_custom_methods(code) {
92 custom.entry(class).or_default().extend(methods);
93 }
94 }
95 for wrapper in bindings.wrappers.values_mut() {
96 if let Some(methods) = custom.get(&wrapper.class_name) {
97 wrapper.skipped_methods = methods.clone();
98 }
99 }
100
101 bindings
102}
103
104fn process_c_method(
105 wrappers: &mut BTreeMap<String, CWrapper>,
106 methods: &mut Vec<Method>,
107 fm: &ItemForeignMod,
108 handler_names: &BTreeSet<String>,
109) {
110 if fm.abi.name.is_some() && fm.abi.name.as_ref().unwrap().value() == "C" {
112 for foreign_item in &fm.items {
113 if let syn::ForeignItem::Fn(f) = foreign_item {
114 let docs = get_doc_comments(&f.attrs);
115 let fn_name = f.sig.ident.to_string();
116
117 if fn_name.contains("exclusive_exclusive") {
124 continue;
125 }
126
127 let args = extract_function_arguments(&f.sig.inputs);
129 let ret = extract_return_type(&f.sig.output);
130
131 let option = if let Some(arg) = args
132 .iter()
133 .skip_while(|a| a.is_mut_pointer() && a.is_primitive())
134 .next()
135 {
136 let ty = &arg.c_type;
137 let ty = ty.split(' ').last().map(|t| t.to_string()).unwrap();
138 if wrappers.contains_key(&ty) {
139 Some(ty)
140 } else {
141 find_closest_wrapper_from_method_name(wrappers, &fn_name)
142 }
143 } else {
144 find_closest_wrapper_from_method_name(wrappers, &fn_name)
145 };
146
147 match option {
148 Some(key) => {
149 let wrapper = wrappers.get_mut(&key).unwrap();
150 wrapper.methods.push(Method {
151 fn_name: fn_name.clone(),
152 struct_method_name: fn_name
153 .replace(&wrapper.type_name[..wrapper.type_name.len() - 1], "")
154 .to_string(),
155 return_type: Arg {
156 name: "".to_string(),
157 c_type: ret.clone(),
158 processing: ArgProcessing::Default,
159 },
160 arguments: process_types(args.clone(), Some(handler_names)),
161 docs: docs.clone(),
162 });
163 }
164 None => methods.push(Method {
165 fn_name: fn_name.clone(),
166 struct_method_name: "".to_string(),
167 return_type: Arg {
168 name: "".to_string(),
169 c_type: ret.clone(),
170 processing: ArgProcessing::Default,
171 },
172 arguments: process_types(args.clone(), Some(handler_names)),
173 docs: docs.clone(),
174 }),
175 }
176 }
177 }
178 }
179}
180
181fn find_closest_wrapper_from_method_name(
182 wrappers: &mut BTreeMap<String, CWrapper>,
183 fn_name: &String,
184) -> Option<String> {
185 let type_names = get_possible_wrappers(&fn_name);
186
187 let mut value = None;
188 for ty in type_names {
189 if wrappers.contains_key(&ty) {
190 value = Some(ty);
191 break;
192 }
193 }
194
195 value
196}
197
198pub fn get_possible_wrappers(fn_name: &str) -> Vec<String> {
199 fn_name
201 .char_indices()
202 .filter(|(_, c)| *c == '_')
203 .map(|(i, _)| format!("{}_t", &fn_name[..i]))
204 .rev()
205 .collect_vec()
206}
207
208fn process_type(wrappers: &mut BTreeMap<String, CWrapper>, handlers: &mut Vec<CHandler>, ty: &ItemType) {
209 let docs = get_doc_comments(&ty.attrs);
211
212 let type_name = ty.ident.to_string();
213 let class_name = snake_to_pascal_case(&type_name);
214
215 if is_struct_typedef(&ty.ty) {
216 wrappers
217 .entry(type_name.clone())
218 .or_insert(CWrapper {
219 class_name,
220 without_name: type_name[..type_name.len() - 2].to_string(),
221 type_name,
222 ..Default::default()
223 })
224 .docs
225 .extend(docs);
226 } else {
227 if let syn::Type::Path(type_path) = &*ty.ty {
229 if let Some(segment) = type_path.path.segments.last() {
230 if segment.ident.to_string() == "Option" {
231 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
232 if let Some(syn::GenericArgument::Type(syn::Type::BareFn(bare_fn))) = args.args.first() {
233 let args: Vec<Arg> = bare_fn
234 .inputs
235 .iter()
236 .map(|arg| {
237 let arg_name = match &arg.name {
238 Some((ident, _)) => ident.to_string(),
239 None => "".to_string(),
240 };
241 let arg_type = arg.ty.to_token_stream().to_string();
242 (arg_name, arg_type)
243 })
244 .map(|(field_name, field_type)| Arg {
245 name: field_name,
246 c_type: field_type,
247 processing: ArgProcessing::Default,
248 })
249 .collect();
250 let string = bare_fn.output.to_token_stream().to_string();
251 let mut return_type = string.trim();
252
253 if return_type.starts_with("-> ") {
254 return_type = &return_type[3..];
255 }
256
257 if return_type.is_empty() {
258 return_type = "()";
259 }
260
261 if is_handler_typedef(&args) {
262 let value = CHandler {
263 type_name: ty.ident.to_string(),
264 args: process_types(args, None),
265 return_type: Arg {
266 name: "".to_string(),
267 c_type: return_type.to_string(),
268 processing: ArgProcessing::Default,
269 },
270 docs: docs.clone(),
271 fn_mut_signature: Default::default(),
272 closure_type_name: Default::default(),
273 };
274 handlers.push(value);
275 }
276 }
277 }
278 }
279 }
280 }
281 }
282}
283
284fn is_handler_typedef(args: &[Arg]) -> bool {
285 args.iter().filter(|arg| arg.is_c_void()).count() == 1
286 || args
287 .first()
288 .map(|arg| arg.is_c_void() && is_client_data_arg(&arg.name))
289 .unwrap_or(false)
290}
291
292fn is_client_data_arg(name: &str) -> bool {
293 name == "clientd"
294 || name == "state"
295 || name == "task_clientd"
296 || name.ends_with("_clientd")
297 || name.ends_with("_state")
298}
299
300fn is_struct_typedef(ty: &syn::Type) -> bool {
301 if let syn::Type::Path(type_path) = ty {
302 if let Some(segment) = type_path.path.segments.last() {
303 return segment.ident.to_string().ends_with("_stct");
304 }
305 }
306
307 false
308}
309
310fn process_struct(wrappers: &mut BTreeMap<String, CWrapper>, s: &ItemStruct, handler_names: &BTreeSet<String>) {
311 let docs = get_doc_comments(&s.attrs);
313 let type_name = s.ident.to_string().replace("_stct", "_t");
314 let class_name = snake_to_pascal_case(&type_name);
315
316 let fields: Vec<Arg> = s
317 .fields
318 .iter()
319 .map(|f| {
320 let field_name = f.ident.as_ref().unwrap().to_string();
321 let field_type = f.ty.to_token_stream().to_string();
322 (field_name, field_type)
323 })
324 .map(|(field_name, field_type)| Arg {
325 name: field_name,
326 c_type: field_type,
327 processing: ArgProcessing::Default,
328 })
329 .collect();
330
331 let w = wrappers.entry(type_name.to_string()).or_insert(CWrapper {
332 class_name,
333 without_name: type_name[..type_name.len() - 2].to_string(),
334 type_name,
335 ..Default::default()
336 });
337 w.docs.extend(docs);
338 w.fields = process_types(fields, Some(handler_names));
339}
340
341fn process_types(mut name_and_type: Vec<Arg>, handler_names: Option<&BTreeSet<String>>) -> Vec<Arg> {
342 for i in 1..name_and_type.len() {
344 let param1 = &name_and_type[i - 1];
345 let param2 = &name_and_type[i];
346
347 let is_int = param2.c_type == "usize" || param2.c_type == "i32";
348 let length_field = matches!(param2.name.as_str(), "length" | "len" | "count" | "capacity")
351 || param2.name.ends_with("_length")
352 || param2.name.ends_with("_len")
353 || param2.name.ends_with("_size");
354 if param2.is_c_void()
355 && !param1.is_mut_pointer()
356 && param1.c_type.ends_with("_t")
357 && handler_names
358 .map(|handler_names| handler_names.contains(¶m1.c_type))
359 .unwrap_or(false)
360 {
361 let processing = ArgProcessing::Handler(vec![param1.clone(), param2.clone()]);
365 name_and_type[i - 1].processing = processing.clone();
366 name_and_type[i].processing = processing.clone();
367 } else if param1.is_c_string_any() && is_int && length_field {
368 let processing = ArgProcessing::StringWithLength(vec![param1.clone(), param2.clone()]);
373 name_and_type[i - 1].processing = processing.clone();
374 name_and_type[i].processing = processing.clone();
375 } else if param1.is_byte_array() && is_int && length_field {
376 let processing = ArgProcessing::ByteArrayWithLength(vec![param1.clone(), param2.clone()]);
379 name_and_type[i - 1].processing = processing.clone();
380 name_and_type[i].processing = processing.clone();
381 }
382
383 }
385
386 name_and_type
387}
388
389fn get_doc_comments(attrs: &[Attribute]) -> BTreeSet<String> {
391 attrs
392 .iter()
393 .filter_map(|attr| {
394 if let Meta::NameValue(MetaNameValue {
396 path,
397 value: syn::Expr::Lit(expr_lit),
398 ..
399 }) = &attr.meta
400 {
401 if path.is_ident("doc") {
403 if let Lit::Str(lit_str) = &expr_lit.lit {
405 return Some(lit_str.value().trim().to_string());
406 }
407 }
408 }
409 None
410 })
411 .collect()
412}
413
414pub fn snake_to_pascal_case(mut snake: &str) -> String {
415 if snake.ends_with("_t") {
416 snake = &snake[..snake.len() - 2];
417 }
418 snake
419 .split('_')
420 .filter(|x| *x != "on") .map(|word| {
422 let mut chars = word.chars();
423 match chars.next() {
425 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
426 None => String::new(),
427 }
428 })
429 .collect()
430}
431
432fn extract_function_arguments(inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>) -> Vec<Arg> {
434 inputs
435 .iter()
436 .map(|arg| match arg {
437 syn::FnArg::Receiver(_) => "self".to_string(), syn::FnArg::Typed(pat_type) => pat_type.to_token_stream().to_string(), })
440 .map(|arg| {
441 arg.splitn(2, ':')
442 .map(|s| s.trim().to_string())
443 .collect_tuple()
444 .unwrap()
445 })
446 .map(|(name, ty)| Arg {
447 name,
448 c_type: ty,
449 processing: ArgProcessing::Default,
450 })
451 .collect_vec()
452}
453
454fn extract_return_type(output: &syn::ReturnType) -> String {
456 match output {
457 syn::ReturnType::Default => "()".to_string(), syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(), }
460}
461
462#[cfg(test)]
463mod tests {
464 use crate::parser::parse_bindings;
465 use crate::ArgProcessing;
466 use std::path::PathBuf;
467
468 fn running_under_valgrind() -> bool {
469 std::env::var_os("RUSTERON_VALGRIND").is_some()
470 }
471
472 #[test]
473 fn media_driver() {
474 if running_under_valgrind() {
475 return;
476 }
477
478 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
479 .join("bindings")
480 .join("media-driver.rs");
481 let bindings = parse_bindings(&path);
482 assert_eq!(
483 "AeronImageFragmentAssembler",
484 bindings
485 .wrappers
486 .get("aeron_image_fragment_assembler_t")
487 .unwrap()
488 .class_name
489 );
490 }
491 #[test]
492 fn client() {
493 if running_under_valgrind() {
494 return;
495 }
496
497 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
498 .join("bindings")
499 .join("client.rs");
500 let bindings = parse_bindings(&path);
501 assert_eq!(
502 "AeronImageFragmentAssembler",
503 bindings
504 .wrappers
505 .get("aeron_image_fragment_assembler_t")
506 .unwrap()
507 .class_name
508 );
509 assert!(bindings.handlers.len() > 1);
510 }
511
512 #[test]
514 fn reserved_value_supplier_buffer_merges_with_frame_length() {
515 if running_under_valgrind() {
516 return;
517 }
518
519 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
520 .join("bindings")
521 .join("client.rs");
522 let bindings = parse_bindings(&path);
523 let handler = bindings
524 .handlers
525 .iter()
526 .find(|h| h.type_name == "aeron_reserved_value_supplier_t")
527 .expect("reserved value supplier handler missing");
528 let buffer = handler.args.iter().find(|a| a.name == "buffer").unwrap();
529 assert!(
530 matches!(buffer.processing, ArgProcessing::ByteArrayWithLength(_)),
531 "buffer must merge with frame_length into a slice, got {:?}",
532 buffer.processing
533 );
534 }
535
536 #[test]
538 fn mut_string_fill_buffer_merges() {
539 if running_under_valgrind() {
540 return;
541 }
542
543 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
544 .join("bindings")
545 .join("client.rs");
546 let bindings = parse_bindings(&path);
547 let reader = bindings.wrappers.get("aeron_counters_reader_t").unwrap();
548 let method = reader
549 .methods
550 .iter()
551 .find(|m| m.fn_name == "aeron_counters_reader_counter_label")
552 .expect("counter_label method missing");
553 let buffer = method.arguments.iter().find(|a| a.name == "buffer").unwrap();
554 assert!(
555 matches!(buffer.processing, ArgProcessing::StringWithLength(_)),
556 "mut string buffer must merge with its length, got {:?}",
557 buffer.processing
558 );
559 }
560}