a bare-bones limbo server in rust (mirror of https://github.com/xoogware/crawlspace)
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}