feat: stuff happend
This commit is contained in:
parent
b8d2cd7b09
commit
fa37331780
11 changed files with 511 additions and 343 deletions
|
|
@ -1,10 +1,12 @@
|
|||
mod protos;
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro2::{Ident, Literal, Span, TokenTree};
|
||||
use proc_macro::TokenStream;
|
||||
use std::iter::FromIterator;
|
||||
use std::mem;
|
||||
use syn::{parse_macro_input, DeriveInput, Data, PathSegment, TraitItem, FieldsNamed, Fields, Visibility, Type, TypePath, Path, ImplItem, ImplItemConst, Expr, ExprLit, Lit, TypeParamBound, TraitBound, TraitBoundModifier, LitInt, Token, FnArg, Receiver, PatType, Pat, TypeInfer, TypeReference, TraitItemFn, Signature, Block, Stmt, Local, LocalInit, LitStr, PathArguments};
|
||||
use syn::{parse_macro_input, DeriveInput, Data, PathSegment, TraitItem, FieldsNamed, Fields, Visibility, Type, TypePath, Path, ImplItem, ImplItemConst, Expr, ExprLit, Lit, TypeParamBound, TraitBound, TraitBoundModifier, LitInt, Token, FnArg, Receiver, PatType, Pat, TypeInfer, TypeReference, TraitItemFn, Signature, Block, Stmt, Local, LocalInit, LitStr, PathArguments, ReturnType};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::buffer::TokenBuffer;
|
||||
use syn::parse::{Parse, ParseBuffer, ParseStream};
|
||||
|
|
@ -12,6 +14,7 @@ use syn::punctuated::Punctuated;
|
|||
use syn::spanned::Spanned;
|
||||
use syn::token::Comma;
|
||||
use syn::Visibility::Public;
|
||||
use crate::protos::{ProtoMethodData, RmcProtocolData};
|
||||
|
||||
fn self_referece_type() -> Type {
|
||||
Type::Reference(
|
||||
|
|
@ -90,9 +93,6 @@ fn single_ident_path(ident: Ident) -> Path{
|
|||
}
|
||||
|
||||
|
||||
/// Example of user-defined [derive mode macro][1]
|
||||
///
|
||||
/// [1]: https://doc.rust-lang.org/reference/procedural-macros.html#derive-mode-macros
|
||||
#[proc_macro_derive(RmcSerialize, attributes(extends, rmc_struct))]
|
||||
pub fn rmc_serialize(input: TokenStream) -> TokenStream {
|
||||
let derive_input = parse_macro_input!(input as DeriveInput);
|
||||
|
|
@ -245,6 +245,30 @@ pub fn rmc_serialize(input: TokenStream) -> TokenStream {
|
|||
tokens.into()
|
||||
}
|
||||
|
||||
/// Macro to automatically generate code to use a specific trait as an rmc protocol for calling to
|
||||
/// remote objects or accepting incoming remote requests.
|
||||
/// This is needed in order to be able to use this as part of an rmc server interface.
|
||||
///
|
||||
/// The protocol id which is needed to be specified is specified as a parameter to this attribute.
|
||||
///
|
||||
/// You will also need to assign each function inside the trait a method id by using the
|
||||
/// [`macro@method_id`] attribute.
|
||||
///
|
||||
/// You can also specify to have the protocol to be non-returning by adding a second parameter to
|
||||
/// the attribute which is just `NoReturn` e.g. ` #[rmc_proto(1, NoReturn)]`
|
||||
///
|
||||
/// Example
|
||||
/// ```
|
||||
/// // this rmc protocol has protocol id 1
|
||||
/// use macros::rmc_proto;
|
||||
///
|
||||
/// #[rmc_proto(1)]
|
||||
/// trait ExampleProtocol{
|
||||
/// // this defines an rmc method with id 1
|
||||
/// #[rmc_method(1)]
|
||||
/// async fn hello_world_method(&self, name: String) -> Result<String, ErrorCode>;
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn rmc_proto(attr: TokenStream, input: TokenStream) -> TokenStream{
|
||||
|
||||
|
|
@ -259,263 +283,63 @@ pub fn rmc_proto(attr: TokenStream, input: TokenStream) -> TokenStream{
|
|||
i.to_string() == "NoReturn"
|
||||
}));
|
||||
|
||||
let param_err_return = match no_return_data{
|
||||
true => quote!{
|
||||
return;
|
||||
},
|
||||
false => quote!{
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
}
|
||||
};
|
||||
|
||||
let mut input = parse_macro_input!(input as syn::ItemTrait);
|
||||
|
||||
let info_struct_ident = format!("Raw{}Info",input.ident.to_string());
|
||||
let info_struct_ident = Ident::new(&info_struct_ident, input.ident.span());
|
||||
|
||||
let raw_details_struct = syn::ItemStruct{
|
||||
vis: Public(Default::default()),
|
||||
struct_token: Default::default(),
|
||||
fields: Fields::Unit,
|
||||
semi_token: Some(Default::default()),
|
||||
ident: info_struct_ident.clone(),
|
||||
attrs: vec![],
|
||||
generics: Default::default()
|
||||
};
|
||||
|
||||
let raw_details_impl_block = syn::ItemImpl{
|
||||
impl_token: Default::default(),
|
||||
generics: Default::default(),
|
||||
attrs: vec![],
|
||||
brace_token: Default::default(),
|
||||
defaultness: None,
|
||||
trait_: None,
|
||||
self_ty: Box::new(Type::Path(TypePath{
|
||||
qself: None,
|
||||
path: Path{
|
||||
segments: {
|
||||
let mut punc = Punctuated::new();
|
||||
punc.push(PathSegment::from(info_struct_ident));
|
||||
punc
|
||||
},
|
||||
leading_colon: None,
|
||||
}
|
||||
})),
|
||||
unsafety: None,
|
||||
items: vec![
|
||||
ImplItem::Const(
|
||||
ImplItemConst{
|
||||
defaultness: None,
|
||||
semi_token: Default::default(),
|
||||
attrs: vec![],
|
||||
generics: Default::default(),
|
||||
ident: Ident::new("PROTOCOL_ID", Span::call_site()),
|
||||
vis: Public(Default::default()),
|
||||
colon_token: Default::default(),
|
||||
const_token: Default::default(),
|
||||
eq_token: Default::default(),
|
||||
expr: Expr::Lit(ExprLit{
|
||||
attrs: vec![],
|
||||
lit: Lit::Int(proto_num),
|
||||
}),
|
||||
ty: Type::Path(TypePath{
|
||||
qself: None,
|
||||
path: Path{
|
||||
segments: {
|
||||
let mut punc = Punctuated::new();
|
||||
punc.push(PathSegment::from(Ident::new("u16", Span::call_site())));
|
||||
punc
|
||||
},
|
||||
leading_colon: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
]
|
||||
};
|
||||
|
||||
let mut raw_trait = input.clone();
|
||||
|
||||
raw_trait.ident = Ident::new(&format!("Raw{}",raw_trait.ident.to_string()), raw_trait.ident.span());
|
||||
raw_trait.colon_token = Some(Default::default());
|
||||
raw_trait.supertraits = {
|
||||
let mut punct = Punctuated::new();
|
||||
punct.push(TypeParamBound::Trait(TraitBound{
|
||||
path: single_ident_path(input.ident.clone()),
|
||||
lifetimes: None,
|
||||
modifier: TraitBoundModifier::None,
|
||||
paren_token: None
|
||||
}));
|
||||
punct
|
||||
};
|
||||
|
||||
let mut functions: Vec<(LitInt, Ident)> = Vec::new();
|
||||
|
||||
let funcs = raw_trait.items.iter_mut().filter_map(|v| if let TraitItem::Fn(v) = v {Some(v)} else { None });
|
||||
|
||||
for func in funcs{
|
||||
|
||||
if matches!(func.default, Some(_)){
|
||||
return syn::Error::new(func.default.span(), "rmc methods may not have bodies").to_compile_error().into();
|
||||
}
|
||||
|
||||
let Some(attr) = func.attrs.iter()
|
||||
.find(|a| a.path().segments.last().is_some_and(|s| s.ident.to_string() == "method_id")) else {
|
||||
let span = func.sig.asyncness.span().join(func.semi_token.unwrap().span()).unwrap_or(func.sig.span());
|
||||
return syn::Error::new(span, "every function inside of an rmc protocol must have a method id").to_compile_error().into();
|
||||
};
|
||||
|
||||
let Ok(func_id): Result<LitInt, _> = attr.parse_args() else {
|
||||
return syn::Error::new(Span::call_site(), "todo: put a propper error message here").to_compile_error().into();
|
||||
};
|
||||
|
||||
|
||||
if !func.sig.inputs.first().is_some_and(|v| matches!(v, FnArg::Receiver(Receiver{
|
||||
colon_token: None,
|
||||
mutability: None,
|
||||
reference: Some(_),
|
||||
..
|
||||
}))){
|
||||
return syn::Error::new(func.sig.inputs.span(), "every protocol function must have a ` & self ` as its first parameter.").to_compile_error().into();
|
||||
}
|
||||
|
||||
let old_ident = func.sig.ident.clone();
|
||||
|
||||
func.sig.ident = Ident::new(&format!("raw_{}", func.sig.ident), func.sig.ident.span());
|
||||
|
||||
let mut new_params: Punctuated<_,_> = Punctuated::new();
|
||||
|
||||
new_params.push_value(FnArg::Receiver(Receiver{
|
||||
attrs: vec![],
|
||||
mutability: None,
|
||||
ty: Box::new(self_referece_type()),
|
||||
colon_token: None,
|
||||
self_token: Default::default(),
|
||||
reference: Some((Default::default(), None))
|
||||
}));
|
||||
|
||||
new_params.push_punct(Comma::default());
|
||||
/*
|
||||
new_params.push_value(FnArg::Typed(PatType{
|
||||
attrs: vec![],
|
||||
pat: Box::new(Pat::Verbatim(quote! { method_id })),
|
||||
colon_token: Default::default(),
|
||||
ty: Box::new(Type::Verbatim(quote!{u32}))
|
||||
}));
|
||||
|
||||
new_params.push_punct(Comma::default());*/
|
||||
|
||||
new_params.push_value(FnArg::Typed(PatType{
|
||||
attrs: vec![],
|
||||
pat: Box::new(Pat::Verbatim(quote! {data})),
|
||||
colon_token: Default::default(),
|
||||
ty: Box::new(Type::Verbatim(quote!{::std::vec::Vec<u8>}))
|
||||
}));
|
||||
|
||||
mem::swap(&mut new_params, &mut func.sig.inputs);
|
||||
let old_params = new_params;
|
||||
|
||||
|
||||
let mut inner_raw_tokens = quote!{
|
||||
let mut cursor = ::std::io::Cursor::new(data);
|
||||
};
|
||||
|
||||
let mut call_params = quote!{};
|
||||
|
||||
|
||||
for param in old_params.iter().skip(1).filter_map(|v| if let FnArg::Typed(t) = v {
|
||||
Some(t)
|
||||
} else {
|
||||
None
|
||||
}){
|
||||
let param_name = &*param.pat;
|
||||
let ty = &*param.ty;
|
||||
|
||||
inner_raw_tokens.append_all(quote!{
|
||||
let Ok(#param_name) = <#ty as crate::rmc::structures::RmcSerialize>::deserialize(&mut cursor) else {
|
||||
#param_err_return
|
||||
// gigantic ass struct initializer (to summarize this gets all of the data)
|
||||
let raw_data = RmcProtocolData{
|
||||
has_returns: !no_return_data,
|
||||
name: input.ident.clone(),
|
||||
id: proto_num,
|
||||
methods: input
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|v| match v{ TraitItem::Fn(v) => Some(v), _ => None })
|
||||
.map(|func|{
|
||||
let Some(attr) = func.attrs.iter()
|
||||
.find(|a| a.path().segments.last().is_some_and(|s| s.ident.to_string() == "method_id")) else {
|
||||
panic!( "every function inside of an rmc protocol must have a method id");
|
||||
};
|
||||
});
|
||||
|
||||
call_params.append_all(quote!{#param_name ,})
|
||||
}
|
||||
let Ok(id): Result<LitInt, _> = attr.parse_args() else {
|
||||
panic!("todo: put a propper error message here");
|
||||
};
|
||||
|
||||
inner_raw_tokens.append_all(quote!{
|
||||
let retval = self.#old_ident(#call_params).await;
|
||||
});
|
||||
let funcs = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|f| {
|
||||
let FnArg::Typed(t) = f else {
|
||||
panic!("what");
|
||||
};
|
||||
let Pat::Ident(i) = &*t.pat else {
|
||||
panic!("unable to handle non identifier patterns as parameter bindings");
|
||||
};
|
||||
|
||||
if !no_return_data{
|
||||
//let
|
||||
(i.ident.clone(), t.ty.as_ref().clone())
|
||||
}).collect();
|
||||
|
||||
//inner_raw_tokens.append_all()
|
||||
}
|
||||
|
||||
|
||||
let braced = quote! {
|
||||
{
|
||||
#inner_raw_tokens
|
||||
}
|
||||
};
|
||||
|
||||
let braced = braced.into();
|
||||
|
||||
func.default = Some(
|
||||
parse_macro_input!(braced as Block)
|
||||
);
|
||||
|
||||
functions.push((func_id, func.sig.ident.clone()));
|
||||
}
|
||||
|
||||
let mut inner_match = quote!{};
|
||||
|
||||
for toks in functions.iter().map(|(lit, ident)|{
|
||||
quote! {
|
||||
#lit => self.#ident(data).await,
|
||||
}
|
||||
}){
|
||||
inner_match.append_all(toks);
|
||||
}
|
||||
|
||||
if no_return_data{
|
||||
inner_match.append_all(quote!{
|
||||
_ => return
|
||||
})
|
||||
} else {
|
||||
//
|
||||
inner_match.append_all(quote!{
|
||||
_ => return
|
||||
})
|
||||
}
|
||||
|
||||
raw_trait.items.push(
|
||||
TraitItem::Verbatim(
|
||||
quote!{
|
||||
async fn rmc_call_proto(&self, method_id: u32, data: Vec<u8>){
|
||||
match method_id{
|
||||
#inner_match
|
||||
}
|
||||
ProtoMethodData{
|
||||
id,
|
||||
name: func.sig.ident.clone(),
|
||||
parameters: funcs
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}).collect()
|
||||
|
||||
|
||||
|
||||
let regular_trait_name = &input.ident;
|
||||
let raw_trait_name = &raw_trait.ident;
|
||||
};
|
||||
|
||||
quote!{
|
||||
#input
|
||||
#raw_details_struct
|
||||
#raw_details_impl_block
|
||||
#raw_trait
|
||||
|
||||
impl<T: #regular_trait_name + crate::rmc::protocols::ImplementRemoteCalls> #raw_trait_name for T{}
|
||||
#raw_data
|
||||
}.into()
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Used to specify the method id of methods when making rmc protocols.
|
||||
/// See [`macro@rmc_proto`] for further details.
|
||||
///
|
||||
/// Note: This attribute doesn't do anything by itself and just returns the thing it was attached to
|
||||
/// unchanged.
|
||||
#[proc_macro_attribute]
|
||||
pub fn method_id(_attr: TokenStream, input: TokenStream) -> TokenStream{
|
||||
// this attribute doesnt do anything by itself, see `rmc_proto`
|
||||
|
|
@ -523,6 +347,7 @@ pub fn method_id(_attr: TokenStream, input: TokenStream) -> TokenStream{
|
|||
}
|
||||
|
||||
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn rmc_struct(attr: TokenStream, input: TokenStream) -> TokenStream{
|
||||
let mut type_data = parse_macro_input!(input as DeriveInput);
|
||||
|
|
@ -542,8 +367,8 @@ pub fn rmc_struct(attr: TokenStream, input: TokenStream) -> TokenStream{
|
|||
}
|
||||
|
||||
impl crate::rmc::protocols::RmcCallable for #struct_name{
|
||||
async fn rmc_call(&self, protocol_id: u16, method_id: u32, rest: Vec<u8>){
|
||||
<Self as #ident>::rmc_call(self, protocol_id, method_id, rest).await;
|
||||
async fn rmc_call(&self, remote_response_connection: &crate::prudp::socket::SendingConnection, protocol_id: u16, method_id: u32, call_id: u32, rest: Vec<u8>){
|
||||
<Self as #ident>::rmc_call(self, remote_response_connection, protocol_id, method_id, call_id, rest).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
173
macros/src/protos.rs
Normal file
173
macros/src/protos.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{LitInt, Token, Type};
|
||||
use syn::token::{Brace, Paren, Semi};
|
||||
|
||||
pub struct ProtoMethodData{
|
||||
pub id: LitInt,
|
||||
pub name: Ident,
|
||||
pub parameters: Vec<(Ident, Type)>
|
||||
}
|
||||
|
||||
|
||||
/// 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 ToTokens for RmcProtocolData{
|
||||
fn to_tokens(&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<u8> }.to_tokens(tokens);
|
||||
});
|
||||
|
||||
quote!{
|
||||
-> ::core::result::Result<Vec<u8>, 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 crate::rmc::structures::RmcSerialize>::deserialize(
|
||||
&mut cursor
|
||||
) else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
}.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();
|
||||
crate::rmc::structures::RmcSerialize::serialize(&retval, &mut vec).ok();
|
||||
Ok(vec)
|
||||
}.to_tokens(tokens);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
quote!{
|
||||
async fn rmc_call_proto(
|
||||
&self,
|
||||
remote_response_connection: &crate::prudp::socket::SendingConnection,
|
||||
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());
|
||||
|
||||
quote!{
|
||||
#id => self.#raw_name(data).await,
|
||||
}.to_tokens(tokens);
|
||||
}
|
||||
quote!{
|
||||
_ => Err(crate::rmc::response::ErrorCode::Core_NotImplemented)
|
||||
}.to_tokens(tokens);
|
||||
});
|
||||
|
||||
Semi::default().to_tokens(tokens);
|
||||
|
||||
if *has_returns{
|
||||
quote!{
|
||||
crate::rmc::response::send_result(
|
||||
remote_response_connection,
|
||||
ret,
|
||||
#id,
|
||||
method_id,
|
||||
call_id,
|
||||
).await
|
||||
}.to_tokens(tokens);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
quote!{
|
||||
impl<T: #name> RawAuth for T{}
|
||||
}.to_tokens(tokens);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue