Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_jdat/dat_map/src/lib.rs

10.5 KiB, 12 runs

created by r1870400018:435, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1#![recursion_limit = "128"]
2/// Procedural macros to derive implementations for [`FromDatMap`] and [`ToDatMap`],
3/// allowing a struct to be converted from and to a [`Dat`].
4///
5/// Credit: https://github.com/ex0dus-0x/structmap
6///
7
8use std::collections::BTreeMap;
9
10use proc_macro::TokenStream;
11use proc_macro2::Span;
12use quote::{
13 quote,
14};
15use syn::{
16 self,
17 DeriveInput,
18};
19
20
21#[proc_macro_derive(FromDatMap, attributes(skip, optional, rename))]
22pub fn from_datmap(input: TokenStream) -> TokenStream {
23 let ast = syn::parse_macro_input!(input as DeriveInput);
24
25 let fields = match ast.data {
26 syn::Data::Struct(st) => st.fields,
27 _ => panic!("from_datmap: Implementation must be a struct."),
28 };
29
30 let skip_map = parse_valueless_attrs(&fields, "skip");
31 let optional_map = parse_valueless_attrs(&fields, "optional");
32 let rename_map = parse_rename_attrs(&fields);
33
34 //let idents: Vec<&syn::Ident> = fields
35 // .iter()
36 // .filter_map(|field| field.ident.as_ref())
37 // .collect::<Vec<&syn::Ident>>();
38
39 let mut keys: Vec<String> = Vec::new();
40 let mut idents: Vec<syn::Ident> = Vec::new();
41 let mut typecalls: Vec<syn::Ident> = Vec::new();
42 let mut raise_error_if_missing: Vec<bool> = Vec::new();
43 for field in fields {
44 if let Some(ident) = field.ident {
45 let name = ident.to_string();
46 if !skip_map.contains_key(&name) {
47 match rename_map.get(&name) {
48 Some(new_name) => keys.push(new_name.clone()),
49 None => keys.push(name.clone()),
50 }
51 raise_error_if_missing.push(match optional_map.get(&name) {
52 Some(()) => false,
53 None => true,
54 });
55 idents.push(ident.clone());
56 match field.ty {
57 syn::Type::Path(typepath) => {
58 // get the type of the specified field, lowercase
59 let type_name: String = quote! {#typepath}.to_string().to_lowercase();
60 let getter_name: String = match &*type_name {
61 "u8" | "u16" | "u32" | "u64" |
62 "i8" | "i16" | "i32" | "i64" |
63 "bool" | "float32" | "float64" |
64 "bigint" | "bigdecimal" |
65 "string" | "dat" => format!("get_{}", type_name),
66 "vec < u8 >" => String::from("get_bytes"),
67 "box < dat >" => String::from("get_box"),
68 "box < option < dat > >" => String::from("get_opt_dat"),
69 "vec < dat >" => String::from("get_list"),
70 "vec < string >" => String::from("get_string_list"),
71 "btreemap < dat, dat >" | "daticlemap" => String::from("get_map"),
72 "btreemap < b32, string >" => String::from("get_b32_string_map"),
73 _ => unimplemented!(
74 "from_datmap: Cannot find an equivalent Dat for type '{}'.",
75 type_name,
76 ),
77 };
78
79 // initialize new Ident for codegen
80 typecalls.push(syn::Ident::new(&getter_name, Span::mixed_site()))
81 }
82 _ => unimplemented!(),
83 }
84 }
85 }
86 }
87
88 let name = &ast.ident;
89 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
90
91 let tokens = quote! {
92
93 impl #impl_generics FromDatMap for #name #ty_generics #where_clause {
94
95 fn from_datmap(
96 mut map: ::std::collections::BTreeMap<Dat, Dat>,
97 ) -> Outcome<#name> {
98 let mut st = #name::default();
99
100 #(
101 match map.entry(Dat::Str(String::from(#keys))) {
102 ::std::collections::btree_map::Entry::Occupied(entry) => {
103 // parse out primitive value from generic type using typed call
104 let value = match entry.get().#typecalls() {
105 Some(val) => val,
106 None => return Err(err!(
107 "from_datmap: The key for the Dat::Map pair {:?} matches \
108 the required struct field '{}' but the value is not a \
109 recognised type.",
110 entry, #keys;
111 Invalid, Input)),
112 };
113 st.#idents = value;
114 },
115 _ => if #raise_error_if_missing {
116 return Err(err!(
117 "from_datmap: The required struct field '{}' cannot be found \
118 in the given Dat::Map {:?}.", #keys, map;
119 Invalid, Input));
120 },
121 }
122 )*
123
124 Ok(st)
125 }
126 }
127 };
128
129 tokens.into()
130}
131
132#[proc_macro_derive(ToDatMap, attributes(rename))]
133pub fn to_datmap(input: TokenStream) -> TokenStream {
134 let ast = syn::parse_macro_input!(input as DeriveInput);
135
136 let fields = match ast.data {
137 syn::Data::Struct(st) => st.fields,
138 _ => panic!("to_datmap: Implementation must be a struct."),
139 };
140
141 let rename_map = parse_rename_attrs(&fields);
142
143 let idents: Vec<&syn::Ident> = fields
144 .iter()
145 .filter_map(|field| field.ident.as_ref())
146 .collect::<Vec<&syn::Ident>>();
147
148 // convert all the field names into strings
149 let keys: Vec<String> = idents
150 .clone()
151 .iter()
152 .map(|ident| ident.to_string())
153 .map(|name| match rename_map.contains_key(&name) {
154 true => rename_map.get(&name).unwrap().clone(),
155 false => name,
156 })
157 .collect::<Vec<String>>();
158
159 let name = &ast.ident;
160 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
161
162 let tokens = quote! {
163
164 impl #impl_generics ToDatMap for #name #ty_generics #where_clause {
165
166 fn to_datmap(input_struct: #name) -> Dat {
167 let mut map = BTreeMap::new();
168 #(
169 map.insert(
170 Dat::Str(#keys.to_string()),
171 Dat::from(input_struct.#idents),
172 );
173 )*
174 Dat::Map(map)
175 }
176 }
177 };
178
179 tokens.into()
180}
181
182/// Helper method used to parse out any `rename` attribute definitions in a struct
183/// marked with the ToMap trait, returning a mapping between the original field name
184/// and the one being changed for later use when doing codegen.
185fn parse_rename_attrs(fields: &syn::Fields) -> BTreeMap<String, String> {
186 let mut rename: BTreeMap<String, String> = BTreeMap::new();
187 match fields {
188 syn::Fields::Named(_) => {
189 // iterate over fields available and attributes
190 for field in fields.iter() {
191 for attr in field.attrs.iter() {
192 // parse original struct field name
193 let field_name = field.ident.as_ref().unwrap().to_string();
194 if attr.path.get_ident().unwrap().to_string() == "rename" {
195 if rename.contains_key(&field_name) {
196 panic!("parse_rename_attrs: Cannot redefine field name multiple \
197 times.");
198 }
199
200 // parse out name value pairs in attributes
201 // first get `lst` in #[rename(lst)]
202 match attr.parse_meta() {
203 Ok(syn::Meta::List(lst)) => {
204 // then parse key-value name
205 match lst.nested.first() {
206 Some(syn::NestedMeta::Meta(syn::Meta::NameValue(nm))) => {
207 // check path to be = `name`
208 let path = nm.path.get_ident().unwrap().to_string();
209 if path != "name" {
210 panic!("parse_rename_attrs: Must be \
211 `#[rename(name = 'VALUE')]`.");
212 }
213
214 let lit = match &nm.lit {
215 syn::Lit::Str(val) => val.value(),
216 _ => {
217 panic!("parse_rename_attrs: Must be \
218 `#[rename(name = 'VALUE')]`.");
219 }
220 };
221 rename.insert(field_name, lit);
222 }
223 _ => {
224 panic!("parse_rename_attrs: Must be \
225 `#[rename(name = 'VALUE')]`.");
226 }
227 }
228 }
229 _ => {
230 panic!("parse_rename_attrs: Must be \
231 `#[rename(name = 'VALUE')]`.");
232 }
233 }
234 }
235 }
236 }
237 }
238 _ => {
239 panic!("parse_rename_attrs: Must have named fields.");
240 }
241 }
242 rename
243}
244
245fn parse_valueless_attrs(fields: &syn::Fields, attr_name: &str) -> BTreeMap<String, ()> {
246 let mut map: BTreeMap<String, ()> = BTreeMap::new();
247 match fields {
248 syn::Fields::Named(_) => {
249 // iterate over fields available and attributes
250 for field in fields.iter() {
251 for attr in field.attrs.iter() {
252 // parse original struct field name
253 let field_name = field.ident.as_ref().unwrap().to_string();
254 if attr.path.get_ident().unwrap().to_string() == attr_name {
255 map.insert(field_name, ());
256 }
257 }
258 }
259 }
260 _ => {
261 panic!("parse_valueless_attrs: Must have named fields.");
262 }
263 }
264 map
265}