typst_wasm_macros/
lib.rs

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 = &param.name;
38        let ty = &param.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}