implement controller traits
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
i do not exist 2026-08-11 17:41:51 +03:00
parent a4931d8e4f
commit def7a1b210
17 changed files with 2209 additions and 1790 deletions

View file

@ -5,18 +5,20 @@ use std::collections::{HashMap, BTreeMap};
use syn::{
Error, Path, Ident, Variant, Fields, BinOp,
Expr, ExprPath, ExprBinary, ExprAssign, ExprLit, Lit, LitStr,
ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature,
MetaList, Type, TypePath, FnArg, PatType,
Item, ItemEnum, ItemImpl, ImplItem, ImplItemFn, ItemTrait, TraitItem, TraitItemFn,
Signature, MetaList, Type, TypePath, FnArg, PatType, Attribute,
parse::{ParseStream, Parse, Result},
spanned::Spanned
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::Def(
syn::parse_macro_input!(meta as self::$name::Meta),
syn::parse_macro_input!(item as self::$name::Item),
write(self::$name::CustomAttribute(
syn::parse_macro_input!(meta as self::$name::CustomAttributeMeta),
syn::parse_macro_input!(item as self::$name::CustomAttributeItem),
))
}
mod $name {
@ -27,70 +29,139 @@ macro_rules! attribute {
}
attribute!(commands {
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
#[derive(Debug, Clone)] pub struct Meta(pub Path, pub LitStr);
#[derive(Debug, Clone)] pub struct Item(
pub Path, pub ItemImpl, pub HashMap<Ident, (Ident, Vec<FnArg>, LitStr)>
#[derive(Debug, Clone)] pub struct CustomAttribute(
pub CustomAttributeMeta,
pub CustomAttributeItem,
);
#[derive(Debug, Clone)] pub struct CustomAttributeMeta(
pub Path,
pub Option<LitStr>
);
#[derive(Debug, Clone)] pub struct CustomAttributeItem(
pub Path,
pub CustomAttributeItemDispatch,
pub Item
);
impl Parse for Meta {
pub type CustomAttributeItemDispatch = HashMap<Ident, (Ident, Vec<FnArg>, LitStr)>;
impl Parse for CustomAttributeMeta {
/// Parse contents of `#[command(...)]` attribute tag.
fn parse (input: ParseStream) -> Result<Self> {
let meta = input.parse()?;
if let Expr::Assign(ExprAssign { ref left, ref right, .. }) = meta
&& let Expr::Path(ExprPath { path, .. }) = &**left
&& let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right
{
Ok(Self(path.clone(), lit.clone()))
} else {
Err(Error::new(meta.span(), format!(
"must be: #[tek_proc::commands(Struct = \"struct\")], got: {meta:?}"
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 Item {
impl Parse for CustomAttributeItem {
/// Parse contents of `trait` or `impl` block annotated with `#[command(...)]`
fn parse (input: ParseStream) -> Result<Self> {
let mut item: ItemImpl = input.parse()?;
let path = if let Type::Path(TypePath { path, .. }) = &*item.self_ty {
path
} else {
return Err(Error::new(item.self_ty.span(), format!("must be path to struct")))
};
let mut dispatch: HashMap<Ident, (Ident, Vec<FnArg>, LitStr)> = Default::default();
for item in item.items.iter_mut() {
if let ImplItem::Fn(ImplItemFn {
attrs, sig: Signature { ident, inputs, .. }, ..
}) = item {
*attrs = 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::<Expr>(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();
}
}
Ok(Self(path.clone(), item, dispatch))
let mut item: Item = input.parse()?;
Ok(Self(
parse_custom_attribute_item_path(&item)?,
parse_custom_attribute_item_dispatch(&mut item)?,
item,
))
}
}
impl ToTokens for Def {
fn parse_custom_attribute_item_path (item: &Item) -> Result<Path> {
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<CustomAttributeItemDispatch>
{
let mut dispatch: CustomAttributeItemDispatch = Default::default();
let mut dispatch_attrs = |
attrs: &Vec<Attribute>, ident: &Ident, inputs: &Punctuated<FnArg, Comma>
| {
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::<Expr>(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(Meta(command, namespace), Item(state, item, items)) = self;
let Self(
CustomAttributeMeta(command, namespace),
CustomAttributeItem(state, items, item),
) = self;
let mut variants = quote! {};
let mut dispatch = quote! {};
let mut keywords = quote! {};
@ -116,7 +187,11 @@ attribute!(commands {
} else {
quote! { #command::#variant => state.#ident(), }
});
let keyword = format!("{}/{}", namespace.value(), keyword.value());
let keyword = if let Some(namespace) = namespace {
format!("{}/{}", namespace.value(), keyword.value())
} else {
keyword.value()
};
if has_args {
append(&mut expressions, quote! {
#keyword (#typed) => { #command::#variant { #params } },
@ -127,24 +202,40 @@ attribute!(commands {
});
}
}
append(out, quote! {
#[derive(Debug, Clone)] pub enum #command { #variants }
impl<'a> dizzle::Namespace<'a, #command> for #state {
symbols!('a |state| -> #command {
#keywords
});
expressions!('a |state| -> #command {
#expressions
});
}
impl #command {
pub fn act (self, state: &mut #state) -> Perhaps<Self> {
match self { #dispatch _ => unreachable!() }
let impls = match item {
Item::Impl(ItemImpl { generics, .. }) => {
let lts = Punctuated::<_, Comma>::from_iter(generics.lifetimes());
let tys = Punctuated::<_, Comma>::from_iter(generics.type_params());
let cns = Punctuated::<_, Comma>::from_iter(generics.const_params());
quote! {
impl<'n, #tys> dizzle::Namespace<'n, #command> for #state {
symbols!('n |state: Self| -> #command { #keywords });
expressions!('n |state: Self| -> #command { #expressions });
}
impl #generics #command {
pub fn act (self, state: &mut #state) -> Perhaps<Self> {
match self { #dispatch _ => unreachable!() }
}
}
}
}
},
Item::Trait { .. } => quote! {
impl<'n, T: #state + 'n> dizzle::Namespaced<'n, T> for #command {
symbols!('n |state: T| -> Self { #keywords });
expressions!('n |state: T| -> Self { #expressions });
}
impl #command {
pub fn act <'n, T: #state + 'n> (self, state: &mut T) -> Perhaps<Self> {
match self { #dispatch _ => unreachable!() }
}
}
},
_ => panic!("trait or inherent impl needed for #[commands]")
};
append(out, quote! {
/// Command variants
#[derive(Debug, Clone)] pub enum #command { #variants }
#impls
#item
})
}
@ -152,15 +243,22 @@ attribute!(commands {
});
attribute!(command {
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
#[derive(Debug, Clone)] pub struct Meta(pub Path);
#[derive(Debug, Clone)] pub struct Item(pub ItemEnum, pub HashMap<Ident, (Fields, Expr)>);
impl Parse for Meta {
#[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<Ident, (Fields, Expr)>
);
impl Parse for CustomAttributeMeta {
fn parse (input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))
}
}
impl Parse for Item {
impl Parse for CustomAttributeItem {
fn parse (input: ParseStream) -> Result<Self> {
let mut item: ItemEnum = input.parse()?;
let mut branches: HashMap<Ident, (Fields, Expr)> = Default::default();
@ -187,9 +285,12 @@ attribute!(command {
Ok(Self(item, branches))
}
}
impl ToTokens for Def {
impl ToTokens for CustomAttribute {
fn to_tokens (&self, out: &mut TokenStream2) {
let Self(Meta(state), Item(item, branches)) = self;
let Self(
CustomAttributeMeta(state),
CustomAttributeItem(item, branches)
) = self;
let ident = &item.ident;
let mut body = quote! {};
for (variant, (fields, handler)) in branches.iter() {
@ -249,21 +350,21 @@ attribute!(command {
});
attribute!(keyword {
#[derive(Debug, Clone)] pub struct Def(
pub Meta, pub Item
#[derive(Debug, Clone)] pub struct CustomAttribute(
pub CustomAttributeMeta, pub CustomAttributeItem
);
#[derive(Debug, Clone)] pub struct Meta(
#[derive(Debug, Clone)] pub struct CustomAttributeMeta(
pub Path
);
#[derive(Debug, Clone)] pub struct Item(
#[derive(Debug, Clone)] pub struct CustomAttributeItem(
pub ItemEnum, pub BTreeMap<Ident, Vec<(Literal, BTreeMap<Ident, Type>)>>
);
impl Parse for Meta {
impl Parse for CustomAttributeMeta {
fn parse (input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))
}
}
impl Parse for Item {
impl Parse for CustomAttributeItem {
fn parse (input: ParseStream) -> Result<Self> {
let mut item: ItemEnum = input.parse()?;
for Variant { attrs, ident, fields, discriminant } in item.variants.iter_mut() {
@ -281,18 +382,19 @@ attribute!(keyword {
Ok(Self(item, Default::default()))
}
}
impl ToTokens for Def {
impl ToTokens for CustomAttribute {
fn to_tokens (&self, out: &mut TokenStream2) {
let Self(Meta(state), Item(item, variants)) = self;
let Self(
CustomAttributeMeta(state),
CustomAttributeItem(item, variants)
) = self;
let ident = &item.ident;
let body = quote! {};
append(out, quote! {
#item
impl<'a> Namespace<'a, #ident> for #state {
symbols!('a |_state| -> #ident {
#body,
});
impl<'n> Namespace<'n, #ident> for #state {
symbols!('n |_state: #state| -> #ident { #body, });
}
})
}