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, #[allow(unused)] ReturnType); 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<'state> ::tengri::dsl::TryFromDsl<'state, #target> for #enumeration { fn try_from_expr <'source: 'state> ( state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> ) -> Option { let mut iter = iter.clone(); match iter.next() { #(#matchers)* _ => None } } } /// Generated by [tengri_proc]. impl<'state> ::tengri::dsl::Context<'state, #enumeration> for #target { fn get <'source> (&self, iter: &mut ::tengri::dsl::TokenIter<'source>) -> Option<#enumeration> { #enumeration::try_from_expr(self, iter) } } /// Generated by [tengri_proc]. impl ::tengri::input::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 has_args (&self) -> bool { self.1.len() > 1 } fn args (&self) -> impl Iterator)> { self.1.iter().skip(1).filter_map(|arg|if let FnArg::Typed(PatType { ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. }) = arg { Some((arg, ty)) } else { unreachable!("only typed args should be present at this position"); }) } 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.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, ty) in self.args() { write_quote_to(&mut out, quote! { #arg : #ty , }); } 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.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, ty) in self.args() { //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 for \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { #arg: ::tengri::dsl::Context::get(state, &mut iter).expect(#give_err), }); } 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.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, _ty) in self.args() { write_quote_to(&mut out, quote! { #arg , }); } out })); } out } fn to_matcher (&self) -> TokenStream2 { let key = LitStr::new(&self.to_key(), Span::call_site()); let variant = self.to_enum_variant_bind(); let pattern = quote! { Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) }; write_quote(quote! { #pattern => { let mut iter = iter.clone(); Some(Self::#variant) }, }) } fn to_implementation (&self) -> TokenStream2 { let ident = &self.0; let variant = self.to_enum_variant_unbind(); let give_rest = write_quote(quote! { /*TODO*/ }); let give_args = self.args() .map(|(arg, _ty)|write_quote(quote! { #arg, })) .collect::>(); write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), }) } }