277 lines
8.3 KiB
Rust
277 lines
8.3 KiB
Rust
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<AttributeInfo> = 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<Option<String>> {
|
|
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<TransactionField>,
|
|
) -> 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<TrackerField> = transactions
|
|
.iter()
|
|
.map(|transaction| TrackerField::from(transaction))
|
|
.collect();
|
|
|
|
let data_types: Vec<TrackerDataType> = tracker_fields
|
|
.iter()
|
|
.map(|tracker| TrackerDataType::from(tracker))
|
|
.collect();
|
|
|
|
let default_type: Vec<TrackerDefault> = data_types
|
|
.iter()
|
|
.map(|tracker| TrackerDefault::from(tracker))
|
|
.collect();
|
|
|
|
let get_transactions: Vec<GetTransaction<'_>> = transactions
|
|
.iter()
|
|
.map(|t| GetTransaction::from(t))
|
|
.collect();
|
|
|
|
let apply_transactions: Vec<ApplyTransaction<'_>> = 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<Option<String>> {
|
|
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(())
|
|
}
|
|
}
|
|
})
|
|
}
|