use inflector::cases::pascalcase::to_pascal_case; use lazy_static::lazy_static; use proc_macro2::{Ident, TokenStream}; use quote::quote; use quote::{format_ident, ToTokens}; use regex::Regex; use std::collections::HashMap; use std::error::Error; use syn::{FnArg, ForeignItem, ForeignItemFn, Item, ReturnType}; type CGResult = Result>; const LIB_PREFIX: &str = "lv_"; lazy_static! { static ref TYPE_MAPPINGS: HashMap<&'static str, &'static str> = [ ("u16", "u16"), ("i32", "i32"), ("u8", "u8"), ("bool", "bool"), ("* const cty :: c_char", "_"), ] .iter() .cloned() .collect(); } #[derive(Debug, Copy, Clone)] pub enum WrapperError { Skip, } pub type WrapperResult = Result; pub trait Rusty { type Parent; fn code(&self, parent: &Self::Parent) -> WrapperResult; } #[derive(Clone)] pub struct LvWidget { name: String, methods: Vec, } impl Rusty for LvWidget { type Parent = (); fn code(&self, _parent: &Self::Parent) -> WrapperResult { // We don't generate for the generic Obj if self.name.as_str().eq("obj") { return Err(WrapperError::Skip); } let widget_name = format_ident!("{}", to_pascal_case(self.name.as_str())); let methods: Vec = self.methods.iter().flat_map(|m| m.code(self)).collect(); Ok(quote! { define_object!(#widget_name); impl #widget_name { #(#methods)* } }) } } #[derive(Clone)] pub struct LvFunc { name: String, args: Vec, ret: Option, } impl LvFunc { pub fn new(name: String, args: Vec, ret: Option) -> Self { Self { name, args, ret } } pub fn is_method(&self) -> bool { if !self.args.is_empty() { let first_arg = &self.args[0]; return first_arg.typ.literal_name.contains("lv_obj_t"); } false } } impl Rusty for LvFunc { type Parent = LvWidget; fn code(&self, parent: &Self::Parent) -> WrapperResult { let templ = format!("{}{}_", LIB_PREFIX, parent.name.as_str()); let new_name = self.name.replace(templ.as_str(), ""); let func_name = format_ident!("{}", new_name); let original_func_name = format_ident!("{}", self.name.as_str()); // generate constructor if new_name.as_str().eq("create") { return Ok(quote! { pub fn new(parent: &mut C) -> crate::LvResult where C: crate::NativeObject, { unsafe { let ptr = lvgl_sys::#original_func_name(parent.raw()?.as_mut(), core::ptr::null_mut()); if let Some(raw) = core::ptr::NonNull::new(ptr) { let core = ::from_raw(raw); Ok(Self { core }) } else { Err(crate::LvError::InvalidReference) } } } }); } // We don't deal with methods that return types yet if self.ret.is_some() { return Err(WrapperError::Skip); } // Make sure all arguments can be generated, skip the first arg (self)! for arg in self.args.iter().skip(1) { arg.code(self)?; } let args_decl = self .args .iter() .enumerate() .fold(quote!(), |args, (i, arg)| { // if first arg is `const`, then it should be immutable let next_arg = if i == 0 { if arg.get_type().is_const() { quote!(&self) } else { quote!(&mut self) } } else { arg.code(self).unwrap() }; if args.is_empty() { quote! { #next_arg } } else { quote! { #args, #next_arg } } }); let args_processing = self .args .iter() .enumerate() .fold(quote!(), |args, (i, arg)| { // if first arg is `const`, then it should be immutable let next_arg = if i == 0 { quote!() } else { let var = arg.get_processing(); quote!(#var) }; if args.is_empty() { quote! { #next_arg } } else { quote! { #args #next_arg } } }); let args_call = self .args .iter() .enumerate() .fold(quote!(), |args, (i, arg)| { // if first arg is `const`, then it should be immutable let next_arg = if i == 0 { quote!(self.core.raw()?.as_mut()) } else { let var = arg.get_value_usage(); quote!(#var) }; if args.is_empty() { quote! { #next_arg } } else { quote! { #args, #next_arg } } }); // TODO: Handle methods that return types Ok(quote! { pub fn #func_name(#args_decl) -> crate::LvResult<()> { #args_processing unsafe { lvgl_sys::#original_func_name(#args_call); } Ok(()) } }) } } impl From for LvFunc { fn from(ffi: ForeignItemFn) -> Self { let ret = match ffi.sig.output { ReturnType::Default => None, ReturnType::Type(_, typ) => Some(typ.into()), }; Self::new( ffi.sig.ident.to_string(), ffi.sig .inputs .iter() .filter_map(|fa| { // Since we know those are foreign functions, we only care about typed arguments if let FnArg::Typed(tya) = fa { Some(tya) } else { None } }) .map(|a| a.clone().into()) .collect::>(), ret, ) } } #[derive(Clone)] pub struct LvArg { name: String, typ: LvType, } impl From for LvArg { fn from(fa: syn::PatType) -> Self { Self::new(fa.pat.to_token_stream().to_string(), fa.ty.into()) } } impl LvArg { pub fn new(name: String, typ: LvType) -> Self { Self { name, typ } } pub fn get_name_ident(&self) -> Ident { // Filter Rust language keywords syn::parse_str::(self.name.as_str()) .unwrap_or_else(|_| format_ident!("r#{}", self.name.as_str())) } pub fn get_processing(&self) -> TokenStream { // TODO: A better way to handle this, instead of `is_sometype()`, is using the Rust // type system itself. // No need to pre-process this type of argument quote! {} } pub fn get_value_usage(&self) -> TokenStream { let ident = self.get_name_ident(); if self.typ.is_str() { quote! { #ident.as_ptr() } } else { quote! { #ident } } } pub fn get_type(&self) -> &LvType { &self.typ } } impl Rusty for LvArg { type Parent = LvFunc; fn code(&self, _parent: &Self::Parent) -> WrapperResult { let name = self.get_name_ident(); let typ = self.typ.code(self)?; Ok(quote! { #name: #typ }) } } #[derive(Clone)] pub struct LvType { literal_name: String, _r_type: Option>, } impl LvType { pub fn new(literal_name: String) -> Self { Self { literal_name, _r_type: None, } } pub fn from(r_type: Box) -> Self { Self { literal_name: r_type.to_token_stream().to_string(), _r_type: Some(r_type), } } pub fn is_const(&self) -> bool { self.literal_name.starts_with("const ") } pub fn is_str(&self) -> bool { self.literal_name.ends_with("* const cty :: c_char") } } impl Rusty for LvType { type Parent = LvArg; fn code(&self, _parent: &Self::Parent) -> WrapperResult { match TYPE_MAPPINGS.get(self.literal_name.as_str()) { Some(name) => { let val = if self.is_str() { quote!(&cstr_core::CStr) } else { let ident = format_ident!("{}", name); quote!(#ident) }; Ok(quote! { #val }) } None => Err(WrapperError::Skip), } } } impl From> for LvType { fn from(t: Box) -> Self { Self::from(t) } } pub struct CodeGen { functions: Vec, widgets: Vec, } impl CodeGen { pub fn from(code: &str) -> CGResult { let functions = Self::load_func_defs(code)?; let widgets = Self::extract_widgets(&functions)?; Ok(Self { functions, widgets }) } pub fn get_widgets(&self) -> &Vec { &self.widgets } fn extract_widgets(functions: &[LvFunc]) -> CGResult> { let widget_names = Self::get_widget_names(functions); let widgets = functions.iter().fold(HashMap::new(), |mut ws, f| { for widget_name in &widget_names { if f.name .starts_with(format!("{}{}", LIB_PREFIX, widget_name).as_str()) && f.is_method() { ws.entry(widget_name.clone()) .or_insert_with(|| LvWidget { name: widget_name.clone(), methods: Vec::new(), }) .methods .push(f.clone()) } } ws }); Ok(widgets.values().cloned().collect()) } fn get_widget_names(functions: &[LvFunc]) -> Vec { let reg = format!("^{}([^_]+)_create$", LIB_PREFIX); let create_func = Regex::new(reg.as_str()).unwrap(); functions .iter() .filter(|e| create_func.is_match(e.name.as_str()) && e.args.len() == 2) .map(|f| { String::from( create_func .captures(f.name.as_str()) .unwrap() .get(1) .unwrap() .as_str(), ) }) .collect::>() } pub fn load_func_defs(bindgen_code: &str) -> CGResult> { let ast: syn::File = syn::parse_str(bindgen_code)?; let fns = ast .items .into_iter() .filter_map(|e| { if let Item::ForeignMod(fm) = e { Some(fm) } else { None } }) .flat_map(|e| { e.items.into_iter().filter_map(|it| { if let ForeignItem::Fn(f) = it { Some(f) } else { None } }) }) .filter(|ff| ff.sig.ident.to_string().starts_with(LIB_PREFIX)) .map(|ff| ff.into()) .collect::>(); Ok(fns) } pub fn get_function_names(&self) -> CGResult> { Ok(self.functions.iter().map(|f| f.name.clone()).collect()) } } #[cfg(test)] mod test { use crate::{CodeGen, LvArg, LvFunc, LvType, LvWidget, Rusty}; use quote::quote; #[test] fn can_load_bindgen_fns() { let bindgen_code = quote! { extern "C" { #[doc = " Return with the screen of an object"] #[doc = " @param obj pointer to an object"] #[doc = " @return pointer to a screen"] pub fn lv_obj_get_screen(obj: *const lv_obj_t) -> *mut lv_obj_t; } }; let cg = CodeGen::load_func_defs(bindgen_code.to_string().as_str()).unwrap(); let ffn = cg.get(0).unwrap(); assert_eq!(ffn.name, "lv_obj_get_screen"); assert_eq!(ffn.args[0].name, "obj"); } #[test] fn can_identify_widgets_from_function_names() { let funcs = vec![ LvFunc::new( "lv_obj_create".to_string(), vec![ LvArg::new("parent".to_string(), LvType::new("abc".to_string())), LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())), ], None, ), LvFunc::new( "lv_btn_create".to_string(), vec![ LvArg::new("parent".to_string(), LvType::new("abc".to_string())), LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())), ], None, ), LvFunc::new( "lv_do_something".to_string(), vec![ LvArg::new("parent".to_string(), LvType::new("abc".to_string())), LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())), ], None, ), LvFunc::new( "lv_invalid_create".to_string(), vec![LvArg::new( "parent".to_string(), LvType::new("abc".to_string()), )], None, ), LvFunc::new( "lv_cb_create".to_string(), vec![ LvArg::new("parent".to_string(), LvType::new("abc".to_string())), LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())), ], None, ), ]; let widget_names = CodeGen::get_widget_names(&funcs); assert_eq!(widget_names.len(), 3); } #[test] fn generate_method_wrapper() { // pub fn lv_arc_set_bg_end_angle(arc: *mut lv_obj_t, end: u16); let arc_set_bg_end_angle = LvFunc::new( "lv_arc_set_bg_end_angle".to_string(), vec![ LvArg::new("arc".to_string(), LvType::new("*mut lv_obj_t".to_string())), LvArg::new("end".to_string(), LvType::new("u16".to_string())), ], None, ); let arc_widget = LvWidget { name: "arc".to_string(), methods: vec![], }; let code = arc_set_bg_end_angle.code(&arc_widget).unwrap(); let expected_code = quote! { pub fn set_bg_end_angle(&mut self, end: u16) -> crate::LvResult<()> { unsafe { lvgl_sys::lv_arc_set_bg_end_angle(self.core.raw()?.as_mut(), end); } Ok(()) } }; assert_eq!(code.to_string(), expected_code.to_string()); } #[test] fn generate_method_wrapper_for_str_types_as_argument() { let bindgen_code = quote! { extern "C" { #[doc = " Set a new text for a label. Memory will be allocated to store the text by the label."] #[doc = " @param label pointer to a label object"] #[doc = " @param text '\\0' terminated character string. NULL to refresh with the current text."] pub fn lv_label_set_text(label: *mut lv_obj_t, text: *const cty::c_char); } }; let cg = CodeGen::load_func_defs(bindgen_code.to_string().as_str()).unwrap(); let label_set_text = cg.get(0).unwrap().clone(); let parent_widget = LvWidget { name: "label".to_string(), methods: vec![], }; let code = label_set_text.code(&parent_widget).unwrap(); let expected_code = quote! { pub fn set_text(&mut self, text: &cstr_core::CStr) -> crate::LvResult<()> { unsafe { lvgl_sys::lv_label_set_text( self.core.raw()?.as_mut(), text.as_ptr() ); } Ok(()) } }; assert_eq!(code.to_string(), expected_code.to_string()); } #[test] fn generate_basic_widget_code() { let arc_widget = LvWidget { name: "arc".to_string(), methods: vec![], }; let code = arc_widget.code(&()).unwrap(); let expected_code = quote! { define_object!(Arc); impl Arc { } }; assert_eq!(code.to_string(), expected_code.to_string()); } #[test] fn generate_widget_with_constructor_code() { // pub fn lv_arc_create(par: *mut lv_obj_t, copy: *const lv_obj_t) -> *mut lv_obj_t; let arc_create = LvFunc::new( "lv_arc_create".to_string(), vec![ LvArg::new("par".to_string(), LvType::new("*mut lv_obj_t".to_string())), LvArg::new( "copy".to_string(), LvType::new("*const lv_obj_t".to_string()), ), ], Some(LvType::new("*mut lv_obj_t".to_string())), ); let arc_widget = LvWidget { name: "arc".to_string(), methods: vec![arc_create], }; let code = arc_widget.code(&()).unwrap(); let expected_code = quote! { define_object!(Arc); impl Arc { pub fn new(parent: &mut C) -> crate::LvResult where C: crate::NativeObject, { unsafe { let ptr = lvgl_sys::lv_arc_create(parent.raw()?.as_mut(), core::ptr::null_mut()); if let Some(raw) = core::ptr::NonNull::new(ptr) { let core = ::from_raw(raw); Ok(Self { core }) } else { Err(crate::LvError::InvalidReference) } } } } }; assert_eq!(code.to_string(), expected_code.to_string()); } }