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