use clang::{Clang, Entity, EntityKind, Index, Type}; use inflector::cases::pascalcase::to_pascal_case; use lazy_static::lazy_static; use proc_macro2::TokenStream; use quote::format_ident; use quote::quote; use regex::Regex; use std::collections::HashMap; use std::error::Error; type CGResult = Result>; const LIB_PREFIX: &str = "lv_"; lazy_static! { static ref TYPE_MAPPINGS: HashMap<&'static str, &'static str> = [ ("uint16_t", "u16"), ("int32_t", "i32"), ("uint8_t", "u8"), ("bool", "bool") ] .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, Eq, PartialEq)] pub struct LvWidget { name: String, methods: Vec, } impl Rusty for LvWidget { type Parent = (); fn code(&self, _parent: &Self::Parent) -> WrapperResult { let widget_name = format_ident!("{}", to_pascal_case(self.name.as_str())); let methods: Vec = self .methods .iter() .take(1) .into_iter() .flat_map(|m| m.code(self)) .collect(); Ok(quote! { define_object!(#widget_name); impl #widget_name { #(#methods)* } }) } } #[derive(Clone, Eq, PartialEq)] 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 } } } 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.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()); let raw = core::ptr::NonNull::new(ptr)?; let core = ::from_raw(raw); Ok(Self { core }) } } }); } // Make sure all argumets 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_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 = format_ident!("{}", arg.name.as_str()); 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) -> LvResult<()> { unsafe { lvgl_sys::#original_func_name(#args_call); } Ok(()) } }) } } impl From> for LvFunc { fn from(entity: Entity) -> Self { let result = entity.get_result_type().unwrap().get_display_name(); let ret_type = match result.as_str() { "void" => None, _ => Some(LvType::new(result)), }; Self::new( entity.get_name().unwrap(), entity .get_arguments() .unwrap() .iter() .map(|e| e.into()) .collect::>(), ret_type, ) } } #[derive(Clone, Eq, PartialEq)] pub struct LvArg { name: String, typ: LvType, } impl LvArg { pub fn new(name: String, typ: LvType) -> Self { Self { name, typ } } pub fn get_type(&self) -> &LvType { &self.typ } } impl Rusty for LvArg { type Parent = LvFunc; fn code(&self, _parent: &Self::Parent) -> WrapperResult { let name = format_ident!("{}", self.name.as_str()); let typ = self.typ.code(self)?; Ok(quote! { #name: #typ }) } } impl From<&Entity<'_>> for LvArg { fn from(entity: &Entity) -> Self { Self::new( entity.get_name().unwrap(), entity.get_type().unwrap().into(), ) } } #[derive(Clone, Eq, PartialEq)] pub struct LvType { typ: String, } impl LvType { pub fn new(typ: String) -> Self { Self { typ } } pub fn is_const(&self) -> bool { self.typ.starts_with("const ") } } impl Rusty for LvType { type Parent = LvArg; fn code(&self, _parent: &Self::Parent) -> WrapperResult { match TYPE_MAPPINGS.get(self.typ.as_str()) { Some(name) => { let ident = format_ident!("{}", name); Ok(quote! { #ident }) } None => Err(WrapperError::Skip), } } } impl From> for LvType { fn from(ty: Type) -> Self { Self::new(ty.get_display_name()) } } pub struct CodeGen { functions: Vec, widgets: Vec, } impl CodeGen { pub fn new() -> CGResult { let functions = Self::load_function_definitions()?; let widgets = Self::extract_widgets(&functions)?; Ok(Self { functions, widgets }) } pub fn get_widgets(&self) -> &Vec { &self.widgets } fn extract_widgets(functions: &Vec) -> 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()) { ws.entry(widget_name.clone()) .or_insert_with(|| LvWidget { name: widget_name.clone(), methods: Vec::new(), }) .methods .push(f.clone()) } } ws }); Ok(widgets.values().map(|v| v.clone()).collect()) } fn get_widget_names(functions: &Vec) -> 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) .filter_map(|f| { Some(String::from( create_func .captures(f.name.as_str()) .unwrap() .get(1) .unwrap() .as_str(), )) }) .collect::>() } pub fn load_function_definitions() -> CGResult> { let clang = Clang::new()?; let index = Index::new(&clang, false, false); let tu = index .parser(concat!(env!("OUT_DIR"), "/lvgl_full.c")) .parse()?; let entities = tu .get_entity() .get_children() .into_iter() .filter(|e| e.get_kind() == EntityKind::FunctionDecl) .filter(|e| e.get_name().is_some()) .map(|e| e.into()) .filter(|e: &LvFunc| e.name.starts_with(LIB_PREFIX)) .collect::>(); Ok(entities) } 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_list_functions() { let lv = CodeGen::new().unwrap(); let func = String::from("lv_obj_create"); let func_names = lv.get_function_names().unwrap(); assert!(func_names.contains(&func)); } #[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() { // void lv_arc_set_bg_end_angle(lv_obj_t * arc, uint16_t end); let arc_set_bg_end_angle = LvFunc::new( "lv_arc_set_bg_end_angle".to_string(), vec![ LvArg::new("arc".to_string(), LvType::new("lv_obj_t *".to_string())), LvArg::new("end".to_string(), LvType::new("uint16_t".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) -> 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_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() { // lv_obj_t * lv_arc_create(lv_obj_t * par, const lv_obj_t * copy); let arc_create = LvFunc::new( "lv_arc_create".to_string(), vec![ LvArg::new("par".to_string(), LvType::new("lv_obj_t *".to_string())), LvArg::new( "copy".to_string(), LvType::new("const lv_obj_t *".to_string()), ), ], Some(LvType::new("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()); let raw = core::ptr::NonNull::new(ptr)?; let core = ::from_raw(raw); Ok(Self { core }) } } } }; assert_eq!(code.to_string(), expected_code.to_string()); } }