ireko/paramacro/src/lib.rs
Joshua Goins 4801c2678a Add new PropertyBase trait, implement more things needed for writing
The biggest change is that structs and properties now provide their own name,
which is needed when we want to write everything back to a file. Also a bunch of
reorganization of stuff I didn't understand before.
2025-03-02 14:07:39 -05:00

107 lines
3.4 KiB
Rust

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Expr, Fields, Item, Lit};
// TODO: clean up this mess.
/// Helps read and write serialized structs. These are structs that have named fields with known types.
/// Note: This adds a hidden "None" at the end of the struct as well.
#[proc_macro_attribute]
pub fn serialized_struct(_metadata: TokenStream, input: TokenStream)
-> TokenStream {
let mut input = syn::parse_macro_input!(input as syn::ItemStruct);
let Lit::Str(struct_name) = syn::parse_macro_input!(_metadata as syn::Lit) else {
panic!("This must be given a name!")
};
let mut field_types = vec![];
let mut field_idents = vec![];
let mut field_names = vec![];
for field in &mut input.fields {
let our_custom = &field.attrs[0];
let our_custom_name = our_custom.meta.require_name_value().unwrap();
let our_custom_name = match &our_custom_name.value {
Expr::Lit(_0) => {
match &_0.lit {
Lit::Str(_0) => {
Some(_0.value())
}
_ => None
}
}
_ => None
}.unwrap();
field.attrs.clear();
field_types.push(field.ty.clone());
field_idents.push(field.ident.clone());
field_names.push(our_custom_name.clone());
let field_tokens = field.to_token_stream();
let new_field_token_stream = quote! {
#[br(parse_with = crate::structs::read_struct_field, args(#our_custom_name))]
#[bw(write_with = crate::structs::write_struct_field, args(#our_custom_name))]
#field_tokens
};
let buffer = ::syn::parse::Parser::parse2(
syn::Field::parse_named,
new_field_token_stream,
).unwrap();
*field = buffer;
}
// Add "None" field
let none_field_stream = quote! {
#[br(temp)]
#[bw(calc = crate::structs::PrimaryAssetNameProperty { property_name: "None".to_string(), type_name: "".to_string(), key: None } )]
none_field: crate::structs::PrimaryAssetNameProperty
};
let buffer = ::syn::parse::Parser::parse2(
syn::Field::parse_named,
none_field_stream,
).unwrap();
match &mut input.fields {
Fields::Named(_0) => {
_0.named.push(buffer)
}
_ => {}
}
let id = &input.ident;
let output = quote! {
#[binrw]
#input
#[automatically_derived]
impl crate::structs::PropertyBase for #id {
fn type_name() -> &'static str {
return "StructProperty";
}
fn struct_name() -> Option<&'static str> {
return Some(#struct_name);
}
fn size_in_bytes(&self) -> u32 {
#( #field_types::size_in_bytes(&self.#field_idents) )+* + #( (crate::structs::calc_struct_field_prelude_byte_size(stringify!(#field_types), #field_names, #field_types::struct_name()) ) )+* + 9 // for "none" field
}
}
};
output.into_token_stream().into()
}
/// Denotes a field with a known name.
#[proc_macro_attribute]
pub fn serialized_field(attr: TokenStream, input: TokenStream) -> TokenStream {
let input2 = parse_macro_input!(input as Item);
let output = quote! {
#[binrw]
#input2
};
output.into_token_stream().into()
}