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 if let Some(v) = end {
107 // Argument needs an into translation if it doesn't match the start
108 if start != v {
109 args.extend(quote! { #end::from(#ident), });
110 } else {
111 args.extend(quote! {#ident,});
112 }
113 }
114 }
115
116 let mut stmts = proc_macro2::TokenStream::new();
117 for stmt in parsed_cptr.stmts {
118 stmts.extend(quote! { #stmt });
119 }
120
121 let dispatcher_str = quote!(#dispatcher).to_string();
122 let tokens = quote! {
123 #[no_mangle]
124 extern "C" fn #ident(#params) {
125 #stmts
126 (get_dispatchers()
127 .lock()
128 .expect("Couldn't lock dispatchers!")
129 .get::<#dispatcher>()
130 .expect(concat!("Couldn't find dispatcher type: ", #dispatcher_str))
131 .clone()
132 .lock()
133 .expect(concat!("Couldn't lock specific dispatcher: ", #dispatcher_str))
134 .dispatch)(#rpath(#args));
135 }
136 };
137
138 TokenStream::from(tokens)
139 }
140
141 // TODO: Replace below macro with a public crate, such as https://crates.io/crates/adorn
142 #[proc_macro_attribute]
143 /// Macro to check if the profile has been initialized
144 ///
145 /// Function who applies this macro should also include log::warn and the self must implement
146 /// fn is_initialized(&self) -> bool
147 ///
148 /// Example:
149 /// ```
150 /// use log::warn;
151 /// #[profile_enabled_or]
152 /// fn foo(&self) {
153 /// // actual code
154 /// }
155 /// ```
156 /// expands as
157 /// ```
158 /// use log::warn;
159 /// fn foo(&self) {
160 /// if !self.is_enabled() {
161 /// warn!("Tried to {} but internal hasn't been enabled", "foo");
162 /// return ;
163 /// }
164 /// // actual code
165 /// }
166 /// ```
167 /// One can specify a return value on uninitialized case
168 /// ```
169 /// use log::warn;
170 /// #[profile_enabled_or("not ready")]
171 /// fn foo(&self) -> &str {
172 /// // actual code
173 /// }
174 /// ```
175 /// expands as
176 /// ```
177 /// use log::warn;
178 /// fn foo(&self) -> &str {
179 /// if !self.is_enabled() {
180 /// warn!("Tried to {} but internal hasn't been enabled", "foo");
181 /// return "not ready";
182 /// }
183 /// // actual code
184 /// return "success"
185 /// }
186 /// ```
profile_enabled_or(attr: TokenStream, item: TokenStream) -> TokenStream187 pub fn profile_enabled_or(attr: TokenStream, item: TokenStream) -> TokenStream {
188 generate_profile_enabled_or_tokenstream(item, attr.to_string())
189 }
190
191 /// Similar to profile_enabled_or but return Default::default() when profile is not enabled.
192 #[proc_macro_attribute]
profile_enabled_or_default(_attr: TokenStream, item: TokenStream) -> TokenStream193 pub fn profile_enabled_or_default(_attr: TokenStream, item: TokenStream) -> TokenStream {
194 generate_profile_enabled_or_tokenstream(item, String::from("Default::default()"))
195 }
196
generate_profile_enabled_or_tokenstream(item: TokenStream, attr_string: String) -> TokenStream197 fn generate_profile_enabled_or_tokenstream(item: TokenStream, attr_string: String) -> TokenStream {
198 let mut input = syn::parse_macro_input!(item as syn::ItemFn);
199
200 let fn_name = input.sig.ident.to_string();
201
202 let ret_stmt: proc_macro2::TokenStream = format!("return {};", attr_string).parse().unwrap();
203
204 let check_block = quote::quote! {
205 if !self.is_enabled() {
206 warn!("Tried to {} but internal hasn't been enabled", #fn_name);
207 #ret_stmt
208 }
209 };
210
211 input.block.stmts.insert(0, syn::parse(check_block.into()).unwrap());
212
213 let output = quote::quote! {
214 #input
215 };
216
217 output.into()
218 }
219
220 /// Generate impl cxx::ExternType for the trivial types in bindings.
221 ///
222 /// This is only needed if they need to be share with the cxx-bridge blocks.
223 ///
224 /// Usage (assume the C++ type some::ns::sample_t is defined in types/some_samples.h):
225 /// ```ignore
226 /// #[gen_cxx_extern_trivial]
227 /// type SampleType = bindings::some::ns::sample_t;
228 /// ```
229 ///
230 /// Which generates the type info below for cxx-bridge:
231 /// ```ignore
232 /// unsafe impl cxx::ExternType for SampleType {
233 /// type Id = cxx::type_id!("some::ns::sample_t");
234 /// type Kind = cxx::kind::Trivial;
235 /// }
236 /// ```
237 ///
238 /// To use the binding type in a cxx::bridge block, include the header and (optionally) assign
239 /// the namespace and name for the C++ type.
240 /// ```ignore
241 /// #[cxx::bridge]
242 /// mod ffi {
243 /// unsafe extern "C++" {
244 /// include!("types/some_samples.h");
245 ///
246 /// #[namespace = "some::ns"]
247 /// #[cxx_name = "sample_t"]
248 /// type SampleType = super::SampleType;
249 /// }
250 /// }
251 /// ```
252 #[proc_macro_attribute]
gen_cxx_extern_trivial(_attr: TokenStream, item: TokenStream) -> TokenStream253 pub fn gen_cxx_extern_trivial(_attr: TokenStream, item: TokenStream) -> TokenStream {
254 let input = syn::parse_macro_input!(item as syn::ItemType);
255
256 let ident = input.ident.clone();
257
258 let segs = match *input.ty {
259 Type::Path(syn::TypePath {
260 qself: None,
261 path: Path { leading_colon: None, ref segments },
262 }) => segments,
263 _ => panic!("Unsupported type"),
264 };
265
266 let mut iter = segs.into_iter();
267
268 match iter.next() {
269 Some(seg) if seg.ident == "bindings" => {}
270 _ => panic!("Unexpected type: Must starts with \"bindings::\""),
271 }
272
273 match iter.clone().next() {
274 Some(seg) if seg.ident == "root" => {
275 // Skip the "root" module in bindings
276 iter.next();
277 }
278 _ => {}
279 }
280
281 let cxx_ident = iter.map(|seg| seg.ident.to_string()).collect::<Vec<String>>().join("::");
282
283 if cxx_ident.is_empty() {
284 panic!("Empty cxx ident");
285 }
286
287 quote! {
288 #input
289
290 unsafe impl cxx::ExternType for #ident {
291 type Id = cxx::type_id!(#cxx_ident);
292 type Kind = cxx::kind::Trivial;
293 }
294 }
295 .into()
296 }
297