use proc_macro::{TokenStream, Literal}; use proc_macro2::{TokenStream as TokenStream2, Span}; use quote::{quote, ToTokens, TokenStreamExt}; use std::collections::{HashMap, BTreeMap}; use syn::{ Error, Path, Ident, Variant, Fields, BinOp, Expr, ExprPath, ExprBinary, ExprAssign, ExprLit, Lit, LitStr, Item, ItemEnum, ItemImpl, ImplItem, ImplItemFn, ItemTrait, TraitItem, TraitItemFn, Signature, MetaList, Type, TypePath, FnArg, PatType, Attribute, parse::{ParseStream, Parse, Result}, spanned::Spanned, punctuated::Punctuated, token::Comma }; macro_rules! attribute { ($name:ident { $($body:tt)* }) => { #[proc_macro_attribute] pub fn $name (meta: TokenStream, item: TokenStream) -> TokenStream { write(self::$name::CustomAttribute( syn::parse_macro_input!(meta as self::$name::CustomAttributeMeta), syn::parse_macro_input!(item as self::$name::CustomAttributeItem), )) } mod $name { use crate::*; $($body)* } } } attribute!(commands { #[derive(Debug, Clone)] pub struct CustomAttribute( pub CustomAttributeMeta, pub CustomAttributeItem, ); #[derive(Debug, Clone)] pub struct CustomAttributeMeta( pub Path, pub Option ); #[derive(Debug, Clone)] pub struct CustomAttributeItem( pub Path, pub CustomAttributeItemDispatch, pub Item ); pub type CustomAttributeItemDispatch = HashMap, LitStr)>; impl Parse for CustomAttributeMeta { /// Parse contents of `#[command(...)]` attribute tag. fn parse (input: ParseStream) -> Result { let meta: Expr = input.parse()?; Ok(match meta { // Struct name only Expr::Path(ExprPath { path, .. }) => Self(path, None), // Struct name with namespace prefix Expr::Assign(ExprAssign { ref left, ref right, .. }) if let Expr::Path(ExprPath { path, .. }) = &**left && let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right => Self(path.clone(), Some(lit.clone())), // All other variants invalid _ => return Err(Error::new(meta.span(), format!( "must be: #[commands(Struct)] or #[commands(Struct = \"struct\")], got: {meta:?}" ))) }) } } impl Parse for CustomAttributeItem { /// Parse contents of `trait` or `impl` block annotated with `#[command(...)]` fn parse (input: ParseStream) -> Result { let mut item: Item = input.parse()?; Ok(Self( parse_custom_attribute_item_path(&item)?, parse_custom_attribute_item_dispatch(&mut item)?, item, )) } } fn parse_custom_attribute_item_path (item: &Item) -> Result { Ok(match item { Item::Trait(ItemTrait { ident, .. }) => Path { leading_colon: None, segments: syn::punctuated::Punctuated::from_iter([ syn::PathSegment { ident: ident.clone(), arguments: syn::PathArguments::None // TODO support generics } ]) }, Item::Impl(ItemImpl { self_ty, .. }) if let Type::Path(TypePath { path, .. }) = &**self_ty => path.clone(), _ => return Err( Error::new(item.span(), format!("#[commands] works on trait or inherent impl")) ) }) } /// Pick out annotated functions from the `trait` or `impl` block, /// adding them to the [CustomAttributeItemDispatch] collection. fn parse_custom_attribute_item_dispatch (item: &mut Item) -> Result { let mut dispatch: CustomAttributeItemDispatch = Default::default(); let mut dispatch_attrs = | attrs: &Vec, ident: &Ident, inputs: &Punctuated | { attrs.iter().filter(|attr|{ if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta && path == &Path::from(Ident::new("command", Span::call_site())) && let Ok(handler) = syn::parse2::(tokens.clone()) && let Expr::Assign(ExprAssign { ref left, ref right, .. }) = handler && let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right && let Expr::Path(ExprPath { path, .. }) = &**left && path.segments.len() == 1 { dispatch.insert(ident.clone(), ( path.segments.first().unwrap().ident.clone(), inputs.iter().cloned().collect(), lit.clone() )); false } else { true } }).cloned().collect() }; match item { Item::Trait(ItemTrait { items, .. }) => { for item in items.iter_mut() { if let TraitItem::Fn(TraitItemFn { attrs, sig: Signature { ident, inputs, .. }, .. }) = item { *attrs = dispatch_attrs(attrs, ident, inputs); } } }, Item::Impl(ItemImpl { items, .. }) => { for item in items.iter_mut() { if let ImplItem::Fn(ImplItemFn { attrs, sig: Signature { ident, inputs, .. }, .. }) = item { *attrs = dispatch_attrs(attrs, ident, inputs); } } }, _ => return Err( Error::new(item.span(), format!("#[commands] works on trait or inherent impl")) ) } Ok(dispatch) } impl ToTokens for CustomAttribute { fn to_tokens (&self, out: &mut TokenStream2) { let Self( CustomAttributeMeta(command, namespace), CustomAttributeItem(state, items, item), ) = self; let mut variants = quote! {}; let mut dispatch = quote! {}; let mut keywords = quote! {}; let mut expressions = quote! {}; for (ident, (variant, inputs, keyword)) in items.iter() { write_variant( &mut expressions, &mut keywords, &mut variants, &mut dispatch, ident, command, variant, inputs, namespace, keyword, ) } let impls = match item { Item::Impl(ItemImpl { generics, .. }) => write_for_struct( keywords, expressions, dispatch, state, command, generics, ), Item::Trait { .. } => write_for_trait( keywords, expressions, dispatch, state, command, ), _ => panic!("trait or inherent impl needed for #[commands]") }; append(out, quote! { /// Command variants #[derive(Debug, Clone)] pub enum #command { #variants } #impls #item }) } } fn write_variant ( exps: &mut TokenStream2, syms: &mut TokenStream2, vars: &mut TokenStream2, disp: &mut TokenStream2, ident: &Ident, command: &Path, variant: &Ident, inputs: &[FnArg], namespace: &Option, keyword: &LitStr, ) { let keyword = if let Some(namespace) = namespace { format!("{}/{}", namespace.value(), keyword.value()) } else { keyword.value() }; let mut typed = quote! {}; let mut params = quote! {}; let mut obtain = quote! {}; let mut has_args = false; for arg in inputs.iter() { if let FnArg::Typed(PatType { pat, ty, .. }) = arg { has_args = true; append(&mut typed, quote! { #pat: #ty, }); append(&mut params, quote! { #pat, }); append(&mut obtain, quote! { let #pat: #ty = { let head = tail.head()?.unwrap_or_default(); let tail = tail.tail()?.unwrap_or_default(); match dizzle::Namespace::<#ty>::namespace(state, &head)? { Some(arg) => arg, None => return Err(format!("{}: arg \"{}\" ({}) got: {head} {tail}", #keyword, stringify!(#pat), stringify!(#ty), ).into()) } };}); } } if has_args { append(vars, quote! { #variant { #typed }, }); append(disp, quote! { #command::#variant { #params } => state.#ident(#params), }); append(exps, quote! { let tail_base = tail; if head.src()? == Some(#keyword) { let tail = tail_base; #obtain return Ok(Some(#command::#variant { #params })) } }); } else { append(vars, quote! { #variant, }); append(disp, quote! { #command::#variant => state.#ident(), }); append(syms, quote! { if word == #keyword { return Ok(Some(#command::#variant)); } }); } } fn write_for_struct ( syms: TokenStream2, exps: TokenStream2, disp: TokenStream2, state: &Path, command: &Path, generics: &syn::Generics, ) -> TokenStream2 { //let lts = Punctuated::<_, Comma>::from_iter(generics.lifetimes()); //let cns = Punctuated::<_, Comma>::from_iter(generics.const_params()); let tys = Punctuated::<_, Comma>::from_iter(generics.type_params()); quote! { impl<#tys> dizzle::Namespace<#command> for #state { //def_namespace_symbols!('n |state: Self| -> #command { //#syms //}); //def_namespace_exps!('n |state: Self| -> #command { //#exps //}); fn namespace_symbol (&self, word: impl Symbol) -> Perhaps<#command> { if let Some(word) = word.word()? { #syms } Ok(None) } fn namespace_expression (&self, expr: impl Expression) -> Perhaps<#command> { let state = self; if let Some(expr) = expr.expr()? { let head = expr.head()?; let tail = expr.tail()?; #exps } Ok(None) } } impl #generics dizzle::Dispatch<#state> for #command { fn dispatch (self, state: &mut #state) -> Perhaps { match self { #disp _ => unreachable!() } } } } } fn write_for_trait ( syms: TokenStream2, exps: TokenStream2, disp: TokenStream2, state: &Path, command: &Path, ) -> TokenStream2 { quote! { impl dizzle::Namespaced for #command { fn namespaced_symbol (state: &T, word: L) -> Perhaps<#command> { if let Some(word) = word.word()? { #syms } Ok(None) } fn namespaced_expression (state: &T, expr: L) -> Perhaps<#command> { if let Some(expr) = expr.expr()? { let head = expr.head()?; let tail = expr.tail()?; #exps } Ok(None) } } impl dizzle::Dispatch for #command { fn dispatch (self, state: &mut T) -> Perhaps { match self { #disp _ => unreachable!() } } } } } }); attribute!(command { #[derive(Debug, Clone)] pub struct CustomAttribute( pub CustomAttributeMeta, pub CustomAttributeItem ); #[derive(Debug, Clone)] pub struct CustomAttributeMeta( pub Path ); #[derive(Debug, Clone)] pub struct CustomAttributeItem( pub ItemEnum, pub HashMap ); impl Parse for CustomAttributeMeta { fn parse (input: ParseStream) -> Result { Ok(Self(input.parse()?)) } } impl Parse for CustomAttributeItem { fn parse (input: ParseStream) -> Result { let mut item: ItemEnum = input.parse()?; let mut branches: HashMap = Default::default(); let mut variants_filtered = item.variants.clone(); variants_filtered.clear(); for variant in item.variants.iter_mut() { let Variant { attrs, ident, fields, discriminant: _ } = variant; let mut attrs_filtered = attrs.clone(); attrs_filtered.clear(); for attr in attrs.iter() { if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta && path == &Path::from(Ident::new("command", Span::call_site())) && let Ok(handler) = syn::parse2::(tokens.clone()) { branches.insert(ident.clone(), (fields.clone(), handler)); } else { attrs_filtered.push(attr.clone()); } } *attrs = attrs_filtered; variants_filtered.push(variant.clone()) } item.variants = variants_filtered; Ok(Self(item, branches)) } } impl ToTokens for CustomAttribute { fn to_tokens (&self, out: &mut TokenStream2) { let Self( CustomAttributeMeta(state), CustomAttributeItem(item, branches) ) = self; let ident = &item.ident; let mut body = quote! {}; for (variant, (fields, handler)) in branches.iter() { let (handler, wrapper) = if let Expr::Path(ExprPath { path, .. }) = handler { (path, None) } else if let Expr::Binary(ExprBinary { op: BinOp::BitOr(_), left, right, .. }) = handler && let Expr::Path(ExprPath { path: handler, .. }) = &**left && let Expr::Path(ExprPath { path: wrapper, .. }) = &**right { (handler, Some(wrapper)) } else { panic!() }; match fields { Fields::Named(_fields) => todo!("named command fields"), Fields::Unnamed(fields) => { let mut params = quote! {}; let mut values = quote! {}; for (index, _field) in fields.unnamed.iter().enumerate() { let name = Ident::new(&format!("arg{index}"), Span::call_site()); append(&mut params, quote! { #name, }); append(&mut values, quote! { #name, }); } let invocation = if let Some(wrapper) = wrapper { quote! { Ok(Some(#wrapper(#handler(state, #values)))) } } else { quote! { #handler(state, #values) } }; append(&mut body, quote! { #ident::#variant (#params) => #invocation, }); }, _ => { append(&mut body, quote! { #ident::#variant => #handler(state), }); } } } append(out, quote! { #item impl dizzle::Act<#state> for #ident { fn act (&self, state: &mut #state) -> Perhaps { match self { #body _ => unreachable!() } } } }) } } }); attribute!(keyword { #[derive(Debug, Clone)] pub struct CustomAttribute( pub CustomAttributeMeta, pub CustomAttributeItem ); #[derive(Debug, Clone)] pub struct CustomAttributeMeta( pub Path ); #[derive(Debug, Clone)] pub struct CustomAttributeItem( pub ItemEnum, pub BTreeMap)>> ); impl Parse for CustomAttributeMeta { fn parse (input: ParseStream) -> Result { Ok(Self(input.parse()?)) } } impl Parse for CustomAttributeItem { fn parse (input: ParseStream) -> Result { let mut item: ItemEnum = input.parse()?; for Variant { attrs, ident: _, fields: _, discriminant: _ } in item.variants.iter_mut() { attrs.retain(|attr|if let syn::Meta::List(MetaList { ref path, tokens: ref _tokens, .. }) = attr.meta && path == &Path::from(Ident::new( "keyword", Span::call_site() )) { // TODO false } else { true }); } Ok(Self(item, Default::default())) } } impl ToTokens for CustomAttribute { fn to_tokens (&self, out: &mut TokenStream2) { let Self( CustomAttributeMeta(state), CustomAttributeItem(item, _variants) ) = self; let ident = &item.ident; let body = quote! {}; append(out, quote! { #item impl Namespace<#ident> for #state { def_namespace_symbols!(|_state: #state| -> #ident { #body, }); } }) } } }); fn write (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); out.into() } fn append (out: &mut TokenStream2, quote: TokenStream2) { for token in quote { out.append(token); } }