diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 38c8b15..82cb6c8 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(str_as_str)] + extern crate proc_macro; pub(crate) use std::collections::{BTreeMap, BTreeSet}; @@ -27,7 +29,7 @@ mod proc_command; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; - write_macro(ViewDef( + write(ViewDef( parse_macro_input!(meta as ViewMeta), parse_macro_input!(item as ViewImpl), )) @@ -36,7 +38,7 @@ pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { use self::proc_expose::{ExposeDef, ExposeMeta, ExposeImpl}; - write_macro(ExposeDef( + write(ExposeDef( parse_macro_input!(meta as ExposeMeta), parse_macro_input!(item as ExposeImpl), )) @@ -45,14 +47,28 @@ 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( + write(CommandDef( parse_macro_input!(meta as CommandMeta), parse_macro_input!(item as CommandImpl), )) } -fn write_macro (t: T) -> TokenStream { +pub(crate) fn write (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); out.into() } + +pub(crate) fn write_quote (quote: TokenStream2) -> TokenStream2 { + let mut out = TokenStream2::new(); + for token in quote { + out.append(token); + } + out +} + +pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { + for token in quote { + out.append(token); + } +} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 8b16dec..8ad7776 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -4,73 +4,79 @@ use crate::*; pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); #[derive(Debug, Clone)] -pub(crate) struct CommandMeta { - target: Ident, -} +pub(crate) struct CommandMeta(Ident); #[derive(Debug, Clone)] pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); #[derive(Debug, Clone)] -struct CommandVariant(Ident, Vec); +struct CommandArm(Ident, Vec, ReturnType); #[derive(Debug, Clone)] -struct CommandArm(Arc, Ident, Vec, ReturnType); +struct CommandVariant(Ident, Vec); impl Parse for CommandMeta { fn parse (input: ParseStream) -> Result { - Ok(Self { - target: input.parse::()?, - }) + Ok(Self(input.parse::()?)) } } impl Parse for CommandImpl { fn parse (input: ParseStream) -> Result { - let block = input.parse::()?; + let block = input.parse::()?; + let exposed = Self::collect(&block.items).map_err(|e|input.error(e))?; + Ok(Self(block, exposed)) + } +} + +impl CommandImpl { + fn collect (items: &Vec) + -> std::result::Result, CommandArm>, String> + { 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(); + for item in items.iter() { + if let ImplItem::Fn( + ImplItemFn { sig: Signature { ident, inputs, output, .. }, .. } + ) = item { + let key = CommandArm::ident_to_key(&ident); if exposed.contains_key(&key) { - return Err(input.error(format!("already defined: {ident}"))); + return Err(format!("already defined: {ident}")); } exposed.insert(key, CommandArm( - variant, ident.clone(), inputs.iter().map(|x|x.clone()).collect(), output.clone(), )); } } - Ok(Self(block, exposed)) + Ok(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)* + let Self(CommandMeta(target), CommandImpl(block, exposed)) = self; + let enumeration = &block.self_ty; + let variants = exposed.values().map(|x|CommandArm::to_enum_variant(x, true, true, false)); + let matchers = exposed.values().map(CommandArm::to_matcher); + let implementations = exposed.values().map(CommandArm::to_implementation); + write_quote_to(out, quote! { + /// Generated by [tengri_proc]. + pub enum #enumeration { + #(#variants)* } + #block + /// Generated by [tengri_proc]. + impl<'a> TryFromDsl<'a, #target> for #enumeration { + fn try_from_expr (state: &#target, iter: TokenIter) -> Option { + let mut iter = iter.clone(); + match iter.next() { + #(#matchers)* + _ => None + } + } + } + /// Generated by [tengri_proc]. impl Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { @@ -78,12 +84,107 @@ impl ToTokens for CommandDef { } } } - } { - out.append(token) + }); + if exposed.len() > 0 { + //panic!("\n{}", quote! {#out}); } } } +impl CommandArm { + fn to_key (&self) -> Arc { + Self::ident_to_key(&self.0) + } + fn to_enum_variant_ident (&self) -> Ident { + Ident::new(&Self::ident_to_enum_variant(&self.0), Span::call_site()) + } + fn ident_to_key (ident: &Ident) -> Arc { + format!("{}", AsKebabCase(format!("{ident}"))).into() + } + fn ident_to_enum_variant (ident: &Ident) -> Arc { + format!("{}", AsUpperCamelCase(format!("{ident}"))).into() + } + fn to_enum_variant (&self, with_types: bool, trailing_comma: bool, with_values: bool) -> TokenStream2 { + let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); + let ident = &self.0; + if self.1.len() > 2 { + out.append(Group::new(Delimiter::Brace, { + let mut out = TokenStream2::new(); + for arg in self.1.iter().skip(2) { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + write_quote_to(&mut out, quote! { #pat }); + if with_types && with_values { + unreachable!(); + } + if with_types { + write_quote_to(&mut out, quote! { : #ty }); + } + if with_values { + let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote_to(&mut out, quote! { + : Context::get(state, &iter.next().expect(#take_err).value) + }); + } + } else { + unreachable!("only typed args should be present at this position") + } + } + out + })); + } + if trailing_comma { + out.append(Punct::new(',', Alone)); + } + out + } + fn to_matcher (&self) -> TokenStream2 { + let mut out = TokenStream2::new(); + let key = LitStr::new(&self.to_key(), Span::call_site()); + let ident = &self.0; + let take_args = self.1.iter().skip(2).map(|arg|{ + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote(quote! { + let #pat: #ty = Context::<#ty>::get( + state, + &iter.next().expect(#take_err).value + ); + }) + } else { + unreachable!("only typed args should be present at this position") + } + }).collect::>(); + let variant = Self::ident_to_enum_variant(&self.0); + let variant = self.to_enum_variant(false, false, true); + write_quote(quote! { + Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { + let mut iter = iter.clone(); + //#(#take_args)* + //let rest = iter; // TODO + Some(Self::#variant) + }, + }) + } + fn to_implementation (&self) -> TokenStream2 { + let ident = &self.0; + let variant = self.to_enum_variant(false, false, false); + let mut give_rest = write_quote(quote! { }); + let give_args = self.1.iter().skip(2).map(|arg|{ + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + //let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", + //quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote(quote! { #pat, }) + } else { + unreachable!("only typed args should be present at this position") + } + }).collect::>(); + write_quote(quote! { Self::#variant => self.#ident(state, #(#give_args)* #give_rest), }) + } +} + impl ToTokens for CommandVariant { fn to_tokens (&self, out: &mut TokenStream2) { let Self(ident, args) = self; @@ -92,9 +193,7 @@ impl ToTokens for CommandVariant { 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 { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { out.append(LitStr::new( &format!("{}", quote! { #ty }), Span::call_site() @@ -110,34 +209,30 @@ impl ToTokens for CommandVariant { 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())); + let Self(ident, args, returnType) = self; + for ident in ["tengri", "dsl", "Value", "Sym"].iter() { + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new(ident, Span::call_site())); + } out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); 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(Ident::new("self", Span::call_site())); + out.append(Punct::new('.', Alone)); + out.append(ident.clone()); 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.append(LitStr::new(&self.to_key(), Span::call_site()).token()); + out.append(Punct::new(',', Alone)); } out }));