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}