ckb_script_ipc/
lib.rs

1#![doc = include_str!("../README.md")]
2extern crate proc_macro;
3extern crate proc_macro2;
4extern crate quote;
5extern crate syn;
6
7use proc_macro::TokenStream;
8use proc_macro2::TokenStream as TokenStream2;
9use quote::{format_ident, quote, ToTokens};
10use syn::{
11    braced,
12    ext::IdentExt,
13    parenthesized,
14    parse::{Parse, ParseStream},
15    parse_macro_input, parse_quote,
16    spanned::Spanned,
17    token::Comma,
18    AttrStyle, Attribute, FnArg, Ident, Pat, PatType, ReturnType, Token, Type, Visibility,
19};
20
21macro_rules! extend_errors {
22    ($errors: ident, $e: expr) => {
23        match $errors {
24            Ok(_) => $errors = Err($e),
25            Err(ref mut errors) => errors.extend($e),
26        }
27    };
28}
29
30struct Service {
31    attrs: Vec<Attribute>,
32    vis: Visibility,
33    ident: Ident,
34    ipcs: Vec<IpcMethod>,
35}
36
37struct IpcMethod {
38    attrs: Vec<Attribute>,
39    ident: Ident,
40    args: Vec<PatType>,
41    output: ReturnType,
42}
43
44impl Parse for Service {
45    fn parse(input: ParseStream) -> syn::Result<Self> {
46        let attrs = input.call(Attribute::parse_outer)?;
47        let vis = input.parse()?;
48        input.parse::<Token![trait]>()?;
49        let ident: Ident = input.parse()?;
50        let content;
51        braced!(content in input);
52        let mut ipcs = Vec::<IpcMethod>::new();
53        while !content.is_empty() {
54            ipcs.push(content.parse()?);
55        }
56        let mut ident_errors = Ok(());
57        for ipc in &ipcs {
58            if ipc.ident == "new" {
59                extend_errors!(
60                    ident_errors,
61                    syn::Error::new(
62                        ipc.ident.span(),
63                        format!(
64                            "method name conflicts with generated fn `{}Client::new`",
65                            ident.unraw()
66                        )
67                    )
68                );
69            }
70            if ipc.ident == "serve" {
71                extend_errors!(
72                    ident_errors,
73                    syn::Error::new(
74                        ipc.ident.span(),
75                        format!("method name conflicts with generated fn `{ident}::serve`")
76                    )
77                );
78            }
79        }
80        ident_errors?;
81
82        Ok(Self {
83            attrs,
84            vis,
85            ident,
86            ipcs,
87        })
88    }
89}
90
91impl Parse for IpcMethod {
92    fn parse(input: ParseStream) -> syn::Result<Self> {
93        let attrs = input.call(Attribute::parse_outer)?;
94        input.parse::<Token![fn]>()?;
95        let ident = input.parse()?;
96        let content;
97        parenthesized!(content in input);
98        let mut args = Vec::new();
99        let mut errors = Ok(());
100        for arg in content.parse_terminated(FnArg::parse, Comma)? {
101            match arg {
102                FnArg::Typed(captured) if matches!(&*captured.pat, Pat::Ident(_)) => {
103                    args.push(captured);
104                }
105                FnArg::Typed(captured) => {
106                    extend_errors!(
107                        errors,
108                        syn::Error::new(captured.pat.span(), "patterns aren't allowed in IPC args")
109                    );
110                }
111                FnArg::Receiver(_) => {
112                    extend_errors!(
113                        errors,
114                        syn::Error::new(arg.span(), "method args cannot start with self")
115                    );
116                }
117            }
118        }
119        errors?;
120        let output = input.parse()?;
121        input.parse::<Token![;]>()?;
122
123        Ok(Self {
124            attrs,
125            ident,
126            args,
127            output,
128        })
129    }
130}
131
132fn collect_cfg_attrs(ipcs: &[IpcMethod]) -> Vec<Vec<&Attribute>> {
133    ipcs.iter()
134        .map(|ipc| {
135            ipc.attrs
136                .iter()
137                .filter(|att| {
138                    matches!(att.style, AttrStyle::Outer)
139                        && match &att.meta {
140                            syn::Meta::List(syn::MetaList { path, .. }) => {
141                                path.get_ident() == Some(&Ident::new("cfg", ipc.ident.span()))
142                            }
143                            _ => false,
144                        }
145                })
146                .collect::<Vec<_>>()
147        })
148        .collect::<Vec<_>>()
149}
150
151#[proc_macro_attribute]
152pub fn service(_attr: TokenStream, input: TokenStream) -> TokenStream {
153    let unit_type: &Type = &parse_quote!(());
154    let Service {
155        ref attrs,
156        ref vis,
157        ref ident,
158        ref ipcs,
159    } = parse_macro_input!(input as Service);
160
161    let camel_case_fn_names: &Vec<_> = &ipcs
162        .iter()
163        .map(|ipc| snake_to_camel(&ipc.ident.unraw().to_string()))
164        .collect();
165    let args: &[&[PatType]] = &ipcs.iter().map(|ipc| &*ipc.args).collect::<Vec<_>>();
166
167    let methods = ipcs.iter().map(|ipc| &ipc.ident).collect::<Vec<_>>();
168    let request_names = methods
169        .iter()
170        .map(|m| format!("{ident}.{m}"))
171        .collect::<Vec<_>>();
172
173    ServiceGenerator {
174        service_ident: ident,
175        server_ident: &format_ident!("Serve{}", ident),
176        client_ident: &format_ident!("{}Client", ident),
177        request_ident: &format_ident!("{}Request", ident),
178        response_ident: &format_ident!("{}Response", ident),
179        vis,
180        args,
181        method_attrs: &ipcs.iter().map(|ipc| &*ipc.attrs).collect::<Vec<_>>(),
182        method_cfgs: &collect_cfg_attrs(ipcs),
183        method_idents: &methods,
184        request_names: &request_names,
185        attrs,
186        ipcs,
187        return_types: &ipcs
188            .iter()
189            .map(|ipc| match ipc.output {
190                ReturnType::Type(_, ref ty) => ty.as_ref(),
191                ReturnType::Default => unit_type,
192            })
193            .collect::<Vec<_>>(),
194        arg_pats: &args
195            .iter()
196            .map(|args| args.iter().map(|arg| &*arg.pat).collect())
197            .collect::<Vec<_>>(),
198        camel_case_idents: &ipcs
199            .iter()
200            .zip(camel_case_fn_names.iter())
201            .map(|(ipc, name)| Ident::new(name, ipc.ident.span()))
202            .collect::<Vec<_>>(),
203    }
204    .into_token_stream()
205    .into()
206}
207
208struct ServiceGenerator<'a> {
209    service_ident: &'a Ident,
210    server_ident: &'a Ident,
211    client_ident: &'a Ident,
212    request_ident: &'a Ident,
213    response_ident: &'a Ident,
214    vis: &'a Visibility,
215    attrs: &'a [Attribute],
216    ipcs: &'a [IpcMethod],
217    camel_case_idents: &'a [Ident],
218    method_idents: &'a [&'a Ident],
219    request_names: &'a [String],
220    method_attrs: &'a [&'a [Attribute]],
221    method_cfgs: &'a [Vec<&'a Attribute>],
222    args: &'a [&'a [PatType]],
223    return_types: &'a [&'a Type],
224    arg_pats: &'a [Vec<&'a Pat>],
225}
226
227impl<'a> ServiceGenerator<'a> {
228    fn trait_service(&self) -> TokenStream2 {
229        let &Self {
230            attrs,
231            ipcs,
232            vis,
233            return_types,
234            service_ident,
235            server_ident,
236            ..
237        } = self;
238
239        let ipc_fns = ipcs.iter().zip(return_types.iter()).map(
240            |(
241                IpcMethod {
242                    attrs, ident, args, ..
243                },
244                output,
245            )| {
246                quote! {
247                    #( #attrs )*
248                    fn #ident(&mut self, #( #args ),*) -> #output;
249                }
250            },
251        );
252
253        quote! {
254            #( #attrs )*
255            #vis trait #service_ident: ::core::marker::Sized {
256                #( #ipc_fns )*
257                fn server(self) -> #server_ident<Self> {
258                    #server_ident { service: self }
259                }
260            }
261        }
262    }
263
264    fn struct_server(&self) -> TokenStream2 {
265        let &Self {
266            vis, server_ident, ..
267        } = self;
268
269        quote! {
270            #[derive(Clone)]
271            #vis struct #server_ident<S> {
272                service: S,
273            }
274        }
275    }
276
277    fn impl_serve_for_server(&self) -> TokenStream2 {
278        let &Self {
279            request_ident,
280            server_ident,
281            service_ident,
282            response_ident,
283            camel_case_idents,
284            arg_pats,
285            method_idents,
286            method_cfgs,
287            ..
288        } = self;
289
290        quote! {
291            impl<S> ckb_script_ipc_common::ipc::Serve for #server_ident<S>
292                where S: #service_ident
293            {
294                type Req = #request_ident;
295                type Resp = #response_ident;
296
297
298                fn serve(&mut self, req: #request_ident)
299                    -> ::core::result::Result<#response_ident, ckb_script_ipc_common::error::IpcError> {
300                    match req {
301                        #(
302                            #( #method_cfgs )*
303                            #request_ident::#camel_case_idents{ #( #arg_pats ),* } => {
304                                let ret = self.service.#method_idents(#( #arg_pats ),*);
305                                Ok(#response_ident::#camel_case_idents(ret))
306                            }
307                        )*
308                    }
309                }
310            }
311        }
312    }
313
314    fn enum_request(&self) -> TokenStream2 {
315        let &Self {
316            vis,
317            request_ident,
318            camel_case_idents,
319            args,
320            method_cfgs,
321            ..
322        } = self;
323
324        quote! {
325            #[derive(serde::Serialize, serde::Deserialize)]
326            #vis enum #request_ident {
327                #(
328                    #( #method_cfgs )*
329                    #camel_case_idents{ #( #args ),* }
330                ),*
331            }
332        }
333    }
334
335    fn enum_response(&self) -> TokenStream2 {
336        let &Self {
337            vis,
338            response_ident,
339            camel_case_idents,
340            return_types,
341            ..
342        } = self;
343
344        quote! {
345            #[derive(serde::Serialize, serde::Deserialize)]
346            #vis enum #response_ident {
347                #( #camel_case_idents(#return_types) ),*
348            }
349        }
350    }
351
352    fn struct_client(&self) -> TokenStream2 {
353        let &Self {
354            vis, client_ident, ..
355        } = self;
356
357        quote! {
358            #[allow(unused)]
359            #vis struct #client_ident<R, W>
360            where
361                R: ckb_script_ipc_common::io::Read,
362                W: ckb_script_ipc_common::io::Write,
363            {
364                channel: ckb_script_ipc_common::channel::Channel<R, W>,
365            }
366        }
367    }
368
369    fn impl_client_new(&self) -> TokenStream2 {
370        let &Self {
371            client_ident, vis, ..
372        } = self;
373
374        quote! {
375            impl<R, W> #client_ident<R, W>
376            where
377                R: ckb_script_ipc_common::io::Read,
378                W: ckb_script_ipc_common::io::Write,
379            {
380                #vis fn new(reader: R, writer: W) -> Self {
381                    let channel = ckb_script_ipc_common::channel::Channel::new(reader, writer);
382                    Self { channel }
383                }
384            }
385        }
386    }
387
388    fn impl_client_ipc_methods(&self) -> TokenStream2 {
389        let &Self {
390            client_ident,
391            request_ident,
392            response_ident,
393            method_attrs,
394            vis,
395            method_idents,
396            args,
397            return_types,
398            arg_pats,
399            camel_case_idents,
400            request_names,
401            ..
402        } = self;
403
404        quote! {
405            impl<R, W> #client_ident<R, W>
406            where
407                R: ckb_script_ipc_common::io::Read,
408                W: ckb_script_ipc_common::io::Write
409            {
410                #(
411                    #[allow(unused)]
412                    #( #method_attrs )*
413                    #vis fn #method_idents(&mut self, #( #args ),*) -> #return_types {
414                        let request = #request_ident::#camel_case_idents { #( #arg_pats ),* };
415                        let resp: Result<_, ckb_script_ipc_common::error::IpcError> = self
416                                .channel
417                                .call::<_, #response_ident>(#request_names, request);
418                        match resp {
419                            Ok(#response_ident::#camel_case_idents(ret)) => ret,
420                            Err(e) => {
421                                panic!("IPC error: {:?}", e);
422                            },
423                            _ => {
424                                panic!("IPC error: wrong method id");
425                            }
426                        }
427                    }
428                )*
429            }
430        }
431    }
432}
433
434impl<'a> ToTokens for ServiceGenerator<'a> {
435    fn to_tokens(&self, output: &mut TokenStream2) {
436        output.extend(vec![
437            self.trait_service(),
438            self.struct_server(),
439            self.impl_serve_for_server(),
440            self.enum_request(),
441            self.enum_response(),
442            self.struct_client(),
443            self.impl_client_new(),
444            self.impl_client_ipc_methods(),
445        ]);
446    }
447}
448
449fn snake_to_camel(ident_str: &str) -> String {
450    ident_str
451        .split('_')
452        .map(|word| word[..1].to_uppercase() + &word[1..].to_lowercase())
453        .collect()
454}