lvgl_codegen/
lib.rs

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        // We don't generate for the generic Obj
60        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        // generate constructor
107        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        // We don't deal with methods that return types yet
137        if self.ret.is_some() {
138            return Err(WrapperError::Skip);
139        }
140
141        // Make sure all arguments can be generated, skip the first arg (self)!
142        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                // if first arg is `const`, then it should be immutable
152                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                // if first arg is `const`, then it should be immutable
178                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                // if first arg is `const`, then it should be immutable
202                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        // TODO: Handle methods that return types
220        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                    // Since we know those are foreign functions, we only care about typed arguments
245                    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        // Filter Rust language keywords
277        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        // TODO: A better way to handle this, instead of `is_sometype()`, is using the Rust
283        //       type system itself.
284
285        // No need to pre-process this type of argument
286        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        // pub fn lv_arc_set_bg_end_angle(arc: *mut lv_obj_t, end: u16);
547        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        // pub fn lv_arc_create(par: *mut lv_obj_t, copy: *const lv_obj_t) -> *mut lv_obj_t;
631        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}