1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::spanned::Spanned;
6
7fn single_path(path: &syn::Path) -> Option<syn::Ident> {
8 if path.segments.len() == 1 {
9 Some(path.segments[0].ident.clone())
10 } else {
11 None
12 }
13}
14
15fn parse_string_lit(lit: &syn::Lit) -> Option<String> {
16 if let syn::Lit::Str(predicate_str) = lit {
17 Some(predicate_str.value())
18 } else {
19 None
20 }
21}
22
23fn generate(item_struct: syn::ItemStruct) -> Result<TokenStream, syn::Error> {
24 let struct_name = &item_struct.ident;
25
26 let mut crate_name = quote! {bitcoin_cash};
27
28 for attr in &item_struct.attrs {
29 match attr.parse_meta()? {
30 syn::Meta::List(list) if list.path.to_token_stream().to_string() == "bitcoin_code" => {
31 for nested_attr in list.nested.iter() {
32 match nested_attr {
33 syn::NestedMeta::Meta(attr_meta) => match attr_meta {
34 syn::Meta::NameValue(name_value) => {
35 let ident = single_path(&name_value.path).ok_or_else(|| {
36 syn::Error::new(attr.span(), "Invalid attribute, invalid name")
37 })?;
38 match ident.to_string().as_str() {
39 "crate" => {
40 let crate_name_str = parse_string_lit(&name_value.lit)
41 .ok_or_else(|| {
42 syn::Error::new(
43 name_value.lit.span(),
44 "Invalid attribute, invalid value",
45 )
46 })?;
47 crate_name =
48 syn::Ident::new(&crate_name_str, name_value.lit.span())
49 .to_token_stream();
50 }
51 _ => {
52 return Err(syn::Error::new(
53 name_value.span(),
54 "Invalid attribute, unknown name",
55 ))
56 }
57 }
58 }
59 _ => {
60 return Err(syn::Error::new(
61 attr_meta.span(),
62 "Invalid parameter, must provide values like this: a=\"b\"",
63 ))
64 }
65 },
66 syn::NestedMeta::Lit(_) => {
67 return Err(syn::Error::new(
68 nested_attr.span(),
69 "Invalid literal, must provide values like this: a=\"b\"",
70 ))
71 }
72 }
73 }
74 }
75 _ => {}
76 }
77 }
78
79 let fields = match &item_struct.fields {
80 syn::Fields::Named(fields) => fields,
81 syn::Fields::Unnamed(_) => {
82 return Err(syn::Error::new(
83 struct_name.span(),
84 "Cannot use macro for unnamed struct fields",
85 ))
86 }
87 syn::Fields::Unit => {
88 return Err(syn::Error::new(
89 struct_name.span(),
90 "Cannot use macro for unit structs",
91 ))
92 }
93 };
94
95 let parts_ident = quote! {__serialize_parts};
96
97 let mut ser_calls = Vec::new();
98 let mut deser_calls = Vec::new();
99 let mut field_idents = Vec::new();
100
101 for field in fields.named.iter() {
102 let mut is_skipped = false;
103 for attr in &field.attrs {
104 match attr.parse_meta()? {
105 syn::Meta::Path(_) => {
106 return Err(syn::Error::new(attr.span(), "Invalid attribute"))
107 }
108 syn::Meta::NameValue(_) => {
109 return Err(syn::Error::new(attr.span(), "Invalid attribute"))
110 }
111 syn::Meta::List(list) => {
112 let params = list
113 .nested
114 .iter()
115 .map(|param| param.to_token_stream().to_string())
116 .collect::<Vec<_>>();
117 match params
118 .iter()
119 .map(|param| param.as_str())
120 .collect::<Vec<_>>()
121 .as_slice()
122 {
123 &["skip"] => is_skipped = true,
124 _ => return Err(syn::Error::new(list.nested.span(), "Invalid attribute")),
125 }
126 }
127 }
128 }
129 let field_name = field.ident.as_ref().unwrap();
130 field_idents.push(field_name);
131 let field_name_str = field_name.to_string();
132 let field_type = &field.ty;
133 if !is_skipped {
134 ser_calls.push(quote! {
135 #parts_ident.push(self.#field_name.ser().named(#field_name_str));
136 });
137 deser_calls.push(quote! {
138 let (#field_name, data) = <#field_type as #crate_name::BitcoinCode>::deser(data)?;
139 })
140 } else {
141 deser_calls.push(quote! {
142 let #field_name = Default::default();
143 })
144 }
145 }
146
147 let capacity = fields.named.len();
148
149 let result = quote! {
150 impl #crate_name::BitcoinCode for #struct_name {
151 fn ser(&self) -> #crate_name::ByteArray {
152 let mut #parts_ident = Vec::with_capacity(#capacity);
153 #(#ser_calls)*
154 #crate_name::ByteArray::from_parts(#parts_ident)
155 }
156
157 fn deser(data: #crate_name::ByteArray) -> std::result::Result<(Self, #crate_name::ByteArray), #crate_name::error::Error> {
158 #(#deser_calls)*
159 Ok((
160 #struct_name { #(#field_idents),* },
161 data,
162 ))
163 }
164 }
165 };
166 Ok(result)
167}
168
169#[proc_macro_derive(BitcoinCode, attributes(bitcoin_code))]
170pub fn serialize_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
171 let item_struct = syn::parse_macro_input!(item as syn::ItemStruct);
172
173 let result = match generate(item_struct) {
174 Ok(tokens) => tokens,
175 Err(err) => err.to_compile_error(),
176 };
177
178 result.into()
179}