diff options
Diffstat (limited to 'support/texlab/crates/jsonrpc_derive/src/lib.rs')
-rw-r--r-- | support/texlab/crates/jsonrpc_derive/src/lib.rs | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/support/texlab/crates/jsonrpc_derive/src/lib.rs b/support/texlab/crates/jsonrpc_derive/src/lib.rs new file mode 100644 index 0000000000..fe4da8d4b6 --- /dev/null +++ b/support/texlab/crates/jsonrpc_derive/src/lib.rs @@ -0,0 +1,210 @@ +#![recursion_limit = "128"] + +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use std::str::FromStr; +use syn::export::TokenStream2; +use syn::*; + +macro_rules! unwrap { + ($input:expr, $arm:pat => $value:expr) => {{ + match $input { + $arm => $value, + _ => panic!(), + } + }}; +} + +enum MethodKind { + Request, + Notification, +} + +struct MethodMeta { + pub name: String, + pub kind: MethodKind, +} + +impl MethodMeta { + pub fn parse(attr: &Attribute) -> Self { + let meta = attr.parse_meta().unwrap(); + if meta.path().get_ident().unwrap() != "jsonrpc_method" { + panic!("Expected jsonrpc_method attribute"); + } + + let nested = unwrap!(meta, Meta::List(x) => x.nested); + let name = unwrap!(&nested[0], NestedMeta::Lit(Lit::Str(x)) => x.value()); + let kind = { + let lit = unwrap!(&nested[1], NestedMeta::Meta(Meta::NameValue(x)) => &x.lit); + let kind = unwrap!(lit, Lit::Str(x) => x.value()); + match kind.as_str() { + "request" => MethodKind::Request, + "notification" => MethodKind::Notification, + _ => panic!( + "Invalid method kind. Valid options are \"request\" and \"notification\"" + ), + } + }; + + Self { name, kind } + } +} + +#[proc_macro_attribute] +pub fn jsonrpc_method(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +#[proc_macro_attribute] +pub fn jsonrpc_server(_attr: TokenStream, item: TokenStream) -> TokenStream { + let impl_: ItemImpl = parse_macro_input!(item); + let generics = &impl_.generics; + let self_ty = &impl_.self_ty; + let (requests, notifications) = generate_server_skeletons(&impl_.items); + + let tokens = quote! { + #impl_ + + impl #generics jsonrpc::RequestHandler for #self_ty { + #[boxed] + async fn handle_request(&self, request: jsonrpc::Request) -> jsonrpc::Response { + use jsonrpc::*; + + match request.method.as_str() { + #(#requests),*, + _ => { + Response::error(Error::method_not_found_error(), Some(request.id)) + } + } + } + + fn handle_notification(&self, notification: jsonrpc::Notification) { + match notification.method.as_str() { + #(#notifications),*, + _ => log::warn!("{}: {}", "Method not found", notification.method), + } + } + } + }; + + tokens.into() +} + +#[proc_macro_attribute] +pub fn jsonrpc_client(attr: TokenStream, item: TokenStream) -> TokenStream { + let item = TokenStream::from_str(&item.to_string().replace("async ", "")).unwrap(); + let trait_: ItemTrait = parse_macro_input!(item); + let trait_ident = &trait_.ident; + let stubs = generate_client_stubs(&trait_.items); + let attr: AttributeArgs = parse_macro_input!(attr); + let struct_ident = unwrap!(attr.first().unwrap(), NestedMeta::Meta(Meta::Path(x)) => x); + + let tokens = quote! { + #trait_ + + pub struct #struct_ident { + client: jsonrpc::Client + } + + impl #struct_ident + { + pub fn new(output: futures::channel::mpsc::Sender<String>) -> Self { + Self { + client: jsonrpc::Client::new(output), + } + } + } + + impl #trait_ident for #struct_ident + { + #(#stubs)* + } + + impl jsonrpc::ResponseHandler for #struct_ident + { + #[boxed] + async fn handle(&self, response: jsonrpc::Response) -> () { + self.client.handle(response).await + } + } + }; + + tokens.into() +} + +fn generate_server_skeletons(items: &Vec<ImplItem>) -> (Vec<TokenStream2>, Vec<TokenStream2>) { + let mut requests = Vec::new(); + let mut notifications = Vec::new(); + for item in items { + let method = unwrap!(item, ImplItem::Method(x) => x); + if method.attrs.is_empty() { + continue; + } + + let ident = &method.sig.ident; + let param_ty = unwrap!(&method.sig.inputs[1], FnArg::Typed(x) => &x.ty); + let meta = MethodMeta::parse(method.attrs.first().unwrap()); + let name = &meta.name.as_str(); + + match meta.kind { + MethodKind::Request => { + requests.push(quote!( + #name => { + let handler = |param: #param_ty| async move { + self.#ident(param).await + }; + + jsonrpc::handle_request(request, handler).await + } + )); + } + MethodKind::Notification => { + notifications.push(quote!( + #name => { + let handler = move |param: #param_ty| { + self.#ident(param); + }; + + jsonrpc::handle_notification(notification, handler); + } + )); + } + } + } + + (requests, notifications) +} + +fn generate_client_stubs(items: &Vec<TraitItem>) -> Vec<TokenStream2> { + let mut stubs = Vec::new(); + for item in items { + let method = unwrap!(item, TraitItem::Method(x) => x); + let attrs = &method.attrs; + let sig = &method.sig; + let param = unwrap!(&sig.inputs[1], FnArg::Typed(x) => &x.pat); + let meta = MethodMeta::parse(attrs.first().unwrap()); + let name = &meta.name; + + let stub = match meta.kind { + MethodKind::Request => quote!( + #[boxed] + #sig { + let result = self.client.send_request(#name.to_owned(), #param).await?; + serde_json::from_value(result).map_err(|_| jsonrpc::Error::deserialize_error()) + } + ), + MethodKind::Notification => quote!( + #[boxed] + #sig { + self.client.send_notification(#name.to_owned(), #param).await + } + ), + }; + + stubs.push(stub); + } + + stubs +} |