use proc_macro::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident}; mod apply_transaction; mod attribute_info; mod attribute_token_type; mod data_type; mod get_transaction; mod tracker_field; mod transaction_field; use apply_transaction::*; use attribute_info::*; use data_type::*; use get_transaction::*; use tracker_field::*; use transaction_field::*; /// # Attributes /// /// * #[transaction(pre_setter_exec = `Function Name`)] ``` Fn(&mut self, t: T) -> T ``` /// * #[transaction(setter_exec = `Function Name`)] ``` Fn(&mut self, t: T) -> Result<()> ``` /// * #[transaction(apply_exec = `Function Name`)] ``` Fn(&self, old: T, i: usize, multi_mut: &mut MultiMut<'_>, scene: &mut Scene) -> Result<()> ``` /// * #[transaction(convert_as = `Data Type`)] /// * #[transaction(skip_getter)] /// * #[transaction(private_setter)] /// * #[transaction(from = `Function Name`)] ``` Fn(t: T) -> U ``` /// * #[transaction(into = `Function Name`)] ``` Fn(&self, u: U) -> T ``` /// * #[transaction(feature = `Feature Name`)] /// * #[transaction(not_feature = `Feature Name`)] /// /// # Examples /// /// ``` /// #[derive(Transaction)] /// struct Test { /// #[transaction] /// test: u32, /// /// #[tracker] /// tracker: TestTransactionTracker, /// } /// ``` /// #[proc_macro_derive(Transaction, attributes(transaction, tracker))] pub fn transaction_builder(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as DeriveInput); let struct_name = ast.ident; let generics = ast.generics.to_token_stream(); let mut tracker = None; let mut transactions = Vec::new(); match ast.data { Data::Struct(data_struct) => match data_struct.fields { Fields::Named(named_fields) => { for field in named_fields.named.iter() { let attribute_infos: Vec = field .attrs .iter() .filter_map(|attribute| AttributeInfo::from_attribute(attribute)) .collect(); if attribute_infos.is_empty() { continue; } AttributeInfo::check_duplicate(&attribute_infos); let transaction_field = TransactionField { variable_name: field .ident .as_ref() .expect("named field should have a name") .clone(), data_type: DataType::new(&field.ty), attribute_infos, tracker: None, }; if AttributeInfo::is_transaction(&transaction_field.attribute_infos) { transactions.push(transaction_field); } else if AttributeInfo::is_tracker(&transaction_field.attribute_infos) { assert!(tracker.is_none(), "Only one tracker allowed"); tracker = Some(transaction_field); } } } Fields::Unnamed(_) => { todo!("Unnamed Field") } Fields::Unit => { todo!("Unit Field") } }, Data::Enum(_) => { todo!("Data Type Enum") } Data::Union(_) => { todo!("Data Type Union") } } if transactions.is_empty() { build_empty(struct_name, generics) } else { build_default( struct_name.clone(), generics, tracker.expect(&format!( "a tracker of type {}TransactionTracker is required", struct_name )), transactions, ) } } fn build_empty(struct_name: Ident, generics: proc_macro2::TokenStream) -> TokenStream { TokenStream::from(quote! { impl #generics Transaction for #struct_name #generics { fn get_transactions(&mut self) -> anyhow::Result> { Ok(None) } fn apply_transactions( &mut self, mut _transactions: &str, _time: std::time::Duration, _multi_mut: &mut MultiMut<'_>, _scene: &mut Scene, ) -> anyhow::Result<()> { Ok(()) } } }) } fn build_default( struct_name: Ident, generics: proc_macro2::TokenStream, tracker: TransactionField, mut transactions: Vec, ) -> TokenStream { let helper_struct = format_ident!("{}TransactionTracker", struct_name); let tracker_variable_name = tracker.variable_name; let mut variable_name_list = String::new(); for transaction in transactions.iter_mut() { transaction.tracker = Some(tracker_variable_name.clone()); variable_name_list += (transaction.variable_name.to_string() + ",").as_str(); } variable_name_list.pop(); let tracker_fields: Vec = transactions .iter() .map(|transaction| TrackerField::from(transaction)) .collect(); let data_types: Vec = tracker_fields .iter() .map(|tracker| TrackerDataType::from(tracker)) .collect(); let default_type: Vec = data_types .iter() .map(|tracker| TrackerDefault::from(tracker)) .collect(); let get_transactions: Vec> = transactions .iter() .map(|t| GetTransaction::from(t)) .collect(); let apply_transactions: Vec> = transactions .iter() .map(|t| ApplyTransaction::from(t)) .collect(); TokenStream::from(quote! { #[derive(Debug)] struct #helper_struct { #( #data_types )* } impl Clone for #helper_struct { fn clone(&self) -> Self { Self::default() } } impl Default for #helper_struct { fn default() -> Self { Self { #( #default_type )* } } } impl #generics #struct_name #generics { #( #transactions )* } impl #generics Transaction for #struct_name #generics { fn get_transactions(&mut self) -> anyhow::Result> { let mut serialized_transactions = String::new(); #( #get_transactions )* // remove the last ';' serialized_transactions.pop(); Ok( if serialized_transactions.is_empty() { None } else { Some(serialized_transactions) } ) } fn apply_transactions( &mut self, mut transactions: &str, time: std::time::Duration, multi_mut: &mut MultiMut<'_>, scene: &mut Scene, ) -> anyhow::Result<()> { let mut splits = transactions.split(';'); for split in splits { let pos = split.find(':').ok_or( anyhow::Error::msg(format!( "Failed parsing in ApplyTransaction ':' split. Found {}", stringify!(#struct_name) )))?; let (var, value) = split.split_at(pos); let value = value.trim_start_matches(':'); unsafe { multi_mut.clear_all_usages() }; match var { #( #apply_transactions )* _ => return Err( anyhow::Error::msg(format!( "Unsupported Transaction {} in {}", var.to_string(), stringify!(#struct_name) )) ), } } Ok(()) } } }) }