From 7df7cb839c14c0e010ce36519c75ffacc0e76c18 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 6 May 2025 21:33:53 +0300 Subject: [PATCH] wip: proc: command macro --- proc/src/lib.rs | 15 +++- proc/src/proc_command.rs | 144 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 proc/src/proc_command.rs diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 16dd8f0..38c8b15 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; pub(crate) use std::collections::{BTreeMap, BTreeSet}; pub(crate) use std::cmp::Ordering; +pub(crate) use std::sync::Arc; pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro2::{ TokenStream as TokenStream2, TokenTree, @@ -11,16 +12,17 @@ pub(crate) use syn::{ parse, parse_macro_input, parse_quote as pq, braced, bracketed, parenthesized, Token, Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, - ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, + ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg, PatType, parse::{Parse, ParseStream, Result}, token::{PathSep, Brace}, punctuated::Punctuated, }; pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; -pub(crate) use heck::AsKebabCase; +pub(crate) use heck::{AsKebabCase, AsUpperCamelCase}; mod proc_view; mod proc_expose; +mod proc_command; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { @@ -40,6 +42,15 @@ pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { )) } +#[proc_macro_attribute] +pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream { + use self::proc_command::{CommandDef, CommandMeta, CommandImpl}; + write_macro(CommandDef( + parse_macro_input!(meta as CommandMeta), + parse_macro_input!(item as CommandImpl), + )) +} + fn write_macro (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs new file mode 100644 index 0000000..ac1e2a9 --- /dev/null +++ b/proc/src/proc_command.rs @@ -0,0 +1,144 @@ +use crate::*; + +#[derive(Debug, Clone)] +pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); + +#[derive(Debug, Clone)] +pub(crate) struct CommandMeta { + target: Ident, +} + +#[derive(Debug, Clone)] +pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); + +#[derive(Debug, Clone)] +struct CommandVariant(Ident, Vec); + +#[derive(Debug, Clone)] +struct CommandArm(Arc, Ident, Vec, ReturnType); + +impl Parse for CommandMeta { + fn parse (input: ParseStream) -> Result { + Ok(Self { + target: input.parse::()?, + }) + } +} + +impl Parse for CommandImpl { + fn parse (input: ParseStream) -> Result { + let block = input.parse::()?; + let mut exposed: BTreeMap, CommandArm> = Default::default(); + for item in block.items.iter() { + if let ImplItem::Fn(ImplItemFn { + sig: Signature { ident, inputs, output, .. }, .. + }) = item { + let key: Arc = + format!("{}", AsKebabCase(format!("{}", &ident))).into(); + let variant: Arc = + format!("{}", AsUpperCamelCase(format!("{}", &ident))).into(); + if exposed.contains_key(&key) { + return Err(input.error(format!("already defined: {ident}"))); + } + exposed.insert(key, CommandArm( + variant, + ident.clone(), + inputs.iter().map(|x|x.clone()).collect(), + output.clone(), + )); + } + } + Ok(Self(block, exposed)) + } +} + +impl ToTokens for CommandDef { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(CommandMeta { target }, CommandImpl(block, exposed)) = self; + let enumeration = &block.self_ty; + let definitions = exposed.values().map(|x|CommandVariant( + x.1.clone(), + x.2.clone(), + )); + let implementations = exposed.values().map(|x|CommandArm( + x.0.clone(), + x.1.clone(), + x.2.clone(), + x.3.clone(), + )); + for token in quote! { + #block + enum #enumeration { + #(#definitions)* + } + impl Command<#target> for #enumeration { + fn execute (self, state: &mut #target) -> Perhaps { + match self { + #(#implementations)* + } + } + } + } { + out.append(token) + } + } +} + +impl ToTokens for CommandVariant { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(ident, args) = self; + out.append(LitStr::new(&format!("{}", ident), Span::call_site()) + .token()); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + for arg in args.iter() { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + out.append(LitStr::new( + &format!("{}", quote! { #ty }), + Span::call_site() + ).token()); + out.append(Punct::new(',', Alone)); + } + } + out + })); + out.append(Punct::new(',', Alone)); + } +} + +impl ToTokens for CommandArm { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(symbol, ident, args, returnType) = self; + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("tengri", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("dsl", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("Value", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("Sym", Span::call_site())); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + for arg in args.iter() { + out.append(LitStr::new(&symbol, Span::call_site()).token()); + } + out + })); + out.append(Punct::new('=', Joint)); + out.append(Punct::new('>', Alone)); + out.append(LitStr::new(&format!("{}", ident), Span::call_site()).token()); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + for arg in args.iter() { + // TODO + //out.append(LitStr::new(&symbol, Span::call_site()).token()); + } + out + })); + out.append(Punct::new(',', Alone)); + } +}