• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Macro for topshim
2 
3 extern crate proc_macro;
4 
5 use proc_macro::TokenStream;
6 use quote::{format_ident, quote};
7 use syn::parse::{Parse, ParseStream, Result};
8 use syn::{parse_macro_input, Block, Ident, Path, Stmt, Token, Type};
9 
10 /// Parsed structure for callback variant
11 struct CbVariant {
12     dispatcher: Type,
13     fn_pair: (Ident, Path),
14     arg_pairs: Vec<(Type, Option<Type>)>,
15     stmts: Vec<Stmt>,
16 }
17 
18 impl Parse for CbVariant {
parse(input: ParseStream) -> Result<Self>19     fn parse(input: ParseStream) -> Result<Self> {
20         // First thing should be the dispatcher
21         let dispatcher: Type = input.parse()?;
22         input.parse::<Token![,]>()?;
23 
24         // Name and return type are parsed
25         let name: Ident = input.parse()?;
26         input.parse::<Token![->]>()?;
27         let rpath: Path = input.parse()?;
28 
29         let mut arg_pairs: Vec<(Type, Option<Type>)> = Vec::new();
30         let mut stmts: Vec<Stmt> = Vec::new();
31 
32         while input.peek(Token![,]) {
33             // Discard the comma
34             input.parse::<Token![,]>()?;
35 
36             // Check if we're expecting the final Block
37             if input.peek(syn::token::Brace) {
38                 let block: Block = input.parse()?;
39                 stmts.extend(block.stmts);
40 
41                 break;
42             }
43 
44             // Grab the next type argument
45             let start_type: Type = input.parse()?;
46 
47             if input.peek(Token![->]) {
48                 // Discard ->
49                 input.parse::<Token![->]>()?;
50 
51                 // Try to parse Token![_]. If that works, we will
52                 // consume this value and not pass it forward.
53                 // Otherwise, try to parse as syn::Type and pass forward for
54                 // conversion.
55                 if input.peek(Token![_]) {
56                     input.parse::<Token![_]>()?;
57                     arg_pairs.push((start_type, None));
58                 } else {
59                     let end_type: Type = input.parse()?;
60                     arg_pairs.push((start_type, Some(end_type)));
61                 }
62             } else {
63                 arg_pairs.push((start_type.clone(), Some(start_type)));
64             }
65         }
66 
67         // TODO: Validate there are no more tokens; currently they are ignored.
68         Ok(CbVariant { dispatcher, fn_pair: (name, rpath), arg_pairs, stmts })
69     }
70 }
71 
72 #[proc_macro]
73 /// Implement C function to convert callback into enum variant.
74 ///
75 /// Expected syntax:
76 ///     ```compile_fail
77 ///     cb_variant(DispatcherType, function_name -> EnumType::Variant, args..., {
78 ///         // Statements (maybe converting types)
79 ///         // Args in order will be _0, _1, etc.
80 ///     })
81 ///     ```
82 ///
83 /// args can do conversions inline as well. In order for conversions to work, the relevant
84 /// From<T> trait should also be implemented.
85 ///
86 /// Example:
87 ///     u32 -> BtStatus (requires impl From<u32> for BtStatus)
88 ///
89 /// To consume a value during conversion, you can use "Type -> _". This is useful when you want
90 /// to convert a pointer + size into a single Vec (i.e. using ptr_to_vec).
91 ///
92 /// Example:
93 ///     u32 -> _
cb_variant(input: TokenStream) -> TokenStream94 pub fn cb_variant(input: TokenStream) -> TokenStream {
95     let parsed_cptr = parse_macro_input!(input as CbVariant);
96 
97     let dispatcher = parsed_cptr.dispatcher;
98     let (ident, rpath) = parsed_cptr.fn_pair;
99 
100     let mut params = proc_macro2::TokenStream::new();
101     let mut args = proc_macro2::TokenStream::new();
102     for (i, (start, end)) in parsed_cptr.arg_pairs.iter().enumerate() {
103         let ident = format_ident!("_{}", i);
104         params.extend(quote! { #ident: #start, });
105 
106         match end {
107             Some(v) => {
108                 // Argument needs an into translation if it doesn't match the start
109                 if start != v {
110                     args.extend(quote! { #end::from(#ident), });
111                 } else {
112                     args.extend(quote! {#ident,});
113                 }
114             }
115             // If there's no end type, just consume it instead.
116             None => (),
117         }
118     }
119 
120     let mut stmts = proc_macro2::TokenStream::new();
121     for stmt in parsed_cptr.stmts {
122         stmts.extend(quote! { #stmt });
123     }
124 
125     let tokens = quote! {
126         #[no_mangle]
127         extern "C" fn #ident(#params) {
128             #stmts
129 
130             unsafe {
131                 (get_dispatchers().lock().unwrap().get::<#dispatcher>().unwrap().clone().lock().unwrap().dispatch)(#rpath(#args));
132             }
133         }
134     };
135 
136     TokenStream::from(tokens)
137 }
138