bitcoin_consensus_derive/
lib.rs1use proc_macro::TokenStream;
3use proc_macro2::{Ident, TokenStream as TokenStream2};
4use quote::{quote, ToTokens};
5use syn::{
6 parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Index,
7 Type,
8};
9
10#[proc_macro_derive(Encodable)]
16pub fn derive_encodable(input: TokenStream) -> TokenStream {
17 let DeriveInput { ident, data, .. } = parse_macro_input!(input);
18 let output = if let Data::Struct(DataStruct {
20 fields: Fields::Named(FieldsNamed { named: fields, .. }),
21 ..
22 }) = data
23 {
24 let field_tokens = fields.iter().map(|field| {
25 let field_name = field.ident.as_ref().unwrap();
26 let field_type = &field.ty;
27 generate_field_encode(true, field_name, field_type)
28 });
29 let output = quote! {
30 impl bitcoin::consensus::Encodable for #ident {
31 fn consensus_encode<W: bitcoin::io::Write + ?Sized>(
32 &self,
33 w: &mut W,
34 ) -> core::result::Result<usize, bitcoin::io::Error> {
35 let mut len = 0;
36 #( #field_tokens )*
37 Ok(len)
38 }
39 }
40 };
41 output
42 } else if let Data::Struct(DataStruct {
43 fields: Fields::Unnamed(FieldsUnnamed {
44 unnamed: fields, ..
45 }),
46 ..
47 }) = data
48 {
49 let field_tokens = fields.iter().enumerate().map(|(i, field)| {
50 let field_name = Index::from(i);
51 let field_type = &field.ty;
52 generate_field_encode(true, &field_name, field_type)
53 });
54 let output = quote! {
55 impl bitcoin::consensus::Encodable for #ident {
56 fn consensus_encode<W: bitcoin::io::Write + ?Sized>(
57 &self,
58 w: &mut W,
59 ) -> core::result::Result<usize, bitcoin::io::Error> {
60 let mut len = 0;
61 #( #field_tokens )*
62 Ok(len)
63 }
64 }
65 };
66 output
67 } else {
68 unimplemented!()
69 };
70 output.into()
71}
72
73#[proc_macro_derive(Decodable)]
77pub fn derive_decodable(input: TokenStream) -> TokenStream {
78 let DeriveInput { ident, data, .. } = parse_macro_input!(input);
79 if let syn::Data::Struct(syn::DataStruct {
81 fields: syn::Fields::Named(FieldsNamed { named: fields, .. }),
82 ..
83 }) = data
84 {
85 let field_tokens = fields.iter().map(|field| {
86 let field_name = field.ident.as_ref().unwrap();
87 let field_type = &field.ty;
88 generate_field_decode(field_name, field_type)
89 });
90 let field_names = fields.iter().map(|field| {
91 let field_name = field.ident.as_ref().unwrap();
92 quote! {
93 #field_name,
94 }
95 });
96 let output = quote! {
97 impl bitcoin::consensus::Decodable for #ident {
98 fn consensus_decode<R: bitcoin::io::Read + ?Sized>(
99 r: &mut R,
100 ) -> core::result::Result<Self, bitcoin::consensus::encode::Error> {
101 #( #field_tokens )*
102 Ok(Self {
103 #( #field_names )*
104 })
105 }
106 }
107 };
108 return output.into();
109 } else if let Data::Struct(DataStruct {
110 fields: Fields::Unnamed(FieldsUnnamed {
111 unnamed: fields, ..
112 }),
113 ..
114 }) = data
115 {
116 let field_tokens = fields.iter().enumerate().map(|(i, field)| {
117 let field_name = Ident::new(&format!("field_{}", i), proc_macro2::Span::call_site());
118 let field_type = &field.ty;
119 generate_field_decode(&field_name, field_type)
120 });
121 let field_names = fields.iter().enumerate().map(|(i, _field)| {
122 let field_name = Ident::new(&format!("field_{}", i), proc_macro2::Span::call_site());
123 quote! {
124 #field_name,
125 }
126 });
127 let output = quote! {
128 impl bitcoin::consensus::Decodable for #ident {
129 fn consensus_decode<R: bitcoin::io::Read + ?Sized>(
130 r: &mut R,
131 ) -> core::result::Result<Self, bitcoin::consensus::encode::Error> {
132 #( #field_tokens )*
133 Ok(Self(
134 #( #field_names )*
135 ))
136 }
137 }
138 };
139 return output.into();
140 } else {
141 unimplemented!()
142 }
143}
144
145fn generate_field_encode(
146 is_self: bool,
147 field_name: &dyn ToTokens,
148 field_type: &Type,
149) -> TokenStream2 {
150 let field_access = if is_self {
151 quote! {
152 self.#field_name
153 }
154 } else {
155 quote! {
156 #field_name
157 }
158 };
159 if get_array_length(field_type).is_some() {
160 quote! {
161 for el in &#field_access {
162 len += el.consensus_encode(w)?;
163 }
164 }
165 } else if is_numeric_type(field_type) {
166 quote! {
167 let buf = #field_access.to_be_bytes();
168 w.write_all(&buf)?;
169 len += buf.len();
170 }
171 } else if let Some(inner_type) = extract_option_type(field_type) {
172 let inner_tokens = generate_field_encode(
173 false,
174 &Ident::new("inner", proc_macro2::Span::call_site()),
175 inner_type,
176 );
177 quote! {
178 len += #field_access.is_some().consensus_encode(w)?;
179 if let Some(inner) = &#field_access {
180 #inner_tokens
181 }
182 }
183 } else {
184 quote! {
185 len += #field_access.consensus_encode(w)?;
186 }
187 }
188}
189
190fn generate_field_decode(var: &Ident, field_type: &Type) -> TokenStream2 {
191 let output = if let Some(size) = get_array_length(field_type) {
192 quote! {
193 use core::convert::TryInto;
194 use alloc::vec::Vec;
195 let mut v = Vec::with_capacity(#size);
196 for _ in 0..#size {
197 let el = bitcoin::consensus::Decodable::consensus_decode(r)?;
198 v.push(el);
199 }
200 let #var = v.try_into().unwrap();
201 }
202 } else if is_numeric_type(field_type) {
203 quote! {
204 let mut buf = [0u8; core::mem::size_of::<#field_type>()];
205 r.read_exact(&mut buf)?;
206 let #var = #field_type::from_be_bytes(buf);
207 }
208 } else if let Some(inner_type) = extract_option_type(field_type) {
209 let inner_tokens = generate_field_decode(
210 &Ident::new("inner", proc_macro2::Span::call_site()),
211 inner_type,
212 );
213 quote! {
214 let is_some: bool = bitcoin::consensus::Decodable::consensus_decode(r)?;
215 let #var = if is_some {
216 let inner = {
217 #inner_tokens
218 inner
219 };
220 Some(inner)
221 } else {
222 None
223 };
224 }
225 } else {
226 quote! {
227 let #var = bitcoin::consensus::Decodable::consensus_decode(r)?;
228 }
229 };
230 output
231}
232
233fn extract_option_type(ty: &syn::Type) -> Option<&syn::Type> {
234 if let syn::Type::Path(syn::TypePath {
235 path: syn::Path { segments, .. },
236 ..
237 }) = ty
238 {
239 if let Some(syn::PathSegment {
240 ident,
241 arguments:
242 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
243 ..
244 }) = segments.first()
245 {
246 if ident == "Option" {
247 if let Some(syn::GenericArgument::Type(inner_type)) = args.first() {
248 return Some(inner_type);
249 }
250 }
251 }
252 }
253 None
254}
255
256fn is_numeric_type(ty: &syn::Type) -> bool {
257 if let syn::Type::Path(syn::TypePath {
258 path: syn::Path { segments, .. },
259 ..
260 }) = ty
261 {
262 if let Some(syn::PathSegment { ident, .. }) = segments.first() {
263 if ident == "u8"
264 || ident == "u16"
265 || ident == "u32"
266 || ident == "u64"
267 || ident == "u128"
268 || ident == "i8"
269 || ident == "i16"
270 || ident == "i32"
271 || ident == "i64"
272 || ident == "i128"
273 {
274 return true;
275 }
276 }
277 }
278 false
279}
280
281fn get_array_length(ty: &syn::Type) -> Option<usize> {
282 if let syn::Type::Array(syn::TypeArray { len, .. }) = ty {
283 if let syn::Expr::Lit(syn::ExprLit {
284 lit: syn::Lit::Int(int),
285 ..
286 }) = len
287 {
288 if let Ok(value) = int.base10_parse::<usize>() {
289 return Some(value);
290 }
291 }
292 }
293 None
294}