rust-nex/macros/src/protos.rs

334 lines
11 KiB
Rust
Raw Normal View History

2025-03-23 10:54:01 +01:00
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{quote, ToTokens};
2025-05-12 10:28:54 +02:00
use syn::{LitInt, LitStr, ReturnType, Token, Type};
use syn::token::{Brace, Bracket, Paren, Semi};
2025-03-23 10:54:01 +01:00
pub struct ProtoMethodData{
pub id: LitInt,
pub name: Ident,
pub parameters: Vec<(Ident, Type)>,
pub ret_val: ReturnType,
2025-03-23 10:54:01 +01:00
}
/// 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<ProtoMethodData>
}
impl RmcProtocolData{
fn generate_raw_trait(&self, tokens: &mut TokenStream){
2025-03-23 10:54:01 +01:00
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<u8> }.to_tokens(tokens);
});
2025-05-12 10:28:54 +02:00
if self.has_returns {
quote! {
-> ::core::result::Result<Vec<u8>, ErrorCode>
}.to_tokens(tokens);
}
2025-03-23 10:54:01 +01:00
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) =
2025-06-13 10:10:04 +02:00
<#param_type as rust_nex::rmc::structures::RmcSerialize>::deserialize(
2025-03-23 10:54:01 +01:00
&mut cursor
2025-05-12 10:28:54 +02:00
) 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);
2025-06-13 10:10:04 +02:00
return Err(rust_nex::rmc::response::ErrorCode::Core_InvalidArgument);
2025-05-12 10:28:54 +02:00
};
}.to_tokens(tokens)
} else {
quote! {
{
log::error!(#error_msg);
return;
};
}.to_tokens(tokens)
}
2025-03-23 10:54:01 +01:00
}
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();
2025-06-13 10:10:04 +02:00
rust_nex::rmc::structures::RmcSerialize::serialize(&retval, &mut vec).ok();
2025-03-23 10:54:01 +01:00
Ok(vec)
}.to_tokens(tokens);
}
})
}
quote!{
async fn rmc_call_proto(
&self,
2025-06-13 10:10:04 +02:00
remote_response_connection: &rust_nex::util::SendingBufferConnection,
2025-03-23 10:54:01 +01:00
method_id: u32,
call_id: u32,
data: Vec<u8>,
)
}.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());
2025-05-12 10:28:54 +02:00
2025-03-23 10:54:01 +01:00
quote!{
#id => self.#raw_name(data).await,
}.to_tokens(tokens);
}
quote!{
2025-05-12 10:28:54 +02:00
v =>
2025-03-23 10:54:01 +01:00
}.to_tokens(tokens);
2025-05-12 10:28:54 +02:00
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! {
2025-06-13 10:10:04 +02:00
Err(rust_nex::rmc::response::ErrorCode::Core_NotImplemented)
2025-05-12 10:28:54 +02:00
}.to_tokens(tokens);
}
});
2025-03-23 10:54:01 +01:00
});
Semi::default().to_tokens(tokens);
if *has_returns{
quote!{
2025-06-13 10:10:04 +02:00
rust_nex::rmc::response::send_result(
2025-03-23 10:54:01 +01:00
remote_response_connection,
ret,
#id,
method_id,
call_id,
).await
}.to_tokens(tokens);
}
});
});
quote!{
2025-05-12 10:28:54 +02:00
impl<T: #name> #raw_name for T{}
2025-03-23 10:54:01 +01:00
}.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)]
2025-06-13 10:10:04 +02:00
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!{
2025-06-13 10:10:04 +02:00
rust_nex::result::ResultExtension::display_err_or_some(
<#param_type as rust_nex::rmc::structures::RmcSerialize>::serialize(
&#param_name,
&mut cursor
)
2025-06-13 10:10:04 +02:00
).ok_or(rust_nex::rmc::response::ErrorCode::Core_InvalidArgument)
2025-05-12 10:28:54 +02:00
}.to_tokens(tokens);
if self.has_returns {
quote! {
?;
}.to_tokens(tokens)
} else {
quote! {
;
}.to_tokens(tokens)
}
}
quote!{
let call_id = rand::random();
2025-06-13 10:10:04 +02:00
let message = rust_nex::rmc::message::RMCMessage{
call_id,
method_id: #method_id,
protocol_id: #proto_id,
rest_of_data: send_data
};
2025-06-13 10:10:04 +02:00
let rmc_conn = <Self as rust_nex::rmc::protocols::HasRmcConnection>::get_connection(self);
}.to_tokens(tokens);
if *has_returns{
quote!{
2025-06-13 10:10:04 +02:00
rust_nex::result::ResultExtension::display_err_or_some(
rmc_conn.make_raw_call(&message).await
2025-06-13 10:10:04 +02:00
).ok_or(rust_nex::rmc::response::ErrorCode::Core_Exception)
}.to_tokens(tokens);
} else {
quote!{
2025-06-13 10:10:04 +02:00
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;
2025-03-23 10:54:01 +01:00
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);
}
}