cl_format_macros/
lib.rs

1#![doc = r#"The macros here should auto generate several traits and the major TildeAble trait.
2
3For example:
4
5```rust
6#[derive(Debug, PartialEq, TildeAble)]
7pub enum TildeKind {
8    /// ~C ~:C
9    #[implTo(char)]
10    Char,
11
12    /// ~$ ~5$ ~f
13    #[implTo(float)]
14    Float(Option<String>),
15
16    /// ~d ~:d ~:@d
17    Digit(Option<String>),
18
19    /// ~a
20    #[implTo(float, char, String)]
21    Va,
22
23    /// loop
24    Loop(Vec<Tilde>),
25
26    /// text inside the tilde
27    Text(String),
28
29    /// vec
30    VecTilde(Vec<Tilde>),
31}
32```
33
34Will generate:
35
36```rust
37/// all default method is return none.
38trait TildeAble {
39    fn len(&self) -> usize;
40    fn into_tildekind_char(&self) -> Option<&dyn TildeKindChar>{None}
41    fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa>{None}
42    // and all other fields...
43}
44
45impl TildeAble for char {
46    fn into_tildekind_char(&self) -> Option<&dyn TildeKindChar> {
47        Some(self)
48    }
49
50    fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa> {
51        Some(self)
52    }
53}
54
55impl TildeAble for float {
56    fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa> {
57        Some(self)
58    }
59}
60
61impl TildeAble for String {
62    fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa> {
63        Some(self)
64    }
65}
66
67trait TildeKindChar {
68    fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
69        Err("un-implenmented yet".into())
70    }
71}
72
73trait TildeKindVa {
74    fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
75        Err("un-implenmented yet".into())
76    }
77}
78
79```
80"#]
81
82use std::{collections::HashMap, error::Error};
83
84use proc_macro::TokenStream;
85use proc_macro2::{Ident, Literal, Span};
86use quote::quote;
87use syn::{parse_macro_input, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Variant};
88
89#[proc_macro_derive(TildeAble, attributes(implTo))]
90pub fn derive_tilde_able(input: TokenStream) -> TokenStream {
91    let input = parse_macro_input!(input as DeriveInput);
92
93    let mut return_types_traits = vec![];
94    let mut all_default_methods = vec![];
95    let mut types_impl_methods = HashMap::new();
96
97    match input.data {
98        Data::Enum(DataEnum { ref variants, .. }) => {
99            let all_vars = variants.iter().map(|var| parse_variant_attrs(var));
100
101            all_vars.for_each(|(field, tys)| {
102                let fname = Ident::new(
103                    &(String::from("into_tildekind_") + &field.to_lowercase()),
104                    Span::call_site(),
105                );
106
107                let return_type =
108                    Ident::new(&(String::from("TildeKind") + &field), Span::call_site());
109
110                // add default methods to TildeAble
111                all_default_methods
112                    .push(quote! {
113						fn #fname(&self) -> Option<&dyn #return_type> {
114							None
115						}});
116
117                // impl for types
118                tys.for_each(|ty| {
119                    let en = types_impl_methods.entry(ty).or_insert(vec![]);
120                    en.push(quote! {fn #fname(&self) -> Option<&dyn #return_type> {
121						Some(self)
122					}})
123                });
124
125                //
126                let doc = Literal::string(&(return_type.to_string() + " is the trait that contains implementation of type for TildeKind::"+ &field + ".\n\nGenerated by cl-format-macro"));
127                return_types_traits.push(quote! {
128                    #[doc = #doc]
129                    pub trait #return_type: Debug {
130                        fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
131                            Err(TildeError::new(ErrorKind::EmptyImplenmentError, "haven't implenmented yet").into(),)
132                        }
133                }})
134            });
135        }
136        _ => panic!("only support the enum"),
137    };
138
139    let mut result = vec![];
140
141    // trait TildeAble defination
142    let tilde_able_trait = quote! {
143        /// TildeAble is used for dispatch types to the specific tilde trait it implemented.
144        ///
145        /// Generated by `cl-format-macro`.
146        pub trait TildeAble:Debug {
147            fn len(&self) -> usize;
148            #(#all_default_methods)*
149        }
150    };
151
152    let mut auto_impl_for_types = types_impl_methods
153        .iter()
154        .map(|(ty, methods)| {
155            quote! {
156                impl TildeAble for #ty {
157                    fn len(&self) -> usize {
158                        1
159                    }
160                    #(#methods)*
161                }
162            }
163        })
164        .collect();
165
166    // merge together
167    result.push(tilde_able_trait);
168    result.append(&mut auto_impl_for_types);
169    result.append(&mut return_types_traits);
170
171    proc_macro2::TokenStream::from_iter(result.into_iter()).into()
172}
173
174/// return the field Ident and all types implTo. Empty if there is no implTo types
175fn parse_variant_attrs(variant: &Variant) -> (String, impl Iterator<Item = Ident> + '_) {
176    let all_impl_to_type = variant
177        .attrs
178        .iter()
179        .filter(|attr| attr.path().get_ident().map(|d| d.to_string()) == Some("implTo".to_string()))
180        .map(|attr| get_types_impl_to(attr).unwrap())
181        .flatten();
182
183    let field = variant.ident.to_string();
184
185    (field.clone(), all_impl_to_type)
186}
187
188/// parse the `implTo` attribute
189fn get_types_impl_to(attribute: &Attribute) -> Result<impl Iterator<Item = Ident>, Box<dyn Error>> {
190    let mut result = vec![];
191    attribute.parse_nested_meta(|meta| {
192        result.push(
193            meta.path
194                .get_ident()
195                .ok_or(syn::Error::new(meta.path.span(), "get_ident issue"))?
196                .clone(),
197        );
198        Ok(())
199    })?;
200
201    Ok(result.into_iter())
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use syn::parse_quote;
208
209    #[test]
210    fn test_get_types_impl_to() -> Result<(), Box<dyn Error>> {
211        let test_case: Attribute = parse_quote! {
212                #[implTo(a,b,c,d)]
213        };
214
215        //dbg!(test_case);
216        assert_eq!(
217            vec!["a", "b", "c", "d"]
218                .into_iter()
219                .map(|s| s.to_string())
220                .collect::<Vec<String>>(),
221            get_types_impl_to(&test_case)
222                .unwrap()
223                .into_iter()
224                .map(|x| x.to_string())
225                .collect::<Vec<String>>()
226        );
227
228        let test_case: Attribute = parse_quote! {
229                #[implTo(a)]
230        };
231
232        //dbg!(test_case);
233        assert_eq!(
234            vec!["a"]
235                .into_iter()
236                .map(|s| s.to_string())
237                .collect::<Vec<String>>(),
238            get_types_impl_to(&test_case)
239                .unwrap()
240                .into_iter()
241                .map(|x| x.to_string())
242                .collect::<Vec<String>>()
243        );
244
245        Ok(())
246    }
247
248    #[test]
249    fn test_parse_variant_attrs() -> Result<(), Box<dyn Error>> {
250        let test_case: Variant = parse_quote! {
251            #[implTo(a,b,c,d)]
252            A
253        };
254
255        //dbg!(test_case);
256        let result = parse_variant_attrs(&test_case);
257        assert_eq!(result.0, "A");
258        assert_eq!(
259            result.1.map(|i| i.to_string()).collect::<Vec<_>>(),
260            vec!["a", "b", "c", "d"]
261                .into_iter()
262                .map(|s| s.to_string())
263                .collect::<Vec<String>>(),
264        );
265
266        //
267        let test_case: Variant = parse_quote! {
268            B
269        };
270
271        //dbg!(&test_case);
272        let mut result = parse_variant_attrs(&test_case);
273        assert_eq!(result.0, "B");
274        assert_eq!(result.1.next(), None);
275
276        Ok(())
277    }
278
279    #[test]
280    fn test_args_picker() -> Result<(), Box<dyn Error>> {
281        //let s: syn::Expr = syn::parse_str("a!(a1, &a2, a3)")?;
282        //let s: Punctuated<Expr, Token![,]> = syn::parse_str("a!(a1, &a2, a3)")?;
283        // let s: TokenStream = "a1, &a2, a3, [[&3]]".parse().unwrap();
284        // let items = Punctuated::<Expr, Token![,]>::parse_terminated
285        //     .parse(s.into())
286        //     .unwrap();
287        // dbg!(items);
288
289        Ok(())
290    }
291}