use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::{quote, ToTokens}; use syn::{LitInt, LitStr, ReturnType, Token, Type}; use syn::token::{Brace, Bracket, Paren, Semi}; pub struct ProtoMethodData{ pub id: LitInt, pub name: Ident, pub parameters: Vec<(Ident, Type)>, pub ret_val: ReturnType, } /// This is a representation of the code generated by `rmc_proto` it serves to split the logic of /// acquiring data from the actual generation to tidy up the process into first getting then /// generating. /// /// Use the [`ToTokens`] trait to generate the actual code. pub struct RmcProtocolData{ pub has_returns: bool, pub id: LitInt, pub name: Ident, pub methods: Vec } impl RmcProtocolData{ fn generate_raw_trait(&self, tokens: &mut TokenStream){ let Self{ has_returns, name, id, methods } = self; // this gives us the name which the identifier of the corresponding Raw trait let raw_name = Ident::new(&format!("Raw{}", name), name.span()); // boilerplate tokens which all raw traits need quote!{ #[doc(hidden)] pub trait #raw_name: #name }.to_tokens(tokens); // generate the body of the raw protocol trait Brace::default().surround(tokens, |tokens|{ //generate each raw method for method in methods{ let ProtoMethodData { name, parameters, .. } = method; let raw_name = Ident::new(&format!("raw_{}", name), name.span()); quote!{ async fn #raw_name }.to_tokens(tokens); Paren::default().surround(tokens, |tokens|{ quote!{ &self, data: ::std::vec::Vec }.to_tokens(tokens); }); if self.has_returns { quote! { -> ::core::result::Result, ErrorCode> }.to_tokens(tokens); } Brace::default().surround(tokens, |tokens|{ quote! { let mut cursor = ::std::io::Cursor::new(data); }.to_tokens(tokens); for (param_name, param_type) in parameters{ quote!{ let Ok(#param_name) = <#param_type as rust_nex::rmc::structures::RmcSerialize>::deserialize( &mut cursor ) else }.to_tokens(tokens); let error_msg = LitStr::new(&format!("an error occurred whilest deserializing {}", param_name), Span::call_site()); if self.has_returns { quote! { { log::error!(#error_msg); return Err(rust_nex::rmc::response::ErrorCode::Core_InvalidArgument); }; }.to_tokens(tokens) } else { quote! { { log::error!(#error_msg); return; }; }.to_tokens(tokens) } } quote!{ let retval = self.#name }.to_tokens(tokens); Paren::default().surround(tokens, |tokens|{ for (paren_name, _) in parameters{ quote!{#paren_name,}.to_tokens(tokens); } }); quote!{ .await; }.to_tokens(tokens); if *has_returns{ quote!{ let retval = retval?; let mut vec = Vec::new(); rust_nex::rmc::structures::RmcSerialize::serialize(&retval, &mut vec).ok(); Ok(vec) }.to_tokens(tokens); } }) } quote!{ async fn rmc_call_proto( &self, remote_response_connection: &rust_nex::util::SendingBufferConnection, method_id: u32, call_id: u32, data: Vec, ) }.to_tokens(tokens); Brace::default().surround(tokens, |tokens|{ quote! { let ret = match method_id }.to_tokens(tokens); Brace::default().surround(tokens, |tokens|{ for method in methods{ let ProtoMethodData{ id, name, .. } = method; let raw_name = Ident::new(&format!("raw_{}", name), name.span()); quote!{ #id => self.#raw_name(data).await, }.to_tokens(tokens); } quote!{ v => }.to_tokens(tokens); Brace::default().surround(tokens, |tokens|{ quote!{ log::error!("(protocol {})unimplemented method id called on protocol: {}", #id, v); }.to_tokens(tokens); if self.has_returns { quote! { Err(rust_nex::rmc::response::ErrorCode::Core_NotImplemented) }.to_tokens(tokens); } }); }); Semi::default().to_tokens(tokens); if *has_returns{ quote!{ rust_nex::rmc::response::send_result( remote_response_connection, ret, #id, method_id, call_id, ).await }.to_tokens(tokens); } }); }); quote!{ impl #raw_name for T{} }.to_tokens(tokens); } fn generate_raw_remote_trait(&self, tokens: &mut TokenStream) { let Self { has_returns, name, id: proto_id, methods, .. } = self; // this gives us the name which the identifier of the corresponding Raw trait let remote_name = Ident::new(&format!("Remote{}", name), name.span()); // boilerplate tokens which all raw traits need quote!{ #[doc(hidden)] pub trait #remote_name: rust_nex::rmc::protocols::HasRmcConnection }.to_tokens(tokens); // generate the body of the raw protocol trait Brace::default().surround(tokens, |tokens|{ //generate each raw method for method in methods{ let ProtoMethodData { name, parameters, ret_val, id: method_id, .. } = method; quote!{ async fn #name }.to_tokens(tokens); Paren::default().surround(tokens, |tokens|{ quote!{ &self, }.to_tokens(tokens); for (param_ident, param_type) in parameters{ quote!{ #param_ident: #param_type, }.to_tokens(tokens); } }); quote!{ #ret_val }.to_tokens(tokens); Brace::default().surround(tokens, |tokens|{ quote! { let mut send_data = Vec::new(); let mut cursor = ::std::io::Cursor::new(&mut send_data); }.to_tokens(tokens); for (param_name, param_type) in parameters{ quote!{ rust_nex::result::ResultExtension::display_err_or_some( <#param_type as rust_nex::rmc::structures::RmcSerialize>::serialize( &#param_name, &mut cursor ) ).ok_or(rust_nex::rmc::response::ErrorCode::Core_InvalidArgument) }.to_tokens(tokens); if self.has_returns { quote! { ?; }.to_tokens(tokens) } else { quote! { ; }.to_tokens(tokens) } } quote!{ let call_id = rand::random(); let message = rust_nex::rmc::message::RMCMessage{ call_id, method_id: #method_id, protocol_id: #proto_id, rest_of_data: send_data }; let rmc_conn = ::get_connection(self); }.to_tokens(tokens); if *has_returns{ quote!{ rust_nex::result::ResultExtension::display_err_or_some( rmc_conn.make_raw_call(&message).await ).ok_or(rust_nex::rmc::response::ErrorCode::Core_Exception) }.to_tokens(tokens); } else { quote!{ rust_nex::result::ResultExtension::display_err_or_some( rmc_conn.make_raw_call_no_response(&message).await ); }.to_tokens(tokens); } }) } }); } fn generate_raw_info(&self, tokens: &mut TokenStream){ let Self{ name, id, .. } = self; let raw_info_name = Ident::new(&format!("Raw{}Info", name), Span::call_site()); quote!{ #[doc(hidden)] pub struct #raw_info_name; impl #raw_info_name { pub const PROTOCOL_ID: u16 = #id; } }.to_tokens(tokens); } } impl ToTokens for RmcProtocolData{ fn to_tokens(&self, tokens: &mut TokenStream) { self.generate_raw_trait(tokens); self.generate_raw_info(tokens); self.generate_raw_remote_trait(tokens); } }