engine/transaction_derive/src/lib.rs
2024-08-23 13:22:09 +02:00

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(())
}
}
})
}