use crate::enums::{TypeHolder, Types};
use crate::types_structs::{Enum, ItemInfo, Struct, Trait, TYPE_CASE};
use crate::{Language, TypeCases};
use derive_new::new;
use std::collections::{HashMap, VecDeque};
use std::fs::{DirEntry, File};
use std::io::Write;
use std::path::PathBuf;
use std::rc::Rc;
use syn::__private::ToTokens;
use syn::{PathArguments, ReturnType, Type};
use std::time::Instant;
pub const F_CLASS: &str = "foreign_class!";
pub const F_CALLBACK: &str = "foreign_callback!";
pub const F_ENUM: &str = "foreign_enum!";
const UNABLE_TO_READ: &str = "Unable to read file: ";
#[derive(new, Debug)]
struct AttrCheck {
is_attribute: bool,
is_constructor: bool,
}
macro_rules! has_gen_attr {
($expr:expr) => {
has_gen_attr!($expr, false)
};
($expr:expr,$check_for_constructor:expr) => {{
let mut is_attribute = false;
let mut is_constructor = false;
$expr.attrs.iter().any(|it| {
is_attribute = it
.path
.segments
.iter()
.any(|it| it.ident == "generate_interface");
if is_attribute && $check_for_constructor {
is_constructor = it.tokens.to_string().contains("constructor");
}
is_attribute
});
AttrCheck::new(is_attribute, is_constructor)
}};
}
macro_rules! has_doc_gen_attr {
($expr:expr) => {
$expr.attrs.iter().any(|it| {
it.path
.segments
.iter()
.any(|it| &it.ident.to_string() == "generate_interface_doc")
})
};
}
macro_rules! get_doc {
($expr:expr) => {
$expr
.attrs
.iter()
.filter_map(|it| {
let doc = it
.path
.segments
.iter()
.map(|it| it.ident.to_string())
.filter(|it| it == "doc")
.next();
if let Some(_) = doc {
Some(it.to_token_stream().to_string())
} else {
None
}
})
.collect::<Vec<String>>()
};
}
macro_rules! correct_string {
($string:expr) => {{
let sign = $string.rfind("<");
if let Some(sign) = sign {
let other = $string.find(">").unwrap();
let new_string = &$string[sign + 1..other];
new_string.trim().to_string()
} else {
$string.trim().into()
}
}};
}
macro_rules! types_in_method {
($expr:expr) => {{
let result = (&$expr)
.sig
.inputs
.iter()
.filter_map(|it| {
match it {
syn::FnArg::Receiver(_) => None, syn::FnArg::Typed(typ) => {
let _type = typ.ty.to_token_stream().to_string().replace(" dyn ", "");
Some(correct_string!(_type))
}
}
})
.collect::<Vec<_>>();
result
}};
}
macro_rules! return_types {
($expr:expr) => {{
let mut return_types: Vec<String> = Vec::new();
match &$expr.sig.output {
ReturnType::Type(_, val) => match &**val {
Type::Path(val) => {
val.path.segments.iter().for_each(|it| {
return_types.push((&it.ident).to_string());
match &it.arguments {
PathArguments::AngleBracketed(val) => {
val.args.iter().for_each(|it| {
let str = it.to_token_stream().to_string();
return_types.push(str);
});
}
_ => {}
}
});
}
_ => {}
},
_ => {}
}
return_types
}};
}
macro_rules! function_signature {
($expr:expr) => {{
{
let signature = &$expr.sig;
let mut iter = signature.to_token_stream().into_iter();
iter.find(|it| it.to_string() == "fn");
let mut result = String::with_capacity(32);
while let Some(val) = iter.next() {
let val = val.to_string();
if val == "'" {
result.push_str(&val); result.push_str(&iter.next().unwrap().to_string());
result.push(' ');
} else {
result.push_str(&val)
}
}
result
}
}};
}
struct ItemsHolder {
list: HashMap<Rc<String>, TypeHolder>,
enums_list: Vec<Enum>,
final_list: VecDeque<Rc<String>>,
}
impl ItemsHolder {
fn new(capacity: usize) -> ItemsHolder {
ItemsHolder {
list: HashMap::with_capacity(capacity),
enums_list: Vec::new(),
final_list: VecDeque::with_capacity(capacity),
}
}
fn add_items(&mut self, name: Rc<String>, item: TypeHolder) {
self.list.insert(name, item);
}
fn sort_items(&mut self) {
if self.list.is_empty() {
eprintln!("Annotate methods and enums to use module rust_interface_file_generator");
return;
}
self.list.keys().next().unwrap().to_string();
let mut values = self
.list
.keys()
.map(|it| (it.clone(), None))
.collect::<HashMap<Rc<String>, Option<()>>>();
fn analyse_item(
item: &TypeHolder,
values: &mut HashMap<Rc<String>, Option<()>>,
map: &HashMap<Rc<String>, TypeHolder>,
out: &mut VecDeque<Rc<String>>,
) {
let types = item.types();
let item_name = item.name().to_string();
values.remove(&item_name);
let item_name = Rc::new(item_name);
out.iter().position(|it| it == &item_name).and_then(|it| {
Some(out.remove(it))
});
out.push_front(item_name);
for _type in types {
if let Some(val) = map.get(_type) {
if val.name() == item.name() {
continue;
}
analyse_item(val, values, map, out);
}
}
}
while !values.is_empty() {
analyse_item(
self.list
.get(&values.iter().next().unwrap().0.to_string())
.unwrap(),
&mut values,
&self.list,
&mut self.final_list,
);
}
}
fn add_enum(&mut self, data: Enum) {
self.enums_list.push(data)
}
fn generate_interface(mut self, language: Language, out_file: &PathBuf) {
let mut file = File::create(out_file).expect("Unable to write to disk");
file.write(b"//Automatically generated by rifgen\nuse crate::*;\n")
.unwrap();
if matches!(language, Language::Java) {
file.write_all(b"use jni_sys::*;\n").unwrap();
}
self.sort_items();
for mut enums in self.enums_list {
file.write_all(enums.generate_interface().as_ref())
.expect("Unable to write to disk");
}
for name in self.final_list {
file.write_all(
self.list
.get_mut(&*name)
.unwrap()
.generate_interface()
.as_ref(),
)
.expect("Unable to write to disk");
}
}
}
fn visit_dirs(dir: &PathBuf, cb: &mut dyn FnMut(&std::fs::DirEntry)) -> std::io::Result<()> {
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path, cb)?;
} else {
cb(&entry);
}
}
}
Ok(())
}
pub struct FileGenerator {
interface_file_path: PathBuf,
starting_point: PathBuf,
}
impl FileGenerator {
pub fn new(
type_case: TypeCases,
interface_file_path: PathBuf,
starting_point: PathBuf,
) -> FileGenerator {
unsafe { TYPE_CASE = type_case }
FileGenerator {
interface_file_path,
starting_point,
}
}
pub fn build(&self, language: Language) {
let start = Instant::now();
let mut file_data: HashMap<Rc<String>, TypeHolder> = HashMap::new();
let mut closure = |file: &DirEntry| {
let file_path = file.path();
let file_contents = std::fs::read_to_string(&file_path).expect(&format!(
"{}{}",
UNABLE_TO_READ,
file_path.to_str().unwrap()
));
let compiled_file = syn::parse_file(&file_contents).expect("Invalid rust file");
for item in &compiled_file.items {
match item {
syn::Item::Struct(item) => {
if has_doc_gen_attr!(item) {
let name = Rc::new((&item).ident.to_string());
if let Some(val) = file_data.get_mut(&name) {
match val {
TypeHolder::Struct(val) => {
val.docs.append(&mut get_doc!(item));
}
_ => {
panic!("Expected {} to be a struct", name)
}
}
} else {
file_data.insert(
name.clone(),
TypeHolder::Struct(Struct::new(
name.to_string(),
Types::Struct,
get_doc!(item),
vec![],
)),
);
}
}
}
syn::Item::Fn(val) => {
let name = val.sig.ident.to_string();
if has_gen_attr!(val).is_attribute {
panic!(
"interface functions should be declared in impl blocks.\nName of function {}",
name
)
}
}
syn::Item::Impl(val) => {
FileGenerator::impl_data(&mut file_data, val);
}
syn::Item::Enum(val) => {
if has_gen_attr!(val).is_attribute {
let name = Rc::new((&val).ident.to_string());
assert!(
!file_data.contains_key(&name),
"Multiple definitions of {}",
&name
); let variants = val
.variants
.iter()
.map(|it| ItemInfo::new_enum(it.ident.to_string(), get_doc!(it)))
.collect();
file_data.insert(
name.clone(),
TypeHolder::Enum(Enum::new(
name.to_string(),
Types::Enum,
get_doc!(val),
variants,
)),
);
}
}
syn::Item::Trait(val) => {
if !has_gen_attr!(val).is_attribute {
continue;
}
let name = Rc::new((&val).ident.to_string());
let mut trait_data: Trait = Trait::new(
name.to_string(),
Types::Trait,
get_doc!(val),
Vec::with_capacity(val.items.len()),
);
for item in &val.items {
if let syn::TraitItem::Method(method) = item {
let method_name = (&method).sig.ident.to_string();
trait_data.extras.push(ItemInfo::new_method(
function_signature!(method),
get_doc!(method),
method_name,
false,
types_in_method!(method),
return_types!(method),
));
}
}
assert!(
!file_data.contains_key(&name),
"Multiple definitions of {}",
&name
); file_data.insert(name.clone(), TypeHolder::Trait(trait_data));
}
_ => {
}
}
}
};
visit_dirs(&self.starting_point, &mut closure).expect("Unable to read directory");
let mut holder = ItemsHolder::new(file_data.len());
for (name, type_holder) in file_data {
match type_holder {
TypeHolder::Struct(_) | TypeHolder::Trait(_) => {
holder.add_items(name, type_holder);
}
TypeHolder::Enum(val) => holder.add_enum(val),
}
}
holder.generate_interface(language, &self.interface_file_path);
println!("Total Time Taken To Generate File {:?}", start.elapsed());
}
fn impl_data(map: &mut HashMap<Rc<String>, TypeHolder>, item: &syn::ItemImpl) {
let self_type = &*item.self_ty;
if let syn::Type::Path(type_path) = self_type {
let name = type_path
.path
.segments
.iter()
.next()
.and_then(|it| Some(it.ident.to_string()));
if let Some(name) = name {
for item in item.items.iter() {
match item {
syn::ImplItem::Method(method) => {
let method_info: AttrCheck = has_gen_attr!(method, true);
if !method_info.is_attribute {
continue;
}
let method_name = (&method).sig.ident.to_string();
let data = map.get_mut(&name);
let item_info = ItemInfo::new_method(
function_signature!(method),
get_doc!(method),
method_name,
method_info.is_constructor,
types_in_method!(method),
return_types!(method),
);
if let Some(data) = data {
match data {
TypeHolder::Struct(val) => {
val.extras.push(item_info);
}
_ => {
unimplemented!("Impl function may only be used for structs")
}
}
} else {
let data = Struct::new(
name.to_string(),
Types::Struct,
vec![],
vec![item_info],
);
map.insert(Rc::new(name.clone()), TypeHolder::Struct(data));
}
}
_ => {}
}
}
}
};
}
}