use proc_macro2::TokenStream; use quote::{quote, TokenStreamExt}; fn title_to_kebab_case(s: &str) -> String { let mut ret = String::new(); let mut start = 0; for (end, _) in s.match_indices(|c: char| c.is_ascii_uppercase()) { if start != end { ret.push_str(&s[start..end].to_ascii_lowercase()); ret.push_str("-"); } start = end; } ret.push_str(&s[start..].to_ascii_lowercase()); // s.split_inclusive(|c: char| c.is_ascii_uppercase()) // .map(|s| s.to_ascii_lowercase()) // .join("-") ret } #[proc_macro_derive(Unifiable)] pub fn derive_unifiable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let name = input.ident; let en = if let syn::Data::Enum(e) = &input.data { e } else { return proc_macro::TokenStream::from(quote! { compile_error!("can only derive unifiable for an enum"); }) }; let mut cases = TokenStream::new(); for v in en.variants.iter() { let vnm = &v.ident; cases.append_all( quote! { (Self :: #vnm (a), Self::Concrete:: #vnm (b) ) => a.unify(b, u), }, ); } let expanded = quote! { impl crate::knowledge::Unifiable for #name { fn unify(&self, other: &Self::Concrete, u: crate::knowledge::Unification) -> Option { match (self, other) { #cases _ => None, } } } }; proc_macro::TokenStream::from(expanded) } #[proc_macro_derive(Substitutable)] pub fn derive_substitutable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let name = input.ident; let en = if let syn::Data::Enum(e) = &input.data { e } else { return proc_macro::TokenStream::from(quote! { compile_error!("can only derive substitutable for an enum"); }) }; let mut subcases = TokenStream::new(); let mut mvcases = TokenStream::new(); for v in en.variants.iter() { let vnm = &v.ident; subcases.append_all( quote! { Self :: #vnm (x) => Self :: #vnm (x.substitute(u)), }, ); mvcases.append_all( quote! { Self :: #vnm (x) => Self :: #vnm (x.map_variables(f)), }, ); } let expanded = quote! { impl crate::knowledge::Substitutable for #name { fn substitute(&self, u: &crate::knowledge::Unification) -> Self { match self { #subcases } } fn map_variables(&self, f: &mut F) -> Self where F: FnMut(crate::knowledge::Var) -> Var { match self { #mvcases } } } }; proc_macro::TokenStream::from(expanded) } #[proc_macro_derive(Concrete)] pub fn derive_concrete(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let name = input.ident; let en = if let syn::Data::Enum(e) = &input.data { e } else { return proc_macro::TokenStream::from(quote! { compile_error!("can only derive concretizable for an enum"); }) }; let hnm = syn::Ident::new(&format!("{}Hole", name), name.span()); let dnm = syn::Ident::new(&format!("{}Discriminants", name), name.span()); let mut cases = TokenStream::new(); let mut dcases = TokenStream::new(); let mut dispcases = TokenStream::new(); let mut variants = TokenStream::new(); for v in en.variants.iter() { let vnm = &v.ident; let kebab = title_to_kebab_case(&vnm.to_string()); let mut vtypes = TokenStream::new(); let mut tuple = false; if let syn::Fields::Unnamed(fu) = &v.fields { for f in fu.unnamed.iter() { if let syn::Type::Tuple(tu) = &f.ty { tuple = true; for tty in tu.elems.iter() { vtypes.append_all(quote! { crate::knowledge::Hole < #tty > , }); } } else { let vty = &f.ty; vtypes.append_all(quote! { crate::knowledge::Hole < #vty > , }); } } } if tuple { variants.append_all(quote! { #vnm (( #vtypes )) , }); } else { variants.append_all(quote! { #vnm ( #vtypes ) , }); } cases.append_all(quote! { Self :: #vnm (a) => Some( #name :: #vnm ( a.concretize()? )) , }); dcases.append_all(quote! { Self :: #vnm {..} => #dnm :: #vnm , }); dispcases.append_all( quote! { Self :: #vnm (x) => { write!(f, "{}", #kebab)?; x.display_tuple(f) }, }, ); } let expanded = quote! { #[derive(Debug, Clone, PartialEq, Eq, Hash, elfmacros::Unifiable, elfmacros::Substitutable)] pub enum #hnm { #variants } impl crate::knowledge::Concretizable for #hnm { type Concrete = #name; fn concretize(&self) -> Option { match self { #cases _ => None, } } } impl #hnm { pub fn discriminant(&self) -> #dnm { match self { #dcases } } } impl std::fmt::Display for #hnm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #dispcases } } } impl std::fmt::Display for #name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #dispcases } } } }; proc_macro::TokenStream::from(expanded) }