azalea_buf_macros/
read.rs

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