a bare-bones limbo server in rust (mirror of https://github.com/xoogware/crawlspace)
at master 9.8 kB view raw
1use proc_macro::{Span, TokenStream}; 2use quote::quote; 3use syn::{parse_macro_input, parse_quote, DeriveInput, Fields, Ident, Index, Lit, Path}; 4 5#[proc_macro_derive(Packet, attributes(packet))] 6pub fn derive_packet(input: TokenStream) -> TokenStream { 7 let input = parse_macro_input!(input as DeriveInput); 8 9 let syn::Data::Struct(_) = input.data else { 10 panic!("Packet must be defined as a struct"); 11 }; 12 13 let mut id = None; 14 let mut state = None; 15 let mut direction = None; 16 17 for attr in input.attrs { 18 if !attr.path().is_ident("packet") { 19 continue; 20 } 21 22 attr.parse_nested_meta(|meta| { 23 if meta.path.is_ident("id") { 24 let lit = meta.value()?.parse()?; 25 match lit { 26 Lit::Str(i) => { 27 id = Some(i); 28 } 29 _ => panic!("attribute value `id` must be a string"), 30 } 31 } else if meta.path.is_ident("state") { 32 let lit = meta 33 .value() 34 .expect("no value for state") 35 .parse() 36 .expect("couldn't parse value for state"); 37 let Lit::Str(v) = lit else { 38 panic!("unable to parse state as string"); 39 }; 40 state = Some( 41 v.parse_with(syn::Path::parse_mod_style) 42 .expect("couldn't parse state as path"), 43 ); 44 } else if meta.path.is_ident("serverbound") { 45 match direction { 46 None => direction = Some("Serverbound"), 47 Some(_) => { 48 panic!("cannot have two directives of type `serverbound` or `clientbound`") 49 } 50 } 51 } else if meta.path.is_ident("clientbound") { 52 match direction { 53 None => direction = Some("Clientbound"), 54 Some(_) => { 55 panic!("cannot have two directives of type `serverbound` or `clientbound`") 56 } 57 } 58 } else { 59 let Some(id) = meta.path.get_ident() else { 60 panic!("unable to get ident for unrecognized directive"); 61 }; 62 panic!("unrecognized directive {}", id); 63 } 64 65 Ok(()) 66 }) 67 .unwrap(); 68 } 69 70 let id = id.expect("id must be provided for packet"); 71 let state = state.expect("state must be provided for packet"); 72 let direction = Ident::new( 73 direction.expect("direction must be provided for packet"), 74 Span::call_site().into(), 75 ); 76 77 let name = input.ident; 78 let where_clause = input.generics.where_clause.clone(); 79 let generics = input.generics; 80 81 quote! { 82 impl #generics Packet for #name #generics #where_clause { 83 fn id() -> &'static str { 84 #id 85 } 86 87 fn state() -> PacketState { 88 #state 89 } 90 91 fn direction() -> PacketDirection { 92 PacketDirection::#direction 93 } 94 } 95 } 96 .into() 97} 98 99/// Automatically implements "straight-across" encoding for the given struct, i.e. fields are 100/// serialized in order as is. Supports #[varint] and #[varlong] attributes on integer types to 101/// serialize as those formats instead. 102#[proc_macro_derive(Encode, attributes(varint, varlong))] 103pub fn derive_encode(input: TokenStream) -> TokenStream { 104 let input = parse_macro_input!(input as DeriveInput); 105 106 let syn::Data::Struct(data) = input.data else { 107 panic!("Can only derive Encode on a struct"); 108 }; 109 110 let name = input.ident; 111 let where_clause = input.generics.where_clause.clone(); 112 let generics = input.generics; 113 114 let mut fields_encoded = proc_macro2::TokenStream::new(); 115 116 match data.fields { 117 Fields::Named(fields) => { 118 for field in fields.named { 119 let field_name = field.ident.unwrap(); 120 121 if field 122 .attrs 123 .iter() 124 .any(|attr| attr.meta.path().is_ident("varint")) 125 { 126 fields_encoded.extend(quote! { 127 VarInt(self.#field_name as i32).encode(&mut w)?; 128 }); 129 } else if field 130 .attrs 131 .iter() 132 .any(|attr| attr.meta.path().is_ident("varlong")) 133 { 134 fields_encoded.extend(quote! { 135 VarLong(self.#field_name as i64).encode(&mut w)?; 136 }); 137 } else { 138 fields_encoded.extend(quote! { 139 self.#field_name.encode(&mut w)?; 140 }); 141 } 142 } 143 } 144 Fields::Unnamed(fields) => { 145 for (i, field) in fields.unnamed.iter().enumerate() { 146 let i = Index::from(i); 147 148 if field 149 .attrs 150 .iter() 151 .any(|attr| attr.meta.path().is_ident("varint")) 152 { 153 fields_encoded.extend(quote! { 154 VarInt(self.#i as i32).encode(&mut w)?; 155 }); 156 } else if field 157 .attrs 158 .iter() 159 .any(|attr| attr.meta.path().is_ident("varlong")) 160 { 161 fields_encoded.extend(quote! { 162 VarLong(self.#i as i64).encode(&mut w)?; 163 }); 164 } else { 165 fields_encoded.extend(quote! { 166 self.#i.encode(&mut w)?; 167 }); 168 } 169 } 170 } 171 Fields::Unit => (), 172 } 173 174 quote! { 175 impl #generics Encode for #name #generics #where_clause { 176 fn encode(&self, mut w: impl std::io::Write) -> color_eyre::Result<()> { 177 #fields_encoded 178 179 Ok(()) 180 } 181 } 182 } 183 .into() 184} 185 186/// Automatically implements "straight-across" decoding for the given struct, i.e. fields are 187/// deserialized in order as is. Supports #[decode_as(type)] to deserialize according to a different type. 188/// uses TryInto to convert to the expected type where necessary. 189#[proc_macro_derive(Decode, attributes(decode_as))] 190pub fn derive_decode(input: TokenStream) -> TokenStream { 191 let input = parse_macro_input!(input as DeriveInput); 192 193 let syn::Data::Struct(data) = input.data else { 194 panic!("Can only derive Decode on a struct"); 195 }; 196 197 let name = input.ident; 198 199 let struct_tokens = match data.fields { 200 Fields::Named(fields) => { 201 let mut field_tokens = proc_macro2::TokenStream::new(); 202 203 for field in fields.named { 204 let field_name = field.ident.expect("couldn't get ident for named field"); 205 let ty = field.ty; 206 207 let wrapped = format!("for field {field_name} in {name}"); 208 209 if let Some(attr) = field 210 .attrs 211 .iter() 212 .find(|attr| attr.meta.path().is_ident("decode_as")) 213 { 214 let ty = attr 215 .parse_args::<Path>() 216 .expect("decode_as value must be a Path"); 217 218 field_tokens.extend(quote! { 219 #field_name: <#ty as Decode>::decode(r) 220 .wrap_err(#wrapped)? 221 .try_into()?, 222 }); 223 } else { 224 field_tokens.extend(quote! { 225 #field_name: <#ty as Decode>::decode(r) 226 .wrap_err(#wrapped)?, 227 }); 228 } 229 } 230 quote! { 231 Self { 232 #field_tokens 233 } 234 } 235 } 236 Fields::Unnamed(fields) => { 237 let mut field_tokens = proc_macro2::TokenStream::new(); 238 for (i, field) in fields.unnamed.into_iter().enumerate() { 239 let ty = field.ty; 240 241 let wrapped = format!("for field {i} in {name}"); 242 243 if let Some(attr) = field 244 .attrs 245 .iter() 246 .find(|attr| attr.meta.path().is_ident("decode_as")) 247 { 248 let ty = attr 249 .parse_args::<Path>() 250 .expect("decode_as value must be a Path"); 251 252 field_tokens.extend(quote! { 253 <#ty as Decode>::decode(r) 254 .wrap_err(#wrapped)? 255 .try_into()?, 256 }); 257 } else { 258 field_tokens.extend(quote! { 259 <#ty as Decode>::decode(r).wrap_err(#wrapped)?, 260 }); 261 } 262 } 263 quote! { 264 Self(#field_tokens) 265 } 266 } 267 Fields::Unit => quote! { Self }, 268 }; 269 270 let struct_generics = input.generics; 271 let where_clause = struct_generics.where_clause.clone(); 272 273 let mut impl_generics = struct_generics.clone(); 274 if impl_generics.lifetimes().count() == 0 { 275 impl_generics.params.push(parse_quote!('a)); 276 } 277 278 quote! { 279 impl #impl_generics Decode #impl_generics for #name #struct_generics #where_clause { 280 fn decode(r: &mut &'a [u8]) -> color_eyre::Result<Self> 281 where 282 Self: Sized, 283 { 284 use color_eyre::eyre::WrapErr; 285 Ok(#struct_tokens) 286 } 287 } 288 } 289 .into() 290}