1mod analysis;
2
3use inflector::cases::pascalcase::to_pascal_case;
4use lazy_static::lazy_static;
5use proc_macro2::{Ident, TokenStream};
6use quote::quote;
7use quote::{format_ident, ToTokens};
8use regex::Regex;
9use std::collections::HashMap;
10use std::error::Error;
11use syn::{FnArg, ForeignItem, ForeignItemFn, Item, ReturnType};
12
13type CGResult<T> = Result<T, Box<dyn Error>>;
14
15const LIB_PREFIX: &str = "lv_";
16
17lazy_static! {
18 static ref TYPE_MAPPINGS: HashMap<&'static str, &'static str> = [
19 ("u16", "u16"),
20 ("i32", "i32"),
21 ("u8", "u8"),
22 ("bool", "bool"),
23 ("* const cty :: c_char", "_"),
24 ]
25 .iter()
26 .cloned()
27 .collect();
28}
29
30#[derive(Debug, Copy, Clone)]
31pub enum WrapperError {
32 Skip,
33}
34
35pub type WrapperResult<T> = Result<T, WrapperError>;
36
37pub trait Rusty {
38 type Parent;
39
40 fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream>;
41}
42
43#[derive(Clone)]
44pub struct LvWidget {
45 name: String,
46 methods: Vec<LvFunc>,
47}
48
49impl LvWidget {
50 fn pascal_name(&self) -> String {
51 to_pascal_case(&self.name)
52 }
53}
54
55impl Rusty for LvWidget {
56 type Parent = ();
57
58 fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
59 if self.name.as_str().eq("obj") {
61 return Err(WrapperError::Skip);
62 }
63
64 let widget_name = format_ident!("{}", self.pascal_name());
65 let methods: Vec<TokenStream> = self.methods.iter().flat_map(|m| m.code(self)).collect();
66 Ok(quote! {
67 define_object!(#widget_name);
68
69 impl #widget_name {
70 #(#methods)*
71 }
72 })
73 }
74}
75
76#[derive(Clone)]
77pub struct LvFunc {
78 name: String,
79 args: Vec<LvArg>,
80 ret: Option<LvType>,
81}
82
83impl LvFunc {
84 pub fn new(name: String, args: Vec<LvArg>, ret: Option<LvType>) -> Self {
85 Self { name, args, ret }
86 }
87
88 pub fn is_method(&self) -> bool {
89 if !self.args.is_empty() {
90 let first_arg = &self.args[0];
91 return first_arg.typ.literal_name.contains("lv_obj_t");
92 }
93 false
94 }
95}
96
97impl Rusty for LvFunc {
98 type Parent = LvWidget;
99
100 fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream> {
101 let templ = format!("{}{}_", LIB_PREFIX, parent.name.as_str());
102 let new_name = self.name.replace(templ.as_str(), "");
103 let func_name = format_ident!("{}", new_name);
104 let original_func_name = format_ident!("{}", self.name.as_str());
105
106 if new_name.as_str().eq("create") {
108 return Ok(quote! {
109
110 pub fn create(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
111 unsafe {
112 let ptr = lvgl_sys::#original_func_name(
113 parent.raw()?.as_mut(),
114 );
115 if let Some(raw) = core::ptr::NonNull::new(ptr) {
116 let core = <crate::Obj as crate::Widget>::from_raw(raw);
117 Ok(Self { core })
118 } else {
119 Err(crate::LvError::InvalidReference)
120 }
121 }
122 }
123
124 pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
125 Ok(Self::create(parent)?)
126 }
127
128 pub fn new() -> crate::LvResult<Self> {
129 let mut parent = crate::display::DefaultDisplay::get_scr_act()?;
130 Ok(Self::create_at(&mut parent)?)
131 }
132
133 });
134 }
135
136 if self.ret.is_some() {
138 return Err(WrapperError::Skip);
139 }
140
141 for arg in self.args.iter().skip(1) {
143 arg.code(self)?;
144 }
145
146 let args_decl = self
147 .args
148 .iter()
149 .enumerate()
150 .fold(quote!(), |args, (i, arg)| {
151 let next_arg = if i == 0 {
153 if arg.get_type().is_const() {
154 quote!(&self)
155 } else {
156 quote!(&mut self)
157 }
158 } else {
159 arg.code(self).unwrap()
160 };
161 if args.is_empty() {
162 quote! {
163 #next_arg
164 }
165 } else {
166 quote! {
167 #args, #next_arg
168 }
169 }
170 });
171
172 let args_processing = self
173 .args
174 .iter()
175 .enumerate()
176 .fold(quote!(), |args, (i, arg)| {
177 let next_arg = if i == 0 {
179 quote!()
180 } else {
181 let var = arg.get_processing();
182 quote!(#var)
183 };
184 if args.is_empty() {
185 quote! {
186 #next_arg
187 }
188 } else {
189 quote! {
190 #args
191 #next_arg
192 }
193 }
194 });
195
196 let args_call = self
197 .args
198 .iter()
199 .enumerate()
200 .fold(quote!(), |args, (i, arg)| {
201 let next_arg = if i == 0 {
203 quote!(self.core.raw()?.as_mut())
204 } else {
205 let var = arg.get_value_usage();
206 quote!(#var)
207 };
208 if args.is_empty() {
209 quote! {
210 #next_arg
211 }
212 } else {
213 quote! {
214 #args, #next_arg
215 }
216 }
217 });
218
219 Ok(quote! {
221 pub fn #func_name(#args_decl) -> crate::LvResult<()> {
222 #args_processing
223 unsafe {
224 lvgl_sys::#original_func_name(#args_call);
225 }
226 Ok(())
227 }
228 })
229 }
230}
231
232impl From<ForeignItemFn> for LvFunc {
233 fn from(ffi: ForeignItemFn) -> Self {
234 let ret = match ffi.sig.output {
235 ReturnType::Default => None,
236 ReturnType::Type(_, typ) => Some(typ.into()),
237 };
238 Self::new(
239 ffi.sig.ident.to_string(),
240 ffi.sig
241 .inputs
242 .iter()
243 .filter_map(|fa| {
244 if let FnArg::Typed(tya) = fa {
246 Some(tya)
247 } else {
248 None
249 }
250 })
251 .map(|a| a.clone().into())
252 .collect::<Vec<LvArg>>(),
253 ret,
254 )
255 }
256}
257
258#[derive(Clone)]
259pub struct LvArg {
260 name: String,
261 typ: LvType,
262}
263
264impl From<syn::PatType> for LvArg {
265 fn from(fa: syn::PatType) -> Self {
266 Self::new(fa.pat.to_token_stream().to_string(), fa.ty.into())
267 }
268}
269
270impl LvArg {
271 pub fn new(name: String, typ: LvType) -> Self {
272 Self { name, typ }
273 }
274
275 pub fn get_name_ident(&self) -> Ident {
276 syn::parse_str::<syn::Ident>(self.name.as_str())
278 .unwrap_or_else(|_| format_ident!("r#{}", self.name.as_str()))
279 }
280
281 pub fn get_processing(&self) -> TokenStream {
282 quote! {}
287 }
288
289 pub fn get_value_usage(&self) -> TokenStream {
290 let ident = self.get_name_ident();
291 if self.typ.is_str() {
292 quote! {
293 #ident.as_ptr()
294 }
295 } else {
296 quote! {
297 #ident
298 }
299 }
300 }
301
302 pub fn get_type(&self) -> &LvType {
303 &self.typ
304 }
305}
306
307impl Rusty for LvArg {
308 type Parent = LvFunc;
309
310 fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
311 let name = self.get_name_ident();
312 let typ = self.typ.code(self)?;
313 Ok(quote! {
314 #name: #typ
315 })
316 }
317}
318
319#[derive(Clone)]
320pub struct LvType {
321 literal_name: String,
322 _r_type: Option<Box<syn::Type>>,
323}
324
325impl LvType {
326 pub fn new(literal_name: String) -> Self {
327 Self {
328 literal_name,
329 _r_type: None,
330 }
331 }
332
333 pub fn from(r_type: Box<syn::Type>) -> Self {
334 Self {
335 literal_name: r_type.to_token_stream().to_string(),
336 _r_type: Some(r_type),
337 }
338 }
339
340 pub fn is_const(&self) -> bool {
341 self.literal_name.starts_with("const ")
342 }
343
344 pub fn is_str(&self) -> bool {
345 self.literal_name.ends_with("* const cty :: c_char")
346 }
347}
348
349impl Rusty for LvType {
350 type Parent = LvArg;
351
352 fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
353 match TYPE_MAPPINGS.get(self.literal_name.as_str()) {
354 Some(name) => {
355 let val = if self.is_str() {
356 quote!(&cstr_core::CStr)
357 } else if self.literal_name.contains("lv_") {
358 let ident = format_ident!("{}", name);
359 quote!(&#ident)
360 } else {
361 let ident = format_ident!("{}", name);
362 quote!(#ident)
363 };
364 Ok(quote! {
365 #val
366 })
367 }
368 None => Err(WrapperError::Skip),
369 }
370 }
371}
372
373impl From<Box<syn::Type>> for LvType {
374 fn from(t: Box<syn::Type>) -> Self {
375 Self::from(t)
376 }
377}
378
379pub struct CodeGen {
380 functions: Vec<LvFunc>,
381 widgets: Vec<LvWidget>,
382}
383
384impl CodeGen {
385 pub fn from(code: &str) -> CGResult<Self> {
386 let functions = Self::load_func_defs(code)?;
387 let widgets = Self::extract_widgets(&functions)?;
388 Ok(Self { functions, widgets })
389 }
390
391 pub fn get_widgets(&self) -> &Vec<LvWidget> {
392 &self.widgets
393 }
394
395 fn extract_widgets(functions: &[LvFunc]) -> CGResult<Vec<LvWidget>> {
396 let widget_names = Self::get_widget_names(functions);
397
398 let widgets = functions.iter().fold(HashMap::new(), |mut ws, f| {
399 for widget_name in &widget_names {
400 if f.name
401 .starts_with(format!("{}{}", LIB_PREFIX, widget_name).as_str())
402 && f.is_method()
403 {
404 ws.entry(widget_name.clone())
405 .or_insert_with(|| LvWidget {
406 name: widget_name.clone(),
407 methods: Vec::new(),
408 })
409 .methods
410 .push(f.clone())
411 }
412 }
413 ws
414 });
415
416 Ok(widgets.values().cloned().collect())
417 }
418
419 fn get_widget_names(functions: &[LvFunc]) -> Vec<String> {
420 let reg = format!("^{}([^_]+)_create$", LIB_PREFIX);
421 let create_func = Regex::new(reg.as_str()).unwrap();
422
423 functions
424 .iter()
425 .filter(|e| create_func.is_match(e.name.as_str()) && e.args.len() == 1)
426 .map(|f| {
427 String::from(
428 create_func
429 .captures(f.name.as_str())
430 .unwrap()
431 .get(1)
432 .unwrap()
433 .as_str(),
434 )
435 })
436 .collect::<Vec<_>>()
437 }
438
439 pub fn load_func_defs(bindgen_code: &str) -> CGResult<Vec<LvFunc>> {
440 let ast: syn::File = syn::parse_str(bindgen_code)?;
441 let fns = ast
442 .items
443 .into_iter()
444 .filter_map(|e| {
445 if let Item::ForeignMod(fm) = e {
446 Some(fm)
447 } else {
448 None
449 }
450 })
451 .flat_map(|e| {
452 e.items.into_iter().filter_map(|it| {
453 if let ForeignItem::Fn(f) = it {
454 Some(f)
455 } else {
456 None
457 }
458 })
459 })
460 .filter(|ff| ff.sig.ident.to_string().starts_with(LIB_PREFIX))
461 .map(|ff| ff.into())
462 .collect::<Vec<LvFunc>>();
463 Ok(fns)
464 }
465
466 pub fn get_function_names(&self) -> CGResult<Vec<String>> {
467 Ok(self.functions.iter().map(|f| f.name.clone()).collect())
468 }
469}
470
471#[cfg(test)]
472mod test {
473 use crate::{CodeGen, LvArg, LvFunc, LvType, LvWidget, Rusty};
474 use quote::quote;
475
476 #[test]
477 fn can_load_bindgen_fns() {
478 let bindgen_code = quote! {
479 extern "C" {
480 #[doc = " Return with the screen of an object"]
481 #[doc = " @param obj pointer to an object"]
482 #[doc = " @return pointer to a screen"]
483 pub fn lv_obj_get_screen(obj: *const lv_obj_t) -> *mut lv_obj_t;
484 }
485 };
486
487 let cg = CodeGen::load_func_defs(bindgen_code.to_string().as_str()).unwrap();
488
489 let ffn = cg.get(0).unwrap();
490 assert_eq!(ffn.name, "lv_obj_get_screen");
491 assert_eq!(ffn.args[0].name, "obj");
492 }
493
494 #[test]
495 fn can_identify_widgets_from_function_names() {
496 let funcs = vec![
497 LvFunc::new(
498 "lv_obj_create".to_string(),
499 vec![LvArg::new(
500 "parent".to_string(),
501 LvType::new("abc".to_string()),
502 )],
503 None,
504 ),
505 LvFunc::new(
506 "lv_btn_create".to_string(),
507 vec![LvArg::new(
508 "parent".to_string(),
509 LvType::new("abc".to_string()),
510 )],
511 None,
512 ),
513 LvFunc::new(
514 "lv_do_something".to_string(),
515 vec![LvArg::new(
516 "parent".to_string(),
517 LvType::new("abc".to_string()),
518 )],
519 None,
520 ),
521 LvFunc::new(
522 "lv_invalid_create".to_string(),
523 vec![
524 LvArg::new("parent".to_string(), LvType::new("abc".to_string())),
525 LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())),
526 ],
527 None,
528 ),
529 LvFunc::new(
530 "lv_cb_create".to_string(),
531 vec![LvArg::new(
532 "parent".to_string(),
533 LvType::new("abc".to_string()),
534 )],
535 None,
536 ),
537 ];
538
539 let widget_names = CodeGen::get_widget_names(&funcs);
540
541 assert_eq!(widget_names.len(), 3);
542 }
543
544 #[test]
545 fn generate_method_wrapper() {
546 let arc_set_bg_end_angle = LvFunc::new(
548 "lv_arc_set_bg_end_angle".to_string(),
549 vec![
550 LvArg::new("arc".to_string(), LvType::new("*mut lv_obj_t".to_string())),
551 LvArg::new("end".to_string(), LvType::new("u16".to_string())),
552 ],
553 None,
554 );
555 let arc_widget = LvWidget {
556 name: "arc".to_string(),
557 methods: vec![],
558 };
559
560 let code = arc_set_bg_end_angle.code(&arc_widget).unwrap();
561 let expected_code = quote! {
562 pub fn set_bg_end_angle(&mut self, end: u16) -> crate::LvResult<()> {
563 unsafe {
564 lvgl_sys::lv_arc_set_bg_end_angle(self.core.raw()?.as_mut(), end);
565 }
566 Ok(())
567 }
568 };
569
570 assert_eq!(code.to_string(), expected_code.to_string());
571 }
572
573 #[test]
574 fn generate_method_wrapper_for_str_types_as_argument() {
575 let bindgen_code = quote! {
576 extern "C" {
577 #[doc = " Set a new text for a label. Memory will be allocated to store the text by the label."]
578 #[doc = " @param label pointer to a label object"]
579 #[doc = " @param text '\\0' terminated character string. NULL to refresh with the current text."]
580 pub fn lv_label_set_text(label: *mut lv_obj_t, text: *const cty::c_char);
581 }
582 };
583 let cg = CodeGen::load_func_defs(bindgen_code.to_string().as_str()).unwrap();
584
585 let label_set_text = cg.get(0).unwrap().clone();
586 let parent_widget = LvWidget {
587 name: "label".to_string(),
588 methods: vec![],
589 };
590
591 let code = label_set_text.code(&parent_widget).unwrap();
592 let expected_code = quote! {
593
594 pub fn set_text(&mut self, text: &cstr_core::CStr) -> crate::LvResult<()> {
595 unsafe {
596 lvgl_sys::lv_label_set_text(
597 self.core.raw()?.as_mut(),
598 text.as_ptr()
599 );
600 }
601 Ok(())
602 }
603
604 };
605
606 assert_eq!(code.to_string(), expected_code.to_string());
607 }
608
609 #[test]
610 fn generate_basic_widget_code() {
611 let arc_widget = LvWidget {
612 name: "arc".to_string(),
613 methods: vec![],
614 };
615
616 let code = arc_widget.code(&()).unwrap();
617 let expected_code = quote! {
618 define_object!(Arc);
619
620 impl Arc {
621
622 }
623 };
624
625 assert_eq!(code.to_string(), expected_code.to_string());
626 }
627
628 #[test]
629 fn generate_widget_with_constructor_code() {
630 let arc_create = LvFunc::new(
632 "lv_arc_create".to_string(),
633 vec![
634 LvArg::new("par".to_string(), LvType::new("*mut lv_obj_t".to_string())),
635 LvArg::new(
636 "copy".to_string(),
637 LvType::new("*const lv_obj_t".to_string()),
638 ),
639 ],
640 Some(LvType::new("*mut lv_obj_t".to_string())),
641 );
642
643 let arc_widget = LvWidget {
644 name: "arc".to_string(),
645 methods: vec![arc_create],
646 };
647
648 let code = arc_widget.code(&()).unwrap();
649 let expected_code = quote! {
650 define_object!(Arc);
651
652 impl Arc {
653 pub fn create(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
654 unsafe {
655 let ptr = lvgl_sys::lv_arc_create(
656 parent.raw()?.as_mut(),
657 );
658 if let Some(raw) = core::ptr::NonNull::new(ptr) {
659 let core = <crate::Obj as crate::Widget>::from_raw(raw);
660 Ok(Self { core })
661 } else {
662 Err(crate::LvError::InvalidReference)
663 }
664 }
665 }
666
667 pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
668 Ok(Self::create(parent)?)
669 }
670
671 pub fn new() -> crate::LvResult<Self> {
672 let mut parent = crate::display::DefaultDisplay::get_scr_act()?;
673 Ok(Self::create_at(&mut parent)?)
674 }
675 }
676 };
677
678 assert_eq!(code.to_string(), expected_code.to_string());
679 }
680}