1extern crate proc_macro;
2
3use proc_macro2::TokenTree;
4use quote::{quote, ToTokens};
5use std::collections::BTreeMap;
6use std::collections::HashMap;
7use syn::parse_macro_input;
8use syn::punctuated::Punctuated;
9use syn::token::PathSep;
10use syn::DeriveInput;
11use syn::Field;
12use syn::Fields;
13use syn::Ident;
14use syn::PathSegment;
15use syn::Token;
16
17#[derive(Clone, Debug)]
18struct Member {
19 ident: Ident,
20 name: String,
21 rename: HashMap<syn::Path, String>,
22 converter: HashMap<syn::Path, syn::Path>,
23 skip_contexts: Vec<syn::Path>,
24 move_contexts: Vec<syn::Path>,
25}
26
27type Members = BTreeMap<String, Member>;
28
29#[derive(Debug)]
30enum TraitNaming {
31 ExistingGeneric {
33 path: syn::Path,
34 method_name: Ident,
35 },
36 Existing {
37 path: syn::Path,
38 method_name: Ident,
39 },
40 }
56
57impl Default for TraitNaming {
58 fn default() -> Self {
59 Self::ExistingGeneric {
60 path: syn::parse2(quote! {::context_mapper::IntoType}).expect("1"),
61 method_name: syn::parse2(quote! {into_type_map}).expect("2"),
62 }
63 }
64}
65
66#[derive(Debug)]
67struct CommonFields {
68 ctx: syn::Path,
69 ctx_type: syn::Type,
70 converter: syn::Path,
71 move_type: bool,
72}
73
74#[derive(Debug)]
75struct Trait {
76 ctx: syn::Path,
77 ctx_type: syn::Type,
78 converter: syn::Path,
79 move_type: bool,
80 naming: TraitNaming,
81}
82
83#[derive(Debug)]
84struct Function {
85 ctx: syn::Path,
86 ctx_type: syn::Type,
87 converter: syn::Path,
88 move_type: bool,
89 naming: Ident,
90 visibility: syn::Visibility,
91}
92
93#[derive(Debug, Default)]
94struct ContextConfig {
95 pub traits: BTreeMap<String, Trait>,
97 pub functions: BTreeMap<String, Function>,
98 pub impls: BTreeMap<String, Function>
99}
100
101fn read_trait_naming(inpname: &str, input: &proc_macro2::TokenStream) -> TraitNaming {
102 let params = group_attributes(
103 input.clone(),
104 &[],
105 &["path", "method_name"]
106 .into_iter()
107 .map(|x| x.to_string())
108 .collect::<Vec<String>>(),
109 );
110
111 let path: Option<syn::Path> = params
112 .iter()
113 .filter(|x| x.0 == "path")
114 .last()
115 .map(|x| syn::parse2(x.1.clone()).expect("name should be parseable to ident"));
116
117 let method_name: Option<syn::Ident> = params
118 .iter()
119 .filter(|x| x.0 == "method_name")
120 .last()
121 .map(|x| syn::parse2(x.1.clone()).expect("method_name should be parseable to ident"));
122
123 match inpname {
124 "simple" => TraitNaming::Existing {
125 path: path.expect("Expected path"),
126 method_name: method_name.expect("Excepected mehtod_name"),
127 },
128 "generic" => TraitNaming::ExistingGeneric {
129 path: path.expect("Expected path"),
130 method_name: method_name.expect("Expected method_name"),
131 },
132 _ => unreachable!("Unknown naming"),
133 }
134}
135
136fn read_common_fields(groups: &[(String, proc_macro2::TokenStream)]) -> CommonFields {
137 let ctx: syn::Path = groups
138 .iter()
139 .filter(|x| x.0 == "context")
140 .last()
141 .map(|x| syn::parse2(x.1.clone()).expect("context mapping error"))
142 .unwrap_or(syn::parse2(quote! { default }).expect("?"));
143
144 let ctx_type: syn::Type = groups
145 .iter()
146 .filter(|x| x.0 == "type")
147 .last()
148 .map(|x| syn::parse2(x.1.clone()).expect("type mapping error"))
149 .unwrap_or(syn::parse2(quote! { std::string::String }).expect("?"));
150
151 let converter: syn::Path = groups
152 .iter()
153 .filter(|x| x.0 == "converter")
154 .last()
155 .map(|x| syn::parse2(x.1.clone()).expect("converter mapping errro"))
156 .unwrap_or(syn::parse2(quote! { std::string::ToString::to_string }).expect("?"));
157
158 let move_type = groups
159 .iter()
160 .filter_map(|x| {
161 if x.0 == "move" {
162 let b: syn::LitBool = syn::parse2(x.1.clone()).expect("Skip expected bool");
163 Some(b.value)
164 } else {
165 None
166 }
167 })
168 .last()
169 .unwrap_or(false);
170
171 CommonFields {
172 ctx,
173 ctx_type,
174 converter,
175 move_type,
176 }
177}
178
179impl ContextConfig {
180 pub fn add_trait(&mut self, input: proc_macro2::TokenStream) -> &mut Self {
181 let grp_attrs: Vec<String> = ["simple", "generic"]
182 .into_iter()
183 .map(|x| x.into())
184 .collect();
185
186 let groups = group_attributes(
187 input,
188 &grp_attrs,
189 &["context", "type", "converter", "move"]
190 .into_iter()
191 .map(|x| x.into())
192 .collect::<Vec<String>>(),
193 );
194
195 let naming_base = groups
196 .iter()
197 .filter(|x| grp_attrs.contains(&x.0))
198 .last()
199 .clone();
200
201 let naming = naming_base
202 .map(|x| read_trait_naming(&x.0, &x.1))
203 .unwrap_or(TraitNaming::default());
204
205 let common_fields = read_common_fields(&groups);
206
207 self.traits.insert(
208 common_fields.ctx.to_token_stream().to_string(),
209 Trait {
210 ctx: common_fields.ctx,
211 ctx_type: common_fields.ctx_type,
212 converter: common_fields.converter,
213 naming,
214 move_type: common_fields.move_type,
215 },
216 );
217
218 self
219 }
220 fn add_fun_base(input: proc_macro2::TokenStream) -> Function {
221 let groups = group_attributes(
222 input,
223 &[],
224 &[
225 "context",
226 "type",
227 "converter",
228 "move",
229 "naming",
230 "fn",
231 "visibility",
232 "vis",
233 ]
234 .into_iter()
235 .map(|x| x.into())
236 .collect::<Vec<String>>(),
237 );
238
239 let common_fields = read_common_fields(&groups);
240
241 let naming: syn::Ident = groups
242 .iter()
243 .filter(|x| x.0 == "naming" || x.0 == "fn")
244 .last()
245 .map(|x| syn::parse2(x.1.clone()).expect("naming mapping error"))
246 .expect("naming field undefined for funcion mapping");
247
248 let visibility: syn::Visibility = groups
249 .iter()
250 .filter(|x| x.0 == "visibility" || x.0 == "vis")
251 .last()
252 .map(|x| syn::parse2(x.1.clone()).expect("visibility mapping error"))
253 .unwrap_or(syn::Visibility::Inherited);
254
255 Function {
256 ctx: common_fields.ctx,
257 ctx_type: common_fields.ctx_type,
258 converter: common_fields.converter,
259 move_type: common_fields.move_type,
260 naming,
261 visibility,
262 }
263 }
264 pub fn add_function(&mut self, input: proc_macro2::TokenStream) -> &mut Self {
265 let base = Self::add_fun_base(input);
266
267 self.functions.insert(
268 base.ctx.to_token_stream().to_string(),
269 base
270 );
271
272 self
273 }
274
275 pub fn add_impl(&mut self, input: proc_macro2::TokenStream) -> &mut Self {
276 let base = Self::add_fun_base(input);
277
278 self.impls.insert(
279 base.ctx.to_token_stream().to_string(),
280 base
281 );
282
283 self
284 }
285}
286
287fn path_is_allowed(ident: syn::Path, allowed: &[String]) -> bool {
288 allowed.contains(&ident.to_token_stream().to_string())
289}
290
291fn group_attributes(
292 input: proc_macro2::TokenStream,
293 allowed_group_idents: &[String],
294 allowed_eq_idents: &[String],
295) -> Vec<(String, proc_macro2::TokenStream)> {
296 let mut groups: Vec<(String, proc_macro2::TokenStream)> = Vec::new();
297
298 let mut last_eq = false;
299 let mut last_path: syn::Path = syn::Path {
300 leading_colon: None,
301 segments: Punctuated::new(),
302 };
303 let mut last_stream: Vec<TokenTree> = Vec::new();
304 let iterator = input.into_iter();
305
306 for tk in iterator {
307 if last_eq {
308 match tk.clone() {
309 TokenTree::Ident(_) | TokenTree::Literal(_) | TokenTree::Group(_) => {
310 last_stream.push(tk);
311 continue;
312 }
313 TokenTree::Punct(x) if x.to_string() != "," => {
314 last_stream.push(x.into());
315 continue;
316 }
317 _ => last_eq = false,
318 }
319 }
320 match tk {
321 TokenTree::Group(group) => {
322 if path_is_allowed(last_path.clone(), &allowed_group_idents) {
323 let last_path = last_path.clone().to_token_stream().to_string();
324 groups.push((last_path, group.stream()));
325 } else {
326 let loc = &group.span().source_text().unwrap();
327 let last_ident = last_path.into_token_stream().to_string();
328 panic!("Unknown identifier detected ({last_ident}); allowed: {allowed_group_idents:?}\n{loc}");
329 }
330 }
331 TokenTree::Ident(ident) => {
332 last_path.segments.push(PathSegment::from(ident));
333 }
334 TokenTree::Punct(punct) => match punct.as_char() {
335 ',' => {
336 if !last_stream.is_empty() {
337 groups.push((
338 last_path.clone().into_token_stream().to_string(),
339 proc_macro2::TokenStream::from_iter(last_stream.clone().into_iter()),
340 ));
341 } else {
342 if !last_path.segments.is_empty()
343 && path_is_allowed(last_path.clone(), &allowed_eq_idents)
344 {
345 groups.push((
346 last_path.clone().into_token_stream().to_string(),
347 quote! {true},
348 ));
349 }
350 }
351 last_stream.clear();
352 last_path.segments.clear()
353 }
354 '.' | ':' => {
355 if !last_path.segments.trailing_punct() {
356 last_path.segments.push_punct(PathSep::default())
357 }
358 }
359 '=' => {
360 if path_is_allowed(last_path.clone(), &allowed_eq_idents) {
361 last_eq = true
362 } else {
363 let lp = last_path.to_token_stream().to_string();
364 panic!("Identifier {lp:?} not allowed. Allowed:
365 {allowed_eq_idents:?}")
366 }
367 }
368 _ => panic!("Unknown punctuation detected"),
369 },
370 TokenTree::Literal(_) => panic!("Literal not allowed in the context definitions"),
371 }
372 }
373 if !last_stream.is_empty() {
374 groups.push((
375 last_path.clone().into_token_stream().to_string(),
376 proc_macro2::TokenStream::from_iter(last_stream.clone().into_iter()),
377 ));
378 } else {
379 if !last_path.segments.is_empty() && path_is_allowed(last_path.clone(), &allowed_eq_idents)
380 {
381 groups.push((
382 last_path.clone().into_token_stream().to_string(),
383 quote! {true},
384 ));
385 }
386 }
387
388 groups
389}
390
391fn read_struct_config(input: &DeriveInput) -> ContextConfig {
392 let mut result = ContextConfig::default();
393
394 for attribute in input.attrs.clone().into_iter().filter_map(|x| {
395 if x.path().is_ident("context_mapper") {
396 Some(x.meta.require_list().and_then(|y| Ok(y.tokens.clone())))
397 } else {
398 None
399 }
400 }) {
401 let groups = group_attributes(
402 attribute.expect("Expected list of attributes"),
403 &["trait", "function", "impl"]
404 .into_iter()
405 .map(|x| x.into())
406 .collect::<Vec<String>>(),
407 &[],
408 );
409
410 for group in groups {
411 match group.0.as_ref() {
412 "trait" => result.add_trait(group.1),
413 "function" => result.add_function(group.1),
414 "impl" => result.add_impl(group.1),
415 _ => unreachable!("Attribtues not parsed properly"),
416 };
417 }
418 }
419
420 result
421}
422
423fn read_member(member: &Field) -> Member {
424 let ident = member.ident.clone().unwrap();
425
426 let mut result = Member {
427 ident: ident.clone(),
428 name: ident.to_string(),
429 rename: Default::default(),
430 skip_contexts: Default::default(),
431 converter: Default::default(),
432 move_contexts: Default::default(),
433 };
434
435 for config in member.attrs.clone().into_iter().filter_map(|x| {
436 if x.path().is_ident("context_attribute") {
437 Some(x.meta.require_list().and_then(|y| Ok(y.tokens.clone())))
438 } else {
439 None
440 }
441 }) {
442 let groups = group_attributes(
443 config.expect("Expected list of attribtues for {ident}"),
444 &["context".to_string()],
445 &[],
446 );
447
448 for group in groups {
449 let attrs = group_attributes(
450 group.1,
451 &[],
452 &["name", "skip", "converter", "rename", "move"]
453 .into_iter()
454 .map(|x| x.into())
455 .collect::<Vec<String>>(),
456 );
457
458 let name: syn::Path = syn::parse2(
459 attrs
460 .iter()
461 .filter_map(|x| {
462 if x.0 == "name" {
463 Some(x.1.clone())
464 } else {
465 None
466 }
467 })
468 .last()
469 .unwrap_or(quote! { default }),
470 )
471 .unwrap();
472
473 let skip = attrs
474 .iter()
475 .filter_map(|x| {
476 if x.0 == "skip" {
477 let b: syn::LitBool = syn::parse2(x.1.clone()).expect("Skip expected bool");
478 Some(b.value)
479 } else {
480 None
481 }
482 })
483 .last()
484 .unwrap_or(false);
485
486 let converter: Option<syn::Path> = attrs
487 .iter()
488 .filter(|x| x.0 == "converter")
489 .last()
490 .map(|x| syn::parse2(x.1.clone()).expect("converter mapping errro"));
491
492 let rename = attrs
493 .iter()
494 .filter_map(|x| {
495 if x.0 == "rename" {
496 let b: syn::LitStr = syn::parse2(x.1.clone()).expect("Rename expected str");
497 Some(b.value())
498 } else {
499 None
500 }
501 })
502 .last();
503
504 let move_type = attrs
505 .iter()
506 .filter_map(|x| {
507 if x.0 == "move" {
508 let b: syn::LitBool = syn::parse2(x.1.clone()).expect("Skip expected bool");
509 Some(b.value)
510 } else {
511 None
512 }
513 })
514 .last()
515 .unwrap_or(false);
516
517 if skip {
518 result.skip_contexts.push(name.clone());
519 }
520 if let Some(converter) = converter {
521 result.converter.insert(name.clone(), converter);
522 }
523 if let Some(rename) = rename {
524 result.rename.insert(name.clone(), rename);
525 }
526 if move_type {
527 result.move_contexts.push(name);
528 }
529 }
530 }
531
532 result
533}
534
535fn read_members(input: &DeriveInput) -> Members {
536 let mut members = Members::new();
537
538 match &input.data {
539 syn::Data::Struct(x) => match &x.fields {
540 Fields::Named(named) => {
541 for field in named.named.iter() {
542 let member = read_member(field);
543 members.insert(member.name.clone(), member);
544 }
545 }
546 Fields::Unnamed(_) => panic!("Unnamed fields are currently unsupported"),
547 Fields::Unit => panic!("Unit structs are unsupported"),
548 },
549 _ => panic!("Currently only structs are supported"),
550 }
551
552 members
553}
554
555fn generate_impls(
556 name: &Ident,
557 config: &ContextConfig,
558 members: &Members,
559) -> proc_macro2::TokenStream {
560 let mut impls: Vec<proc_macro2::TokenStream> = Vec::new();
561
562 for trt in config.impls.iter() {
563 let ctx = &trt.1.ctx;
564 let ctx_type = &trt.1.ctx_type;
565 let naming = &trt.1.naming;
566 let visibility = &trt.1.visibility;
567
568 let members_code = members.iter().fold(
569 quote! {
570 let mut result = std::collections::HashMap::new();
571 },
572 |acc, member| {
573 if member.1.skip_contexts.contains(ctx) {
574 acc
575 } else {
576 let field = &member.1.ident;
577 let name = member
578 .1
579 .rename
580 .get(ctx)
581 .cloned()
582 .unwrap_or(member.1.name.clone());
583 let converter = member
584 .1
585 .converter
586 .get(ctx)
587 .cloned()
588 .unwrap_or(trt.1.converter.clone());
589
590 let move_type = if member.1.move_contexts.contains(ctx) || trt.1.move_type {
591 quote! {}
592 } else {
593 quote! {&}
594 };
595
596 quote! {
597 #acc
598 result.insert(#name.to_string(), #converter(#move_type self.#field));
599 }
600 }
601 },
602 );
603 let members_code = quote! {
604 #members_code
605 result
606 };
607
608 impls.push(quote! {
609 #visibility fn #naming(&self) -> std::collections::HashMap<String, #ctx_type> {
610 #members_code
611 }
612 })
613 }
614
615 let partial = impls.into_iter().fold(quote! {}, |acc, x| {
616 quote! {
617 #acc
618 #x
619 }
620 });
621
622 quote! {
623 impl #name {
624 #partial
625 }
626 }
627}
628
629fn generate_functions(
630 name: &Ident,
631 config: &ContextConfig,
632 members: &Members,
633) -> proc_macro2::TokenStream {
634 let mut functions: Vec<proc_macro2::TokenStream> = Vec::new();
635
636 for trt in config.functions.iter() {
637 let ctx = &trt.1.ctx;
638 let ctx_type = &trt.1.ctx_type;
639 let naming = &trt.1.naming;
640 let visibility = &trt.1.visibility;
641
642 let members_code = members.iter().fold(
643 quote! {
644 let mut result = std::collections::HashMap::new();
645 },
646 |acc, member| {
647 if member.1.skip_contexts.contains(ctx) {
648 acc
649 } else {
650 let field = &member.1.ident;
651 let name = member
652 .1
653 .rename
654 .get(ctx)
655 .cloned()
656 .unwrap_or(member.1.name.clone());
657 let converter = member
658 .1
659 .converter
660 .get(ctx)
661 .cloned()
662 .unwrap_or(trt.1.converter.clone());
663
664 let move_type = if member.1.move_contexts.contains(ctx) || trt.1.move_type {
665 quote! {}
666 } else {
667 quote! {&}
668 };
669
670 quote! {
671 #acc
672 result.insert(#name.to_string(), #converter(#move_type arg.#field));
673 }
674 }
675 },
676 );
677 let members_code = quote! {
678 #members_code
679 result
680 };
681
682 functions.push(quote! {
683 #visibility fn #naming(arg: &#name) -> std::collections::HashMap<String, #ctx_type> {
684 #members_code
685 }
686 })
687 }
688
689 functions.into_iter().fold(quote! {}, |acc, x| {
690 quote! {
691 #acc
692 #x
693 }
694 })
695}
696
697fn generate_traits_impl(
698 name: &Ident,
699 config: &ContextConfig,
700 members: &Members,
701) -> proc_macro2::TokenStream {
702 let mut impls: Vec<proc_macro2::TokenStream> = Vec::new();
703
704 for trt in config.traits.iter() {
705 let ctx = &trt.1.ctx;
706 let ctx_type = &trt.1.ctx_type;
707
708 let members_code = members.iter().fold(
709 quote! {
710 let mut result = std::collections::HashMap::new();
711 },
712 |acc, member| {
713 if member.1.skip_contexts.contains(ctx) {
714 acc
715 } else {
716 let field = &member.1.ident;
717 let name = member
718 .1
719 .rename
720 .get(ctx)
721 .cloned()
722 .unwrap_or(member.1.name.clone());
723 let converter = member
724 .1
725 .converter
726 .get(ctx)
727 .cloned()
728 .unwrap_or(trt.1.converter.clone());
729
730 let move_type = if member.1.move_contexts.contains(ctx) || trt.1.move_type {
731 quote! {}
732 } else {
733 quote! {&}
734 };
735
736 quote! {
737 #acc
738 result.insert(#name.to_string(), #converter(#move_type self.#field));
739 }
740 }
741 },
742 );
743 let members_code = quote! {
744 #members_code
745 result
746 };
747
748 match &trt.1.naming {
749 TraitNaming::ExistingGeneric { path, method_name } => impls.push(quote! {
750 impl #path<#ctx_type> for #name {
751 fn #method_name(&self) -> std::collections::HashMap<String, #ctx_type> {
752 #members_code
753 }
754 }
755 }),
756 TraitNaming::Existing { path, method_name } => impls.push(quote! {
757 impl #path for #name {
758 fn #method_name(&self) -> std::collections::HashMap<String, #ctx_type> {
759 #members_code
760 }
761 }
762 }),
763 }
764 }
765
766 impls.into_iter().fold(quote! {}, |acc, x| {
767 quote! {
768 #acc
769 #x
770 }
771 })
772}
773
774#[proc_macro_derive(ContextMapper, attributes(context_mapper, context_attribute))]
926pub fn context_mapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
927 let input = parse_macro_input!(input as DeriveInput);
928 let name = input.ident.clone();
929
930 let config = read_struct_config(&input);
931 let members = read_members(&input);
932
933 let traits_impl = generate_traits_impl(&name, &config, &members);
934 let functs = generate_functions(&name, &config, &members);
935 let impls = generate_impls(&name, &config, &members);
936
937 let expanded = quote! {
938 #traits_impl
939 #functs
940 #impls
941 };
942
943 proc_macro::TokenStream::from(expanded)
944}