implement passable #[command]
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
i do not exist 2026-08-09 09:23:24 +03:00
parent 68236a7210
commit ae496987c8
7 changed files with 458 additions and 172 deletions

189
proc/src/lib.rs Normal file
View file

@ -0,0 +1,189 @@
use proc_macro::{TokenStream, Literal};
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}
};
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),
))
}
mod $name {
use crate::*;
$($body)*
}
}
}
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()?))
}
}
impl Parse for Item {
fn parse (input: ParseStream) -> Result<Self> {
let mut item: ItemEnum = input.parse()?;
let mut branches: HashMap<Ident, (Fields, Expr)> = 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::<Expr>(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 Def {
fn to_tokens (&self, out: &mut TokenStream2) {
let Self(Meta(state), Item(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<Self> {
match self {
#body
_ => unreachable!()
}
}
}
})
}
}
});
attribute!(keyword {
#[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 BTreeMap<Ident, Vec<(Literal, BTreeMap<Ident, Type>)>>
);
impl Parse for Meta {
fn parse (input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))
}
}
impl Parse for Item {
fn parse (input: ParseStream) -> Result<Self> {
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, 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 Def {
fn to_tokens (&self, out: &mut TokenStream2) {
let Self(Meta(state), Item(item, variants)) = self;
let ident = &item.ident;
let body = quote! {};
append(out, quote! {
#item
impl<'a> Namespace<'a, #ident> for #state {
symbols!('a |stte| -> #ident {
#body,
});
}
})
}
}
});
fn write <T: ToTokens> (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);
}
}