use proc_macro::TokenStream; use proc_macro_error2::{abort, abort_call_site, proc_macro_error}; use pumpkin_data::Block; use pumpkin_data::tag::{RegistryKey, get_tag_ids}; use quote::quote; use syn::spanned::Spanned; use syn::{self, Attribute, DeriveInput, LitStr, Type, parse_quote}; use syn::{Expr, Field, Fields, ItemStruct, Stmt, parse_macro_input}; /// Derives the `Payload` trait for an event struct, enabling it to be used in the plugin system. /// /// # Arguments /// - `item` – The input `TokenStream` representing the struct to derive `Event` for. #[proc_macro_derive(Event)] pub fn event(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as DeriveInput); let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); quote! { impl #impl_generics crate::plugin::Payload for #name #ty_generics #where_clause { fn get_name_static() -> &'static str { stringify!(#name) } fn get_name(&self) -> &'static str { stringify!(#name) } fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } fn as_any(&self) -> &dyn std::any::Any { self } } } .into() } /// Marks a struct as cancellable by adding a `cancelled: bool` field and /// implementing the `Cancellable` trait. /// /// # Arguments /// - `_args` – `TokenStream` of arguments passed to the attribute (unused). /// - `input` – The input `TokenStream` representing the struct to modify. #[proc_macro_attribute] pub fn cancellable(_args: TokenStream, input: TokenStream) -> TokenStream { let mut item_struct = parse_macro_input!(input as ItemStruct); let name = &item_struct.ident; let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl(); match &mut item_struct.fields { Fields::Named(fields) => { if fields .named .iter() .any(|f| f.ident.as_ref().is_some_and(|i| i == "cancelled")) { abort!(fields.span(), "Struct already has a `cancelled` field"); } let field: Field = parse_quote! { pub cancelled: bool }; fields.named.push(field); } _ => abort!( item_struct.span(), "#[cancellable] can only be used on structs with named fields" ), } quote! { #item_struct impl #impl_generics crate::plugin::Cancellable for #name #ty_generics #where_clause { fn cancelled(&self) -> bool { self.cancelled } fn set_cancelled(&mut self, cancelled: bool) { self.cancelled = cancelled; } } } .into() } /// Sends a cancellable event asynchronously. /// /// # Arguments /// - `input` – The input `TokenStream` representing the labelled blocks and event expressions. #[proc_macro_error] #[proc_macro] pub fn send_cancellable(input: TokenStream) -> TokenStream { let block = parse_macro_input!(input as syn::Block); let mut server_expr = None; let mut event_expr = None; let mut after_block = None; let mut cancelled_block = None; for stmt in block.stmts { match stmt { Stmt::Expr(expr, _) => { // Check if it is a labelled block first. let mut is_special_block = false; if let Expr::Block(ref b) = expr && let Some(ref label) = b.label { let label_name = label.name.ident.to_string(); if label_name == "after" { after_block = Some(b.clone()); // Clone strictly necessary here as we split AST. is_special_block = true; } else if label_name == "cancelled" { cancelled_block = Some(b.clone()); is_special_block = true; } } // If it wasn't a special block, it must be the event expression. if !is_special_block { if server_expr.is_none() { server_expr = Some(expr); } else if event_expr.is_none() { event_expr = Some(expr); } else { abort!( expr.span(), "Multiple event expressions found. Only one event expression allowed." ); } } } // Abort on other statements (like `let x = ...`) if strictness is desired. _ => abort!( stmt.span(), "Only event expressions and labeled blocks allowed in `send_cancellable!`" ), } } let Some(server) = server_expr else { abort_call_site!("Server expression must be specified"); }; let Some(event) = event_expr else { abort_call_site!("Event expression must be specified"); }; // Construct the if/else logic. let logic = match (after_block, cancelled_block) { (Some(after), Some(cancelled)) => quote! { if !event.cancelled { #after } else { #cancelled } }, (Some(after), None) => quote! { if !event.cancelled { #after } }, (None, Some(cancelled)) => quote! { if event.cancelled { #cancelled } }, (None, None) => quote! {}, }; quote! { let event = #server.plugin_manager.fire(#event).await; #logic } .into() } /// Attaches a fixed packet ID to a struct implementing `Packet`. /// /// # Arguments /// - `args` – The `TokenStream` representing the packet ID expression. /// - `item` – The input `TokenStream` representing the struct to implement `Packet` for. #[proc_macro_attribute] pub fn packet(args: TokenStream, item: TokenStream) -> TokenStream { let packet_id_expr = parse_macro_input!(args as Expr); let ast = parse_macro_input!(item as DeriveInput); let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); quote! { #ast impl #impl_generics crate::packet::Packet for #name #ty_generics #where_clause { const PACKET_ID: i32 = #packet_id_expr; } } .into() } /// Attaches a multi-version packet ID to a struct implementing `MultiVersionJavaPacket`. /// /// # Arguments /// - `args` – The `TokenStream` representing the packet ID expression. /// - `item` – The input `TokenStream` representing the struct to implement the trait for. #[proc_macro_attribute] pub fn java_packet(args: TokenStream, item: TokenStream) -> TokenStream { let packet_id_expr = parse_macro_input!(args as Expr); let ast = parse_macro_input!(item as DeriveInput); let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); quote! { #ast impl #impl_generics crate::packet::MultiVersionJavaPacket for #name #ty_generics #where_clause { #[must_use] #[inline] fn to_id(version: pumpkin_util::version::MinecraftVersion) -> i32 { #packet_id_expr.to_id(version) } } } .into() } /// Marks a struct as representing a specific block by its name. /// /// # Arguments /// - `args` – The `TokenStream` representing the block name literal. /// - `item` – The input `TokenStream` representing the struct to implement `BlockMetadata` for. #[proc_macro_attribute] pub fn pumpkin_block(args: TokenStream, item: TokenStream) -> TokenStream { let input_item = item.clone(); let arg_lit = parse_macro_input!(args as LitStr); let arg_value = arg_lit.value(); let block_name = arg_value.strip_prefix("minecraft:").unwrap_or(&arg_value); let Some(block) = Block::from_name(block_name) else { return syn::Error::new(arg_lit.span(), "Invalid block name") .to_compile_error() .into(); }; let block_id = block.id; let ast = parse_macro_input!(item as DeriveInput); let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); let generated = quote! { impl #impl_generics crate::block::BlockMetadata for #name #ty_generics #where_clause { fn ids() -> Box<[u16]> { [#block_id].into() } } }; // Combine the original item and new impl. let mut output = input_item; output.extend(TokenStream::from(generated)); output } /// Marks a struct as representing a set of blocks from a given tag. /// /// # Arguments /// - `args` – The `TokenStream` representing the block tag literal. /// - `item` – The input `TokenStream` representing the struct to implement `BlockMetadata` for. #[proc_macro_attribute] pub fn pumpkin_block_from_tag(args: TokenStream, item: TokenStream) -> TokenStream { let original_item = item.clone(); let arg_lit = parse_macro_input!(args as LitStr); let ast = parse_macro_input!(item as DeriveInput); let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); let full_tag = arg_lit.value(); let Some(values) = get_tag_ids(RegistryKey::Block, &full_tag) else { return syn::Error::new(arg_lit.span(), format!("Failed to get tag IDs: {full_tag}")) .to_compile_error() .into(); }; let expanded = quote! { impl #impl_generics crate::block::BlockMetadata for #name #ty_generics #where_clause { fn ids() -> Box<[u16]> { Box::new([ #(#values),* ]) } } }; let mut output = original_item; output.extend(TokenStream::from(expanded)); output } // #[proc_macro_error] // #[proc_macro_attribute] // pub fn block_property(input: TokenStream, item: TokenStream) -> TokenStream { // let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap(); // let name = &ast.ident; // let (impl_generics, ty_generics, _) = ast.generics.split_for_impl(); // let input_string = input.to_string(); // let input_parts: Vec<&str> = input_string.split("[").collect(); // let property_name = input_parts[0].trim_ascii().trim_matches(&['"', ','][..]); // let mut property_values: Vec<&str> = Vec::new(); // if input_parts.len() > 1 { // property_values = input_parts[1] // .trim_matches(']') // .split(", ") // .map(|p| p.trim_ascii().trim_matches(&['"', ','][..])) // .collect::>(); // } // let item: proc_macro2::TokenStream = item.into(); // let (variants, is_enum): (Vec, bool) = match ast.data { // syn::Data::Enum(enum_item) => ( // enum_item.variants.into_iter().map(|v| v.ident).collect(), // true, // ), // syn::Data::Struct(s) => { // let fields = match s.fields { // Fields::Named(f) => abort!(f.span(), "Block properties can't have named fields"), // Fields::Unnamed(fields) => fields.unnamed, // Fields::Unit => abort!(s.fields.span(), "Block properties must have fields"), // }; // if fields.len() != 1 { // abort!( // fields.span(), // "Block properties `struct's must have exactly one field" // ); // } // let field = fields.first().unwrap(); // let ty = &field.ty; // let struct_type = match field.ty { // syn::Type::Path(ref type_path) => { // type_path.path.segments.first().unwrap().ident.to_string() // } // ref other => abort!( // other.span(), // "Block properties can only have primitive types" // ), // }; // match struct_type.as_str() { // "bool" => ( // vec![ // proc_macro2::Ident::new("true", proc_macro2::Span::call_site()), // proc_macro2::Ident::new("false", proc_macro2::Span::call_site()), // ], // false, // ), // other => abort!( // ty.span(), // format!("`{other}` is not supported (why not implement it yourself?)") // ), // } // } // _ => abort_call_site!("Block properties can only be `enum`s or `struct's"), // }; // let values = variants.iter().enumerate().map(|(i, v)| match is_enum { // true => { // let mut value = v.to_string().to_snake_case(); // if !property_values.is_empty() && i < property_values.len() { // value = property_values[i].to_string(); // } // quote! { // Self::#v => #value.to_string(), // } // } // false => { // let value = v.to_string(); // quote! { // Self(#v) => #value.to_string(), // } // } // }); // let from_values = variants.iter().enumerate().map(|(i, v)| match is_enum { // true => { // let mut value = v.to_string().to_snake_case(); // if !property_values.is_empty() && i < property_values.len() { // value = property_values[i].to_string(); // } // quote! { // #value => Self::#v, // } // } // false => { // let value = v.to_string(); // quote! { // #value => Self(#v), // } // } // }); // let extra_fns = variants.iter().map(|v| { // let title = proc_macro2::Ident::new( // &v.to_string().to_pascal_case(), // proc_macro2::Span::call_site(), // ); // quote! { // pub fn #title() -> Self { // Self(#v) // } // } // }); // let extra = if is_enum { // quote! {} // } else { // quote! { // impl #name { // #(#extra_fns)* // } // } // }; // let code = quote! { // #item // impl #impl_generics pumpkin_world::block::properties::BlockPropertyMetadata for #name #ty_generics { // fn name(&self) -> &'static str { // #property_name // } // fn value(&self) -> String { // match self { // #(#values)* // } // } // fn from_value(value: String) -> Self { // match value.as_str() { // #(#from_values)* // _ => panic!("Invalid value for block property"), // } // } // } // #extra // }; // code.into() // } /// Derives the `PacketWrite` trait for a struct, enabling serialization. /// /// # Arguments /// - `input` – The input `TokenStream` representing the struct to derive `PacketWrite` for. #[rustfmt::skip] #[proc_macro_derive(PacketWrite, attributes(serial))] pub fn derive_serialize(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let fields = if let syn::Data::Struct(data) = &input.data { data.fields.iter().map(|f| { let ident = f.ident.as_ref().unwrap(); let (is_big_endian, no_prefix) = check_serial_attributes(&f.attrs); let is_vec = is_vec(&f.ty); if is_vec && !no_prefix { // Vec with prefix: write VarUInt length, then data if is_big_endian { quote! { crate::codec::var_uint::VarUInt(self.#ident.len() as u32).write(writer)?; self.#ident.write_be(writer)?; } } else { quote! { crate::codec::var_uint::VarUInt(self.#ident.len() as u32).write(writer)?; self.#ident.write(writer)?; } } } else { // Non-Vec or Vec with no_prefix: write directly if is_big_endian { quote! { self.#ident.write_be(writer)?; } } else { quote! { self.#ident.write(writer)?; } } } }) } else { unimplemented!() }; let expanded = quote! { impl PacketWrite for #name { fn write(&self, writer: &mut W) -> Result<(), std::io::Error> { #(#fields)* Ok(()) } } }; expanded.into() } /// Derives the `PacketRead` trait for a struct, enabling deserialization. /// /// # Arguments /// - `input` – The input `TokenStream` representing the struct to derive `PacketRead` for. #[rustfmt::skip] #[proc_macro_derive(PacketRead, attributes(serial))] pub fn derive_deserialize(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let fields = if let syn::Data::Struct(data) = &input.data { data.fields.iter().map(|f| { let ident = f.ident.as_ref().unwrap(); let (is_big_endian, no_prefix) = check_serial_attributes(&f.attrs); let is_vec = is_vec(&f.ty); if is_vec && !no_prefix { // Vec with prefix: read VarUInt length, then data quote! { #ident: { let len = crate::codec::var_uint::VarUInt::read(reader)?.0 as usize; let mut buf = vec![0u8; len]; reader.read_exact(&mut buf)?; buf } } } else { // Non-Vec or Vec with no_prefix: read directly if is_big_endian { quote! { #ident: PacketRead::read_be(reader)? } } else { quote! { #ident: PacketRead::read(reader)? } } } }) } else { unimplemented!() }; let expanded = quote! { impl PacketRead for #name { fn read(reader: &mut R) -> std::io::Result { Ok(Self { #(#fields),* }) } } }; expanded.into() } /// Checks a field's `#[serial(...)]` attributes. /// /// # Arguments /// - `attrs` – Slice of `Attribute`s to inspect for serial-specific metadata. /// /// # Returns /// Tuple `(is_big_endian, no_prefix)` indicating whether the field is big-endian /// and/or has no length prefix. fn check_serial_attributes(attrs: &[Attribute]) -> (bool, bool) { let mut is_big_endian = false; let mut no_prefix = false; for attr in attrs { if attr.path().is_ident("serial") { let _ = attr.parse_nested_meta(|meta| { if meta.path.is_ident("big_endian") { is_big_endian = true; } else if meta.path.is_ident("no_prefix") { no_prefix = true; } Ok(()) }); } } (is_big_endian, no_prefix) } /// Returns true if the type is a `Vec<_>`. /// /// # Arguments /// - `ty` – The `Type` to check. /// /// # Returns /// `true` if the type is a `Vec`, otherwise `false`. fn is_vec(ty: &Type) -> bool { if let Type::Path(type_path) = ty { type_path .path .segments .iter() .next_back() .is_some_and(|segment| segment.ident == "Vec") } else { false } }