1use proc_macro::TokenStream;
2use quote::{format_ident, quote, ToTokens};
3use venial::*;
4
5fn construct_export(
6 name: &proc_macro2::Ident,
7 vis_marker: Option<VisMarker>,
8 params: Vec<FnTypedParam>,
9) -> proc_macro2::TokenStream {
10 fn param_idx(name: &proc_macro2::Ident) -> proc_macro2::Ident {
11 format_ident!("__{}_idx", name)
12 }
13
14 let inner_name = format_ident!("__wasm_minimal_protocol_internal_function_{}", name);
15 let export_name = proc_macro2::Literal::string(&name.to_string());
16 let p_idx = params
17 .iter()
18 .map(|p| &p.name)
19 .map(param_idx)
20 .collect::<Vec<_>>();
21 let param_names = params.iter().map(|p| p.name.clone()).collect::<Vec<_>>();
22
23 let get_unsplit_params = if params.len() == 0 {
24 quote!()
25 } else {
26 quote!(
27 let __total_len = #(#p_idx + )* 0;
28 let mut __unsplit_params = vec![0u8; __total_len];
29 typst_wasm_protocol::write_args_to_buffer(__unsplit_params.as_mut_ptr());
30 )
31 };
32
33 let mut set_args = quote!(
34 let mut start: usize = 0;
35 );
36 for param in params.iter() {
37 let name = ¶m.name;
38 let ty = ¶m.ty;
39 let idx = param_idx(name);
40 set_args.extend(quote!(
41 let #name: #ty = (&__unsplit_params[start..start + #idx]).into();
42 start += #idx;
43 ));
44 }
45
46 quote!(
47 #[export_name = #export_name]
48 #vis_marker extern "C" fn #inner_name(#(#p_idx: usize),*) -> i32 {
49 #get_unsplit_params
50 #set_args
51
52 let result = #name(#(#param_names),*);
53 typst_wasm_protocol::PluginResult::send_result(result)
54 }
55 )
56 .into()
57}
58
59#[proc_macro_attribute]
60pub fn wasm_export(_: TokenStream, item: TokenStream) -> TokenStream {
61 let mut item = proc_macro2::TokenStream::from(item);
62 let decl = parse_item(item.clone()).expect("invalid declaration");
63 let func = match decl.as_function() {
64 Some(func) => func.clone(),
65 None => {
66 let error = venial::Error::new_at_tokens(
67 &item,
68 "#[wasm_export] can only be applied to a function",
69 );
70 item.extend(error.to_compile_error());
71 return item.into();
72 }
73 };
74 let Function {
75 name,
76 params,
77 vis_marker,
78 ..
79 } = func.clone();
80
81 let mut error = None;
82 let p = params
83 .items()
84 .filter_map(|x| match x {
85 FnParam::Receiver(_p) => {
86 let x = x.to_token_stream();
87 error = Some(venial::Error::new_at_tokens(
88 &x,
89 format!("the {x} argument is not allowed by the protocol"),
90 ));
91 None
92 }
93 FnParam::Typed(p) => Some(p.clone()),
94 })
95 .collect::<Vec<_>>();
96
97 let mut result = quote!(#func);
98 if let Some(error) = error {
99 result.extend(error.to_compile_error());
100 } else {
101 result.extend(construct_export(&name, vis_marker, p));
102 }
103 result.into()
104}