1use proc_macro::TokenStream;
2use quote::{format_ident, quote, ToTokens};
3use syn::{parse::Parser, Expr};
4use venial::*;
5
6fn construct_export(
7 name: &proc_macro2::Ident,
8 vis_marker: Option<VisMarker>,
9 params: Vec<FnTypedParam>,
10 export_rename: &Option<Expr>,
11) -> proc_macro2::TokenStream {
12 fn param_idx(name: &proc_macro2::Ident) -> proc_macro2::Ident {
13 format_ident!("__{}_idx", name)
14 }
15
16 let inner_name = format_ident!("__wasm_minimal_protocol_internal_function_{}", name);
17 let export_name = export_rename
19 .as_ref()
20 .unwrap_or(&Expr::Lit(syn::ExprLit {
21 attrs: vec![],
22 lit: syn::Lit::Str(syn::LitStr::new(&name.to_string(), name.span())),
23 }))
24 .to_token_stream();
25 let p_idx = params
26 .iter()
27 .map(|p| &p.name)
28 .map(param_idx)
29 .collect::<Vec<_>>();
30 let param_names = params.iter().map(|p| p.name.clone()).collect::<Vec<_>>();
31
32 let get_unsplit_params = if params.len() == 0 {
33 quote!()
34 } else {
35 quote!(
36 let __total_len = #(#p_idx + )* 0;
37 let mut __unsplit_params = vec![0u8; __total_len];
38 typst_wasm_protocol::write_args_to_buffer(__unsplit_params.as_mut_ptr());
39 )
40 };
41
42 let mut set_args = quote!(
43 let mut start: usize = 0;
44 );
45 for param in params.iter() {
46 let name = ¶m.name;
47 let ty = ¶m.ty;
48 let idx = param_idx(name);
49 set_args.extend(quote!(
50 let #name: #ty = (&__unsplit_params[start..start + #idx]).into();
51 start += #idx;
52 ));
53 }
54
55 quote!(
56 #[export_name = #export_name]
57 #vis_marker extern "C" fn #inner_name(#(#p_idx: usize),*) -> i32 {
58 #get_unsplit_params
59 #set_args
60
61 let result = #name(#(#param_names),*);
62 typst_wasm_protocol::PluginResult::send_result(result)
63 }
64 )
65 .into()
66}
67
68struct MacroOptions {
69 pub export_rename: Option<Expr>,
70}
71
72#[proc_macro_attribute]
73pub fn wasm_export(args: TokenStream, item: TokenStream) -> TokenStream {
74 let args = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated
75 .parse(args)
76 .expect("invalid arguments");
77
78 let mut options = MacroOptions {
79 export_rename: None,
80 };
81 for arg in args {
82 let syn::MetaNameValue { path, value, .. } = arg;
83 match path.get_ident().expect("invalid argument") {
84 x if x == "export_rename" => {
85 options.export_rename = Some(value);
86 }
87 _ => panic!("invalid argument"),
88 }
89 }
90
91 let mut item = proc_macro2::TokenStream::from(item);
92 let decl = parse_item(item.clone()).expect("invalid declaration");
93 let func = match decl.as_function() {
94 Some(func) => func.clone(),
95 None => {
96 let error = venial::Error::new_at_tokens(
97 &item,
98 "#[wasm_export] can only be applied to a function",
99 );
100 item.extend(error.to_compile_error());
101 return item.into();
102 }
103 };
104 let Function {
105 name,
106 params,
107 vis_marker,
108 ..
109 } = func.clone();
110
111 let mut error = None;
112 let p = params
113 .items()
114 .filter_map(|x| match x {
115 FnParam::Receiver(_p) => {
116 let x = x.to_token_stream();
117 error = Some(venial::Error::new_at_tokens(
118 &x,
119 format!("the {x} argument is not allowed by the protocol"),
120 ));
121 None
122 }
123 FnParam::Typed(p) => Some(p.clone()),
124 })
125 .collect::<Vec<_>>();
126
127 let mut result = quote!(#func);
128 if let Some(error) = error {
129 result.extend(error.to_compile_error());
130 } else {
131 result.extend(construct_export(
132 &name,
133 vis_marker,
134 p,
135 &options.export_rename,
136 ));
137 }
138 result.into()
139}