lvgl-rs/lvgl-codegen/src/lib.rs
2022-05-17 22:52:57 +02:00

655 lines
19 KiB
Rust

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<T> = Result<T, Box<dyn Error>>;
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<T> = Result<T, WrapperError>;
pub trait Rusty {
type Parent;
fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream>;
}
#[derive(Clone)]
pub struct LvWidget {
name: String,
methods: Vec<LvFunc>,
}
impl Rusty for LvWidget {
type Parent = ();
fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
// 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<TokenStream> = 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<LvArg>,
ret: Option<LvType>,
}
impl LvFunc {
pub fn new(name: String, args: Vec<LvArg>, ret: Option<LvType>) -> 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<TokenStream> {
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<C>(parent: &mut C) -> crate::LvResult<Self>
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 = <crate::Obj as crate::Widget>::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<ForeignItemFn> 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::<Vec<LvArg>>(),
ret,
)
}
}
#[derive(Clone)]
pub struct LvArg {
name: String,
typ: LvType,
}
impl From<syn::PatType> 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::<syn::Ident>(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<TokenStream> {
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<Box<syn::Type>>,
}
impl LvType {
pub fn new(literal_name: String) -> Self {
Self {
literal_name,
_r_type: None,
}
}
pub fn from(r_type: Box<syn::Type>) -> 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<TokenStream> {
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<Box<syn::Type>> for LvType {
fn from(t: Box<syn::Type>) -> Self {
Self::from(t)
}
}
pub struct CodeGen {
functions: Vec<LvFunc>,
widgets: Vec<LvWidget>,
}
impl CodeGen {
pub fn from(code: &str) -> CGResult<Self> {
let functions = Self::load_func_defs(code)?;
let widgets = Self::extract_widgets(&functions)?;
Ok(Self { functions, widgets })
}
pub fn get_widgets(&self) -> &Vec<LvWidget> {
&self.widgets
}
fn extract_widgets(functions: &[LvFunc]) -> CGResult<Vec<LvWidget>> {
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<String> {
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::<Vec<_>>()
}
pub fn load_func_defs(bindgen_code: &str) -> CGResult<Vec<LvFunc>> {
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::<Vec<LvFunc>>();
Ok(fns)
}
pub fn get_function_names(&self) -> CGResult<Vec<String>> {
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<C>(parent: &mut C) -> crate::LvResult<Self>
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 = <crate::Obj as crate::Widget>::from_raw(raw);
Ok(Self { core })
} else {
Err(crate::LvError::InvalidReference)
}
}
}
}
};
assert_eq!(code.to_string(), expected_code.to_string());
}
}