azalea_buf_macros/
read.rs

1use quote::{ToTokens, quote};
2use syn::{Data, Field, FieldsNamed, Generics, Ident, punctuated::Punctuated, token::Comma};
3
4pub fn create_impl_azalearead(
5    ident: &Ident,
6    generics: &Generics,
7    data: &Data,
8) -> proc_macro2::TokenStream {
9    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
10
11    match data {
12        syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
13            syn::Fields::Named(FieldsNamed { named, .. }) => {
14                let (read_fields, read_field_names) = read_named_fields(named);
15
16                quote! {
17                impl #impl_generics azalea_buf::AzaleaRead for #ident #ty_generics #where_clause {
18                    fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> {
19                        #(#read_fields)*
20                        Ok(Self {
21                            #(#read_field_names: #read_field_names),*
22                        })
23                    }
24                }
25                }
26            }
27            syn::Fields::Unit => {
28                quote! {
29                impl #impl_generics azalea_buf::AzaleaRead for #ident #ty_generics #where_clause {
30                    fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> {
31                        Ok(Self)
32                    }
33                }
34                }
35            }
36            syn::Fields::Unnamed(fields) => {
37                let read_fields = read_unnamed_fields(&fields.unnamed);
38
39                quote! {
40                impl #impl_generics azalea_buf::AzaleaRead for #ident #ty_generics #where_clause {
41                    fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> {
42                        Ok(Self(
43                            #(#read_fields),*
44                        ))
45                    }
46                }
47                }
48            }
49        },
50        syn::Data::Enum(syn::DataEnum { variants, .. }) => {
51            let mut match_contents = quote!();
52            let mut variant_discrim: u32 = 0;
53            let mut first = true;
54            let mut first_reader = None;
55            for variant in variants {
56                let variant_name = &variant.ident;
57                match &variant.discriminant.as_ref() {
58                    Some(d) => {
59                        variant_discrim = match &d.1 {
60                            syn::Expr::Lit(e) => match &e.lit {
61                                syn::Lit::Int(i) => i.base10_parse().unwrap(),
62                                _ => panic!("Error parsing enum discriminant as int (is {e:?})"),
63                            },
64                            syn::Expr::Unary(_) => {
65                                panic!("Negative enum discriminants are not supported")
66                            }
67                            _ => {
68                                panic!("Error parsing enum discriminant as literal (is {:?})", d.1)
69                            }
70                        }
71                    }
72                    None => {
73                        if !first {
74                            variant_discrim += 1;
75                        }
76                    }
77                }
78                let reader = match &variant.fields {
79                    syn::Fields::Named(f) => {
80                        let (read_fields, read_field_names) = read_named_fields(&f.named);
81
82                        quote! {
83                            #(#read_fields)*
84                            Ok(#ident::#variant_name {
85                                #(#read_field_names: #read_field_names),*
86                            })
87                        }
88                    }
89                    syn::Fields::Unnamed(fields) => {
90                        let mut reader_code = quote! {};
91                        for f in &fields.unnamed {
92                            let is_variable_length =
93                                f.attrs.iter().any(|a| a.path().is_ident("var"));
94                            let limit =
95                                f.attrs
96                                    .iter()
97                                    .find(|a| a.path().is_ident("limit"))
98                                    .map(|a| {
99                                        a.parse_args::<syn::LitInt>()
100                                            .unwrap()
101                                            .base10_parse::<usize>()
102                                            .unwrap()
103                                    });
104
105                            if is_variable_length && limit.is_some() {
106                                panic!("Fields cannot have both var and limit attributes");
107                            }
108
109                            if is_variable_length {
110                                reader_code.extend(quote! {
111                                    Self::#variant_name(azalea_buf::AzaleaReadVar::azalea_read_var(buf)?),
112                                });
113                            } else if let Some(limit) = limit {
114                                reader_code.extend(quote! {
115                                    Self::#variant_name(azalea_buf::AzaleaReadLimited::azalea_read_limited(buf, #limit)?),
116                                });
117                            } else {
118                                reader_code.extend(quote! {
119                                    Self::#variant_name(azalea_buf::AzaleaRead::azalea_read(buf)?),
120                                });
121                            }
122                        }
123                        quote! { Ok(#reader_code) }
124                    }
125                    syn::Fields::Unit => quote! {
126                        Ok(Self::#variant_name)
127                    },
128                };
129                if first {
130                    first_reader = Some(reader.clone());
131                    first = false;
132                };
133
134                match_contents.extend(quote! {
135                    #variant_discrim => {
136                        #reader
137                    },
138                });
139            }
140
141            let first_reader = first_reader.expect("There should be at least one variant");
142
143            quote! {
144            impl #impl_generics azalea_buf::AzaleaRead for #ident #ty_generics #where_clause {
145                fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> {
146                    let id = azalea_buf::AzaleaReadVar::azalea_read_var(buf)?;
147                    Self::azalea_read_id(buf, id)
148                }
149            }
150
151            impl #impl_generics #ident #ty_generics #where_clause {
152                pub fn azalea_read_id(buf: &mut std::io::Cursor<&[u8]>, id: u32) -> std::result::Result<Self, azalea_buf::BufReadError> {
153                    match id {
154                        #match_contents
155                        // you'd THINK this throws an error, but mojang decided to make it default for some reason
156                        _ => {#first_reader}
157                    }
158                }
159            }
160            }
161        }
162        _ => panic!("#[derive(AzBuf)] can only be used on structs"),
163    }
164}
165
166fn read_named_fields(
167    named: &Punctuated<Field, Comma>,
168) -> (Vec<proc_macro2::TokenStream>, Vec<&Option<Ident>>) {
169    let read_fields = named
170        .iter()
171        .map(|f| {
172            let field_name = &f.ident;
173
174            let reader_call = get_reader_call(f);
175            quote! { let #field_name = #reader_call; }
176        })
177        .collect::<Vec<_>>();
178    let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
179
180    (read_fields, read_field_names)
181}
182
183fn read_unnamed_fields(unnamed: &Punctuated<Field, Comma>) -> Vec<proc_macro2::TokenStream> {
184    unnamed
185        .iter()
186        .map(|f| {
187            let reader_call = get_reader_call(f);
188            quote! { #reader_call }
189        })
190        .collect::<Vec<_>>()
191}
192
193fn get_reader_call(f: &Field) -> proc_macro2::TokenStream {
194    let is_variable_length = f
195        .attrs
196        .iter()
197        .any(|a: &syn::Attribute| a.path().is_ident("var"));
198    let limit = f
199        .attrs
200        .iter()
201        .find(|a| a.path().is_ident("limit"))
202        .map(|a| {
203            a.parse_args::<syn::LitInt>()
204                .unwrap()
205                .base10_parse::<usize>()
206                .unwrap()
207        });
208
209    if is_variable_length && limit.is_some() {
210        panic!("Fields cannot have both var and limit attributes");
211    }
212
213    let field_type = &f.ty;
214
215    // do a different buf.write_* for each field depending on the type
216    // if it's a string, use buf.write_string
217    match field_type {
218        syn::Type::Path(_) | syn::Type::Array(_) => {
219            if is_variable_length {
220                quote! {
221                    azalea_buf::AzaleaReadVar::azalea_read_var(buf)?
222                }
223            } else if let Some(limit) = limit {
224                quote! {
225                    azalea_buf::AzaleaReadLimited::azalea_read_limited(buf, #limit)?
226                }
227            } else {
228                quote! {
229                    azalea_buf::AzaleaRead::azalea_read(buf)?
230                }
231            }
232        }
233        _ => panic!(
234            "Error reading field {:?}: {}",
235            f.ident.clone(),
236            field_type.to_token_stream()
237        ),
238    }
239}