azalea_buf_macros/
read.rs1use 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 _ => {#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 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}