use crate::*; #[derive(Debug, Clone)] pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); #[derive(Debug, Clone)] pub(crate) struct CommandMeta(Ident); #[derive(Debug, Clone)] pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); #[derive(Debug, Clone)] struct CommandArm(Ident, Vec, ReturnType); #[derive(Debug, Clone)] struct CommandVariant(Ident, Vec); impl Parse for CommandMeta { fn parse (input: ParseStream) -> Result { Ok(Self(input.parse::()?)) } } impl Parse for CommandImpl { fn parse (input: ParseStream) -> Result { 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 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(format!("already defined: {ident}")); } exposed.insert(key, CommandArm( ident.clone(), inputs.iter().map(|x|x.clone()).collect(), output.clone(), )); } } 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 variants = exposed.values().map(|x|x.to_enum_variant_def()); 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]. #[derive(Clone, Debug)] 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 { #(#implementations)* } } } }); //if exposed.len() > 0 { //panic!("{:#?}", block.self_ty); //if let Type::Path(ref path) = *block.self_ty { //if path.path.segments.get(0).unwrap().ident == "TekCommand" { //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_def (&self) -> 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 { ty, pat: box Pat::Ident(PatIdent { ident, .. }), .. }) = arg { write_quote_to(&mut out, quote! { #ident : #ty , }); } else { unreachable!("only typed args should be present at this position") } } out })); } out.append(Punct::new(',', Alone)); out } fn to_enum_variant_bind (&self) -> 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 { ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. }) = arg { let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { #arg : Context::get(state, &iter.next().expect(#take_err).value) .expect(#give_err) , }); } else { unreachable!("only typed args should be present at this position") } } out })); } out } fn to_enum_variant_unbind (&self) -> 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 { ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. }) = arg { write_quote_to(&mut out, quote! { #arg , }); } else { unreachable!("only typed args should be present at this position") } } out })); } 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 { ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. }) = arg { let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote(quote! { let #ident: #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_bind(); 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_unbind(); let mut give_rest = write_quote(quote! { /*TODO*/ }); let give_args = self.1.iter().skip(2).map(|arg|{ if let FnArg::Typed(PatType { ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. }) = arg { //let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", //quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); write_quote(quote! { #arg, }) } 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; 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(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 })); out.append(Punct::new('=', Joint)); out.append(Punct::new('>', Alone)); 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(&self.to_key(), Span::call_site()).token()); out.append(Punct::new(',', Alone)); } out })); out.append(Punct::new(',', Alone)); } }