use crate::*; #[derive(Debug, Clone)] pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); #[derive(Debug, Clone)] pub(crate) struct CommandMeta(TypePath); #[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(state), CommandImpl(block, exposed)) = self; let command_enum = &block.self_ty; let variants = exposed.values().map(|arm|{ let mut out = TokenStream2::new(); out.append(arm.to_enum_variant_ident()); //let ident = &arm.0; if arm.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, ty) in arm.args() { write_quote_to(&mut out, quote! { #arg : #ty , }); } out })); } out.append(Punct::new(',', Alone)); out }); let _matchers = exposed.values().map(|arm|{ let key = LitStr::new(&arm.to_key(), Span::call_site()); let variant = { let mut out = TokenStream2::new(); out.append(arm.to_enum_variant_ident()); let _ident = &arm.0; if arm.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, _ty) in arm.args() { write_quote_to(&mut out, quote! { #arg: Take::take_or_fail(self, words)?, }); } out })); } out }; write_quote(quote! { Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { let mut words = words.clone(); Some(#command_enum::#variant) }, //Some(::tengri::dsl::Token { //value: ::tengri::dsl::Value::Key(#key), .. //}) => { //let mut iter = iter.clone(); Some(#command_enum::#variant) //}, }) }); let implementations = exposed.values().map(|arm|{ let ident = &arm.0; let variant = { let mut out = TokenStream2::new(); out.append(arm.to_enum_variant_ident()); //let ident = &arm.0; if arm.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, _ty) in arm.args() { write_quote_to(&mut out, quote! { #arg , }); } out })); } out }; let give_rest = write_quote(quote! { /*TODO*/ }); let give_args = arm.args() .map(|(arg, _ty)|write_quote(quote! { #arg, })) .collect::>(); write_quote(quote! { #command_enum::#variant => #command_enum::#ident(state, #(#give_args)* #give_rest), }) }); write_quote_to(out, quote! { /// Generated by [tengri_proc]. #[derive(Clone, Debug)] pub enum #command_enum { #(#variants)* } /// Not generated by [tengri_proc]. #block /// Generated by [tengri_proc::command]. /// /// Means [#command_enum] is now a [Command] over [#state]. /// Instances of [#command_enum] can be consumed by a /// mutable pointer to [#state], invoking predefined operations /// and optionally returning undo history data. impl ::tengri::input::Command<#state> for #command_enum { fn execute (self, state: &mut #state) -> Perhaps { match self { #(#implementations)* } } } /// Generated by [tengri_proc::command]. impl ::tengri::dsl::Take<#state> for #command_enum { fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { let mut words = words.clone(); let token = words.next(); todo!()//Ok(match token { #(#matchers)* _ => None }) } } }); //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 } }