inverse dispatch: #[commands]
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
i do not exist 2026-08-10 00:21:40 +03:00
parent ae496987c8
commit 7fcab73b04
12 changed files with 811 additions and 559 deletions

54
proc/Cargo.lock generated Normal file
View file

@ -0,0 +1,54 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "case"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tek_proc"
version = "0.1.0"
dependencies = [
"case",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"

View file

@ -6,6 +6,7 @@ edition = "2024"
[lib]
proc-macro = true
[dependencies]
case = "1.0.0"
proc-macro2 = "1.0.106"
quote = "1.0.46"
syn = { version = "2.0.119", features = ["full", "extra-traits"] }

View file

@ -3,8 +3,12 @@ use proc_macro2::{TokenStream as TokenStream2, Span};
use quote::{quote, ToTokens, TokenStreamExt};
use std::collections::{HashMap, BTreeMap};
use syn::{
Path, ItemEnum, Ident, Type, Variant, MetaList, Expr, ExprPath, ExprBinary, Fields, BinOp,
parse::{ParseStream, Parse, Result}
Error, Path, Lit, Ident, Variant, Fields, BinOp,
Expr, ExprPath, ExprBinary, ExprAssign, ExprLit,
ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature,
MetaList, Type, TypePath, FnArg, PatType,
parse::{ParseStream, Parse, Result},
spanned::Spanned
};
macro_rules! attribute {
@ -22,16 +26,114 @@ macro_rules! attribute {
}
}
attribute!(command {
#[derive(Debug, Clone)] pub struct Def(
pub Meta, pub Item
);
#[derive(Debug, Clone)] pub struct Meta(
pub Path
);
attribute!(commands {
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
#[derive(Debug, Clone)] pub struct Meta(pub Path, pub Lit);
#[derive(Debug, Clone)] pub struct Item(
pub ItemEnum, pub HashMap<Ident, (Fields, Expr)>
pub Path, pub ItemImpl, pub HashMap<Ident, (Ident, Vec<FnArg>, Lit)>
);
impl Parse for Meta {
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, .. }) = &**right
{
Ok(Self(path.clone(), lit.clone()))
} else {
Err(Error::new(meta.span(), format!(
"must be: #[tek_proc::commands(Struct = \"struct\")], got: {meta:?}"
)))
}
}
}
impl Parse for Item {
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>, Lit)> = 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, .. }) = &**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))
}
}
impl ToTokens for Def {
fn to_tokens (&self, out: &mut TokenStream2) {
let Self(Meta(command, namespace), Item(ident, item, items)) = self;
let mut variants = quote! {};
let mut dispatch = quote! {};
for (ident, (variant, inputs, keyword)) in items.iter() {
let mut params = quote! {};
let mut args = quote! {};
let mut has_args = false;
for arg in inputs.iter() {
match arg {
FnArg::Receiver(_) => {},
FnArg::Typed(PatType { pat, ty, .. }) => {
has_args = true;
append(&mut params, quote! { #pat: #ty, });
append(&mut args, quote! { #pat, })
}
}
}
if has_args {
params = quote! { { #params } };
args = quote! { { #args } };
}
append(&mut variants, quote! { #variant #params, });
append(&mut dispatch, quote! { #command::#variant #args => { todo!() }, });
}
append(out, quote! {
#item
pub enum #command { #variants }
impl dizzle::Act<#ident> for #command {
fn act (&self, state: &mut #ident) -> Perhaps<Self> {
match self {
#dispatch
_ => unreachable!()
}
}
}
})
}
}
});
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 {
fn parse (input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))