chiark / gitweb /
1a158a7d10824e063a94c9189bad2ab19b61299d
[hippotat.git] / macros / macros.rs
1 // Copyright 2021-2022 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 use syn::{parse_macro_input, parse_quote};
6 use syn::{Data, DataStruct, DeriveInput, LitStr, Meta, NestedMeta};
7 use quote::{quote, quote_spanned, ToTokens};
8 use proc_macro2::{Literal, TokenStream};
9
10 use std::cell::RefCell;
11
12 use itertools::Itertools;
13
14 /// Generates config resolver method
15 /// 
16 /// Each field ends up having an SKL and a method.
17 /// The method actually looks up the value in a particular link context.
18 /// SKL is passed to the method, which usually uses it to decide which
19 /// sections to look in.  But it is also used by general validation,
20 /// unconditionally, to reject settings in the wrong section.
21 ///
22 /// Atrributes:
23 ///
24 ///  * `limited`, `server`, `client`: cooked sets of settings;
25 ///    default `SKL` is `PerClient` except for `limited`
26 ///  * `global` and `per_client`: set the SKL.
27 ///  * `special(method, SKL)`
28 ///
29 /// Generated code
30 ///
31 /// ```no_run
32 /// impl<'c> ResolveContext<'c> {
33 ///
34 ///   // SKL here is used by SectionKindList::contains()
35 ///   const FIELDS: &'static [(&'static str, SectionKindList)] = &[ ... ];
36 ///
37 ///   #[throws(AE)]
38 ///   fn resolve_instance(&self) -> InstanceConfig {
39 ///     InstanceConfig {
40 ///       ...
41 ///        // SKL here is usually passed to first_of, but the method
42 ///        // can do something more special.
43 ///        max_batch_down: self.limited("max_batch_down", SKL::PerClient)?,
44 ///        ...
45 ///      }
46 ///   }
47 /// }
48 ///
49 /// pub struct InstanceConfigCommon { ... }
50 /// impl InstanceConfigCommon {
51 ///   pub fn from(l: &[InstanceConfig]) { InstanceConfigCommon {
52 ///     field: <Type as ResolveGlobal>::resolve(l.iter().map(|e| &e.field)),
53 ///     ...
54 ///   } }
55 /// }
56 /// ```
57 #[proc_macro_derive(ResolveConfig, attributes(
58   limited, server, client, computed, special,
59   per_client, global,
60 ))]
61 pub fn resolve(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
62   let input = parse_macro_input!(input as DeriveInput);
63
64   let (fields, top_ident) = match input {
65     DeriveInput {
66       ref ident,
67       data: Data::Struct(DataStruct {
68         fields: syn::Fields::Named(ref f),
69         ..
70       }),
71       ..
72     } => (f, ident),
73     _ => panic!(),
74   };
75
76   let target = &input.ident;
77
78   let mut names = vec![];
79   let mut output = vec![];
80   let mut global_fields = vec![];
81   let mut global_assignments = vec![];
82   let mut inspects = vec![];
83   for field in &fields.named {
84     //dbg!(field);
85     let fname = &field.ident.as_ref().unwrap();
86     let ty = &field.ty;
87     let fname_span = fname.span();
88     let skl = RefCell::new(None);
89     let set_skl = |new| {
90       let mut skl = skl.borrow_mut();
91       if let Some(old) = &*skl { panic!("dup SKL {} and {} for field {}",
92                                         old, new, &fname); }
93       *skl = Some(new);
94     };
95     let mut method = quote_spanned!{fname_span=> ordinary };
96     for attr in &field.attrs {
97       let atspan = attr.path.segments.last().unwrap().ident.span();
98       if attr.tokens.is_empty() {
99         if &attr.path == &parse_quote!{ per_client } {
100           set_skl(quote_spanned!{fname_span=> SectionKindList::PerClient });
101           continue;
102         } else if &attr.path == &parse_quote!{ global } {
103           set_skl(quote_spanned!{fname_span=> SectionKindList::Global });
104           global_fields.push(syn::Field {
105             attrs: vec![],
106             ..field.clone()
107           });
108           global_assignments.push(quote_spanned!(fname_span=>
109             #fname: <#ty as ResolveGlobal>::resolve
110                     (l.iter().map(|e| &e.#fname)),
111           ));
112           continue;
113         }
114         method = attr.path.to_token_stream();
115         if &attr.path == &parse_quote!{ limited } {
116           set_skl(quote_spanned!{atspan=> SectionKindList::Limited });
117         } else if &attr.path == &parse_quote!{ client } {
118           set_skl(quote_spanned!{atspan=> SectionKindList::PerClient });
119         } else if &attr.path == &parse_quote!{ computed } {
120           set_skl(quote_spanned!{atspan=> SectionKindList::None });
121         }
122       } else if &attr.path == &parse_quote!{ special } {
123         let meta = match attr.parse_meta().unwrap() {
124           Meta::List(list) => list,
125           _ => panic!(),
126         };
127         let (tmethod, tskl) = meta.nested.iter().collect_tuple().unwrap();
128         fn get_path(meta: &NestedMeta) -> TokenStream {
129           match meta {
130             NestedMeta::Meta(Meta::Path(ref path)) => path.to_token_stream(),
131             _ => panic!(),
132           }
133         }
134         method = get_path(tmethod);
135         *skl.borrow_mut() = Some(get_path(tskl));
136       }
137     }
138     let fname_string = fname.to_string();
139     let fname_lit = Literal::string( &fname_string );
140     let skl = skl.into_inner()
141       .expect(&format!("SKL not specified! (field {})!", fname));
142
143     names.push(quote!{
144       (#fname_lit, #skl),
145     });
146     //dbg!(&method);
147     output.push(quote!{
148       #fname: rctx. #method ( #fname_lit, #skl )?,
149     });
150     inspects.push(quote!{
151       #fname_lit => &self.#fname,
152     });
153     //eprintln!("{:?} method={:?} skl={:?}", field.ident, method, skl);
154   }
155   //dbg!(&output);
156
157   let global = syn::Ident::new(&format!("{}Global", top_ident),
158                                top_ident.span());
159
160   let output = quote! {
161     impl #target {
162       const FIELDS: &'static [(&'static str, SectionKindList)]
163         = &[ #( #names )* ];
164
165       fn resolve_instance(rctx: &ResolveContext)
166           -> ::std::result::Result<#target, anyhow::Error>
167       {
168         ::std::result::Result::Ok(#target {
169           #( #output )*
170         })
171       }
172
173       pub fn inspect_key(&self, field: &'_ str)
174                          -> Option<&dyn InspectableConfigValue> {
175         Some(match field {
176           #( #inspects )*
177           _ => return None,
178         })
179       }
180     }
181
182     #[derive(Debug)]
183     pub struct #global {
184       #( #global_fields ),*
185     }
186
187     impl #global {
188       pub fn from(l: &[#top_ident]) -> #global { #global {
189         #( #global_assignments )*
190       } }
191     }
192   };
193
194   //eprintln!("{}", &output);
195   output.into()
196 }
197
198 #[proc_macro]
199 pub fn into_crlfs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
200   let input: proc_macro2::TokenStream = input.into();
201   let token: LitStr = syn::parse2(input).expect("expected literal");
202   let input = token.value();
203   let output = input.split_inclusive('\n')
204     .map(|s| s.trim_start_matches(&[' ','\t'][..]))
205     .map(|s| match s.strip_suffix("\n") {
206       None => [s, ""],
207       Some(l) => [l, "\r\n"],
208     })
209     .flatten()
210     .collect::<String>();
211   //dbg!(&output);
212   let output = LitStr::new(&output, token.span());
213   let output = quote!(#output);
214   output.into()
215 }