tinymist_derive/
lib.rs

1//! Derives for tinymist.
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{DeriveInput, parse_macro_input};
8
9/// Derives the `BindTyCtx` trait.
10///
11/// # Example
12///
13/// ```ignore
14/// #[derive(BindTyCtx)]
15/// struct MyStruct {
16///     #[bind]
17///     tyctx: TyCtx,
18/// }
19/// ```
20#[proc_macro_derive(BindTyCtx, attributes(bind))]
21pub fn bind_ty_ctx(input: TokenStream) -> TokenStream {
22    // Parse the input tokens into a syntax tree
23    let input = parse_macro_input!(input as DeriveInput);
24
25    // Build the output, possibly using quasi-quotation
26    let expanded = match input.data {
27        syn::Data::Struct(..) => {
28            let name = &input.ident;
29            let bind_name = input
30                .attrs
31                .iter()
32                .find_map(|attr| {
33                    if attr.path().is_ident("bind") {
34                        Some(attr.parse_args::<syn::Expr>().unwrap())
35                    } else {
36                        None
37                    }
38                })
39                .unwrap_or_else(|| {
40                    let t = syn::Ident::new("tyctx", input.ident.span());
41                    syn::parse_quote!(#t)
42                });
43            let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
44
45            quote! {
46                impl #impl_generics TyCtx for #name #ty_generics #where_clause {
47                    fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<DynTypeBounds> {
48                        self.#bind_name.global_bounds(var, pol)
49                    }
50                    fn local_bind_of(&self, var: &Interned<TypeVar>) -> Option<Ty> {
51                        self.#bind_name.local_bind_of(var)
52                    }
53                }
54            }
55        }
56        _ => panic!("only structs are supported"),
57    };
58
59    // Hand the output tokens back to the compiler
60    TokenStream::from(expanded)
61}
62
63/// Derives the `DeclEnum` trait.
64///
65/// # Example
66///
67/// ```ignore
68/// #[derive(DeclEnum)]
69/// enum MyEnum {
70///     Sub1(SpannedDecl),
71///     Sub2(SpannedDecl),
72/// }
73/// ```
74#[proc_macro_derive(DeclEnum)]
75pub fn gen_decl_enum(input: TokenStream) -> TokenStream {
76    // In form of
77    // ```
78    // pub enum Decl {
79    //   Sub1(X),
80    //   Sub2(Y),
81    // }
82    // ```
83
84    // Parse the input tokens into a list of variants
85    let input = parse_macro_input!(input as DeriveInput);
86
87    let variants = match input.data {
88        syn::Data::Enum(data) => data.variants,
89        _ => panic!("only enums are supported"),
90    };
91
92    let names = variants.iter().map(|v| &v.ident).collect::<Vec<_>>();
93
94    let input_name = &input.ident;
95
96    let expanded = quote! {
97        impl #input_name {
98            /// Gets the name of the item.
99            pub fn name(&self) -> &Interned<str> {
100                match self {
101                    #(Self::#names(x) => x.name()),*
102                }
103            }
104            /// Gets the span of the item.
105            pub fn span(&self) -> Span {
106                match self {
107                    #(Self::#names(x) => x.span()),*
108                }
109            }
110        }
111
112        impl fmt::Debug for Decl {
113            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114                match self {
115                    #(Self::#names(x) => write!(f, concat!(stringify!(#names), "({:?})"), x)),*
116                }
117            }
118        }
119
120    };
121
122    TokenStream::from(expanded)
123}
124
125/// Derives the `TypliteAttr` trait.
126///
127/// # Example
128///
129/// ```ignore
130/// #[derive(TypliteAttr, Default)]
131/// pub struct FigureAttr {
132///     pub caption: EcoString,
133/// }
134/// ```
135#[proc_macro_derive(TypliteAttr)]
136pub fn gen_typlite_element(input: TokenStream) -> TokenStream {
137    // Parse the input tokens into a syntax tree
138    let input = parse_macro_input!(input as DeriveInput);
139
140    // extract the fields from the struct
141    let field_parsers = match &input.data {
142        syn::Data::Struct(data) => match &data.fields {
143            syn::Fields::Named(fields) => fields
144                .named
145                .iter()
146                .map(|f| {
147                    let name = f.ident.as_ref().unwrap();
148
149                    let ty = &f.ty;
150
151                    quote! {
152                        md_attr::#name => {
153                            let value = <#ty>::parse_attr(content)?;
154                            result.#name = value;
155                        }
156                    }
157                })
158                .collect::<Vec<_>>(),
159            syn::Fields::Unnamed(_) => panic!("unnamed fields are not supported"),
160            syn::Fields::Unit => panic!("unit structs are not supported"),
161        },
162        _ => panic!("only structs are supported"),
163    };
164
165    let input_name = &input.ident;
166
167    // generate parse trait
168    let expanded = quote! {
169        impl TypliteAttrsParser for #input_name {
170            fn parse(attrs: &HtmlAttrs) -> Result<Self> {
171                let mut result = Self::default();
172                for (name, content) in attrs.0.iter() {
173                    match *name {
174                        #(#field_parsers)*
175                        _ => {
176                            return Err(format!("unknown attribute: {name}").into());
177                        }
178                    }
179                }
180
181                Ok(result)
182            }
183        }
184    };
185
186    TokenStream::from(expanded)
187}