1use crate::{primitives::Primitive, utils::extract_path_from_type};
2use proc_macro::{TokenStream, TokenTree};
3use proc_macro_error::{abort, proc_macro_error, ResultExt};
4use quote::{format_ident, quote, ToTokens};
5use std::{
6 collections::{HashMap, HashSet},
7 iter::once,
8};
9use syn::{
10 AttributeArgs, FnArg, ForeignItemFn, GenericParam, ItemFn, ItemType, ItemUse, Pat, PatPath,
11 Path, PathArguments, PathSegment, ReturnType,
12};
13use utils::{flatten_using_statement, normalize_return_type};
14
15mod primitives;
16mod serializable;
17mod typing;
18mod utils;
19
20#[proc_macro_derive(Serializable, attributes(fp))]
22pub fn derive_serializable(item: TokenStream) -> TokenStream {
23 crate::serializable::impl_derive_serializable(item)
24}
25
26#[proc_macro]
28pub fn fp_import(token_stream: TokenStream) -> TokenStream {
29 let ParsedStatements {
30 functions,
31 collectable_types,
32 aliases,
33 } = parse_statements(token_stream);
34 let collectable_types = collectable_types.iter();
35 let alias_keys = aliases.keys();
36 let alias_paths = aliases
37 .values()
38 .map(|path| path.to_token_stream().to_string());
39
40 let replacement = quote! {
41 fn __fp_declare_import_fns() -> (fp_bindgen::prelude::FunctionList, fp_bindgen::prelude::TypeMap) {
42 let mut import_types = fp_bindgen::prelude::TypeMap::new();
43 #( #collectable_types::collect_types(&mut import_types); )*
44 #( import_types.insert(TypeIdent::from(#alias_keys), Type::Alias(#alias_keys.to_owned(), std::str::FromStr::from_str(#alias_paths).unwrap())); )*
45
46 let mut list = fp_bindgen::prelude::FunctionList::new();
47 #( list.add_function(#functions); )*
48
49 (list, import_types)
50 }
51 };
52 replacement.into()
53}
54
55#[proc_macro]
57pub fn fp_export(token_stream: TokenStream) -> TokenStream {
58 let ParsedStatements {
59 functions,
60 collectable_types,
61 aliases,
62 } = parse_statements(token_stream);
63 let collectable_types = collectable_types.iter();
64 let alias_keys = aliases.keys();
65 let alias_paths = aliases
66 .values()
67 .map(|path| path.to_token_stream().to_string());
68
69 let replacement = quote! {
70 fn __fp_declare_export_fns() -> (fp_bindgen::prelude::FunctionList, fp_bindgen::prelude::TypeMap) {
71 let mut export_types = fp_bindgen::prelude::TypeMap::new();
72 #( #collectable_types::collect_types(&mut export_types); )*
73 #( export_types.insert(TypeIdent::from(#alias_keys), Type::Alias(#alias_keys.to_owned(), std::str::FromStr::from_str(#alias_paths).unwrap())); )*
74
75 let mut list = fp_bindgen::prelude::FunctionList::new();
76 #( list.add_function(#functions); )*
77
78 (list, export_types)
79 }
80 };
81 replacement.into()
82}
83
84struct ParsedStatements {
87 pub functions: Vec<String>,
88 pub collectable_types: HashSet<CollectableTypeDefinition>,
89 pub aliases: HashMap<String, CollectableTypeDefinition>,
90}
91
92#[derive(Debug, Eq, PartialEq, Hash)]
94struct CollectableTypeDefinition {
95 pub path: Path,
96 pub array_len: usize,
97}
98
99impl ToTokens for CollectableTypeDefinition {
100 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
101 let path = &self.path;
102 if self.array_len > 0 {
103 let len = self.array_len;
104 tokens.extend(quote! { <[#path; #len]> })
105 } else {
106 tokens.extend(quote! { #path })
107 }
108 }
109}
110
111fn parse_statements(token_stream: TokenStream) -> ParsedStatements {
117 let mut functions = Vec::new();
118 let mut collectable_types = HashSet::new();
119 let mut aliases = HashMap::new();
120
121 let mut current_item_tokens = Vec::<TokenTree>::new();
122 for token in token_stream.into_iter() {
123 match token {
124 TokenTree::Punct(punct) if punct.as_char() == ';' => {
125 current_item_tokens.push(TokenTree::Punct(punct));
126
127 let stream = current_item_tokens.into_iter().collect::<TokenStream>();
128
129 if let Ok(function) = syn::parse::<ForeignItemFn>(stream.clone()) {
130 for input in &function.sig.inputs {
131 match input {
132 FnArg::Receiver(_) => panic!(
133 "Methods are not supported. Found `self` in function declaration: {:?}",
134 function.sig
135 ),
136 FnArg::Typed(arg) => {
137 collectable_types.insert(
138 extract_path_from_type(arg.ty.as_ref()).unwrap_or_else(|| {
139 panic!(
140 "Only value types are supported. \
141 Incompatible argument type in function declaration: {:?}",
142 function.sig
143 )
144 }),
145 );
146 }
147 }
148 }
149
150 if let Some(ty) = normalize_return_type(&function.sig.output) {
151 collectable_types.insert(extract_path_from_type(ty).unwrap_or_else(|| {
152 panic!(
153 "Only value types are supported. \
154 Incompatible return type in function declaration: {:?}",
155 function.sig
156 )
157 }));
158 }
159
160 functions.push(function.into_token_stream().to_string());
161 } else if let Ok(using) = syn::parse::<ItemUse>(stream.clone()) {
162 for path in flatten_using_statement(using) {
163 collectable_types.insert(CollectableTypeDefinition { path, array_len: 0 });
164 }
165 } else if let Ok(type_alias) = syn::parse::<ItemType>(stream) {
166 aliases.insert(
167 type_alias.ident.to_string(),
168 extract_path_from_type(type_alias.ty.as_ref()).unwrap_or_else(|| {
169 panic!(
170 "Only value types are supported. \
171 Incompatible type in alias: {:?}",
172 type_alias
173 )
174 }),
175 );
176 }
177
178 current_item_tokens = Vec::new();
179 }
180 other => current_item_tokens.push(other),
181 }
182 }
183
184 ParsedStatements {
185 functions,
186 collectable_types,
187 aliases,
188 }
189}
190
191#[proc_macro]
193pub fn fp_bindgen(args: TokenStream) -> TokenStream {
194 let args: proc_macro2::TokenStream = args.into();
195 let replacement = quote! {
196 let (import_functions, import_types) = __fp_declare_import_fns();
197 let (export_functions, mut export_types) = __fp_declare_export_fns();
198
199 let mut types = import_types;
200 types.append(&mut export_types);
201
202 fp_bindgen::generate_bindings(
203 import_functions,
204 export_functions,
205 types,
206 #args
207 );
208 };
209 replacement.into()
210}
211
212#[doc(hidden)]
213#[proc_macro]
214pub fn primitive_impls(_: TokenStream) -> TokenStream {
215 let primitives = [
216 Primitive::Bool,
217 Primitive::F32,
218 Primitive::F64,
219 Primitive::I8,
220 Primitive::I16,
221 Primitive::I32,
222 Primitive::I64,
223 Primitive::U8,
224 Primitive::U16,
225 Primitive::U32,
226 Primitive::U64,
227 ];
228
229 let mut token_stream = TokenStream::new();
230 for primitive in primitives {
231 token_stream.extend(primitive.gen_impl().into_iter());
232 }
233 token_stream
234}
235
236#[proc_macro_attribute]
239#[proc_macro_error]
240pub fn fp_export_signature(_attributes: TokenStream, input: TokenStream) -> TokenStream {
241 proc_macro_error::set_dummy(input.clone().into());
242
243 let func = syn::parse_macro_input::parse::<ForeignItemFn>(input.clone()).unwrap_or_abort();
244 let args = typing::extract_args(&func.sig).collect::<Vec<_>>();
245
246 let mut sig = func.sig.clone();
247 {
249 typing::morph_signature(&mut sig, "fp_bindgen_support");
250 sig.inputs = sig
251 .inputs
252 .into_iter()
253 .chain(once({
255 let input_types = args.iter().map(|(_, pt, _)| pt.ty.as_ref());
256 let output = if func.sig.asyncness.is_some() {
257 syn::parse::<ReturnType>((quote! {-> FUT}).into()).unwrap_or_abort()
258 } else {
259 func.sig.output.clone()
260 };
261
262 syn::parse::<FnArg>((quote! {fptr: fn (#(#input_types),*) #output}).into())
263 .unwrap_or_abort()
264 }))
265 .collect();
266 sig.generics.params.clear();
267 if func.sig.asyncness.is_some() {
268 let output = typing::get_output_type(&func.sig.output);
269 sig.generics.params.push(
270 syn::parse::<GenericParam>(
271 match output {
273 Some(output) => {
274 (quote! {FUT: std::future::Future<Output=#output> + 'static}).into()
275 }
276 None => (quote! {FUT: std::future::Future<Output=()> + 'static}).into(),
277 },
278 )
279 .unwrap_or_abort(),
280 )
281 }
282 }
283
284 let (complex_names, complex_types): (Vec<_>, Vec<_>) = args
285 .iter()
286 .filter_map(|&(_, pt, is_complex)| {
287 if is_complex {
288 Some((pt.pat.as_ref(), pt.ty.as_ref()))
289 } else {
290 None
291 }
292 })
293 .unzip();
294
295 let names = args.iter().map(|(_, pt, _)| pt.pat.as_ref());
296 let func_call = quote! {(fptr)(#(#names),*)};
297
298 let func_wrapper = if func.sig.asyncness.is_some() {
299 quote! {
300 let ret = fp_bindgen_support::guest::r#async::task::Task::alloc_and_spawn(#func_call);
301 }
302 } else {
303 let return_wrapper = if typing::is_ret_type_complex(&func.sig.output) {
305 quote! {let ret = fp_bindgen_support::guest::io::export_value_to_host(&ret);}
306 } else {
307 Default::default()
308 };
309 quote! {
310 let ret = #func_call;
311 #return_wrapper
312 }
313 };
314
315 (quote! {
317 #[inline(always)]
319 pub #sig {
320 #(let #complex_names = unsafe { fp_bindgen_support::guest::io::import_value_from_host::<#complex_types>(#complex_names) };)*
321 #func_wrapper
322 ret
323 }
324 })
325 .into()
326}
327
328#[proc_macro_attribute]
339#[proc_macro_error]
340pub fn fp_export_impl(attributes: TokenStream, input: TokenStream) -> TokenStream {
341 proc_macro_error::set_dummy(input.clone().into());
342
343 let func = syn::parse_macro_input::parse::<ItemFn>(input.clone()).unwrap_or_abort();
344 let attrs =
345 syn::parse_macro_input::parse::<AttributeArgs>(attributes.clone()).unwrap_or_abort();
346
347 let protocol_path = attrs
348 .get(0)
349 .map(|om| match om {
350 syn::NestedMeta::Meta(meta) => match meta {
351 syn::Meta::Path(path) => path,
352 _ => abort!(meta, "unsupported attribute, must name a path"),
353 },
354 _ => abort!(om, "unsupported attribute, must name a path"),
355 })
356 .unwrap_or_else(|| abort!(func, "missing attribute. Must name which provider is being implemented eg: #[fp_export_impl(foobar)]"));
357
358 let args = typing::extract_args(&func.sig).collect::<Vec<_>>();
359
360 let mut sig = func.sig.clone();
361 {
363 typing::morph_signature(
364 &mut sig,
365 protocol_path.to_token_stream().to_string().as_str(),
366 );
367 sig.ident = format_ident!("__fp_gen_{}", sig.ident);
368 }
369
370 let fn_name = &func.sig.ident;
371
372 let impl_fn_pat = Pat::Path(PatPath {
373 attrs: vec![],
374 qself: None,
375 path: PathSegment {
376 ident: func.sig.ident.clone(),
377 arguments: PathArguments::None,
378 }
379 .into(),
380 });
381 let call_args = args
382 .iter()
383 .map(|&(_, pt, _)| pt.pat.as_ref())
384 .chain(once(&impl_fn_pat))
385 .collect::<Vec<_>>();
386
387 let ts: proc_macro2::TokenStream = input.clone().into();
388 (quote! {
390 #[no_mangle]
391 pub #sig {
392 #protocol_path::#fn_name(#(#call_args),*)
393 }
394 #ts
395 })
396 .into()
397}
398
399#[proc_macro_attribute]
402#[proc_macro_error]
403pub fn fp_import_signature(_attributes: TokenStream, input: TokenStream) -> TokenStream {
404 proc_macro_error::set_dummy(input.clone().into());
405
406 let func = syn::parse_macro_input::parse::<ForeignItemFn>(input.clone()).unwrap_or_abort();
407 let args = typing::extract_args(&func.sig).collect::<Vec<_>>();
408
409 let wrapper_sig = func.sig.clone();
410 let mut extern_sig = wrapper_sig.clone();
411 {
413 extern_sig.ident = format_ident!("__fp_gen_{}", extern_sig.ident);
414 typing::morph_signature(&mut extern_sig, "fp_bindgen_support");
415 }
416
417 let complex_names: Vec<_> = args
418 .iter()
419 .filter_map(|&(_, pt, is_complex)| {
420 if is_complex {
421 Some(pt.pat.as_ref())
422 } else {
423 None
424 }
425 })
426 .collect();
427
428 let names = args.iter().map(|(_, pt, _)| pt.pat.as_ref());
429 let extern_ident = &extern_sig.ident;
430 let func_call = quote! {#extern_ident(#(#names),*)};
431
432 let ret_wrapper = if func.sig.asyncness.is_some() {
433 quote! {
434 let ret = unsafe {
435 fp_bindgen_support::guest::io::import_value_from_host(fp_bindgen_support::guest::r#async::HostFuture::new(ret).await)
436 };
437 }
438 } else {
439 if typing::is_ret_type_complex(&func.sig.output) {
441 quote! {
442 let ret = unsafe { fp_bindgen_support::guest::io::import_value_from_host(ret) };
443 }
444 } else {
445 Default::default()
446 }
447 };
448
449 let attrs = &func.attrs;
450
451 (quote! {
453 #[link(wasm_import_module = "fp")]
454 extern "C" { #extern_sig; }
455
456 #[inline(always)]
457 #(#attrs)*
458 pub #wrapper_sig {
459 #(let #complex_names = fp_bindgen_support::guest::io::export_value_to_host(&#complex_names);)*
460 let ret = unsafe { #func_call };
461 #ret_wrapper
462 ret
463 }
464 })
465 .into()
466}